[
  {
    "path": ".cargo/audit.toml",
    "content": "[advisories]\nignore = [\n  # rsa Marvin Attack\n  \"RUSTSEC-2023-0071\",\n  # difference is unmaintained\n  \"RUSTSEC-2020-0095\",\n  # proc-macro-error is unmaintained\n  \"RUSTSEC-2024-0370\",\n  # time crate can't be updated in the repo because of MSRV, users are unaffected\n  \"RUSTSEC-2026-0009\",\n]\n"
  },
  {
    "path": ".cargo/config.toml",
    "content": "[env]\n# workaround needed to prevent `STATUS_ENTRYPOINT_NOT_FOUND` error in tests\n# see https://github.com/tauri-apps/tauri/pull/4383#issuecomment-1212221864\n__TAURI_WORKSPACE__ = \"true\"\n"
  },
  {
    "path": ".changes/README.md",
    "content": "# Changes\n\n##### via https://github.com/jbolda/covector\n\nAs you create PRs and make changes that require a version bump, please add a new markdown file in this folder. You do not note the version _number_, but rather the type of bump that you expect: major, minor, or patch. The filename is not important, as long as it is a `.md`, but we recommend that it represents the overall change for organizational purposes.\n\nWhen you select the version bump required, you do _not_ need to consider dependencies. Only note the package with the actual change, and any packages that depend on that package will be bumped automatically in the process.\n\nUse the following format:\n\n```md\n---\n'package-a': 'patch:enhance'\n'package-b': 'patch:enhance'\n---\n\nChange summary goes here\n```\n\nSummaries do not have a specific character limit, but are text only. These summaries are used within the (future implementation of) changelogs. They will give context to the change and also point back to the original PR if more details and context are needed.\n\nChanges will be designated as a `major`, `minor` or `patch` as further described in [semver](https://semver.org/).\n\nGiven a version number MAJOR.MINOR.PATCH, increment the:\n\n- MAJOR version when you make incompatible API changes,\n- MINOR version when you add functionality in a backwards compatible manner, and\n- PATCH version when you make backwards compatible bug fixes.\n\nAdditional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format, but will be discussed prior to usage (as extra steps will be necessary in consideration of merging and publishing).\n\nAdditionally you could specify a tag for the change file to group it with other changes by prefixing the bump with `:<tag>`, for example:\n\n```md\n---\n'package-a': 'patch:enhance'\n---\n\nChange summary goes here\n```\n\nwhich will group this change file with other changes that specify the `bug` tag.\n\nFor list of available tags, see the `changeTags` key in [./config.json](./config.json)\n"
  },
  {
    "path": ".changes/base64.md",
    "content": "---\n\"tauri-macos-sign\": patch:enhance\n---\n\nDo not rely on system base64 CLI to decode certificates.\n"
  },
  {
    "path": ".changes/config.json",
    "content": "{\n  \"gitSiteUrl\": \"https://www.github.com/tauri-apps/tauri/\",\n  \"changeTags\": {\n    \"feat\": \"New Features\",\n    \"enhance\": \"Enhancements\",\n    \"bug\": \"Bug Fixes\",\n    \"perf\": \"Performance Improvements\",\n    \"changes\": \"What's Changed\",\n    \"sec\": \"Security fixes\",\n    \"deps\": \"Dependencies\",\n    \"breaking\": \"Breaking Changes\"\n  },\n  \"defaultChangeTag\": \"changes\",\n  \"pkgManagers\": {\n    \"rust\": {\n      \"version\": true,\n      \"getPublishedVersion\": {\n        \"use\": \"fetch:check\",\n        \"options\": {\n          \"url\": \"https://crates.io/api/v1/crates/${ pkg.pkgFile.pkg.package.name }/${ pkg.pkgFile.version }\"\n        }\n      },\n      \"prepublish\": [\n        \"cargo install cargo-audit --features=fix\",\n        {\n          \"command\": \"echo '<details>\\n<summary><em><h4>Cargo Audit</h4></em></summary>\\n\\n```'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"cargo audit ${ process.env.CARGO_AUDIT_OPTIONS || '' }\",\n          \"dryRunCommand\": true,\n          \"runFromRoot\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"echo '```\\n\\n</details>\\n'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        }\n      ],\n      \"publish\": [\n        {\n          \"command\": \"echo '<details>\\n<summary><em><h4>Cargo Publish</h4></em></summary>\\n\\n```'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"cargo publish\",\n          \"dryRunCommand\": \"cargo publish --dry-run\",\n          \"pipe\": true\n        },\n        {\n          \"command\": \"echo '```\\n\\n</details>\\n'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        }\n      ],\n      \"postpublish\": {\n        \"use\": \"fetch:check\",\n        \"options\": {\n          \"url\": \"https://crates.io/api/v1/crates/${ pkg.pkgFile.pkg.package.name }/${ pkg.pkgFile.version }\"\n        },\n        \"retries\": [5000, 5000, 5000]\n      }\n    },\n    \"javascript\": {\n      \"version\": true,\n      \"getPublishedVersion\": {\n        \"use\": \"fetch:check\",\n        \"options\": {\n          \"url\": \"https://registry.npmjs.com/${ pkg.pkgFile.pkg.name }/${ pkg.pkgFile.version }\"\n        }\n      },\n      \"prepublish\": [\n        {\n          \"command\": \"echo '<details>\\n<summary><em><h4>PNPM Audit</h4></em></summary>\\n\\n```'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"pnpm i --frozen-lockfile\",\n          \"dryRunCommand\": true\n        },\n        {\n          \"command\": \"pnpm audit\",\n          \"dryRunCommand\": true,\n          \"runFromRoot\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"echo '```\\n\\n</details>\\n'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        }\n      ],\n      \"publish\": [\n        {\n          \"command\": \"echo '<details>\\n<summary><em><h4>PNPM Publish</h4></em></summary>\\n\\n```'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"pnpm publish --access public --loglevel silly --no-git-checks\",\n          \"dryRunCommand\": \"npm publish --dry-run --access public --no-git-checks\",\n          \"pipe\": true\n        },\n        {\n          \"command\": \"echo '```\\n\\n</details>\\n'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        }\n      ],\n      \"postpublish\": {\n        \"use\": \"fetch:check\",\n        \"options\": {\n          \"url\": \"https://registry.npmjs.com/${ pkg.pkgFile.pkg.name }/${ pkg.pkgFile.version }\"\n        },\n        \"retries\": [5000, 5000, 5000]\n      }\n    }\n  },\n  \"packages\": {\n    \"@tauri-apps/api\": {\n      \"path\": \"./packages/api\",\n      \"manager\": \"javascript\",\n      \"publish\": [\n        {\n          \"command\": \"echo '<details>\\n<summary><em><h4>PNPM Publish</h4></em></summary>\\n\\n```'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"pnpm npm-publish\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"echo '```\\n\\n</details>\\n'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        }\n      ]\n    },\n    \"tauri-utils\": {\n      \"path\": \"./crates/tauri-utils\",\n      \"manager\": \"rust\"\n    },\n    \"tauri-macos-sign\": {\n      \"path\": \"./crates/tauri-macos-sign\",\n      \"manager\": \"rust\"\n    },\n    \"tauri-bundler\": {\n      \"path\": \"./crates/tauri-bundler\",\n      \"manager\": \"rust\",\n      \"dependencies\": [\"tauri-utils\", \"tauri-macos-sign\"]\n    },\n    \"tauri-runtime\": {\n      \"path\": \"./crates/tauri-runtime\",\n      \"manager\": \"rust\",\n      \"dependencies\": [\"tauri-utils\"]\n    },\n    \"tauri-runtime-wry\": {\n      \"path\": \"./crates/tauri-runtime-wry\",\n      \"manager\": \"rust\",\n      \"dependencies\": [\"tauri-utils\", \"tauri-runtime\"]\n    },\n    \"tauri-codegen\": {\n      \"path\": \"./crates/tauri-codegen\",\n      \"manager\": \"rust\",\n      \"dependencies\": [\"tauri-utils\"]\n    },\n    \"tauri-macros\": {\n      \"path\": \"./crates/tauri-macros\",\n      \"manager\": \"rust\",\n      \"dependencies\": [\"tauri-codegen\", \"tauri-utils\"]\n    },\n    \"tauri-plugin\": {\n      \"path\": \"./crates/tauri-plugin\",\n      \"manager\": \"rust\",\n      \"dependencies\": [\"tauri-utils\"],\n      \"postversion\": \"node ../../.scripts/ci/sync-cli-metadata.js ${ pkg.pkg } ${ release.type }\"\n    },\n    \"tauri-build\": {\n      \"path\": \"./crates/tauri-build\",\n      \"manager\": \"rust\",\n      \"dependencies\": [\"tauri-codegen\", \"tauri-utils\"],\n      \"postversion\": \"node ../../.scripts/ci/sync-cli-metadata.js ${ pkg.pkg } ${ release.type }\"\n    },\n    \"tauri\": {\n      \"path\": \"./crates/tauri\",\n      \"manager\": \"rust\",\n      \"dependencies\": [\n        \"tauri-macros\",\n        \"tauri-utils\",\n        \"tauri-runtime\",\n        \"tauri-runtime-wry\",\n        \"tauri-build\"\n      ],\n      \"postversion\": [\n        \"node ../../.scripts/ci/sync-cli-metadata.js ${ pkg.pkg } ${ release.type }\",\n        \"cargo build --manifest-path ../tauri-schema-generator/Cargo.toml\"\n      ]\n    },\n    \"@tauri-apps/cli\": {\n      \"path\": \"./packages/cli\",\n      \"manager\": \"javascript\",\n      \"dependencies\": [\"tauri-cli\"],\n      \"postversion\": \"node ../../.scripts/ci/sync-cli-metadata.js ${ pkg.pkg } ${ release.type }\",\n      \"prepublish\": [],\n      \"publish\": [],\n      \"postpublish\": []\n    },\n    \"tauri-cli\": {\n      \"path\": \"./crates/tauri-cli\",\n      \"manager\": \"rust\",\n      \"dependencies\": [\"tauri-bundler\", \"tauri-utils\", \"tauri-macos-sign\"]\n    },\n    \"tauri-driver\": {\n      \"path\": \"./crates/tauri-driver\",\n      \"manager\": \"rust\"\n    }\n  }\n}\n"
  },
  {
    "path": ".changes/data-tauri-drag-region-deep.md",
    "content": "---\n\"tauri\": minor:feat\n---\n\nAdd `data-tauri-drag-region=\"deep\"` so clicks on non-clickable children will drag as well. Can still opt out of drag on some regions using `data-tauri-drag-region=\"false\"`\n"
  },
  {
    "path": ".changes/fix-build-bundles-arg.md",
    "content": "---\n\"tauri-bundler\": patch:bug\n\"tauri-cli\": patch:bug\n\"@tauri-apps/cli\": patch:bug\n---\n\nFix `build --bundles` to allow `nsis` arg in linux+macOS\n"
  },
  {
    "path": ".changes/linux-deploy-link.md",
    "content": "---\n\"tauri-bundler\": patch:bug\n---\n\nCorrect GitHub Release URL path for Linux i686 tooling.\n"
  },
  {
    "path": ".changes/prompt-signing-key-password-context.md",
    "content": "---\n\"tauri-cli\": patch:enhance\n\"@tauri-apps/cli\": patch:enhance\n---\n\nShow the context before prompting for updater signing key password\n"
  },
  {
    "path": ".changes/toml-ver.md",
    "content": "---\ntauri-utils: patch:deps\n---\n\nChanged `toml` crate version from `0.9` to `\">=0.9, <=1\"`\n"
  },
  {
    "path": ".devcontainer/Dockerfile",
    "content": "# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/master/containers/ubuntu/.devcontainer/base.Dockerfile\n# [Choice] Ubuntu version (use jammy or bionic on local arm64/Apple Silicon): jammy, focal, bionic\nARG VARIANT=\"jammy\"\nFROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT}\n\n# Derived from Tauri contribution and setup guides:\n# See: https://github.com/tauri-apps/tauri/blob/dev/.github/CONTRIBUTING.md#development-guide\n# See: https://v2.tauri.app/start/prerequisites/\nARG TAURI_BUILD_DEPS=\"build-essential curl libappindicator3-dev libgtk-3-dev librsvg2-dev libssl-dev libwebkit2gtk-4.1-dev wget\"\n\nRUN apt-get update && export DEBIAN_FRONTEND=noninteractive \\\n    && apt-get install -y --no-install-recommends $TAURI_BUILD_DEPS\n"
  },
  {
    "path": ".devcontainer/README.md",
    "content": "# VS Code Devcontainer for Tauri\n\n## Overview\n\nPlease note that most of these instructions are derived from Microsoft's VS Code documentation: [Developing inside a Container](https://code.visualstudio.com/docs/remote/containers). Check the official documentation if you encounter problems and submit a PR with any corrections you find for the instructions below.\n\nThe development container included in this repository is derived from [Microsoft's default Ubuntu development container](https://github.com/microsoft/vscode-dev-containers/tree/master/containers/ubuntu). Contents of the Ubuntu Docker image can be in the [VS Code devcontainer Ubuntu base Dockerfile](https://github.com/microsoft/vscode-dev-containers/blob/main/containers/ubuntu/.devcontainer/base.Dockerfile). The contents of the container used for development can be found in the [Dockerfile](./Dockerfile) located in the same directory as this README.\n\n## Usage\n\n1. Ensure you have all [Devcontainer Prerequisites](#devcontainer-prerequisites)\n2. Open the directory containing your [`tauri-apps/tauri`](https://github.com/tauri-apps/tauri) code.\n3. Install the [Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) extension pack for VS Code. This will be included if you install recommended workspace extensions upon opening this repository.\n4. Ensure Docker is running\n5. [Open your workspace in the provided devcontainer](https://code.visualstudio.com/docs/remote/containers#_open-an-existing-workspace-in-a-container): Open this repository in VS Code and run **Remote-Containers: Reopen in Container...** from the Command Palette (<kbd>F1</kbd>).\n\n### Devcontainer Prerequisites\n\nPrerequisites are mainly derived from VS Code's instructions for usage of development containers, documented here: [Developing inside a Container: Getting Started](https://code.visualstudio.com/docs/remote/containers#_getting-started).\n\n1. Docker (Docker Desktop recommended)\n2. VS Code\n3. X window host - required if you want to be able to interact with a GUI from your Docker host\n\n### A note on filesystem performance\n\nDue to limitations in how Docker shares files between the Docker host and a container, it's also recommended that developers [clone Tauri source code into a container volume](https://code.visualstudio.com/remote/advancedcontainers/improve-performance#_use-clone-repository-in-container-volume). This is optional, but highly advised as many filesystem/IO heavy operations (`cargo build`, `pnpm install`, etc) will be very slow if they operate on directories shared with a Docker container from the Docker host.\n\nTo do this, open your project with VS Code and run **Remote-Containers: Clone Repository in Container Volume...** from the Command Palette (<kbd>F1</kbd>).\n\n### Accessing a Tauri application running you the devcontainer\n\nDocker Desktop provides facilities for [allowing the development container to connect to a service on the Docker host](https://docs.docker.com/desktop/windows/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host). So long as you have an X window server running on your Docker host, the devcontainer can connect to it and expose your Tauri GUI via an X window.\n\n**Export the `DISPLAY` variable within the devcontainer terminal you launch your Tauri application from to expose your GUI outside of the devcontainer**.\n\n```bash\nexport DISPLAY=\"host.docker.internal:0\"\n```\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:\n// https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/ubuntu\n{\n  \"name\": \"Ubuntu\",\n  \"build\": {\n    \"dockerfile\": \"Dockerfile\",\n    // Update 'VARIANT' to pick an Ubuntu version: jammy / ubuntu-22.04, focal / ubuntu-20.04, bionic /ubuntu-18.04\n    // Use ubuntu-22.04 or ubuntu-18.04 on local arm64/Apple Silicon.\n    \"args\": { \"VARIANT\": \"ubuntu-22.04\" }\n  },\n\n  // Set *default* container specific settings.json values on container create.\n  \"settings\": {},\n\n  // Add the IDs of extensions you want installed when the container is created.\n  \"extensions\": [],\n\n  // Use 'forwardPorts' to make a list of ports inside the container available locally.\n  // \"forwardPorts\": [],\n\n  // Use 'postCreateCommand' to run commands after the container is created.\n  // \"postCreateCommand\": \"uname -a\",\n\n  // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.\n  \"remoteUser\": \"vscode\",\n  \"features\": {\n    \"node\": \"lts\",\n    \"rust\": \"latest\"\n  }\n}\n"
  },
  {
    "path": ".docker/cross/aarch64.Dockerfile",
    "content": "FROM ubuntu:22.04\nARG DEBIAN_FRONTEND=noninteractive\n\nCOPY common.sh lib.sh /\nRUN /common.sh\n\nCOPY cmake.sh /\nRUN /cmake.sh\n\nCOPY xargo.sh /\nRUN /xargo.sh\n\nRUN apt-get update && apt-get install --assume-yes --no-install-recommends \\\n    g++-aarch64-linux-gnu \\\n    libc6-dev-arm64-cross\n\nCOPY deny-debian-packages.sh /\nRUN TARGET_ARCH=arm64 /deny-debian-packages.sh \\\n    binutils \\\n    binutils-aarch64-linux-gnu\n\nCOPY qemu.sh /\nRUN /qemu.sh aarch64 softmmu\n\nCOPY dropbear.sh /\nRUN /dropbear.sh\n\nCOPY linux-image.sh /\nRUN /linux-image.sh aarch64\n\nCOPY linux-runner /\n\nENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \\\n    CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER=\"/linux-runner aarch64\" \\\n    CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \\\n    CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \\\n    BINDGEN_EXTRA_CLANG_ARGS_aarch64_unknown_linux_gnu=\"--sysroot=/usr/aarch64-linux-gnu\" \\\n    QEMU_LD_PREFIX=/usr/aarch64-linux-gnu \\\n    RUST_TEST_THREADS=1 \\\n    PKG_CONFIG_PATH=\"/usr/lib/aarch64-linux-gnu/pkgconfig/:${PKG_CONFIG_PATH}\"\n\nRUN dpkg --add-architecture arm64\nRUN apt-get update\nRUN apt-get install --assume-yes --no-install-recommends libssl-dev:arm64 libdbus-1-dev:arm64 libsoup2.4-dev:arm64 libssl-dev:arm64 libgtk-3-dev:arm64 webkit2gtk-4.1-dev:arm64 libappindicator3-1:arm64 librsvg2-dev:arm64 patchelf:arm64\n"
  },
  {
    "path": ".docker/cross/cmake.sh",
    "content": "#!/usr/bin/env bash\n\nset -x\nset -euo pipefail\n\n# shellcheck disable=SC1091\n. lib.sh\n\nmain() {\n    local version=3.23.1\n\n    install_packages curl\n\n    local td\n    td=\"$(mktemp -d)\"\n\n    pushd \"${td}\"\n\n    curl --retry 3 -sSfL \"https://github.com/Kitware/CMake/releases/download/v${version}/cmake-${version}-linux-x86_64.sh\" -o cmake.sh\n    sh cmake.sh --skip-license --prefix=/usr/local\n\n    popd\n\n    purge_packages\n\n    rm -rf \"${td}\"\n    rm -rf /var/lib/apt/lists/*\n    rm \"${0}\"\n}\n\nmain \"${@}\"\n"
  },
  {
    "path": ".docker/cross/common.sh",
    "content": "#!/usr/bin/env bash\n\nset -x\nset -euo pipefail\n\n# shellcheck disable=SC1091\n. lib.sh\n\n# For architectures except amd64 and i386, look for packages on ports.ubuntu.com instead.\n# This is important if you enable additional architectures so you can install libraries to cross-compile against.\n# Look for 'dpkg --add-architecture' in the README for more details.\nif grep -i ubuntu /etc/os-release >/dev/null; then\n    sed 's/http:\\/\\/\\(.*\\).ubuntu.com\\/ubuntu\\//[arch-=amd64,i386] http:\\/\\/ports.ubuntu.com\\/ubuntu-ports\\//g' /etc/apt/sources.list > /etc/apt/sources.list.d/ports.list\n    sed -i 's/http:\\/\\/\\(.*\\).ubuntu.com\\/ubuntu\\//[arch=amd64,i386] http:\\/\\/\\1.archive.ubuntu.com\\/ubuntu\\//g' /etc/apt/sources.list\nfi\n\ninstall_packages \\\n    autoconf \\\n    automake \\\n    binutils \\\n    ca-certificates \\\n    curl \\\n    file \\\n    gcc \\\n    git \\\n    libtool \\\n    m4 \\\n    make\n\nif_centos install_packages \\\n    clang-devel \\\n    gcc-c++ \\\n    glibc-devel \\\n    pkgconfig\n\nif_ubuntu install_packages \\\n    g++ \\\n    libc6-dev \\\n    libclang-dev \\\n    pkg-config\n"
  },
  {
    "path": ".docker/cross/deny-debian-packages.sh",
    "content": "#!/usr/bin/env bash\n\nset -x\nset -euo pipefail\n\nmain() {\n    local package\n\n    for package in \"${@}\"; do\n        echo \"Package: ${package}:${TARGET_ARCH}\nPin: release *\nPin-Priority: -1\" > \"/etc/apt/preferences.d/${package}\"\n        echo \"${package}\"\n    done\n\n    rm \"${0}\"\n}\n\nmain \"${@}\"\n"
  },
  {
    "path": ".docker/cross/dropbear.sh",
    "content": "#!/usr/bin/env bash\n\nset -x\nset -euo pipefail\n\n# shellcheck disable=SC1091\n. lib.sh\n\nmain() {\n    local version=2022.82\n\n    install_packages \\\n        autoconf \\\n        automake \\\n        bzip2 \\\n        curl \\\n        make\n\n    if_centos install_packages zlib-devel\n    if_ubuntu install_packages zlib1g-dev\n\n    local td\n    td=\"$(mktemp -d)\"\n\n    pushd \"${td}\"\n\n    curl --retry 3 -sSfL \"https://matt.ucc.asn.au/dropbear/releases/dropbear-${version}.tar.bz2\" -O\n    tar --strip-components=1 -xjf \"dropbear-${version}.tar.bz2\"\n\n    # Remove some unwanted message\n    sed -i '/skipping hostkey/d' cli-kex.c\n    sed -i '/failed to identify current user/d' cli-runopts.c\n\n    ./configure \\\n       --disable-syslog \\\n       --disable-shadow \\\n       --disable-lastlog \\\n       --disable-utmp \\\n       --disable-utmpx \\\n       --disable-wtmp \\\n       --disable-wtmpx \\\n       --disable-pututline \\\n       --disable-pututxline\n\n    make \"-j$(nproc)\" PROGRAMS=dbclient\n    cp dbclient /usr/local/bin/\n\n    purge_packages\n\n    popd\n\n    rm -rf \"${td}\"\n    rm \"${0}\"\n}\n\nmain \"${@}\"\n"
  },
  {
    "path": ".docker/cross/lib.sh",
    "content": "purge_list=()\n\ninstall_packages() {\n    if grep -i ubuntu /etc/os-release; then\n        apt-get update\n\n        for pkg in \"${@}\"; do\n            if ! dpkg -L \"${pkg}\" >/dev/null 2>/dev/null; then\n                apt-get install --assume-yes --no-install-recommends \"${pkg}\"\n\n                purge_list+=( \"${pkg}\" )\n            fi\n        done\n    else\n        for pkg in \"${@}\"; do\n            if ! yum list installed \"${pkg}\" >/dev/null 2>/dev/null; then\n                yum install -y \"${pkg}\"\n\n                purge_list+=( \"${pkg}\" )\n            fi\n        done\n    fi\n}\n\npurge_packages() {\n    if (( ${#purge_list[@]} )); then\n        if grep -i ubuntu /etc/os-release; then\n            apt-get purge --assume-yes --auto-remove \"${purge_list[@]}\"\n        else\n            yum remove -y \"${purge_list[@]}\"\n        fi\n    fi\n}\n\nif_centos() {\n    if grep -q -i centos /etc/os-release; then\n        eval \"${@}\"\n    fi\n}\n\nif_ubuntu() {\n    if grep -q -i ubuntu /etc/os-release; then\n        eval \"${@}\"\n    fi\n}\n"
  },
  {
    "path": ".docker/cross/linux-image.sh",
    "content": "#!/usr/bin/env bash\n\nset -x\nset -euo pipefail\n\n# shellcheck disable=SC1091\n. lib.sh\n\nmain() {\n    # arch in the rust target\n    local arch=\"${1}\" \\\n          kversion=4.19.0-20\n\n    local debsource=\"deb http://http.debian.net/debian/ buster main\"\n    debsource=\"${debsource}\\ndeb http://security.debian.org/ buster/updates main\"\n\n    local dropbear=\"dropbear-bin\"\n\n    local -a deps\n    local kernel=\n    local libgcc=\"libgcc1\"\n\n    # select debian arch and kernel version\n    case \"${arch}\" in\n        aarch64)\n            arch=arm64\n            kernel=\"${kversion}-arm64\"\n            ;;\n        armv7)\n            arch=armhf\n            kernel=\"${kversion}-armmp\"\n            ;;\n        i686)\n            arch=i386\n            kernel=\"${kversion}-686\"\n            ;;\n        mips|mipsel)\n            kernel=\"${kversion}-4kc-malta\"\n            ;;\n        mips64el)\n            kernel=\"${kversion}-5kc-malta\"\n            ;;\n        powerpc)\n            # there is no buster powerpc port, so we use jessie\n            # use a more recent kernel from backports\n            kversion='4.9.0-0.bpo.6'\n            kernel=\"${kversion}-powerpc\"\n            debsource=\"deb http://archive.debian.org/debian jessie main\"\n            debsource=\"${debsource}\\ndeb http://archive.debian.org/debian jessie-backports main\"\n            debsource=\"${debsource}\\ndeb http://ftp.ports.debian.org/debian-ports unstable main\"\n            debsource=\"${debsource}\\ndeb http://ftp.ports.debian.org/debian-ports unreleased main\"\n\n            # archive.debian.org Release files are expired.\n            echo \"Acquire::Check-Valid-Until false;\" | tee -a /etc/apt/apt.conf.d/10-nocheckvalid\n            echo \"APT::Get::AllowUnauthenticated true;\" | tee -a /etc/apt/apt.conf.d/10-nocheckvalid\n\n            dropbear=\"dropbear\"\n            deps=(libcrypt1:\"${arch}\")\n            ;;\n        powerpc64)\n            # there is no stable port\n            arch=ppc64\n            # https://packages.debian.org/en/sid/linux-image-powerpc64\n            kversion='5.*'\n            kernel=\"${kversion}-powerpc64\"\n            libgcc=\"libgcc-s1\"\n            debsource=\"deb http://ftp.ports.debian.org/debian-ports unstable main\"\n            debsource=\"${debsource}\\ndeb http://ftp.ports.debian.org/debian-ports unreleased main\"\n            # sid version of dropbear requires these dependencies\n            deps=(libcrypt1:\"${arch}\")\n            ;;\n        powerpc64le)\n            arch=ppc64el\n            kernel=\"${kversion}-powerpc64le\"\n            ;;\n        s390x)\n            arch=s390x\n            kernel=\"${kversion}-s390x\"\n            ;;\n        sparc64)\n            # there is no stable port\n            # https://packages.debian.org/en/sid/linux-image-sparc64\n            kernel='*-sparc64'\n            libgcc=\"libgcc-s1\"\n            debsource=\"deb http://ftp.ports.debian.org/debian-ports unstable main\"\n            debsource=\"${debsource}\\ndeb http://ftp.ports.debian.org/debian-ports unreleased main\"\n            # sid version of dropbear requires these dependencies\n            deps=(libcrypt1:\"${arch}\")\n            ;;\n        x86_64)\n            arch=amd64\n            kernel=\"${kversion}-amd64\"\n            ;;\n        *)\n            echo \"Invalid arch: ${arch}\"\n            exit 1\n            ;;\n    esac\n\n    install_packages ca-certificates \\\n        curl \\\n        cpio \\\n        sharutils \\\n        gnupg\n\n    # Download packages\n    mv /etc/apt/sources.list /etc/apt/sources.list.bak\n    echo -e \"${debsource}\" > /etc/apt/sources.list\n\n    # Old ubuntu does not support --add-architecture, so we directly change multiarch file\n    if [ -f /etc/dpkg/dpkg.cfg.d/multiarch ]; then\n        cp /etc/dpkg/dpkg.cfg.d/multiarch /etc/dpkg/dpkg.cfg.d/multiarch.bak\n    fi\n    dpkg --add-architecture \"${arch}\" || echo \"foreign-architecture ${arch}\" > /etc/dpkg/dpkg.cfg.d/multiarch\n\n    # Add Debian keys.\n    curl --retry 3 -sSfL 'https://ftp-master.debian.org/keys/archive-key-{7.0,8,9,10}.asc' -O\n    curl --retry 3 -sSfL 'https://ftp-master.debian.org/keys/archive-key-{8,9,10}-security.asc' -O\n    curl --retry 3 -sSfL 'https://ftp-master.debian.org/keys/release-{7,8,9,10}.asc' -O\n    curl --retry 3 -sSfL 'https://www.ports.debian.org/archive_{2020,2021,2022}.key' -O\n\n    for key in *.asc *.key; do\n      apt-key add \"${key}\"\n      rm \"${key}\"\n    done\n\n    # allow apt-get to retry downloads\n    echo 'APT::Acquire::Retries \"3\";' > /etc/apt/apt.conf.d/80-retries\n\n    apt-get update\n\n    mkdir -p \"/qemu/${arch}\"\n    chmod 777 /qemu \"/qemu/${arch}\"\n\n    cd \"/qemu/${arch}\"\n    apt-get -d --no-install-recommends download \\\n        ${deps[@]+\"${deps[@]}\"} \\\n        \"busybox:${arch}\" \\\n        \"${dropbear}:${arch}\" \\\n        \"libtommath1:${arch}\" \\\n        \"libtomcrypt1:${arch}\" \\\n        \"libgmp10:${arch}\" \\\n        \"libc6:${arch}\" \\\n        \"${libgcc}:${arch}\" \\\n        \"libstdc++6:${arch}\" \\\n        \"linux-image-${kernel}:${arch}\" \\\n        ncurses-base \\\n        \"zlib1g:${arch}\"\n    cd /qemu\n\n    # Install packages\n    root=\"root-${arch}\"\n    mkdir -p \"${root}\"/{bin,etc/dropbear,root,sys,dev,proc,sbin,tmp,usr/{bin,sbin},var/log}\n    for deb in \"${arch}\"/*deb; do\n        dpkg -x \"${deb}\" \"${root}\"/\n    done\n\n    cp \"${root}/boot/vmlinu\"* kernel\n\n    # initrd\n    mkdir -p \"${root}/modules\"\n    cp -v \\\n        \"${root}/lib/modules\"/*/kernel/drivers/net/net_failover.ko \\\n        \"${root}/lib/modules\"/*/kernel/drivers/net/virtio_net.ko \\\n        \"${root}/lib/modules\"/*/kernel/drivers/virtio/* \\\n        \"${root}/lib/modules\"/*/kernel/fs/netfs/netfs.ko \\\n        \"${root}/lib/modules\"/*/kernel/fs/9p/9p.ko \\\n        \"${root}/lib/modules\"/*/kernel/fs/fscache/fscache.ko \\\n        \"${root}/lib/modules\"/*/kernel/net/9p/9pnet.ko \\\n        \"${root}/lib/modules\"/*/kernel/net/9p/9pnet_virtio.ko \\\n        \"${root}/lib/modules\"/*/kernel/net/core/failover.ko \\\n        \"${root}/modules\" || true # some file may not exist\n    rm -rf \"${root:?}/boot\"\n    rm -rf \"${root:?}/lib/modules\"\n\n    cat << 'EOF' > \"${root}/etc/hosts\"\n127.0.0.1 localhost qemu\nEOF\n\n    cat << 'EOF' > $root/etc/hostname\nqemu\nEOF\n\n    cat << 'EOF' > $root/etc/passwd\nroot::0:0:root:/root:/bin/sh\nEOF\n\ncat << 'EOF' | uudecode -o $root/etc/dropbear/dropbear_rsa_host_key\nbegin 600 dropbear_rsa_host_key\nM````!W-S:\"UR<V$````#`0`!```!`0\"N!-<%K,3Z.!Z,OEMB2.N\\O.$IWQ*F\nM#5%(_;(^2YKY_J_.RQW/7U@_MK&J#!Z0_\\;EH#98ZW*E1\\.<FF%P/*Y.W56-\nM31.'EJE`TN@=T5EC(8\"Y%3'ZBYH)^WIVJ]S*G/_;#RH\\_?S\"U^1L_<<.F`O+\nMZVI?*]\\KTDOT&QV0#B-M;\"%_7:\\>+3[X=QMH,B<HM$+0E[\\B6*^!XKLR@V,K\nM)<V80HHK:_#;D]26XKN&CB./EZAC%4)78R!G\"\"4HT@UK<5I4B^$/\"\"`,?*\\T\nM>*4$RYULV,V3X6]K:7@Q?80\"#WXGGQZNFN6CZ7LTDX(F6J[\\]F5<0`HEOF:Z\nMX;^53`L'4I/A```!``$L:$Z*#6<^3@+O%.[-#/5H+.C'3\\#QQZN[1;J>L`8I\nMZ_&T'!\"J'/Y+?R?55G:M^=]R*-&I3TOJYZA8@&H51ZOAF59'1_>>Z@?E4#)$\nMQU)X/RWH51ZB5KSDWJS:D'7GD(!?NAY`C'7\\)I:_4)J\")QBV/P\"RJQGHG'%B\nM1BT2LE6676>`1K,0\\NIMZTKQNB(IC+88<7#8%_-=P<&6<\"9LH>60TSS?3?-C\nMN`T36YB/3^<(Q;`N1NT>I9EZS`BAC^-?.:,R\\7EL\"<4>7E=]^1]B\\K9])AQU\nMBM\\]M;4V(S(6KH-I.4[6>9E+@\\UEM.J6:[2LUEEJDG:G:+:/EVF^Y75@(S$`\nM``\"!`.O+KW=&*CBCHL\"11&SVO4/K]$R-]7MV7,3RR)Q[X'0;6.?4JHW!3VR6\nM*FGBY--37ZD-+UV.8_+\"$<?B\"#&K$.[V)F7V2\\UY!7(0FZ@A2`0ADDY*J-_B\nM4AU&.*GP#F/!I([:?E],.>6PH9)(/E.\\G19#G0K`LRM?JWS!58&;D0C1````\nM@0\"\\[@NYWSTW(?Q@:_A*1Y3/AKYO5?S=0\"<2>#V-AH6W-NCSDTSRP=2D79FS\nM\"D?[;.)V>8'#9&I3\"MU@+:2\\Z%$0-MG0+J'(0>T1_C6?*C=4U0I$DI<=@D]1\nH_&DE8Y(OT%%EPG]!$H&5HX*),_D1A2\\P=R.7G'`0L%YM-79Y\"T\">$0``\n`\nend\nEOF\n\n    # dropbear complains when this file is missing\n    touch \"${root}/var/log/lastlog\"\n\n    cat << 'EOF' > $root/init\n#!/bin/busybox sh\n\nset -e\n\n/bin/busybox --install\n\nmount -t devtmpfs devtmpfs /dev\nmount -t proc none /proc\nmount -t sysfs none /sys\nmkdir /dev/pts\nmount -t devpts none /dev/pts/\n\n# some archs does not have virtio modules\ninsmod /modules/failover.ko || true\ninsmod /modules/net_failover.ko || true\ninsmod /modules/virtio.ko || true\ninsmod /modules/virtio_ring.ko || true\ninsmod /modules/virtio_mmio.ko || true\ninsmod /modules/virtio_pci_legacy_dev.ko || true\ninsmod /modules/virtio_pci_modern_dev.ko || true\ninsmod /modules/virtio_pci.ko || true\ninsmod /modules/virtio_net.ko || true\ninsmod /modules/netfs.ko || true\ninsmod /modules/fscache.ko\ninsmod /modules/9pnet.ko\ninsmod /modules/9pnet_virtio.ko || true\ninsmod /modules/9p.ko\n\nifconfig lo 127.0.0.1\nifconfig eth0 10.0.2.15\nroute add default gw 10.0.2.2 eth0\n\nmkdir /target\nmount -t 9p -o trans=virtio target /target -oversion=9p2000.u || true\n\nexec dropbear -F -E -B\nEOF\n\n    chmod +x \"${root}/init\"\n    cd \"${root}\"\n    find . | cpio --create --format='newc' --quiet | gzip > ../initrd.gz\n    cd -\n\n    # Clean up\n    rm -rf \"/qemu/${root}\" \"/qemu/${arch}\"\n    mv -f /etc/apt/sources.list.bak /etc/apt/sources.list\n    if [ -f /etc/dpkg/dpkg.cfg.d/multiarch.bak ]; then\n        mv /etc/dpkg/dpkg.cfg.d/multiarch.bak /etc/dpkg/dpkg.cfg.d/multiarch\n    fi\n    # can fail if arch is used (amd64 and/or i386)\n    dpkg --remove-architecture \"${arch}\" || true\n    apt-get update\n\n    purge_packages\n\n    ls -lh /qemu\n}\n\nmain \"${@}\"\n"
  },
  {
    "path": ".docker/cross/linux-runner",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nLOG=/tmp/qemu.log\nLOCK=/tmp/qemu.lock\n\nif [ -n \"${CROSS_DEBUG}\" ]; then\n    set -x\nfi\n\n# arch in the rust target\narch=\"${1}\"\nshift\n\nif [ \"${CROSS_RUNNER}\" = \"\" ]; then\n    if [[ \"${arch}\" == i?86 ]] || [[ \"${arch}\" == x86_64 ]]; then\n        CROSS_RUNNER=native\n    else\n        CROSS_RUNNER=qemu-user\n    fi\nfi\n\n# select qemu arch\nqarch=\"${arch}\"\ncase \"${arch}\" in\n    armv7)\n        qarch=\"arm\"\n        ;;\n    i686)\n        qarch=\"i386\"\n        ;;\n    powerpc)\n        qarch=\"ppc\"\n        ;;\n    powerpc64)\n        qarch=\"ppc64\"\n        ;;\n    powerpc64le)\n        if [ \"${CROSS_RUNNER}\" = \"qemu-user\" ]; then\n            qarch=\"ppc64le\"\n        else\n            qarch=\"ppc64\"\n        fi\n        ;;\nesac\n\ncase \"${CROSS_RUNNER}\" in\n    native)\n        exec \"${@}\"\n        ;;\n    qemu-user)\n        exec \"qemu-${qarch}\" \"${@}\"\n        ;;\n    qemu-system)\n        true\n        ;;\n    *)\n        echo \"Invalid runner: \\\"${CROSS_RUNNER}\\\"\";\n        echo \"Valid runners are: native, qemu-user and qemu-system\"\n        exit 1\n        ;;\nesac\n\nn=\"$(nproc)\"\nmemory=1G\ndriver9p=\"virtio-9p-pci\"\ndrivernet=\"virtio-net-pci\"\n\n# select qemu parameters\ncase \"${arch}\" in\n    aarch64)\n        # 8 is the max number of cpu supported by qemu-aarch64\n        n=$(( n > 8 ? 8 : n ))\n        opt=\"-machine virt -cpu cortex-a57\"\n        ;;\n    armv7)\n        opt=\"-machine virt\"\n        driver9p=\"virtio-9p-device\"\n        drivernet=\"virtio-net-device\"\n        ;;\n    i686)\n        opt=\"-append console=ttyS0\"\n        ;;\n    mips|mipsel)\n        # avoid kernel error\n        # https://blahcat.github.io/2017/07/14/building-a-debian-stretch-qemu-image-for-mipsel/\n        opt=\"-append nokaslr\"\n        n=1\n        ;;\n    mips64el)\n        # avoid kernel error\n        # https://blahcat.github.io/2017/07/14/building-a-debian-stretch-qemu-image-for-mipsel/\n        opt=\"-append nokaslr -cpu MIPS64R2-generic\"\n        n=1\n        ;;\n    powerpc)\n        opt=\"-append console=ttyPZ0\"\n        n=1\n        ;;\n    powerpc64|powerpc64le)\n        opt=\"-append console=hvc0 --nodefaults -serial stdio\"\n        ;;\n    s390x)\n        n=1\n        driver9p=\"virtio-9p-ccw\"\n        drivernet=\"virtio-net-ccw\"\n        ;;\n    sparc64)\n        n=1\n        driver9p+=\",bus=pciB\"\n        drivernet+=\",bus=pciB\"\n        ;;\n    x86_64)\n        opt=\"-append console=ttyS0\"\n        ;;\nesac\n\n(\n    flock -n 200 || exit 0\n\n    echo Booting QEMU virtual machine with $n cpus...\n\n    QEMU_CMD=\"qemu-system-${qarch} \\\n        -m ${memory} \\\n        -smp ${n} \\\n        -nographic \\\n        -monitor none \\\n        -netdev user,id=net0,hostfwd=tcp::10022-:22 \\\n        -device ${drivernet},netdev=net0 \\\n        -kernel /qemu/kernel \\\n        -initrd /qemu/initrd.gz \\\n        ${opt} \\\n        -fsdev local,id=fs0,path=/target,security_model=mapped \\\n        -device ${driver9p},fsdev=fs0,mount_tag=target\"\n\n    touch \"${LOG}\"\n    if [[ -n \"${CROSS_DEBUG}\" ]]; then\n        (${QEMU_CMD} 2>&1 | tee -a \"${LOG}\") &\n    else\n        ${QEMU_CMD} >> \"${LOG}\" 2>&1 &\n    fi\n\n    # wait for dropbear\n    for _ in $(seq 240); do\n        if grep -q \"Not backgrounding\" \"${LOG}\"; then\n            READY=1\n            break\n        fi\n        sleep 0.5s\n    done\n\n    if [ -z \"${READY}\" ]; then\n        if [ -n \"${CROSS_DEBUG}\" ]; then\n            echo \"Not ready but continuing because CROSS_DEBUG is set\"\n        else\n            echo \"Qemu is not ready after ${SECONDS} seconds...\"\n            echo \"Set the environment variable CROSS_DEBUG=1 to debug\"\n            echo \"Last 100 lines of qemu output:\"\n            tail -n 100 \"${LOG}\"\n            exit 1\n        fi\n    fi\n\n    echo \"Booted in ${SECONDS} seconds\"\n\n) 200>\"${LOCK}\"\n\nif [[ -t 1 ]] && [[ -t 2 ]]; then\n  tty_flag='-t'\nfi\n\nexec dbclient ${tty_flag} -p 10022 -y -y root@localhost \"${@}\"\n"
  },
  {
    "path": ".docker/cross/qemu.sh",
    "content": "#!/usr/bin/env bash\n\nset -x\nset -euo pipefail\n\n# shellcheck disable=SC1091\n. lib.sh\n\nbuild_static_libffi () {\n    local version=3.0.13\n\n    local td\n    td=\"$(mktemp -d)\"\n\n    pushd \"${td}\"\n\n\n    curl --retry 3 -sSfL \"https://github.com/libffi/libffi/archive/refs/tags/v${version}.tar.gz\" -O -L\n    tar --strip-components=1 -xzf \"v${version}.tar.gz\"\n    ./configure --prefix=\"$td\"/lib --disable-builddir --disable-shared --enable-static\n    make \"-j$(nproc)\"\n    install -m 644 ./.libs/libffi.a /usr/lib64/\n\n    popd\n\n    rm -rf \"${td}\"\n}\n\nbuild_static_libmount () {\n    local version_spec=2.23.2\n    local version=2.23\n    local td\n    td=\"$(mktemp -d)\"\n\n    pushd \"${td}\"\n\n    curl --retry 3 -sSfL \"https://kernel.org/pub/linux/utils/util-linux/v${version}/util-linux-${version_spec}.tar.xz\" -O -L\n    tar --strip-components=1 -xJf \"util-linux-${version_spec}.tar.xz\"\n    ./configure --disable-shared --enable-static --without-ncurses\n    make \"-j$(nproc)\" mount blkid\n    install -m 644 ./.libs/*.a /usr/lib64/\n\n    popd\n\n    rm -rf \"${td}\"\n}\n\n\nbuild_static_libattr() {\n    local version=2.4.46\n\n    local td\n    td=\"$(mktemp -d)\"\n\n    pushd \"${td}\"\n\n    yum install -y gettext\n\n    curl --retry 3 -sSfL \"https://download.savannah.nongnu.org/releases/attr/attr-${version}.src.tar.gz\" -O\n    tar --strip-components=1 -xzf \"attr-${version}.src.tar.gz\"\n    cp /usr/share/automake*/config.* .\n\n    ./configure\n    make \"-j$(nproc)\"\n    install -m 644 ./libattr/.libs/libattr.a /usr/lib64/\n\n    yum remove -y gettext\n\n    popd\n\n    rm -rf \"${td}\"\n}\n\nbuild_static_libcap() {\n    local version=2.22\n\n    local td\n    td=\"$(mktemp -d)\"\n\n    pushd \"${td}\"\n\n    curl --retry 3 -sSfL \"https://www.kernel.org/pub/linux/libs/security/linux-privs/libcap2/libcap-${version}.tar.xz\" -O\n    tar --strip-components=1 -xJf \"libcap-${version}.tar.xz\"\n    make \"-j$(nproc)\"\n    install -m 644 libcap/libcap.a /usr/lib64/\n\n    popd\n\n    rm -rf \"${td}\"\n}\n\nbuild_static_pixman() {\n    local version=0.34.0\n\n    local td\n    td=\"$(mktemp -d)\"\n\n    pushd \"${td}\"\n\n    curl --retry 3 -sSfL \"https://www.cairographics.org/releases/pixman-${version}.tar.gz\" -O\n    tar --strip-components=1 -xzf \"pixman-${version}.tar.gz\"\n    ./configure\n    make \"-j$(nproc)\"\n    install -m 644 ./pixman/.libs/libpixman-1.a /usr/lib64/\n\n    popd\n\n    rm -rf \"${td}\"\n}\n\nmain() {\n    local version=5.1.0\n\n    if_centos version=4.2.1\n\n    local arch=\"${1}\" \\\n          softmmu=\"${2:-}\"\n\n    install_packages \\\n        autoconf \\\n        automake \\\n        bison \\\n        bzip2 \\\n        curl \\\n        flex \\\n        libtool \\\n        make \\\n        patch \\\n        python3 \\\n\n    if_centos install_packages \\\n        gcc-c++ \\\n        pkgconfig \\\n        xz \\\n        glib2-devel \\\n        glib2-static \\\n        glibc-static \\\n        libattr-devel \\\n        libcap-devel \\\n        libfdt-devel \\\n        pcre-static \\\n        pixman-devel \\\n        libselinux-devel \\\n        libselinux-static \\\n        libffi \\\n        libuuid-devel \\\n        libblkid-devel \\\n        libmount-devel \\\n        zlib-devel \\\n        zlib-static\n\n    if_centos 'curl --retry 3 -sSfL \"https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD\" -o /usr/share/automake*/config.guess'\n    if_centos 'curl --retry 3 -sSfL \"https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD\" -o /usr/share/automake*/config.sub'\n\n    # these are not packaged as static libraries in centos; build them manually\n    if_centos build_static_libffi\n    if_centos build_static_libmount\n    if_centos build_static_libattr\n    if_centos build_static_libcap\n    if_centos build_static_pixman\n\n    if_ubuntu install_packages \\\n        g++ \\\n        pkg-config \\\n        xz-utils \\\n        libattr1-dev \\\n        libcap-ng-dev \\\n        libffi-dev \\\n        libglib2.0-dev \\\n        libpixman-1-dev \\\n        libselinux1-dev \\\n        zlib1g-dev\n\n    # if we have python3.6+, we can install qemu 6.1.0, which needs ninja-build\n    # ubuntu 16.04 only provides python3.5, so remove when we have a newer qemu.\n    is_ge_python36=$(python3 -c \"import sys; print(int(sys.version_info >= (3, 6)))\")\n    if [[ \"${is_ge_python36}\" == \"1\" ]]; then\n        if_ubuntu version=6.1.0\n        if_ubuntu install_packages ninja-build\n    fi\n\n    local td\n    td=\"$(mktemp -d)\"\n\n    pushd \"${td}\"\n\n    curl --retry 3 -sSfL \"https://download.qemu.org/qemu-${version}.tar.xz\" -O\n    tar --strip-components=1 -xJf \"qemu-${version}.tar.xz\"\n\n    local targets=\"${arch}-linux-user\"\n    local virtfs=\"\"\n    case \"${softmmu}\" in\n        softmmu)\n            if [ \"${arch}\" = \"ppc64le\" ]; then\n                targets=\"${targets},ppc64-softmmu\"\n            else\n                targets=\"${targets},${arch}-softmmu\"\n            fi\n            virtfs=\"--enable-virtfs\"\n            ;;\n        \"\")\n            true\n            ;;\n        *)\n            echo \"Invalid softmmu option: ${softmmu}\"\n            exit 1\n            ;;\n    esac\n\n    ./configure \\\n        --disable-kvm \\\n        --disable-vnc \\\n        --disable-guest-agent \\\n        --enable-linux-user \\\n        --static \\\n        ${virtfs} \\\n        --target-list=\"${targets}\"\n    make \"-j$(nproc)\"\n    make install\n\n    # HACK the binfmt_misc interpreter we'll use expects the QEMU binary to be\n    # in /usr/bin. Create an appropriate symlink\n    ln -s \"/usr/local/bin/qemu-${arch}\" \"/usr/bin/qemu-${arch}-static\"\n\n    purge_packages\n\n    popd\n\n    rm -rf \"${td}\"\n    rm \"${0}\"\n}\n\nmain \"${@}\"\n"
  },
  {
    "path": ".docker/cross/xargo.sh",
    "content": "#!/usr/bin/env bash\n\nset -x\nset -euo pipefail\n\n# shellcheck disable=SC1091\n. lib.sh\n\nmain() {\n    install_packages ca-certificates curl\n\n    export RUSTUP_HOME=/tmp/rustup\n    export CARGO_HOME=/tmp/cargo\n\n    curl --retry 3 -sSfL https://sh.rustup.rs -o rustup-init.sh\n    sh rustup-init.sh -y --no-modify-path --profile minimal\n    rm rustup-init.sh\n\n    PATH=\"${CARGO_HOME}/bin:${PATH}\" cargo install xargo --root /usr/local\n\n    rm -r \"${RUSTUP_HOME}\" \"${CARGO_HOME}\"\n\n    purge_packages\n\n    rm \"${0}\"\n}\n\nmain \"${@}\"\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# Current WG Code Sub Teams:\n# @tauri-apps/wg-tauri\n# @tauri-apps/wg-devops\n\n# Order is important; the last matching pattern takes the most precedence.\n* @tauri-apps/wg-tauri\n\n.github @tauri-apps/wg-devops\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, caste, color, religion, or sexual\nidentity and orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n- Demonstrating empathy and kindness toward other people\n- Being respectful of differing opinions, viewpoints, and experiences\n- Giving and gracefully accepting constructive feedback\n- Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n- Focusing on what is best not just for us as individuals, but for the overall\n  community\n\nExamples of unacceptable behavior include:\n\n- The use of sexualized language or imagery, and sexual attention or advances of\n  any kind\n- Trolling, insulting or derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or email address,\n  without their explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\n[contact@tauri.app](mailto:contact@tauri.app).\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series of\nactions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or permanent\nban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within the\ncommunity.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.1, available at\n[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].\n\nCommunity Impact Guidelines were inspired by\n[Mozilla's code of conduct enforcement ladder][Mozilla CoC].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at\n[https://www.contributor-covenant.org/translations][translations].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Tauri Contributing Guide\n\nHi! We, the maintainers, are really excited that you are interested in contributing to Tauri. Before submitting your contribution though, please make sure to take a moment and read through the [Code of Conduct](CODE_OF_CONDUCT.md), as well as the appropriate section for the contribution you intend to make:\n\n- [Issue Reporting Guidelines](#issue-reporting-guidelines)\n- [Pull Request Guidelines](#pull-request-guidelines)\n- [Development Guide](#development-guide)\n- [AI Tool Policy](#ai-tool-policy)\n\n## Issue Reporting Guidelines\n\n- The issue list of this repo is **exclusively** for bug reports and feature requests. Non-conforming issues will be closed immediately.\n\n- If you have a question, you can get quick answers from the [Tauri Discord chat](https://discord.gg/SpmNs4S).\n\n- Try to search for your issue, it may have already been answered or even fixed in the development branch (`dev`).\n\n- Check if the issue is reproducible with the latest stable version of Tauri. If you are using a pre-release, please indicate the specific version you are using.\n\n- It is **required** that you clearly describe the steps necessary to reproduce the issue you are running into. Although we would love to help our users as much as possible, diagnosing issues without clear reproduction steps is extremely time-consuming and simply not sustainable.\n\n- Use only the minimum amount of code necessary to reproduce the unexpected behavior. A good bug report should isolate specific methods that exhibit unexpected behavior and precisely define how expectations were violated. What did you expect the method or methods to do, and how did the observed behavior differ? The more precisely you isolate the issue, the faster we can investigate.\n\n- Issues with no clear repro steps will not be triaged. If an issue labeled \"need repro\" receives no further input from the issue author for more than 5 days, it will be closed.\n\n- If your issue is resolved but still open, don't hesitate to close it. In case you found a solution by yourself, it could be helpful to explain how you fixed it.\n\n- Most importantly, we beg your patience: the team must balance your request against many other responsibilities — fixing other bugs, answering other questions, new features, new documentation, etc. The issue list is not paid support and we cannot make guarantees about how fast your issue can be resolved.\n\n## Pull Request Guidelines\n\n- You have to [sign your commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits).\n\n- It's OK to have multiple small commits as you work on the PR - we will let GitHub automatically squash it before merging.\n\n- If adding new feature:\n  - Provide convincing reason to add this feature. Ideally you should open a suggestion issue first and have it greenlighted before working on it.\n\n- If fixing a bug:\n  - If you are resolving a special issue, add `(fix: #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `fix: update entities encoding/decoding (fix #3899)`.\n  - Provide detailed description of the bug in the PR, or link to an issue that does.\n\n- If the PR is meant to be released, follow the instructions in `.changes/readme.md` to log your changes. ie. [readme.md](https://github.com/tauri-apps/tauri/blob/dev/.changes/README.md)\n\n## Development Guide\n\n**NOTE: If you have any question don't hesitate to ask in our Discord server. We try to keep this guide to up guide, but if something doesn't work let us know.**\n\n### General Setup\n\nFirst, [join our Discord server](https://discord.gg/SpmNs4S) and let us know that you want to contribute. This way we can point you in the right direction and help ensure your contribution will be as helpful as possible.\n\nTo set up your machine for development, follow the [Tauri setup guide](https://v2.tauri.app/start/prerequisites/) to get all the tools you need to develop Tauri apps. The only additional tool you may need is [PNPM](https://pnpm.io/), it is only required if you are developing the Node CLI or API packages (`packages/cli` and `packages/api`).\n\nNext, [fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) and clone [this repository](https://github.com/tauri-apps).\nThe development process varies depending on what part of Tauri you are contributing to, see the guides below for per-package instructions.\n\nSome Tauri packages will be automatically built when running one of the examples. Others, however, will need to be built beforehand. To initialize, execute these commands in the repository root:\n\n```bash\npnpm install\npnpm build\n```\n\n### Overview\n\nSee [Architecture](../ARCHITECTURE.md#major-components) for an overview of the packages in this repository.\n\n### Developing Tauri Core and Related Components (Rust API, Macros, Codegen, and Utils)\n\nThe code for the Rust crates, including the Core, Macros, Utils, WRY runtime, and a few more are located in the [main Tauri repository](https://github.com/tauri-apps/tauri/tree/dev/crates).\n\nThe easiest way to test your changes is to use the [helloworld](https://github.com/tauri-apps/tauri/tree/dev/examples/helloworld) example app. It automatically rebuilds and uses your local copy of the Tauri core packages. Just run `cargo run --example helloworld` after making changes to test them out.\n\nTo test local changes against your own application simply point the Tauri create to your local repository. In `src-tauri/Cargo.toml` file change:\n\n`tauri = { version = \"2.1.1\" }`\n\nto:\n\n`tauri = { path = \"path/to/local/tauri/crates/tauri\" }`\n\nIf any other crates depend on Tauri you will have to point them to the local repo as well.\n\n### Developing Tauri Bundler and Rust CLI\n\nThe code for the bundler is located in [crates/tauri-bundler](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-bundler), and the code for the Rust CLI is located in [tauri-cli](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-cli).\nRunning `cargo install --path .` in the Rust CLI directory will allow you to run `cargo tauri build` and `cargo tauri dev` anywhere, using the updated copy of the bundler and cli. You will have to run this command each time you make a change in either package.\nYou can use `cargo install --path . --debug` to speed up test builds.\n\n### Developing The Node.js CLI (`@tauri-apps/cli`)\n\n[`@tauri-apps/cli`](https://github.com/tauri-apps/tauri/tree/dev/packages/cli) is a small wrapper around `tauri-cli` so most changes should be happen in the Rust CLI (see above).\n\n#### Building the documentation locally\n\nYou can build the Rust documentation locally running the following script:\n\n```bash\n$ cargo +nightly doc --all-features --open\n```\n\n### Developing the JS API\n\nThe JS API provides bindings between the developer's JS in the Webview and the built-in Tauri APIs, written in Rust. Its code is located in [/packages/api](https://github.com/tauri-apps/tauri/tree/dev/packages/api).\nAfter making changes to the code, run `pnpm build` to build it. To test your changes, we recommend using the API example app, located in [/examples/api](https://github.com/tauri-apps/tauri/tree/dev/examples/api). It will automatically use your local copy of the JS API and provides a helpful UI to test the various commands.\n\n## AI Tool Policy\n\nIt takes a lot of time to review a Pull Request while it's very easy to make a nonsensical but plausible looking one using AI tools.\nIt is unfair for other contributors and the reviewers to spend much of the time dealing with this, hence these rules:\n\n1. Review and test all LLM-generated content before submitting, you're the one responsible for it, not the AI.\n2. Don't use AI to respond to review comments (except for translations).\n\nWe will close the Pull Request with a `ai-slop` tag if you failed to do so.\n\n## Financial Contribution\n\nTauri is an MIT-licensed open source project. Its ongoing development can be supported via [GitHub Sponsors](https://github.com/sponsors/tauri-apps) or [Open Collective](https://opencollective.com/tauri). We prefer GitHub Sponsors as donations made are doubled through the matching fund program.\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\n# These are supported funding model platforms\n\ngithub: tauri-apps\npatreon: #\nopen_collective: tauri\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncustom: # Replace with a single custom sponsorship URL\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: 🐞 Bug Report\ntitle: '[bug] '\ndescription: Report a bug\nlabels: ['type: bug', 'status: needs triage']\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        ## First of all\n        1. Please search for [existing issues](https://github.com/tauri-apps/tauri/issues?q=is%3Aissue) about this problem first.\n        2. Make sure `rustc` and all relevant Tauri packages are up to date.\n        3. Make sure it's an issue with Tauri and not something else you are using.\n        4. Remember to follow our community guidelines and be friendly.\n\n  - type: textarea\n    id: description\n    attributes:\n      label: Describe the bug\n      description: A clear description of what the bug is. Include screenshots if applicable.\n      placeholder: Bug description\n    validations:\n      required: true\n\n  - type: textarea\n    id: reproduction\n    attributes:\n      label: Reproduction\n      description: A link to a reproduction repo or steps to reproduce the behaviour.\n      placeholder: |\n        Please provide a minimal reproduction or steps to reproduce, see this guide https://stackoverflow.com/help/minimal-reproducible-example\n        Why reproduction is required? see this article https://antfu.me/posts/why-reproductions-are-required\n\n  - type: textarea\n    id: expected-behavior\n    attributes:\n      label: Expected behavior\n      description: A clear description of what you expected to happen.\n\n  - type: textarea\n    id: info\n    attributes:\n      label: Full `tauri info` output\n      description: 'Output of `npm run tauri info` or `cargo tauri info`'\n      render: text\n    validations:\n      required: true\n\n  - type: textarea\n    id: logs\n    attributes:\n      label: Stack trace\n      render: text\n\n  - type: textarea\n    id: context\n    attributes:\n      label: Additional context\n      description: Add any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\ncontact_links:\n  - name: 💬 Discord Chat\n    url: https://discord.com/invite/tauri\n    about: Ask questions and talk to other Tauri users and the maintainers\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/docs_report.md",
    "content": "---\nname: 📚 Docs Report\nabout: Create a report to help us improve the docs\ntitle: '[docs] '\nlabels: 'type: documentation'\nassignees: ''\n---\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: 💡 Feature Request\ntitle: '[feat] '\ndescription: Suggest an idea\nlabels: ['type: feature request']\n\nbody:\n  - type: textarea\n    id: problem\n    attributes:\n      label: Describe the problem\n      description: A clear description of the problem this feature would solve\n      placeholder: \"I'm always frustrated when...\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: solution\n    attributes:\n      label: \"Describe the solution you'd like\"\n      description: A clear description of what change you would like\n      placeholder: 'I would like to...'\n    validations:\n      required: true\n\n  - type: textarea\n    id: alternatives\n    attributes:\n      label: Alternatives considered\n      description: \"Any alternative solutions you've considered\"\n\n  - type: textarea\n    id: context\n    attributes:\n      label: Additional context\n      description: Add any other context about the problem here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\nBefore submitting a PR, please read https://github.com/tauri-apps/tauri/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines\n\n1. Give the PR a descriptive title.\n\n  Examples of good title:\n    - fix(windows): fix race condition in event loop\n    - docs: update example for `App::show`\n    - feat: add `Window::set_fullscreen`\n\n  Examples of bad title:\n    - fix #7123\n    - update docs\n    - fix bugs\n\n2. If there is a related issue, reference it in the PR text, e.g. closes #123.\n3. If this change requires a new version, then add a change file in `.changes` directory with the appropriate bump, see https://github.com/tauri-apps/tauri/blob/dev/.changes/README.md\n4. Ensure that all your commits are signed https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits\n5. Ensure `cargo test` and `cargo clippy` passes.\n6. Propose your changes as a draft PR if your work is still in progress.\n-->\n"
  },
  {
    "path": ".github/RELEASING.md",
    "content": "# Tauri Releasing Handbook\n\nThis handbook contains information about our release pipeline and how to deal with common issues.\nThis document is mainly intended for team members responsible for maintaining the project.\n\n- [Covector](#covector)\n- [Version Pull Request](#version-pull-request)\n- [Releasing and Publishing](#releasing-and-publishing)\n- [Publishing failed, what to do?](#publishing-failed-what-to-do)\n\n## Covector\n\nWe use [`covector`](https://github.com/jbolda/covector) to manage our version bumps and release pipeline.\nIt can be configured in [`.changes/config.json`](../.changes/config.json) which includes how each package should be published step by step.\n\nSome packages can't be published directly using `covector` as it requires to be built on a matrix of platforms\nsuch as `tauri-cli` prebuilt binaries which is published using [publish-cli-rs.yml](./workflows/publish-cli-rs.yml)\nand `@tauri-apps/cli` native Node.js modules which is published using using [publish-cli-js.yml](./workflows/publish-cli-js.yml)\nboth of which are triggered after `covector` has created a github release for both of them, see `Trigger @tauri-apps/cli publishing workflow`\nand `Trigger tauri-cli publishing workflow` steps in [covector-version-or-publish.yml](./workflows/covector-version-or-publish.yml)\n\n## Version Pull Request\n\nOn each pull request merged, [covector-version-or-publish.yml](./workflows/covector-version-or-publish.yml) workflow will run, and:\n\nWhen there're change files inside `.changes` folder and they're not all included in `pre.json` (usually this is only when we are in `-alpha` to `-rc` phase), it will open/update an `Apply Version Updates From Current Changes` PR (https://github.com/tauri-apps/tauri/pull/11029 for example) that bumps all packages based on current existing change files and generate `CHANGELOG.md` entries. see `Create Pull Request With Versions Bumped` step in [covector-version-or-publish.yml](./workflows/covector-version-or-publish.yml).\n\nOtherwise, covector will start to publish packages configured in [`.changes/config.json`](../.changes/config.json).\n\n## Releasing and Publishing\n\nReleasing can be as easy as merging the version pull request but here is a checklist to follow:\n\n- [ ] Double check that every package is bumped correctly and there are no accidental major or minor being released unless that is indeed the intention.\n- [ ] Make sure that there are no pending or unfinished [covector-version-or-publish.yml](./workflows/covector-version-or-publish.yml) workflow runs.\n- [ ] Approve and merge the version pull request\n\n## Publishing failed, what to do?\n\nIt is possible and due to many factors that one or many packages release can fail to release, there is no reason to panic, we can fix this.\n\nDid all of the packages fail to release?\n\n- yes?\n  - [ ] `git checkout -b revert-branch`\n  - [ ] `git revert HEAD~1`\n- no?\n  - [ ] `git checkout -b revert-branch`\n  - [ ] `git revert HEAD~1 --no-commit`\n  - [ ] Edit the commit and revert only changes related to packages that failed to publish\n  - [ ] `git revert --continue`\n\nThen:\n\n- [ ] Make a pull request with reverted changes, get it approved and merged\n- [ ] Fix the issue that caused releases to fail in another PR, get it approved and merged\n- [ ] Repeat the release process again.\n"
  },
  {
    "path": ".github/workflows/audit.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: Audit\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '0 0 * * *'\n  pull_request:\n    paths:\n      - '.github/workflows/audit.yml'\n      - '**/Cargo.lock'\n      - '**/Cargo.toml'\n      - '**/package.json'\n      - '**/pnpm-lock.yaml'\n  push:\n    branches:\n      - dev\n    paths:\n      - '.github/workflows/audit.yml'\n      - '**/Cargo.lock'\n      - '**/Cargo.toml'\n      - '**/package.json'\n      - '**/pnpm-lock.yaml'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  audit-rust:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: rust audit\n        uses: rustsec/audit-check@v2\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n\n  audit-js:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: npm i -g --force corepack\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 'lts/*'\n      - run: pnpm audit\n"
  },
  {
    "path": ".github/workflows/bench.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: bench\n\non:\n  push:\n    branches:\n      - dev\n  workflow_dispatch:\n  pull_request:\n    paths:\n      - '.github/workflows/bench.yml'\n      - 'bench/**'\n\nenv:\n  RUST_BACKTRACE: 1\n  CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency.\n  LC_ALL: en_US.UTF-8 # This prevents strace from changing its number format to use commas.\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  bench:\n    strategy:\n      fail-fast: false\n      matrix:\n        rust: [nightly]\n        platform:\n          - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest }\n\n    runs-on: ${{ matrix.platform.os }}\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: install Rust ${{ matrix.rust }}\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ matrix.rust }}\n          components: rust-src\n          targets: ${{ matrix.platform.target }}\n\n      - name: setup python\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.10'\n          architecture: x64\n\n      - name: install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          sudo apt-get update\n          sudo apt-get install -y --no-install-recommends \\\n            webkit2gtk-4.1 libayatana-appindicator3-dev \\\n            xvfb \\\n            at-spi2-core\n          wget https://github.com/sharkdp/hyperfine/releases/download/v1.18.0/hyperfine_1.18.0_amd64.deb\n          sudo dpkg -i hyperfine_1.18.0_amd64.deb\n          pip install memory_profiler\n\n      - uses: Swatinem/rust-cache@v2\n\n      - name: run benchmarks\n        run: |\n          cargo build --manifest-path bench/tests/cpu_intensive/src-tauri/Cargo.toml --release -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target ${{ matrix.platform.target }}\n          cargo build --manifest-path bench/tests/files_transfer/src-tauri/Cargo.toml --release -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target ${{ matrix.platform.target }}\n          cargo build --manifest-path bench/tests/helloworld/src-tauri/Cargo.toml --release -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target ${{ matrix.platform.target }}\n          xvfb-run --auto-servernum cargo run --manifest-path bench/Cargo.toml --bin run_benchmark\n\n      - name: clone benchmarks_results\n        if: github.repository == 'tauri-apps/tauri' && github.ref == 'refs/heads/dev'\n        uses: actions/checkout@v4\n        with:\n          token: ${{ secrets.BENCH_PAT }}\n          path: gh-pages\n          repository: tauri-apps/benchmark_results\n\n      - name: push new benchmarks\n        if: github.repository == 'tauri-apps/tauri' && github.ref == 'refs/heads/dev'\n        run: |\n          cargo run --manifest-path bench/Cargo.toml --bin build_benchmark_jsons\n          cd gh-pages\n          git pull\n          git config user.name \"tauri-bench\"\n          git config user.email \"gh.tauribot@gmail.com\"\n          git add .\n          git commit --message \"Update Tauri benchmarks\"\n          git push origin gh-pages\n\n      - name: Print worker info\n        run: |\n          cat /proc/cpuinfo\n          cat /proc/meminfo\n"
  },
  {
    "path": ".github/workflows/check-change-tags.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: check change tags\n\non:\n  pull_request:\n    paths:\n      - '.changes/*.md'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: check change files end with .md\n        run: |\n          for file in .changes/*\n          do\n            if [[ ! \"$file\" =~ \\.(md|json)$ ]]; then\n              echo \".changes directory should only contain files that end with .md\"\n              echo \"found an invalid file in .changes directory:\"\n              echo \"$file\"\n              exit 1\n            fi\n          done\n\n      - uses: dorny/paths-filter@v3\n        id: filter\n        with:\n          list-files: shell\n          filters: |\n            changes:\n              - added|modified: '.changes/*.md'\n\n      - name: check\n        run: node ./.scripts/ci/check-change-tags.js ${{ steps.filter.outputs.changes_files }}\n        if: ${{ steps.filter.outputs.changes == 'true' }}\n"
  },
  {
    "path": ".github/workflows/check-generated-files.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: check generated files\n\non:\n  pull_request:\n    paths:\n      - '.github/workflows/check-generated-files.yml'\n      - 'pnpm-lock.yaml'\n      - 'packages/api/src/**'\n      - 'crates/tauri/scripts/bundle.global.js'\n      - 'crates/tauri-utils/src/config.rs'\n      - 'crates/tauri-cli/config.schema.json'\n      - 'crates/tauri-schema-generator/schemas/*.json'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  changes:\n    runs-on: ubuntu-latest\n    outputs:\n      api: ${{ steps.filter.outputs.api }}\n      schema: ${{ steps.filter.outputs.schema }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dorny/paths-filter@v3\n        id: filter\n        with:\n          filters: |\n            api:\n              - 'pnpm-lock.yaml'\n              - 'packages/api/src/**'\n              - 'crates/tauri/scripts/bundle.global.js'\n            schema:\n              - 'crates/tauri-utils/src/config.rs'\n              - 'crates/tauri-cli/config.schema.json'\n              - 'crates/tauri-schema-generator/schemas/*.json'\n\n  api:\n    runs-on: ubuntu-latest\n    needs: changes\n    if: needs.changes.outputs.api == 'true'\n    steps:\n      - uses: actions/checkout@v4\n      - run: npm i -g --force corepack\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 'lts/*'\n          cache: 'pnpm'\n\n      - name: install deps\n        run: pnpm i --frozen-lockfile\n      - name: build api\n        run: pnpm build\n        working-directory: packages/api\n      - name: check api\n        run: ./.scripts/ci/has-diff.sh\n\n  schema:\n    runs-on: ubuntu-latest\n    needs: changes\n    if: needs.changes.outputs.schema == 'true'\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: install stable\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: install Linux dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y libgtk-3-dev\n\n      - uses: Swatinem/rust-cache@v2\n\n      - name: generate schemas\n        run: cargo build --manifest-path ./crates/tauri-schema-generator/Cargo.toml\n\n      - name: check schemas\n        run: ./.scripts/ci/has-diff.sh\n"
  },
  {
    "path": ".github/workflows/check-license-header.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: check license headers\n\non:\n  pull_request:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dorny/paths-filter@v3\n        id: filter\n        with:\n          list-files: shell\n          filters: |\n            added:\n              - added: '**'\n      - name: check header license on new files\n        if: ${{ steps.filter.outputs.added == 'true' }}\n        run: node ./.scripts/ci/check-license-header.js ${{ steps.filter.outputs.added_files }}\n"
  },
  {
    "path": ".github/workflows/covector-comment-on-fork.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: covector comment\non:\n  workflow_run:\n    workflows: [covector status] # the `name` of the workflow run on `pull_request` running `status` with `comment: true`\n    types:\n      - completed\n\n# note all other permissions are set to none if not specified\n#  and these set the permissions for `secrets.GITHUB_TOKEN`\npermissions:\n  # to read the action artifacts on `covector status` workflows\n  actions: read\n  # to write the comment\n  pull-requests: write\n\njobs:\n  comment:\n    runs-on: ubuntu-latest\n    if: github.event.workflow_run.conclusion == 'success' &&\n      (github.event.workflow_run.head_repository.full_name != github.repository || github.actor == 'dependabot[bot]')\n    steps:\n      - name: covector status\n        uses: jbolda/covector/packages/action@covector-v0\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          command: 'status'\n"
  },
  {
    "path": ".github/workflows/covector-status.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: covector status\non: [pull_request]\n\njobs:\n  covector:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - name: covector status\n        uses: jbolda/covector/packages/action@covector-v0\n        id: covector\n        with:\n          command: 'status'\n          token: ${{ secrets.GITHUB_TOKEN }}\n          comment: true\n"
  },
  {
    "path": ".github/workflows/covector-version-or-publish.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: covector version or publish\n\non:\n  push:\n    branches:\n      - dev\n\njobs:\n  run-integration-tests:\n    runs-on: ${{ matrix.platform }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - run: npm i -g --force corepack\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: 'pnpm'\n\n      - name: install stable\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: install Linux dependencies\n        if: matrix.platform == 'ubuntu-latest'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev libfuse2 librsvg2-dev\n\n      - uses: Swatinem/rust-cache@v2\n\n      - name: build CLI\n        run: cargo build --manifest-path ./crates/tauri-cli/Cargo.toml\n\n      - name: run integration tests\n        run: cargo test --test '*' -- --ignored\n\n      - name: run CLI tests\n        timeout-minutes: 30\n        run: |\n          cd ./packages/cli\n          pnpm i --frozen-lockfile\n          pnpm build\n          pnpm test\n\n  version-or-publish:\n    runs-on: ubuntu-latest\n    timeout-minutes: 65\n    permissions:\n      actions: write # required for workflow_dispatch\n      contents: write # required to create new releases\n      pull-requests: write # required to open version update pr\n      id-token: write # pnpm provenance / oidc token\n    outputs:\n      change: ${{ steps.covector.outputs.change }}\n      commandRan: ${{ steps.covector.outputs.commandRan }}\n      successfulPublish: ${{ steps.covector.outputs.successfulPublish }}\n    needs:\n      - run-integration-tests\n\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - run: npm i -g --force corepack\n      - uses: actions/setup-node@v6\n        with:\n          node-version: 24\n\n      - name: cargo login\n        run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}\n      - name: git config\n        run: |\n          git config --global user.name \"${{ github.event.pusher.name }}\"\n          git config --global user.email \"${{ github.event.pusher.email }}\"\n\n      - name: install Linux dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev librsvg2-dev\n\n      - name: covector version or publish (publish when no change files present)\n        uses: jbolda/covector/packages/action@covector-v0\n        id: covector\n        env:\n          CARGO_AUDIT_OPTIONS: ${{ secrets.CARGO_AUDIT_OPTIONS }}\n          NPM_CONFIG_PROVENANCE: true\n        with:\n          command: 'version-or-publish'\n          token: ${{ secrets.GITHUB_TOKEN }}\n          createRelease: true\n          recognizeContributors: true\n\n      - name: Sync Cargo.lock\n        if: steps.covector.outputs.commandRan == 'version'\n        run: cargo tree --depth 0\n\n      - name: Create Pull Request With Versions Bumped\n        if: steps.covector.outputs.commandRan == 'version'\n        uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # 7.0.6\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          branch: release/version-updates\n          title: Apply Version Updates From Current Changes\n          commit-message: 'apply version updates'\n          labels: 'version updates'\n          body: ${{ steps.covector.outputs.change }}\n          sign-commits: true\n\n      - name: Trigger doc update\n        if: |\n          steps.covector.outputs.successfulPublish == 'true' &&\n          steps.covector.outputs.packagesPublished != ''\n        uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # 3.0.0\n        with:\n          token: ${{ secrets.ORG_TAURI_BOT_PAT }}\n          repository: tauri-apps/tauri-docs\n          event-type: update-docs\n\n      - name: Trigger `@tauri-apps/cli` publishing workflow\n        if: |\n          steps.covector.outputs.successfulPublish == 'true' &&\n          contains(steps.covector.outputs.packagesPublished, '@tauri-apps/cli')\n        run: gh workflow run 31554138 -r dev -f releaseId=${{ steps.covector.outputs['-tauri-apps-cli-releaseId'] }}\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Trigger `tauri-cli` publishing workflow\n        if: |\n          steps.covector.outputs.successfulPublish == 'true' &&\n          contains(steps.covector.outputs.packagesPublished, 'tauri-cli')\n        run: gh workflow run 31554139 -r dev\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/deploy-schema-worker.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: deploy schema worker\n\non:\n  push:\n    branches:\n      - dev\n    paths:\n      - '.github/workflows/deploy-schema-worker.yml'\n      - 'crates/tauri-schema-worker/**'\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: cloudflare/wrangler-action@v3\n        with:\n          command: deploy\n          workingDirectory: 'crates/tauri-schema-worker'\n          apiToken: ${{ secrets.SCHEMA_WORKER_CLOUDFLARE_API_TOKEN }}\n          accountId: ${{ secrets.SCHEMA_WORKER_CLOUDFLARE_ACCOUNT_ID  }}\n"
  },
  {
    "path": ".github/workflows/docker.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: docker\n\non:\n  workflow_dispatch:\n  #pull_request:\n  #  paths:\n  #    - '.docker/**'\n  #    - '.github/workflows/docker.yml'\n\njobs:\n  setup:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: install stable\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: install Linux dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y libgtk-3-dev\n\n      - name: install cross\n        run: cargo install cross --git https://github.com/cross-rs/cross\n\n      - name: Upload cross\n        uses: actions/upload-artifact@v4\n        with:\n          name: cross\n          path: '~/.cargo/bin/cross'\n          if-no-files-found: error\n\n      - name: build CLI\n        run: cargo build --manifest-path ./crates/tauri-cli/Cargo.toml\n\n      - name: Upload CLI\n        uses: actions/upload-artifact@v4\n        with:\n          name: cargo-tauri\n          path: crates/tauri-cli/target/debug/cargo-tauri\n          if-no-files-found: error\n\n  docker:\n    needs: setup\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: false\n      matrix:\n        target:\n          - { name: 'aarch64-unknown-linux-gnu', filename: 'aarch64' }\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: install stable\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: ${{ matrix.target.name }}\n\n      - run: npm i -g --force corepack\n      - name: Setup node\n        uses: actions/setup-node@v4\n        with:\n          node-version: 'lts/*'\n\n      - name: Download cross\n        uses: actions/download-artifact@v4.1.7\n        with:\n          name: cross\n          path: '~/.cargo/bin'\n\n      - name: Download CLI\n        uses: actions/download-artifact@v4.1.7\n        with:\n          name: cargo-tauri\n          path: 'examples/api'\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v2\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v2\n\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v2\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Build and export to Docker\n        uses: docker/build-push-action@v3\n        with:\n          context: .docker/cross\n          file: .docker/cross/${{ matrix.target.filename }}.Dockerfile\n          load: true\n          tags: ${{ matrix.target.name }}:latest\n\n      - name: install dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev\n\n      - name: Test\n        run: |\n          cd packages/api\n          pnpm i --frozen-lockfile && pnpm build\n          cd ../../examples/api\n          pnpm i --frozen-lockfile\n          . .setup-cross.sh\n          chmod +x cargo-tauri\n          chmod +x $HOME/.cargo/bin/cross\n          ./cargo-tauri build --runner cross --bundles deb --target ${{ matrix.target.name }} --verbose\n\n      - name: Build and push\n        uses: docker/build-push-action@v3\n        with:\n          context: .docker/cross\n          file: .docker/cross/${{ matrix.target.filename }}.Dockerfile\n          push: true\n          tags: ghcr.io/${{ github.repository }}/${{ matrix.target.name }}:latest\n"
  },
  {
    "path": ".github/workflows/fmt.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: check formatting\n\non:\n  pull_request:\n\njobs:\n  rustfmt:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: install Rust stable and rustfmt\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          components: rustfmt\n\n      - name: run cargo fmt\n        run: cargo fmt --all -- --check\n\n  prettier:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: npm i -g --force corepack\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 'lts/*'\n          cache: 'pnpm'\n      - run: pnpm i --frozen-lockfile\n      - run: pnpm format:check\n\n  taplo:\n    name: taplo (.toml files)\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: install Rust stable\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: install taplo-cli\n        uses: taiki-e/install-action@v2\n        with:\n          tool: taplo-cli\n\n      - run: taplo fmt --check --diff\n"
  },
  {
    "path": ".github/workflows/lint-js.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: lint js\n\non:\n  pull_request:\n    paths:\n      - '.github/workflows/lint-js.yml'\n      - 'packages/**'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  eslint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: npm i -g --force corepack\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 'lts/*'\n          cache: 'pnpm'\n      - run: pnpm i --frozen-lockfile\n      - run: pnpm eslint:check\n\n  typescript:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: npm i -g --force corepack\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 'lts/*'\n          cache: 'pnpm'\n      - run: pnpm i --frozen-lockfile\n      - run: pnpm ts:check\n"
  },
  {
    "path": ".github/workflows/lint-rust.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: lint rust\n\non:\n  push:\n    branches:\n      - dev\n  pull_request:\n    paths:\n      - '.github/workflows/lint-rust.yml'\n      - 'crates/**'\n\nenv:\n  RUST_BACKTRACE: 1\n  CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency.\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  clippy:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: install rust stable and clippy\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          components: clippy\n\n      - name: install dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y libgtk-3-dev webkit2gtk-4.1 libayatana-appindicator3-dev\n\n      - uses: Swatinem/rust-cache@v2\n\n      - run: cargo clippy --all-targets --all-features -- -D warnings\n"
  },
  {
    "path": ".github/workflows/publish-cli-js.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: publish `@tauri-apps/cli`\nenv:\n  DEBUG: napi:*\n  APP_NAME: cli\n  MACOSX_DEPLOYMENT_TARGET: '10.13'\non:\n  workflow_dispatch:\n    inputs:\n      releaseId:\n        description: 'ID of the `@tauri-apps/cli` release'\n        required: true\n  repository_dispatch:\n    types: [publish-js-cli]\n\ndefaults:\n  run:\n    working-directory: packages/cli/\n\npermissions:\n  contents: write # update release\n  id-token: write # oidc token\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        settings:\n          - host: macos-latest\n            target: x86_64-apple-darwin\n            architecture: x64\n            build: |\n              pnpm build --target=x86_64-apple-darwin\n              strip -x *.node\n          - host: windows-latest\n            build: pnpm build\n            target: x86_64-pc-windows-msvc\n            architecture: x64\n          - host: windows-latest\n            build: pnpm build --target i686-pc-windows-msvc\n            target: i686-pc-windows-msvc\n            architecture: x64\n          - host: windows-latest\n            architecture: x64\n            target: aarch64-pc-windows-msvc\n            build: pnpm build --target aarch64-pc-windows-msvc\n          - host: ubuntu-22.04\n            target: x86_64-unknown-linux-gnu\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian\n            build: |\n              npm i -g --force corepack\n              cd packages/cli\n              pnpm build --target x86_64-unknown-linux-gnu\n              strip *.node\n          - host: ubuntu-22.04\n            target: x86_64-unknown-linux-musl\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine\n            build: |\n              cd packages/cli\n              pnpm build\n              strip *.node\n          - host: macos-latest\n            target: aarch64-apple-darwin\n            build: |\n              pnpm build --features native-tls-vendored --target=aarch64-apple-darwin\n              strip -x *.node\n          - host: ubuntu-22.04\n            target: aarch64-unknown-linux-gnu\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64\n            build: |\n              npm i -g --force corepack\n              cd packages/cli\n              pnpm build --target aarch64-unknown-linux-gnu\n              aarch64-unknown-linux-gnu-strip *.node\n          - host: ubuntu-22.04\n            architecture: x64\n            target: armv7-unknown-linux-gnueabihf\n            setup: |\n              sudo apt-get update\n              sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y\n            build: |\n              pnpm build --target=armv7-unknown-linux-gnueabihf\n              arm-linux-gnueabihf-strip *.node\n          - host: ubuntu-22.04\n            architecture: x64\n            target: aarch64-unknown-linux-musl\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine\n            build: |\n              cd packages/cli\n              rustup target add aarch64-unknown-linux-musl\n              pnpm build --target aarch64-unknown-linux-musl\n              /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node\n          - host: ubuntu-22.04\n            architecture: x64\n            target: riscv64gc-unknown-linux-gnu\n            setup: |\n              sudo apt-get update\n              sudo apt-get install gcc-riscv64-linux-gnu g++-riscv64-linux-gnu -y\n            build: |\n              pnpm build --target=riscv64gc-unknown-linux-gnu\n              riscv64-linux-gnu-strip *.node\n    name: stable - ${{ matrix.settings.target }} - node@20\n    runs-on: ${{ matrix.settings.host }}\n    steps:\n      - uses: actions/checkout@v4\n      - run: npm i -g --force corepack\n      - name: Setup node\n        uses: actions/setup-node@v4\n        if: ${{ !matrix.settings.docker }}\n        with:\n          node-version: 20\n          cache: 'pnpm'\n          architecture: ${{ matrix.settings.architecture }}\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n        if: ${{ !matrix.settings.docker }}\n        with:\n          targets: ${{ matrix.settings.target }}\n      - uses: Swatinem/rust-cache@v2\n        with:\n          key: ${{ matrix.settings.target }}\n        if: ${{ matrix.settings.docker }}\n      - name: Setup toolchain\n        run: ${{ matrix.settings.setup }}\n        if: ${{ matrix.settings.setup }}\n        shell: bash\n      - name: Install dependencies\n        run: pnpm i --frozen-lockfile --ignore-scripts\n\n      - name: Build in docker\n        uses: addnab/docker-run-action@v3\n        if: ${{ matrix.settings.docker }}\n        with:\n          image: ${{ matrix.settings.docker }}\n          options: --user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/root/.cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/root/.cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/root/.cargo/registry/index -v ${{ github.workspace }}:/build -w /build\n          run: ${{ matrix.settings.build }}\n\n      - name: Build\n        run: ${{ matrix.settings.build }}\n        if: ${{ !matrix.settings.docker }}\n        shell: bash\n\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: bindings-${{ matrix.settings.target }}\n          path: packages/cli/${{ env.APP_NAME }}.*.node\n          if-no-files-found: error\n  #  build-freebsd:\n  #    runs-on: macos-10.15\n  #    name: Build FreeBSD\n  #    steps:\n  #      - uses: actions/checkout@v4\n  #      - name: Build\n  #        id: build\n  #        uses: vmactions/freebsd-vm@v0.1.6\n  #        env:\n  #          DEBUG: napi:*\n  #          RUSTUP_HOME: /usr/local/rustup\n  #          CARGO_HOME: /usr/local/cargo\n  #          RUSTUP_IO_THREADS: 1\n  #        with:\n  #          envs: DEBUG RUSTUP_HOME CARGO_HOME RUSTUP_IO_THREADS\n  #          usesh: true\n  #          mem: 3000\n  #          prepare: |\n  #            pkg install -y curl node14 python2\n  #            curl -qL https://www.npmjs.com/install.sh | sh\n  #            npm install -g pnpm\n  #            curl https://sh.rustup.rs -sSf --output rustup.sh\n  #            sh rustup.sh -y --profile minimal --default-toolchain stable\n  #            export PATH=\"/usr/local/cargo/bin:$PATH\"\n  #            echo \"~~~~ rustc --version ~~~~\"\n  #            rustc --version\n  #            echo \"~~~~ node -v ~~~~\"\n  #            node -v\n  #            echo \"~~~~ pnpm --version ~~~~\"\n  #            pnpm --version\n  #          run: |\n  #            export PATH=\"/usr/local/cargo/bin:$PATH\"\n  #            pwd\n  #            ls -lah\n  #            whoami\n  #            env\n  #            freebsd-version\n  #            cd ./packages/cli/\n  #            pnpm i --frozen-lockfile --ignore-scripts\n  #            pnpm build\n  #            strip -x *.node\n  #            rm -rf node_modules\n  #            rm -rf ../../target\n  #      - name: Upload artifact\n  #        uses: actions/upload-artifact@v4\n  #        with:\n  #          name: bindings-freebsd\n  #          path: packages/cli/${{ env.APP_NAME }}.*.node\n  #          if-no-files-found: error\n  test-macOS-windows-binding:\n    name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        settings:\n          - host: macos-latest\n            target: aarch64-apple-darwin\n          - host: windows-latest\n            target: x86_64-pc-windows-msvc\n        node:\n          - '18'\n          - '20'\n    runs-on: ${{ matrix.settings.host }}\n    steps:\n      - uses: actions/checkout@v4\n      - run: npm i -g --force corepack\n      - name: Setup node\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node }}\n          cache: 'pnpm'\n      - name: Install dependencies\n        run: pnpm i --frozen-lockfile --ignore-scripts\n      - name: Download artifacts\n        uses: actions/download-artifact@v4.1.7\n        with:\n          name: bindings-${{ matrix.settings.target }}\n          path: 'packages/cli/'\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: Test bindings\n        run: pnpm test\n  test-linux-x64-gnu-binding:\n    name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - '18'\n          - '20'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: npm i -g --force corepack\n      - name: Setup node\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node }}\n          cache: 'pnpm'\n      - name: Install dependencies\n        run: pnpm i --frozen-lockfile --ignore-scripts\n      - name: Download artifacts\n        uses: actions/download-artifact@v4.1.7\n        with:\n          name: bindings-x86_64-unknown-linux-gnu\n          path: 'packages/cli'\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: install system dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev librsvg2-dev\n      - name: Test bindings\n        run: pnpm test\n  test-linux-x64-musl-binding:\n    name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - '18'\n          - '20'\n    runs-on: ubuntu-latest\n    container:\n      image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine\n    steps:\n      - uses: actions/checkout@v4\n      - run: npm i -g --force corepack\n      - name: Setup node\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node }}\n          cache: 'pnpm'\n      - name: Install dependencies\n        run: pnpm i --frozen-lockfile --ignore-scripts\n      - name: Download artifacts\n        uses: actions/download-artifact@v4.1.7\n        with:\n          name: bindings-x86_64-unknown-linux-musl\n          path: 'packages/cli/'\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: Setup and run tests\n        run: |\n          pnpm tauri --help\n          ls -la\n      #- name: Setup and run tests\n      #  run: |\n      #    rustup install stable\n      #    rustup default stable\n      #    pnpm test\n      #    ls -la\n  test-linux-arm-bindings:\n    name: Test bindings on ${{ matrix.image }} - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - '18'\n          - '20'\n        image:\n          - ghcr.io/napi-rs/napi-rs/nodejs:aarch64-16\n    runs-on: ubuntu-latest\n    steps:\n      - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset\n        working-directory: ${{ github.workspace }}\n      - uses: actions/checkout@v4\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: Download aarch64-gnu artifacts\n        uses: actions/download-artifact@v4.1.7\n        with:\n          name: bindings-aarch64-unknown-linux-gnu\n          path: 'packages/cli'\n      - name: Download armv7-gnueabihf artifacts\n        uses: actions/download-artifact@v4.1.7\n        with:\n          name: bindings-armv7-unknown-linux-gnueabihf\n          path: 'packages/cli/'\n      # TODO: actually run test, blocked by https://github.com/rust-lang/cargo/issues/8719\n      - uses: addnab/docker-run-action@v3\n        with:\n          image: ${{ matrix.image }}\n          options: '-v ${{ github.workspace }}:/build -w /build -e RUSTUP_HOME=/usr/local/rustup -e CARGO_HOME=/usr/local/cargo'\n          shell: bash\n          run: |\n            set -e\n            export PATH=/usr/local/cargo/bin/:/usr/local/fnm:$PATH\n            apt-get update\n            DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install --no-install-recommends -y unzip libayatana-appindicator3-dev\n            bash\n            curl https://sh.rustup.rs -sSf | bash -s -- -y\n            curl -fsSL https://fnm.vercel.app/install | bash -s -- --install-dir \"/usr/local/fnm\" --skip-shell\n            eval \"$(fnm env --use-on-cd)\"\n            fnm install ${{ matrix.node }}\n            fnm use ${{ matrix.node }}\n            cd packages/cli\n            node tauri.js --help\n            ls -la\n  publish:\n    name: Publish\n    runs-on: ubuntu-latest\n    needs:\n      #- build-freebsd\n      - test-macOS-windows-binding\n      - test-linux-x64-gnu-binding\n      - test-linux-x64-musl-binding\n      #- test-linux-arm-bindings\n    steps:\n      - uses: actions/checkout@v4\n      - run: npm i -g --force corepack\n      - name: Setup node\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          cache: 'pnpm'\n      - name: Install dependencies\n        run: pnpm i --frozen-lockfile --ignore-scripts\n      - name: Download all artifacts\n        uses: actions/download-artifact@v4.1.7\n        with:\n          path: packages/cli/artifacts\n      - name: Move artifacts\n        run: pnpm artifacts\n      - name: List packages\n        run: ls -R ./npm\n        shell: bash\n      - name: Publish\n        run: |\n          npm publish\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          NODE_AUTH_TOKEN: ''\n          RELEASE_ID: ${{ github.event.client_payload.releaseId || inputs.releaseId }}\n"
  },
  {
    "path": ".github/workflows/publish-cli-rs.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: publish `tauri-cli`\nenv:\n  MACOSX_DEPLOYMENT_TARGET: '10.13'\non:\n  workflow_dispatch:\n  repository_dispatch:\n    types: [publish-clirs]\n\njobs:\n  build:\n    runs-on: ${{ matrix.config.os }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n          - os: ubuntu-22.04\n            rust_target: x86_64-unknown-linux-gnu\n            ext: ''\n            args: ''\n          - os: macos-latest\n            rust_target: x86_64-apple-darwin\n            ext: ''\n            args: ''\n          - os: macos-latest\n            rust_target: aarch64-apple-darwin\n            ext: ''\n            args: ''\n          - os: windows-latest\n            rust_target: x86_64-pc-windows-msvc\n            ext: '.exe'\n            args: ''\n          - os: windows-latest\n            rust_target: aarch64-pc-windows-msvc\n            ext: '.exe'\n            args: ''\n          - os: ubuntu-22.04\n            rust_target: riscv64gc-unknown-linux-gnu\n            ext: ''\n            args: ''\n            cross: true\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: 'Setup Rust'\n        if: ${{ !matrix.config.cross }}\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: ${{ matrix.config.rust_target }}\n\n      - uses: Swatinem/rust-cache@v2\n        if: ${{ !matrix.config.cross }}\n        with:\n          key: ${{ matrix.config.rust_target }}\n\n      - name: install Linux dependencies\n        if: ${{ !matrix.config.cross && startsWith(matrix.config.os, 'ubuntu') }}\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y libgtk-3-dev\n\n      - name: Install cross\n        if: ${{ matrix.config.cross }}\n        uses: taiki-e/install-action@v2\n        with:\n          tool: cross@0.2.5\n\n      - name: Build CLI\n        if: ${{ !matrix.config.cross }}\n        run: cargo build --manifest-path ./crates/tauri-cli/Cargo.toml --profile release-size-optimized ${{ matrix.config.args }}\n\n      - name: Build CLI (cross)\n        if: ${{ matrix.config.cross }}\n        run: cross build --manifest-path ./crates/tauri-cli/Cargo.toml --target ${{ matrix.config.rust_target }} --profile release-size-optimized ${{ matrix.config.args }}\n\n      - name: Upload CLI\n        if: ${{ !matrix.config.cross }}\n        uses: actions/upload-artifact@v4\n        with:\n          name: cargo-tauri-${{ matrix.config.rust_target }}${{ matrix.config.ext }}\n          path: target/release-size-optimized/cargo-tauri${{ matrix.config.ext }}\n          if-no-files-found: error\n\n      - name: Upload CLI (cross)\n        if: ${{ matrix.config.cross }}\n        uses: actions/upload-artifact@v4\n        with:\n          name: cargo-tauri-${{ matrix.config.rust_target }}${{ matrix.config.ext }}\n          path: target/${{ matrix.config.rust_target }}/release-size-optimized/cargo-tauri${{ matrix.config.ext }}\n          if-no-files-found: error\n\n  upload:\n    needs: build\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Download built CLIs\n        uses: actions/download-artifact@v4.1.7\n        with:\n          path: outputs\n\n      - name: Pack archives\n        run: ./.scripts/ci/pack-cli.sh\n\n      - name: Get CLI version\n        run: echo \"CLI_VERSION=$(cat crates/tauri-cli/metadata-v2.json | jq '.\"cli.js\".version' -r)\" >> $GITHUB_ENV\n\n      - name: Publish release\n        uses: softprops/action-gh-release@50195ba7f6f93d1ac97ba8332a178e008ad176aa\n        with:\n          tag_name: tauri-cli-v${{ env.CLI_VERSION }}\n          files: |\n            outputs/cargo-tauri-*.zip\n            outputs/cargo-tauri-*.tgz\n"
  },
  {
    "path": ".github/workflows/supply-chain.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: supply chain health status\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '0 0 * * *'\n  push:\n    branches:\n      - dev\n    paths:\n      - '.github/workflows/supply-chain.yml'\n      - '**/Cargo.lock'\n      - '**/Cargo.toml'\njobs:\n  cargo-vet:\n    name: check rust dependencies with cargo vet\n    runs-on: ubuntu-latest\n    env:\n      CARGO_VET_VERSION: 0.9.1\n    steps:\n      - uses: actions/checkout@master\n      - name: Install Rust\n        run: rustup update stable && rustup default stable\n\n      - uses: actions/cache@v4\n        with:\n          path: ${{ runner.tool_cache }}/cargo-vet\n          key: cargo-vet-bin-${{ env.CARGO_VET_VERSION }}\n\n      - name: Add the tool cache directory to the search path\n        run: echo \"${{ runner.tool_cache }}/cargo-vet/bin\" >> $GITHUB_PATH\n\n      - name: Ensure that the tool cache is populated with the cargo-vet binary\n        run: cargo install --root ${{ runner.tool_cache }}/cargo-vet --version ${{ env.CARGO_VET_VERSION }} cargo-vet\n\n      # Enable this again to break the workflow once we have a reasonable amount of suggestions to get to a clean base line\n      #      - name: Invoke cargo-vet\n      #        run: cargo vet --locked\n\n      - name: Provide audit suggestions\n        run: cargo vet suggest\n"
  },
  {
    "path": ".github/workflows/test-android.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: test android\n\non:\n  pull_request:\n    paths:\n      - '.github/workflows/test-android.yml'\n      - 'crates/tauri-cli/templates/mobile/android/**'\n      - 'crates/tauri-cli/src/mobile/**'\n      - '!crates/tauri-cli/src/mobile/ios.rs'\n      - '!crates/tauri-cli/src/mobile/ios/**'\n      - 'crates/tauri-build/src/mobile.rs'\n      - 'crates/tauri/mobile/android/**'\n      - 'crates/tauri/mobile/android-codegen/**'\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  test:\n    runs-on: ${{ matrix.platform }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: install Rust 1.77.2\n        uses: dtolnay/rust-toolchain@1.77.2\n\n      - name: install Linux dependencies\n        if: matrix.platform == 'ubuntu-latest'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y libgtk-3-dev webkit2gtk-4.1\n\n      - run: npm i -g --force corepack\n      - name: setup node\n        uses: actions/setup-node@v4\n        with:\n          node-version: lts/*\n          cache: 'pnpm'\n\n      - uses: actions/setup-java@v3\n        with:\n          distribution: temurin\n          java-version: 17\n          cache: 'gradle'\n\n      - name: Setup NDK\n        uses: nttld/setup-ndk@v1\n        id: setup-ndk\n        with:\n          ndk-version: r25b\n          local-cache: true\n\n      # TODO check after https://github.com/nttld/setup-ndk/issues/518 is fixed\n      - name: Restore Android Symlinks\n        if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'macos-latest'\n        run: |\n          directory=\"${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin\"\n          find \"$directory\" -type l | while read link; do\n              current_target=$(readlink \"$link\")\n              new_target=\"$directory/$(basename \"$current_target\")\"\n              ln -sf \"$new_target\" \"$link\"\n              echo \"Changed $(basename \"$link\") from $current_target to $new_target\"\n          done\n\n      - uses: Swatinem/rust-cache@v2\n\n      - name: build CLI\n        run: cargo build --manifest-path ./crates/tauri-cli/Cargo.toml\n\n      - name: move CLI to cargo bin dir\n        if: matrix.platform != 'windows-latest'\n        run: mv ./target/debug/cargo-tauri $HOME/.cargo/bin\n\n      - name: move CLI to cargo bin dir\n        if: matrix.platform == 'windows-latest'\n        run: mv ./target/debug/cargo-tauri.exe $HOME/.cargo/bin\n\n      - run: pnpm i --frozen-lockfile\n\n      - name: build Tauri API\n        working-directory: ./packages/api\n        run: pnpm build\n\n      - name: init Android Studio project\n        working-directory: ./examples/api\n        run: cargo tauri android init\n        env:\n          NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}\n\n      - name: build APK\n        working-directory: ./examples/api\n        run: cargo tauri android build\n        env:\n          NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}\n"
  },
  {
    "path": ".github/workflows/test-cli-js.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: test `@tauri-apps/cli`\n\non:\n  push:\n    branches:\n      - dev\n  pull_request:\n    paths:\n      - '.github/workflows/test-cli-js.yml'\n      - 'packages/cli/**'\n      # currently` @tauri-apps/cli` only tests the template\n      - 'crates/tauri-cli/templates/app/**'\n\nenv:\n  RUST_BACKTRACE: 1\n  CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency.\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  test:\n    runs-on: ${{ matrix.platform }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: install Rust stable\n        uses: dtolnay/rust-toolchain@stable\n\n      - run: npm i -g --force corepack\n      - name: setup node\n        uses: actions/setup-node@v4\n        with:\n          node-version: 'lts/*'\n          cache: 'pnpm'\n\n      - name: install Linux dependencies\n        if: matrix.platform == 'ubuntu-latest'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev librsvg2-dev\n\n      - uses: Swatinem/rust-cache@v2\n\n      - name: test\n        timeout-minutes: 30\n        run: |\n          cd ./packages/cli\n          pnpm i --frozen-lockfile\n          pnpm build\n          pnpm test\n"
  },
  {
    "path": ".github/workflows/test-cli-rs.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: test `tauri-cli`\n\non:\n  push:\n    branches:\n      - dev\n  pull_request:\n    paths:\n      - '.github/workflows/test-cli-rs.yml'\n      - 'crates/tauri-utils/**'\n      - 'crates/tauri-bundler/**'\n      - 'crates/tauri-cli/**'\n\nenv:\n  RUST_BACKTRACE: 1\n  CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency.\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  test:\n    runs-on: ${{ matrix.platform.os }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        platform:\n          - { target: x86_64-pc-windows-msvc, os: windows-latest }\n          - {\n              target: aarch64-pc-windows-msvc,\n              os: windows-latest,\n              args: --no-default-features --features native-tls-vendored\n            }\n          - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest }\n          - { target: x86_64-apple-darwin, os: macos-latest }\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: 'Setup Rust'\n        uses: dtolnay/rust-toolchain@1.77.2\n        with:\n          targets: ${{ matrix.platform.target }}\n\n      - name: install Linux dependencies\n        if: matrix.platform.os == 'ubuntu-latest'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y libgtk-3-dev webkit2gtk-4.1 libayatana-appindicator3-dev librsvg2-dev\n\n      - uses: Swatinem/rust-cache@v2\n\n      - name: test CLI\n        run: cargo test --manifest-path ./crates/tauri-cli/Cargo.toml ${{ matrix.platform.args }}\n"
  },
  {
    "path": ".github/workflows/test-core.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: test core\n\non:\n  push:\n    branches:\n      - dev\n  pull_request:\n    paths:\n      - '.github/workflows/test-core.yml'\n      - 'crates/**'\n      - '!crates/tauri/scripts/**'\n      - '!crates/tauri-cli/**'\n      - '!crates/tauri-bundler/**'\n      - '!crates/tauri-macos-sign/**'\n      - '!crates/tauri-schema-generator/**'\n\nenv:\n  RUST_BACKTRACE: 1\n  CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency.\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  test:\n    runs-on: ${{ matrix.platform.os }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        platform:\n          - {\n              target: x86_64-pc-windows-msvc,\n              os: windows-latest,\n              toolchain: '1.77.2',\n              cross: false,\n              command: 'test'\n            }\n          - {\n              target: x86_64-unknown-linux-gnu,\n              os: ubuntu-latest,\n              toolchain: '1.77.2',\n              cross: false,\n              command: 'test'\n            }\n          - {\n              target: aarch64-apple-darwin,\n              os: macos-14,\n              toolchain: '1.77.2',\n              cross: false,\n              command: 'test'\n            }\n          - {\n              target: aarch64-apple-ios,\n              os: macos-latest,\n              toolchain: '1.77.2',\n              cross: false,\n              command: 'build'\n            }\n          - {\n              target: aarch64-linux-android,\n              os: ubuntu-latest,\n              toolchain: '1.77.2',\n              cross: true,\n              command: 'build'\n            }\n        features:\n          - { args: --no-default-features, key: no-default }\n          - { args: --all-features, key: all }\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: install Rust\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ matrix.platform.toolchain }}\n          targets: ${{ matrix.platform.target }}\n\n      - name: install Linux dependencies\n        if: contains(matrix.platform.target, 'unknown-linux')\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y webkit2gtk-4.1 libxdo-dev libayatana-appindicator3-dev\n\n      - uses: Swatinem/rust-cache@v2\n        with:\n          key: ${{ matrix.platform.target }}\n          save-if: ${{ matrix.features.key == 'all' }}\n\n      - name: test tauri-utils\n        if: ${{ !matrix.platform.cross }}\n        # Using --lib --bins --tests to skip doc tests\n        run: cargo ${{ matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --lib --bins --tests --manifest-path crates/tauri-utils/Cargo.toml\n\n      - name: test tauri-utils (using cross)\n        if: ${{ matrix.platform.cross }}\n        # Using --lib --bins --tests to skip doc tests\n        run: |\n          cargo install cross --git https://github.com/cross-rs/cross --rev 51f46f296253d8122c927c5bb933e3c4f27cc317 --locked\n          cross ${{ matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --lib --bins --tests --manifest-path crates/tauri-utils/Cargo.toml\n\n      - name: test tauri\n        if: ${{ !matrix.platform.cross }}\n        run: cargo ${{ matrix.features.key == 'no-default' && 'check' || matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --manifest-path crates/tauri/Cargo.toml\n\n      - name: test tauri (using cross)\n        if: ${{ matrix.platform.cross }}\n        run: |\n          cargo install cross --git https://github.com/cross-rs/cross --rev 51f46f296253d8122c927c5bb933e3c4f27cc317 --locked\n          cross ${{ matrix.features.key == 'no-default' && 'check' || matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} --manifest-path crates/tauri/Cargo.toml\n"
  },
  {
    "path": ".github/workflows/udeps.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname: Udeps\n\non:\n  push:\n    branches:\n      - dev\n\nenv:\n  RUST_BACKTRACE: 1\n  CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency.\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  changes:\n    runs-on: ubuntu-latest\n    outputs:\n      tauri: ${{ steps.filter.outputs.tauri }}\n      build: ${{ steps.filter.outputs.build }}\n      codegen: ${{ steps.filter.outputs.codegen }}\n      macros: ${{ steps.filter.outputs.macros }}\n      runtime: ${{ steps.filter.outputs.runtime }}\n      wry: ${{ steps.filter.outputs.wry }}\n      utils: ${{ steps.filter.outputs.utils }}\n      bundler: ${{ steps.filter.outputs.bundler }}\n      cli: ${{ steps.filter.outputs.cli }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dorny/paths-filter@v3\n        id: filter\n        with:\n          filters: |\n            tauri:\n              - 'crates/tauri/**'\n              - '!crates/tauri/scripts/**'\n            build:\n              - 'crates/tauri-build/**'\n            codegen:\n              - 'crates/tauri-codegen/**'\n            macros:\n              - 'crates/tauri-macros/**'\n            runtime:\n              - 'crates/tauri-runtime/**'\n            wry:\n              - 'crates/tauri-runtime-wry/**'\n            utils:\n              - 'crates/tauri-utils/**'\n            bundler:\n              - 'crates/tauri-bundler/**'\n            cli:\n              - 'crates/tauri-cli/**'\n            macossign:\n              - 'crates/tauri-macos-sign/**'\n\n  setup:\n    runs-on: ubuntu-latest\n    needs: changes\n    if: |\n      needs.changes.outputs.tauri == 'true' ||\n      needs.changes.outputs.build == 'true' ||\n      needs.changes.outputs.codegen == 'true' ||\n      needs.changes.outputs.macros == 'true' ||\n      needs.changes.outputs.runtime == 'true' ||\n      needs.changes.outputs.wry == 'true' ||\n      needs.changes.outputs.utils == 'true' ||\n      needs.changes.outputs.bundler == 'true' ||\n      needs.changes.outputs.cli == 'true' ||\n      needs.changes.outputs.macossign == 'true'\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install Rust nightly\n        uses: dtolnay/rust-toolchain@nightly\n\n      - name: Install udeps\n        run: cargo install cargo-udeps --locked --force\n\n      - name: Upload udeps\n        uses: actions/upload-artifact@v4\n        with:\n          name: udeps\n          path: '~/.cargo/bin/cargo-udeps'\n          if-no-files-found: error\n\n      - name: Create udeps matrix\n        id: create-matrix\n        env:\n          TAURI: ${{ needs.changes.outputs.tauri == 'true' }}\n          BUILD: ${{ needs.changes.outputs.build == 'true' }}\n          CODEGEN: ${{ needs.changes.outputs.codegen == 'true' }}\n          MACROS: ${{ needs.changes.outputs.macros == 'true' }}\n          RUNTIME: ${{ needs.changes.outputs.runtime == 'true' }}\n          WRY: ${{ needs.changes.outputs.wry == 'true' }}\n          UTILS: ${{ needs.changes.outputs.utils == 'true' }}\n          BUNDLER: ${{ needs.changes.outputs.bundler == 'true' }}\n          CLI: ${{ needs.changes.outputs.cli == 'true' }}\n          MACOSSIGN: ${{ needs.changes.outputs.macossign == 'true' }}\n        run: |\n          crates=()\n          if [ \"${TAURI}\" == \"true\" ]; then crates[${#crates[@]}]=\"\\\"./crates/tauri\\\"\"; fi\n          if [ \"${BUILD}\" == \"true\" ]; then crates[${#crates[@]}]=\"\\\"./crates/tauri-build\\\"\"; fi\n          if [ \"${CODEGEN}\" == \"true\" ]; then crates[${#crates[@]}]=\"\\\"./crates/tauri-codegen\\\"\"; fi\n          if [ \"${MACROS}\" == \"true\" ]; then crates[${#crates[@]}]=\"\\\"./crates/tauri-macros\\\"\"; fi\n          if [ \"${RUNTIME}\" == \"true\" ]; then crates[${#crates[@]}]=\"\\\"./crates/tauri-runtime\\\"\"; fi\n          if [ \"${WRY}\" == \"true\" ]; then crates[${#crates[@]}]=\"\\\"./crates/tauri-runtime-wry\\\"\"; fi\n          if [ \"${UTILS}\" == \"true\" ]; then crates[${#crates[@]}]=\"\\\"./crates/tauri-utils\\\"\"; fi\n          if [ \"${BUNDLER}\" == \"true\" ]; then crates[${#crates[@]}]=\"\\\"./crates/tauri-bundler\\\"\"; fi\n          if [ \"${CLI}\" == \"true\" ]; then crates[${#crates[@]}]=\"\\\"./crates/tauri-cli\\\"\"; fi\n          if [ \"${MACOSSIGN}\" == \"true\" ]; then crates[${#crates[@]}]=\"\\\"./crates/tauri-macos-sign\\\"\"; fi\n          echo \"matrix=[$crates]\" >> \"$GITHUB_OUTPUT\"\n    outputs:\n      matrix: ${{ steps.create-matrix.outputs.matrix }}\n\n  udeps:\n    runs-on: ubuntu-latest\n    needs: setup\n    strategy:\n      matrix:\n        path: ${{ fromJson(needs.setup.outputs.matrix) }}\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install Rust nightly\n        uses: dtolnay/rust-toolchain@nightly\n\n      - name: install dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y libgtk-3-dev\n\n      - uses: Swatinem/rust-cache@v2\n\n      - name: Download udeps\n        uses: actions/download-artifact@v4.1.7\n        with:\n          name: udeps\n          path: '~/.cargo/bin'\n\n      - run: chmod +x $HOME/.cargo/bin/cargo-udeps\n\n      - name: Install required packages\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev\n\n      - name: Run udeps\n        run: cargo udeps --manifest-path ${{ matrix.path }}/Cargo.toml --all-targets --all-features\n"
  },
  {
    "path": ".gitignore",
    "content": "# dependency directories\r\nnode_modules/\r\n\r\n# Optional npm and yarn cache directory\r\n.npm/\r\n.yarn/\r\n\r\n# Output of 'npm pack'\r\n*.tgz\r\n\r\n# dotenv environment variables file\r\n.env\r\n\r\n# .vscode workspace settings file\r\n.vscode/settings.json\r\n.vscode/launch.json\r\n.vscode/tasks.json\r\n\r\n# npm, yarn and bun lock files\r\npackage-lock.json\r\nyarn.lock\r\nbun.lockb\r\n\r\n# rust compiled folders\r\ntarget/\r\n\r\n# test video for streaming example\r\nstreaming_example_test_video.mp4\r\n\r\n# examples /gen directory\r\n/examples/**/src-tauri/gen/\r\n/bench/**/src-tauri/gen/\r\n\r\n# logs\r\nlogs\r\n*.log\r\nnpm-debug.log*\r\nyarn-debug.log*\r\nyarn-error.log*\r\n\r\n# runtime data\r\npids\r\n*.pid\r\n*.seed\r\n*.pid.lock\r\n\r\n# miscellaneous\r\n/.vs\r\n.DS_Store\r\n.Thumbs.db\r\n*.sublime*\r\n.idea\r\ndebug.log\r\nTODO.md\r\n.aider*\r\n"
  },
  {
    "path": ".prettierignore",
    "content": "/audits\n/.vscode\n\n# change files are hand-written and shouldn't be formatted\n/.changes/*\n!/.changes/config.json\n\n# dependcies and artifacts directories\nnode_modules/\ntarget/\ndist/\n\n# lock files\npnpm-lock.yaml\n\n# autogenerated and minimized js file\ncrates/tauri/scripts/bundle.global.js\n\n# this file is an IIFE, shouldn't be formatted\ncrates/tauri/scripts/process-ipc-message-fn.js\n\n# cli templates should be formatted manually\n# it also includes invalid json files that\n# prettier can't handle\ncrates/tauri-cli/templates\n\n# autogenerated files\n**/autogenerated/**/*.md\npackages/cli/index.js\npackages/cli/index.d.ts\ncrates/tauri-schema-worker/.wrangler\nCHANGELOG.md\n*schema.json\n\n# WiX templates\n*.wxs\n\n# examples /gen directory\nexamples/**/src-tauri/gen\nbench/**/src-tauri/gen\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"semi\": false,\n  \"trailingComma\": \"none\",\n  \"experimentalOperatorPosition\": \"start\"\n}\n"
  },
  {
    "path": ".scripts/ci/check-change-tags.js",
    "content": "#!/usr/bin/env node\n\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nconst fs = require('fs')\nconst path = require('path')\nconst ignorePackages = [\n  'tauri-macros',\n  'tauri-codegen',\n  'tauri-runtime',\n  'tauri-runtime-wry',\n  'tauri-driver'\n]\n\nconst covectorConfig = JSON.parse(\n  fs.readFileSync('.changes/config.json', 'utf8')\n)\nconst tags = Object.keys(covectorConfig.changeTags)\n\nconst missingTagsFiles = {}\nconst unknownTagsFiles = {}\n\nfunction checkChangeFiles(changeFiles) {\n  for (const file of changeFiles) {\n    const content = fs.readFileSync(file, 'utf8')\n    const [frontMatter] = /^---[\\s\\S.]*---\\n/i.exec(content)\n    const packages = frontMatter\n      .split('\\n')\n      .filter((l) => !(l === '---' || !l))\n      .map((l) => l.replace(/('|\")/g, '').split(':'))\n\n    for (const [package, _, tag] of packages) {\n      if (!tag) {\n        if (ignorePackages.includes(package)) continue\n\n        if (!missingTagsFiles[file]) missingTagsFiles[file] = []\n        missingTagsFiles[file].push(package)\n      } else if (!tags.includes(tag)) {\n        if (!unknownTagsFiles[file]) unknownTagsFiles[file] = []\n        unknownTagsFiles[file].push({ package, tag })\n      }\n    }\n  }\n  const missingTagsEntries = Object.entries(missingTagsFiles)\n  const unknownTagsEntries = Object.entries(unknownTagsFiles)\n  if (missingTagsEntries.length > 0 || unknownTagsEntries.length > 0) {\n    for (const [file, packages] of missingTagsEntries) {\n      for (const package of packages) {\n        console.error(\n          `Package \\`${package}\\` is missing a change tag in ${file} `\n        )\n      }\n    }\n\n    for (const [file, packages] of unknownTagsEntries) {\n      for (const { package, tag } of packages) {\n        console.error(\n          `Package \\`${package}\\` has an unknown change tag ${tag} in ${file} `\n        )\n      }\n    }\n\n    process.exit(1)\n  }\n}\n\nconst [_bin, _script, ...files] = process.argv\n\nif (files.length > 0) {\n  checkChangeFiles(\n    files.filter((f) => f.toLowerCase() !== '.changes/readme.md')\n  )\n} else {\n  const changeFiles = fs\n    .readdirSync('.changes')\n    .filter((f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md')\n    .map((p) => path.join('.changes', p))\n  checkChangeFiles(changeFiles)\n}\n"
  },
  {
    "path": ".scripts/ci/check-license-header.js",
    "content": "#!/usr/bin/env node\n\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nconst fs = require('fs')\nconst path = require('path')\nconst readline = require('readline')\n\nconst header = `Copyright 2019-2024 Tauri Programme within The Commons Conservancy\nSPDX-License-Identifier: Apache-2.0\nSPDX-License-Identifier: MIT`\nconst bundlerLicense =\n  '// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>'\nconst denoLicense =\n  '// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.'\n\nconst extensions = ['.rs', '.js', '.ts', '.yml', '.swift', '.kt']\nconst ignore = [\n  'target',\n  'templates',\n  'node_modules',\n  'gen',\n  'dist',\n  'bundle.global.js'\n]\n\nasync function checkFile(file) {\n  if (\n    extensions.some((e) => file.endsWith(e))\n    && !ignore.some((i) => file.includes(`/${i}/`) || path.basename(file) === i)\n  ) {\n    const fileStream = fs.createReadStream(file)\n    const rl = readline.createInterface({\n      input: fileStream,\n      crlfDelay: Infinity\n    })\n\n    let contents = ``\n    let i = 0\n    for await (let line of rl) {\n      // ignore empty lines, allow shebang and bundler license\n      if (\n        line.length === 0\n        || line.startsWith('#!')\n        || line.startsWith('// swift-tools-version:')\n        || line === bundlerLicense\n        || line === denoLicense\n      ) {\n        continue\n      }\n\n      // strip comment marker\n      if (line.startsWith('// ')) {\n        line = line.substring(3)\n      } else if (line.startsWith('# ')) {\n        line = line.substring(2)\n      }\n\n      contents += line\n      if (++i === 3) {\n        break\n      }\n      contents += '\\n'\n    }\n    if (contents !== header) {\n      return true\n    }\n  }\n  return false\n}\n\nasync function check(src) {\n  const missingHeader = []\n\n  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n    const p = path.join(src, entry.name)\n\n    if (entry.isSymbolicLink() || ignore.includes(entry.name)) {\n      continue\n    }\n\n    if (entry.isDirectory()) {\n      const missing = await check(p)\n      missingHeader.push(...missing)\n    } else {\n      const isMissing = await checkFile(p)\n      if (isMissing) {\n        missingHeader.push(p)\n      }\n    }\n  }\n\n  return missingHeader\n}\n\nconst [_bin, _script, ...files] = process.argv\n\nif (files.length > 0) {\n  async function run() {\n    const missing = []\n    for (const f of files) {\n      const isMissing = await checkFile(f)\n      if (isMissing) {\n        missing.push(f)\n      }\n    }\n    if (missing.length > 0) {\n      console.log(missing.join('\\n'))\n      process.exit(1)\n    }\n  }\n\n  run()\n} else {\n  check('.').then((missing) => {\n    if (missing.length > 0) {\n      console.log(missing.join('\\n'))\n      process.exit(1)\n    }\n  })\n}\n"
  },
  {
    "path": ".scripts/ci/has-diff.sh",
    "content": "#!/bin/bash\n\n# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\ngit_output=$(git diff --ignore-submodules --name-only HEAD)\nif [ -z \"$git_output\" ];\nthen\n  echo \"✔ working directory is clean\"\nelse\n  echo \"✘ found diff:\"\n  echo \"$git_output\"\n  exit 1\nfi\n"
  },
  {
    "path": ".scripts/ci/pack-cli.sh",
    "content": "#!/bin/bash\n\n# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nset -euxo pipefail\n\nfor o in outputs/*; do\n  pushd \"$o\"\n\n  chmod +x cargo-tauri*\n  cp ../../crates/tauri-cli/LICENSE* ../../crates/tauri-cli/README.md .\n\n  target=$(basename \"$o\" | cut -d. -f1)\n  if grep -qE '(apple|windows)' <<< \"$target\"; then\n    zip \"../${target}.zip\" *\n  else\n    tar cv * | gzip -9 > \"../${target}.tgz\"\n  fi\n\n  popd\ndone\n"
  },
  {
    "path": ".scripts/ci/sync-cli-metadata.js",
    "content": "#!/usr/bin/env node\n\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/*\nThis script is solely intended to be run as part of the `covector version` step to\nkeep the `../../crates/tauri-cli/metadata-v2.json` up to date with other version bumps. Long term\nwe should look to find a more \"rusty way\" to import / \"pin\" a version value in our tauri-cli\nrust binaries.\n*/\n\nconst { readFileSync, writeFileSync } = require('fs')\nconst { resolve } = require('path')\n\nconst packageNickname = process.argv[2]\nconst filePath = resolve(__dirname, '../../crates/tauri-cli/metadata-v2.json')\nconst bump = process.argv[3]\nlet index = null\n\nswitch (bump) {\n  case 'major':\n  case 'premajor':\n    index = 0\n    break\n  case 'minor':\n    index = 1\n    break\n  case 'patch':\n    index = 2\n    break\n  case 'prerelease':\n  case 'prepatch':\n    index = 3\n    break\n  default:\n    throw new Error('unexpected bump ' + bump)\n}\n\nconst inc = (version) => {\n  const v = version.split('.')\n  for (let i = 0; i < v.length; i++) {\n    if (i === index) {\n      v[i] = String(Number(v[i]) + 1)\n    } else if (i > index) {\n      v[i] = 0\n    }\n  }\n  if (bump === 'premajor') {\n    const pre = JSON.parse(\n      readFileSync(resolve(__dirname, '../../.changes/pre.json'), 'utf-8')\n    )\n    return `${v.join('.')}-${pre.tag}.0`\n  }\n  return v.join('.')\n}\n\n// read file into js object\nconst metadata = JSON.parse(readFileSync(filePath, 'utf-8'))\n\n// set field version\nlet version\nif (packageNickname === '@tauri-apps/cli') {\n  version = inc(metadata['cli.js'].version)\n  metadata['cli.js'].version = version\n} else {\n  version = inc(metadata[packageNickname])\n  metadata[packageNickname] = version\n}\n\nwriteFileSync(filePath, JSON.stringify(metadata, null, 2) + '\\n')\nconsole.log(`wrote ${version} for ${packageNickname} into metadata-v2.json`)\nconsole.dir(metadata)\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"rust-lang.rust-analyzer\",\n    \"EditorConfig.EditorConfig\",\n    \"esbenp.prettier-vscode\",\n    \"tamasfe.even-better-toml\"\n  ]\n}\n"
  },
  {
    "path": "ARCHITECTURE.md",
    "content": "# The Tauri Architecture\n\n<https://tauri.app>\n\n<https://github.com/tauri-apps/tauri>\n\n## Introduction\n\nTauri is a polyglot and generic toolkit that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.\n\nTauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the OS's webview. They do not ship a runtime, since the final binary is compiled from Rust. This makes the reversing of Tauri apps not a trivial task.\n\n## What Tauri is NOT\n\n- Tauri is not a lightweight kernel wrapper...instead it directly uses [WRY](#wry) and [TAO](#tao) to do the heavy-lifting in making system calls to the OS.\n- Tauri is not a VM or virtualized environment...instead it is an application toolkit that allows making Webview OS applications.\n\n## Major Components\n\nThe following section briefly describes the roles of the various parts of Tauri.\n\n### Tauri Core [STABLE RUST]\n\n#### [tauri](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri)\n\nThis is the major crate that holds everything together. It brings the runtimes, macros, utilities and API into one final product. It reads the `tauri.conf.json` file at compile time in order to bring in features and undertake actual configuration of the app (and even the `Cargo.toml` file in the project's folder). It handles script injection (for polyfills / prototype revision) at runtime, hosts the API for systems interaction, and even manages updating.\n\n#### [tauri-build](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-build)\n\nApply the macros at build-time in order to rig some special features needed by `cargo`.\n\n#### [tauri-codegen](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-codegen)\n\n- Embed, hash, and compress assets, including icons for the app as well as the system-tray.\n- Parse `tauri.conf.json` at compile time and generate the Config struct.\n\n#### [tauri-macros](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-macros)\n\nCreate macros for the context, handler, and commands by leveraging the `tauri-codegen` crate.\n\n#### [tauri-runtime](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-runtime)\n\nThis is the glue layer between tauri itself and lower level webview libraries.\n\n#### [tauri-runtime-wry](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-runtime-wry)\n\nThis crate opens up direct systems-level interactions specifically for WRY, such as printing, monitor detection, and other windowing related tasks. `tauri-runtime` implementation for WRY.\n\n#### [tauri-utils](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-utils)\n\nThis is common code that is reused in many places and offers useful utilities like parsing configuration files, detecting platform triples, injecting the CSP, and managing assets.\n\n### Tauri Tooling\n\n#### [@tauri-apps/api](https://github.com/tauri-apps/tauri/tree/dev/packages/api) [TS -> JS]\n\nA TypeScript library that creates `cjs` and `esm` JavaScript endpoints for you to import into your Frontend framework so that the Webview can call and listen to backend activity. We also ship the pure TypeScript, because for some frameworks this is more optimal. It uses the message passing of webviews to their hosts.\n\n#### [bundler](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-bundler) [RUST / SHELL]\n\nThe bundler is a library that builds a Tauri App for the platform triple it detects / is told. At the moment it currently supports macOS, Windows and Linux - but in the near future will support mobile platforms as well. May be used outside of Tauri projects.\n\n#### [@tauri-apps/cli](https://github.com/tauri-apps/tauri/tree/dev/packages/cli) [JS]\n\nIt is a wrapper around [tauri-cli](https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-cli) using [napi-rs](https://github.com/napi-rs/napi-rs) to produce NPM packages for each platform.\n\n#### [tauri-cli](https://github.com/tauri-apps/tauri/tree/dev/crates/tauri-cli) [RUST]\n\nThis rust executable provides the full interface to all of the required activities for which the CLI is required. It will run on macOS, Windows, and Linux.\n\n#### [create-tauri-app](https://github.com/tauri-apps/create-tauri-app) [JS]\n\nThis is a toolkit that will enable engineering teams to rapidly scaffold out a new tauri-apps project using the frontend framework of their choice (as long as it has been configured).\n\n# External Crates\n\nThe Tauri-Apps organisation maintains two \"upstream\" crates from Tauri, namely TAO for creating and managing application windows, and WRY for interfacing with the Webview that lives within the window.\n\n## [TAO](https://github.com/tauri-apps/tao)\n\nCross-platform application window creation library in Rust that supports all major platforms like Windows, macOS, Linux, iOS and Android. Written in Rust, it is a fork of [winit](https://github.com/rust-windowing/winit) that we have extended for our own needs like menu bar and system tray.\n\n## [WRY](https://github.com/tauri-apps/wry)\n\nWRY is a cross-platform WebView rendering library in Rust that supports all major desktop platforms like Windows, macOS, and Linux.\nTauri uses WRY as the abstract layer responsible to determine which webview is used (and how interactions are made).\n\n# Additional tooling\n\n## [tauri-action](https://github.com/tauri-apps/tauri-action)\n\nThis is a github workflow that builds tauri binaries for all platforms. It is not the fastest out there, but it gets the job done and is highly configurable. Even allowing you to create a (very basic) tauri app even if tauri is not setup.\n\n## [create-pull-request](https://github.com/tauri-apps/create-pull-request)\n\nBecause this is a very risky (potentially destructive) github action, we forked it in order to have strong guarantees that the code we think is running is actually the code that is running.\n\n## [vue-cli-plugin-tauri](https://github.com/tauri-apps/vue-cli-plugin-tauri)\n\nThis plugin allows you to very quickly install tauri in a vue-cli project.\n\n## [tauri-vscode](https://github.com/tauri-apps/tauri-vscode)\n\nThis project enhances the VS Code interface with several nice-to-have features.\n\n# Tauri Plugins [documentation](https://v2.tauri.app/develop/plugins/)\n\nGenerally speaking, plugins are authored by third parties (even though there may be official, supported plugins). A plugin generally does 3 things:\n\n1. It provides rust code to do \"something\".\n2. It provides interface glue to make it easy to integrate into an app.\n3. It provides a JS API for interfacing with the rust code.\n\nHere are several examples of Tauri Plugins:\n\n- <https://github.com/tauri-apps/tauri-plugin-sql>\n- <https://github.com/tauri-apps/tauri-plugin-stronghold>\n- <https://github.com/tauri-apps/tauri-plugin-authenticator>\n\n# Workflows\n\n## What does the Development flow look like?\n\nA developer must first install the prerequisite toolchains for creating a Tauri app. At the very least this will entail installing rust & cargo, and most likely also a modern version of node.js and potentially another package manager. Some platforms may also require other tooling and libraries, but this has been documented carefully in the respective platform docs.\n\nBecause of the many ways to build front-ends, we will stick with a common node.js based approach for development. (Note: Tauri does not by default ship a node.js runtime.)\n\nThe easiest way to do this is to run the following:\n\n```\nnpm create tauri-app\n```\n\nWhich will ask you a bunch of questions about the framework you want to install and then create everything you need in a single folder - some via the placement of template files and some through normal installation procedures of your framework.\n\n> If you don't use this process, you will have to manually install the tauri cli, initialise tauri and manually configure the `tauri.conf.json` file.\n\nOnce everything is installed, you can run:\n\n```\npnpm tauri dev\n-or-\nyarn tauri dev\n-or-\nnpm run tauri dev\n```\n\nThis will do several things:\n\n1. start the JS Framework devserver\n2. begin the long process of downloading and compiling the rust libraries\n3. open an application window with devtools enabled\n4. keep a long-lived console alive\n\nIf you change your HTML/CSS/TS/JS, your framework devserver should give you its best shot at instant hot module reloading and you will see the changes instantly.\n\nIf you modify your rust code or anything in the Cargo.toml, the window will close while rust recompiles. When finished it will reload.\n\nIf you need to get deeper insight into your current project, or triage requires investigation of installed components, just run:\n\n```\npnpm tauri info\n```\n\n## What does the Release flow look like?\n\nThe release flow begins with proper configuration in the `tauri.conf.json` file. In this file, the developer can configure not only the basic behaviour of the application (like window size and decoration), they can also provide settings for signing and updating.\n\nDepending upon the operating system that the developer (or CI) is building the application on, there will be an app built for them for that system. (Cross compilation is not currently available, however there is an official [GitHub Action](https://github.com/tauri-apps/tauri-action) that can be used to build for all platforms.)\n\nTo kick off this process, just:\n\n```\npnpm tauri build\n```\n\nAfter some time, the process will end and you can see the results in the `./src-tauri/target/release` folder.\n\n## What does the End-User flow look like?\n\nEnd users will be provided with binaries in ways that are appropriate for their systems. Whether macOS, Linux, or Windows, direct download or store installations - they will be able to follow procedures for installing and removing that they are used to.\n\n## What does the Updating flow look like?\n\nWhen a new version is ready, the developer publishes the new signed artifacts to a server (that they have configured within `tauri.conf.json`).\n\nThe application can poll this server to see if there is a new release. When there is a new release, the user is prompted to update. The application update is downloaded, verified (checksum & signature), updated, closed, and restarted.\n\n## License\n\nTauri itself is licensed under MIT or Apache-2.0. If you repackage it and modify any source code, it is your responsibility to verify that you are complying with all upstream licenses. Tauri is provided AS-IS with no explicit claim for suitability for any purpose.\n\nHere you may peruse our [Software Bill of Materials](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri).\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nmembers = [\n  \"crates/tauri\",\n  \"crates/tauri-runtime\",\n  \"crates/tauri-runtime-wry\",\n  \"crates/tauri-macros\",\n  \"crates/tauri-utils\",\n  \"crates/tauri-build\",\n  \"crates/tauri-codegen\",\n  \"crates/tauri-plugin\",\n  \"crates/tauri-schema-generator\",\n  \"crates/tauri-schema-worker\",\n  \"crates/tauri-cli\",\n  \"crates/tauri-bundler\",\n  \"crates/tauri-macos-sign\",\n  \"crates/tauri-driver\",\n\n  # @tauri-apps/cli rust project\n  \"packages/cli\",\n\n  # integration tests\n  \"crates/tests/restart\",\n  \"crates/tests/acl\",\n\n  # bench\n  \"bench\",\n  \"bench/tests/cpu_intensive/src-tauri\",\n  \"bench/tests/files_transfer/src-tauri\",\n  \"bench/tests/helloworld/src-tauri\",\n\n  # examples\n  \"examples/file-associations/src-tauri\",\n  \"examples/resources/src-tauri\",\n  \"examples/api/src-tauri\",\n  \"examples/api/src-tauri/tauri-plugin-sample\",\n]\nresolver = \"2\"\n\n[workspace.package]\nauthors = [\"Tauri Programme within The Commons Conservancy\"]\nhomepage = \"https://tauri.app/\"\nrepository = \"https://github.com/tauri-apps/tauri\"\ncategories = [\"gui\", \"web-programming\"]\nlicense = \"Apache-2.0 OR MIT\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\n\n# default to small, optimized workspace release binaries\n[profile.release]\npanic = \"abort\"\ncodegen-units = 1\nlto = true\nincremental = false\nopt-level = \"s\"\nstrip = true\n\n# profiles for tauri-cli\n[profile.dev.package.miniz_oxide]\nopt-level = 3\n\n[profile.release-size-optimized]\ninherits = \"release\"\ncodegen-units = 1\nlto = true\nincremental = false\nopt-level = \"s\"\n\n# Temporary patch to schemars to preserve newlines in docstrings for our reference docs schemas\n# See https://github.com/GREsau/schemars/issues/120 for reference\n[patch.crates-io]\nschemars_derive = { git = 'https://github.com/tauri-apps/schemars.git', branch = 'feat/preserve-description-newlines' }\ntauri = { path = \"./crates/tauri\" }\ntauri-plugin = { path = \"./crates/tauri-plugin\" }\ntauri-utils = { path = \"./crates/tauri-utils\" }\n"
  },
  {
    "path": "LICENSE.spdx",
    "content": "SPDXVersion: SPDX-2.1\nDataLicense: CC0-1.0\nPackageName: tauri\nDataFormat: SPDXRef-1\nPackageSupplier: Organization: The Tauri Programme in the Commons Conservancy\nPackageHomePage: https://tauri.app\nPackageLicenseDeclared: Apache-2.0\nPackageLicenseDeclared: MIT\nPackageCopyrightText: 2019-2025, The Tauri Programme in the Commons Conservancy\nPackageSummary: <text>Tauri is a rust project that enables developers to make secure\nand small desktop applications using a web frontend.\n                </text>\nPackageComment: <text>The package includes the following libraries; see\nRelationship information.\n                </text>\nCreated: 2019-05-20T09:00:00Z\nPackageDownloadLocation: git://github.com/tauri-apps/tauri\nPackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git\nPackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git\nCreator: Person: Daniel Thompson-Yvetot\n"
  },
  {
    "path": "LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2017 - Present Tauri Apps Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<img src=\".github/splash.png\" alt=\"Tauri\" />\n\n[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev)\n[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri)\n[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield)\n[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.com/invite/tauri)\n[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app)\n[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)\n[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri)\n\n## Introduction\n\nTauri is a framework for building tiny, blazingly fast binaries for all major desktop platforms. Developers can integrate any front-end framework that compiles to HTML, JS and CSS for building their user interface. The backend of the application is a rust-sourced binary with an API that the front-end can interact with.\n\nThe user interface in Tauri apps currently leverages [`tao`](https://docs.rs/tao) as a window handling library on macOS, Windows, Linux, Android and iOS. To render your application, Tauri uses [WRY](https://github.com/tauri-apps/wry), a library which provides a unified interface to the system webview, leveraging WKWebView on macOS & iOS, WebView2 on Windows, WebKitGTK on Linux and Android System WebView on Android.\n\nTo learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.\n\n## Getting Started\n\nIf you are interested in making a tauri app, please visit the [documentation website](https://tauri.app).\n\nThe quickest way to get started is to install the [prerequisites](https://v2.tauri.app/start/prerequisites/) for your system and create a new project with [`create-tauri-app`](https://github.com/tauri-apps/create-tauri-app/#usage). For example with `npm`:\n\n```sh\nnpm create tauri-app@latest\n```\n\n## Features\n\nThe list of Tauri's features includes, but is not limited to:\n\n- Built-in app bundler to create app bundles in formats like `.app`, `.dmg`, `.deb`, `.rpm`, `.AppImage` and Windows installers like `.exe` (via NSIS) and `.msi` (via WiX).\n- Built-in self updater (desktop only)\n- System tray icons\n- Native notifications\n- Native WebView Protocol (tauri doesn't create a localhost http(s) server to serve the WebView contents)\n- GitHub action for streamlined CI\n- VS Code extension\n\n### Platforms\n\nTauri currently supports development and distribution on the following platforms:\n\n| Platform   | Versions                                                                                                        |\n| :--------- | :-------------------------------------------------------------------------------------------------------------- |\n| Windows    | 7 and above                                                                                                     |\n| macOS      | 10.15 and above                                                                                                 |\n| Linux      | webkit2gtk 4.0 for Tauri v1 (for example Ubuntu 18.04). webkit2gtk 4.1 for Tauri v2 (for example Ubuntu 22.04). |\n| iOS/iPadOS | 9 and above                                                                                                     |\n| Android    | 7 and above (currently 8 and above)                                                                             |\n\n## Contributing\n\nBefore you start working on something, it's best to check if there is an existing issue first. It's also a good idea to stop by the Discord server and confirm with the team if it makes sense or if someone else is already working on it.\n\nPlease make sure to read the [Contributing Guide](./.github/CONTRIBUTING.md) before making a pull request.\n\nThank you to everyone contributing to Tauri!\n\n### Documentation\n\nDocumentation in a polyglot system is a tricky proposition. To this end, we prefer to use inline documentation in the Rust & JS source code as much as possible. Check out the hosting repository for the documentation site for further information: <https://github.com/tauri-apps/tauri-docs>\n\n## Partners\n\n<table>\n  <tbody>\n    <tr>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://crabnebula.dev\" target=\"_blank\">\n          <img src=\".github/sponsors/crabnebula.svg\" alt=\"CrabNebula\" width=\"283\">\n        </a>\n      </td>\n    </tr>\n  </tbody>\n</table>\n\nFor the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri).\n\n## Organization\n\nTauri aims to be a sustainable collective based on principles that guide sustainable free and open software communities. To this end it has become a Programme within the [Commons Conservancy](https://commonsconservancy.org/), and you can contribute financially via [Open Collective](https://opencollective.com/tauri).\n\n## Licenses\n\nCode: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy.\n\nMIT or MIT/Apache 2.0 where applicable.\n\nLogo: CC-BY-NC-ND\n\n- Original Tauri Logo Designs by [Alve Larsson](https://alve.io/), [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)\n\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_large)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported          |\n| ------- | ------------------ |\n| > 1.0   | :white_check_mark: |\n| < 1.0   | :x:                |\n\n## Reporting a Vulnerability\n\nIf you have found a potential security threat, vulnerability or exploit in Tauri\nor one of its upstream dependencies, please DON'T create a pull-request, DON'T\nfile an issue on GitHub, DON'T mention it on Discord and DON'T create a forum thread.\n\nPlease submit your report via the GitHub Private Vulnerability Disclosure functionality.\n\nFind out more about the reporting process [here](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability).\n\nOur team will triage your report and keep you informed about the progress.\nWe may ask questions or request further guidance on reproduction of the vulnerability in the comments of the advisory, which will be publicized.\n\nAdditionally, we may ask you to independently verify our patch, which will be available in the private advisory branch. Please do not publish your vulnerability during the process or before coordinated public disclosure from our side. We try to adhere to common standards of publication within 90-Days of disclosure.\n\nDepending on your decision to accept or deny credit for the vulnerability, you will be publicly attributed to the vulnerability and may be mentioned in our announcements.\n\nAt the current time we do not have the financial ability to reward bounties,\nbut in extreme cases will at our discretion consider a reward.\n"
  },
  {
    "path": "bench/Cargo.toml",
    "content": "[package]\nname = \"tauri_bench\"\nversion = \"0.1.0\"\nauthors = [\"Tauri Programme within The Commons Conservancy\"]\nedition = \"2021\"\nrust-version = \"1.77.2\"\nlicense = \"Apache-2.0 OR MIT\"\ndescription = \"Cross-platform WebView rendering library\"\nrepository = \"https://github.com/tauri-apps/wry\"\n\n[dependencies]\nanyhow = \"1\"\ntime = { version = \"0.3\", features = [\"formatting\"] }\ntempfile = \"3\"\nserde_json = \"1\"\nserde = { version = \"1\", features = [\"derive\"] }\n\n[[bin]]\nname = \"run_benchmark\"\npath = \"src/run_benchmark.rs\"\n\n[[bin]]\nname = \"build_benchmark_jsons\"\npath = \"src/build_benchmark_jsons.rs\"\n"
  },
  {
    "path": "bench/README.md",
    "content": "# Tauri Bench\n\n <img align=\"right\" src=\"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\" height=\"128\" width=\"128\">\n\n[![status](https://img.shields.io/badge/Status-beta-green.svg)](https://github.com/tauri-apps/tauri)\n[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri)\n[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield)\n[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S)\n[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app)\n[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)\n[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri)\n\n## About Tauri\n\nTauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.\n\nTauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.\n\n## This module\n\nThis rust module run on CI, provides internal metrics results of Tauri. To learn more see [benchmark_results](https://github.com/tauri-apps/benchmark_results) repository.\n\n**\\*_Internal use only_**\n\n## Semver\n\n**tauri** is following [Semantic Versioning 2.0](https://semver.org/).\n\n## Licenses\n\nCode: (c) 2015 - 2021 - The Tauri Programme within The Commons Conservancy.\n\nMIT or MIT/Apache 2.0 where applicable.\n\nLogo: CC-BY-NC-ND\n\n- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)\n"
  },
  {
    "path": "bench/src/build_benchmark_jsons.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! This Rust binary runs on CI and provides internal metrics results of Tauri. To learn more see [benchmark_results](https://github.com/tauri-apps/benchmark_results) repository.\n//!\n//! ***_Internal use only_**\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n// file is used by multiple binaries\n#![allow(dead_code)]\n\nuse std::{fs::File, io::BufReader};\nmod utils;\n\nfn main() {\n  let tauri_data = &utils::tauri_root_path()\n    .join(\"gh-pages\")\n    .join(\"tauri-data.json\");\n  let tauri_recent = &utils::tauri_root_path()\n    .join(\"gh-pages\")\n    .join(\"tauri-recent.json\");\n\n  // current data\n  let current_data_buffer = BufReader::new(\n    File::open(utils::target_dir().join(\"bench.json\")).expect(\"Unable to read current data file\"),\n  );\n  let current_data: utils::BenchResult =\n    serde_json::from_reader(current_data_buffer).expect(\"Unable to read current data buffer\");\n\n  // all data's\n  let all_data_buffer =\n    BufReader::new(File::open(tauri_data).expect(\"Unable to read all data file\"));\n  let mut all_data: Vec<utils::BenchResult> =\n    serde_json::from_reader(all_data_buffer).expect(\"Unable to read all data buffer\");\n\n  // add current data to all data\n  all_data.push(current_data);\n\n  // use only latest 20 elements from all data\n  let recent: Vec<utils::BenchResult> = if all_data.len() > 20 {\n    all_data[all_data.len() - 20..].to_vec()\n  } else {\n    all_data.clone()\n  };\n\n  // write json's\n  utils::write_json(\n    tauri_data\n      .to_str()\n      .expect(\"Something wrong with tauri_data\"),\n    &serde_json::to_value(all_data).expect(\"Unable to build final json (all)\"),\n  )\n  .unwrap_or_else(|_| panic!(\"Unable to write {tauri_data:?}\"));\n\n  utils::write_json(\n    tauri_recent\n      .to_str()\n      .expect(\"Something wrong with tauri_recent\"),\n    &serde_json::to_value(recent).expect(\"Unable to build final json (recent)\"),\n  )\n  .unwrap_or_else(|_| panic!(\"Unable to write {tauri_recent:?}\"));\n}\n"
  },
  {
    "path": "bench/src/run_benchmark.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! This Rust binary runs on CI and provides internal metrics results of Tauri.\n//! To learn more see [benchmark_results](https://github.com/tauri-apps/benchmark_results) repository.\n//!\n//! ***_Internal use only_***\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n\nuse anyhow::{Context, Result};\nuse std::{\n  collections::{HashMap, HashSet},\n  env,\n  path::Path,\n  process::{Command, Stdio},\n};\n\nmod utils;\n\n/// The list of examples for benchmarks\nfn get_all_benchmarks(target: &str) -> Vec<(String, String)> {\n  vec![\n    (\n      \"tauri_hello_world\".into(),\n      format!(\"../target/{target}/release/bench_helloworld\"),\n    ),\n    (\n      \"tauri_cpu_intensive\".into(),\n      format!(\"../target/{target}/release/bench_cpu_intensive\"),\n    ),\n    (\n      \"tauri_3mb_transfer\".into(),\n      format!(\"../target/{target}/release/bench_files_transfer\"),\n    ),\n  ]\n}\n\nfn run_strace_benchmarks(new_data: &mut utils::BenchResult, target: &str) -> Result<()> {\n  use std::io::Read;\n\n  let mut thread_count = HashMap::<String, u64>::new();\n  let mut syscall_count = HashMap::<String, u64>::new();\n\n  for (name, example_exe) in get_all_benchmarks(target) {\n    let mut file = tempfile::NamedTempFile::new()\n      .context(\"failed to create temporary file for strace output\")?;\n\n    let exe_path = utils::bench_root_path().join(&example_exe);\n    let exe_path_str = exe_path\n      .to_str()\n      .context(\"executable path contains invalid UTF-8\")?;\n    let temp_path_str = file\n      .path()\n      .to_str()\n      .context(\"temporary file path contains invalid UTF-8\")?;\n\n    Command::new(\"strace\")\n      .args([\"-c\", \"-f\", \"-o\", temp_path_str, exe_path_str])\n      .stdout(Stdio::inherit())\n      .spawn()\n      .context(\"failed to spawn strace process\")?\n      .wait()\n      .context(\"failed to wait for strace process\")?;\n\n    let mut output = String::new();\n    file\n      .as_file_mut()\n      .read_to_string(&mut output)\n      .context(\"failed to read strace output\")?;\n\n    let strace_result = utils::parse_strace_output(&output);\n    // Count clone/clone3 syscalls as thread creation indicators\n    let clone_calls = strace_result.get(\"clone\").map(|d| d.calls).unwrap_or(0)\n      + strace_result.get(\"clone3\").map(|d| d.calls).unwrap_or(0);\n\n    if let Some(total) = strace_result.get(\"total\") {\n      thread_count.insert(name.clone(), clone_calls);\n      syscall_count.insert(name, total.calls);\n    }\n  }\n\n  new_data.thread_count = thread_count;\n  new_data.syscall_count = syscall_count;\n\n  Ok(())\n}\n\nfn run_max_mem_benchmark(target: &str) -> Result<HashMap<String, u64>> {\n  let mut results = HashMap::<String, u64>::new();\n\n  for (name, example_exe) in get_all_benchmarks(target) {\n    let benchmark_file = utils::target_dir().join(format!(\"mprof{name}_.dat\"));\n    let benchmark_file_str = benchmark_file\n      .to_str()\n      .context(\"benchmark file path contains invalid UTF-8\")?;\n\n    let exe_path = utils::bench_root_path().join(&example_exe);\n    let exe_path_str = exe_path\n      .to_str()\n      .context(\"executable path contains invalid UTF-8\")?;\n\n    let proc = Command::new(\"mprof\")\n      .args([\"run\", \"-C\", \"-o\", benchmark_file_str, exe_path_str])\n      .stdout(Stdio::null())\n      .stderr(Stdio::piped())\n      .spawn()\n      .with_context(|| format!(\"failed to spawn mprof for benchmark {name}\"))?;\n\n    let proc_result = proc\n      .wait_with_output()\n      .with_context(|| format!(\"failed to wait for mprof {name}\"))?;\n\n    if !proc_result.status.success() {\n      eprintln!(\n        \"mprof failed for {name}: {}\",\n        String::from_utf8_lossy(&proc_result.stderr)\n      );\n    }\n\n    if let Some(mem) = utils::parse_max_mem(benchmark_file_str)\n      .with_context(|| format!(\"failed to parse mprof data for {name}\"))?\n    {\n      results.insert(name, mem);\n    }\n\n    // Clean up the temporary file\n    if let Err(e) = std::fs::remove_file(&benchmark_file) {\n      eprintln!(\"Warning: failed to remove temporary file {benchmark_file_str}: {e}\");\n    }\n  }\n\n  Ok(results)\n}\n\nfn rlib_size(target_dir: &Path, prefix: &str) -> Result<u64> {\n  let mut size = 0;\n  let mut seen = HashSet::new();\n\n  let deps_dir = target_dir.join(\"deps\");\n  for entry in std::fs::read_dir(&deps_dir).with_context(|| {\n    format!(\n      \"failed to read target deps directory: {}\",\n      deps_dir.display()\n    )\n  })? {\n    let entry = entry.context(\"failed to read directory entry\")?;\n    let name = entry.file_name().to_string_lossy().to_string();\n\n    if name.starts_with(prefix) && name.ends_with(\".rlib\") {\n      if let Some(start) = name.split('-').next() {\n        if seen.insert(start.to_string()) {\n          size += entry\n            .metadata()\n            .context(\"failed to read file metadata\")?\n            .len();\n        }\n      }\n    }\n  }\n\n  if size == 0 {\n    anyhow::bail!(\n      \"no rlib files found for prefix {prefix} in {}\",\n      deps_dir.display()\n    );\n  }\n\n  Ok(size)\n}\n\nfn get_binary_sizes(target_dir: &Path, target: &str) -> Result<HashMap<String, u64>> {\n  let mut sizes = HashMap::<String, u64>::new();\n\n  let wry_size = rlib_size(target_dir, \"libwry\")?;\n  sizes.insert(\"wry_rlib\".to_string(), wry_size);\n\n  for (name, example_exe) in get_all_benchmarks(target) {\n    let exe_path = utils::bench_root_path().join(&example_exe);\n    let meta = std::fs::metadata(&exe_path)\n      .with_context(|| format!(\"failed to read metadata for {}\", exe_path.display()))?;\n    sizes.insert(name, meta.len());\n  }\n\n  Ok(sizes)\n}\n\n/// (target OS, target triple)\nconst TARGETS: &[(&str, &[&str])] = &[\n  (\n    \"Windows\",\n    &[\n      \"x86_64-pc-windows-gnu\",\n      \"i686-pc-windows-gnu\",\n      \"i686-pc-windows-msvc\",\n      \"x86_64-pc-windows-msvc\",\n    ],\n  ),\n  (\n    \"Linux\",\n    &[\n      \"x86_64-unknown-linux-gnu\",\n      \"i686-unknown-linux-gnu\",\n      \"aarch64-unknown-linux-gnu\",\n    ],\n  ),\n  (\"macOS\", &[\"x86_64-apple-darwin\", \"aarch64-apple-darwin\"]),\n];\n\nfn cargo_deps() -> HashMap<String, usize> {\n  let mut results = HashMap::new();\n  for (os, targets) in TARGETS {\n    for target in *targets {\n      let mut cmd = Command::new(\"cargo\");\n      cmd.arg(\"tree\");\n      cmd.arg(\"--no-dedupe\");\n      cmd.args([\"--edges\", \"normal\"]);\n      cmd.args([\"--prefix\", \"none\"]);\n      cmd.args([\"--target\", target]);\n      cmd.current_dir(utils::tauri_root_path());\n\n      match cmd.output() {\n        Ok(output) if output.status.success() => {\n          let full_deps = String::from_utf8_lossy(&output.stdout);\n          let count = full_deps\n            .lines()\n            .collect::<HashSet<_>>()\n            .len()\n            .saturating_sub(1); // output includes wry itself\n\n          // set the count to the highest count seen for this OS\n          let existing = results.entry(os.to_string()).or_default();\n          *existing = count.max(*existing);\n\n          if count <= 10 {\n            eprintln!(\"Warning: dependency count for {target} seems low: {count}\");\n          }\n        }\n        Ok(output) => {\n          eprintln!(\n            \"cargo tree failed for {target}: {}\",\n            String::from_utf8_lossy(&output.stderr)\n          );\n        }\n        Err(e) => {\n          eprintln!(\"Failed to run cargo tree for {target}: {e}\");\n        }\n      }\n    }\n  }\n  results\n}\n\nconst RESULT_KEYS: &[&str] = &[\"mean\", \"stddev\", \"user\", \"system\", \"min\", \"max\"];\n\nfn run_exec_time(target: &str) -> Result<HashMap<String, HashMap<String, f64>>> {\n  let target_dir = utils::target_dir();\n  let benchmark_file = target_dir.join(\"hyperfine_results.json\");\n  let benchmark_file_str = benchmark_file\n    .to_str()\n    .context(\"benchmark file path contains invalid UTF-8\")?;\n\n  let mut command = vec![\n    \"hyperfine\",\n    \"--export-json\",\n    benchmark_file_str,\n    \"--show-output\",\n    \"--warmup\",\n    \"3\",\n  ];\n\n  let benchmarks = get_all_benchmarks(target);\n  let mut benchmark_paths = Vec::new();\n\n  for (_, example_exe) in &benchmarks {\n    let exe_path = utils::bench_root_path().join(example_exe);\n    let exe_path_str = exe_path\n      .to_str()\n      .context(\"executable path contains invalid UTF-8\")?;\n    benchmark_paths.push(exe_path_str.to_string());\n  }\n\n  for path in &benchmark_paths {\n    command.push(path.as_str());\n  }\n\n  utils::run(&command)?;\n\n  let mut results = HashMap::<String, HashMap<String, f64>>::new();\n  let hyperfine_results = utils::read_json(benchmark_file_str)?;\n\n  if let Some(results_array) = hyperfine_results\n    .as_object()\n    .and_then(|obj| obj.get(\"results\"))\n    .and_then(|val| val.as_array())\n  {\n    for ((name, _), data) in benchmarks.iter().zip(results_array.iter()) {\n      if let Some(data_obj) = data.as_object() {\n        let filtered_data: HashMap<String, f64> = data_obj\n          .iter()\n          .filter(|(key, _)| RESULT_KEYS.contains(&key.as_str()))\n          .filter_map(|(key, val)| val.as_f64().map(|v| (key.clone(), v)))\n          .collect();\n\n        results.insert(name.clone(), filtered_data);\n      }\n    }\n  }\n\n  Ok(results)\n}\n\nfn main() -> Result<()> {\n  let json_3mb = utils::home_path().join(\".tauri_3mb.json\");\n\n  if !json_3mb.exists() {\n    println!(\"Downloading test data...\");\n    utils::download_file(\n      \"https://github.com/lemarier/tauri-test/releases/download/v2.0.0/json_3mb.json\",\n      json_3mb,\n    )\n    .context(\"failed to download test data\")?;\n  }\n\n  println!(\"Starting tauri benchmark\");\n\n  let target_dir = utils::target_dir();\n  let target = utils::get_target();\n\n  env::set_current_dir(utils::bench_root_path())\n    .context(\"failed to set working directory to bench root\")?;\n\n  let now = std::time::SystemTime::now()\n    .duration_since(std::time::UNIX_EPOCH)\n    .context(\"failed to get current time\")?;\n  let timestamp = format!(\"{}\", now.as_secs());\n\n  println!(\"Running execution time benchmarks...\");\n  let exec_time = run_exec_time(target)?;\n\n  println!(\"Getting binary sizes...\");\n  let binary_size = get_binary_sizes(&target_dir, target)?;\n\n  println!(\"Analyzing cargo dependencies...\");\n  let cargo_deps = cargo_deps();\n\n  let mut new_data = utils::BenchResult {\n    created_at: timestamp,\n    sha1: {\n      let output = utils::run_collect(&[\"git\", \"rev-parse\", \"HEAD\"])?;\n      output.0.trim().to_string()\n    },\n    exec_time,\n    binary_size,\n    cargo_deps,\n    ..Default::default()\n  };\n\n  if cfg!(target_os = \"linux\") {\n    println!(\"Running Linux-specific benchmarks...\");\n    run_strace_benchmarks(&mut new_data, target)?;\n    new_data.max_memory = run_max_mem_benchmark(target)?;\n  }\n\n  println!(\"===== <BENCHMARK RESULTS>\");\n  serde_json::to_writer_pretty(std::io::stdout(), &new_data)\n    .context(\"failed to serialize benchmark results\")?;\n  println!(\"\\n===== </BENCHMARK RESULTS>\");\n\n  let bench_file = target_dir.join(\"bench.json\");\n  if let Some(filename) = bench_file.to_str() {\n    utils::write_json(filename, &serde_json::to_value(&new_data)?)\n      .context(\"failed to write benchmark results to file\")?;\n    println!(\"Results written to: {filename}\");\n  } else {\n    eprintln!(\"Cannot write bench.json, path contains invalid UTF-8\");\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "bench/src/utils.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Utility functions for benchmarking tasks in the Tauri project.\n//!\n//! This module provides helpers for:\n//! - Paths to project directories and targets\n//! - Running and collecting process outputs\n//! - Parsing memory profiler (`mprof`) and syscall profiler (`strace`) outputs\n//! - JSON read/write utilities\n//! - File download utilities (via `curl` or file copy)\n\nuse anyhow::{bail, Context, Result};\nuse serde::{Deserialize, Serialize};\nuse serde_json::Value;\nuse std::{\n  collections::HashMap,\n  fs,\n  io::{BufRead, BufReader},\n  path::PathBuf,\n  process::{Command, Output, Stdio},\n};\n\n/// Holds the results of a benchmark run.\n#[derive(Default, Clone, Serialize, Deserialize, Debug)]\npub struct BenchResult {\n  pub created_at: String,\n  pub sha1: String,\n  pub exec_time: HashMap<String, HashMap<String, f64>>,\n  pub binary_size: HashMap<String, u64>,\n  pub max_memory: HashMap<String, u64>,\n  pub thread_count: HashMap<String, u64>,\n  pub syscall_count: HashMap<String, u64>,\n  pub cargo_deps: HashMap<String, usize>,\n}\n\n/// Represents a single line of parsed `strace` output.\n#[derive(Debug, Clone, Serialize)]\npub struct StraceOutput {\n  pub percent_time: f64,\n  pub seconds: f64,\n  pub usecs_per_call: Option<u64>,\n  pub calls: u64,\n  pub errors: u64,\n}\n\n/// Get the compilation target triple for the current platform.\npub fn get_target() -> &'static str {\n  #[cfg(target_os = \"macos\")]\n  return if cfg!(target_arch = \"aarch64\") {\n    \"aarch64-apple-darwin\"\n  } else {\n    \"x86_64-apple-darwin\"\n  };\n\n  #[cfg(target_os = \"ios\")]\n  return if cfg!(target_arch = \"aarch64\") {\n    \"aarch64-apple-ios\"\n  } else {\n    \"x86_64-apple-ios\"\n  };\n\n  #[cfg(target_os = \"linux\")]\n  return \"x86_64-unknown-linux-gnu\";\n\n  #[cfg(target_os = \"windows\")]\n  unimplemented!(\"Windows target not implemented yet\");\n}\n\n/// Get the `target/release` directory path for benchmarks.\npub fn target_dir() -> PathBuf {\n  bench_root_path()\n    .join(\"..\")\n    .join(\"target\")\n    .join(get_target())\n    .join(\"release\")\n}\n\n/// Get the root path of the current benchmark crate.\npub fn bench_root_path() -> PathBuf {\n  PathBuf::from(env!(\"CARGO_MANIFEST_DIR\"))\n}\n\n/// Get the home directory of the current user.\npub fn home_path() -> PathBuf {\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\", target_os = \"linux\"))]\n  {\n    PathBuf::from(std::env::var(\"HOME\").unwrap_or_default())\n  }\n\n  #[cfg(target_os = \"windows\")]\n  {\n    PathBuf::from(std::env::var(\"USERPROFILE\").unwrap_or_default())\n  }\n}\n\n/// Get the root path of the Tauri repository.\npub fn tauri_root_path() -> PathBuf {\n  bench_root_path().parent().map(|p| p.to_path_buf()).unwrap()\n}\n\n/// Run a command and collect its stdout and stderr as strings.\n/// Returns an error if the command fails or exits with a non-zero status.\npub fn run_collect(cmd: &[&str]) -> Result<(String, String)> {\n  let output: Output = Command::new(cmd[0])\n    .args(&cmd[1..])\n    .stdin(Stdio::piped())\n    .stdout(Stdio::piped())\n    .stderr(Stdio::piped())\n    .output()\n    .with_context(|| format!(\"failed to execute command: {cmd:?}\"))?;\n\n  if !output.status.success() {\n    bail!(\n      \"Command {:?} exited with {:?}\\nstdout:\\n{}\\nstderr:\\n{}\",\n      cmd,\n      output.status.code(),\n      String::from_utf8_lossy(&output.stdout),\n      String::from_utf8_lossy(&output.stderr)\n    );\n  }\n\n  Ok((\n    String::from_utf8_lossy(&output.stdout).to_string(),\n    String::from_utf8_lossy(&output.stderr).to_string(),\n  ))\n}\n\n/// Parse a memory profiler (`mprof`) output file and return the maximum\n/// memory usage in bytes. Returns `None` if no values are found.\npub fn parse_max_mem(file_path: &str) -> Result<Option<u64>> {\n  let file = fs::File::open(file_path)\n    .with_context(|| format!(\"failed to open mprof output file {file_path}\"))?;\n  let output = BufReader::new(file);\n\n  let mut highest: u64 = 0;\n\n  for line in output.lines().map_while(Result::ok) {\n    let split: Vec<&str> = line.split(' ').collect();\n    if split.len() == 3 {\n      if let Ok(mb) = split[1].parse::<f64>() {\n        let current_bytes = (mb * 1024.0 * 1024.0) as u64;\n        highest = highest.max(current_bytes);\n      }\n    }\n  }\n\n  // Best-effort cleanup\n  let _ = fs::remove_file(file_path);\n\n  Ok(if highest > 0 { Some(highest) } else { None })\n}\n\n/// Parse the output of `strace -c` and return a summary of syscalls.\npub fn parse_strace_output(output: &str) -> HashMap<String, StraceOutput> {\n  let mut summary = HashMap::new();\n\n  let mut lines = output\n    .lines()\n    .filter(|line| !line.is_empty() && !line.contains(\"detached ...\"));\n\n  let count = lines.clone().count();\n  if count < 4 {\n    return summary;\n  }\n\n  let total_line = lines.next_back().unwrap();\n  lines.next_back(); // Drop separator\n  let data_lines = lines.skip(2);\n\n  for line in data_lines {\n    let syscall_fields: Vec<&str> = line.split_whitespace().collect();\n    let len = syscall_fields.len();\n\n    if let Some(&syscall_name) = syscall_fields.last() {\n      if (5..=6).contains(&len) {\n        let output = StraceOutput {\n          percent_time: syscall_fields[0].parse().unwrap_or(0.0),\n          seconds: syscall_fields[1].parse().unwrap_or(0.0),\n          usecs_per_call: syscall_fields[2].parse().ok(),\n          calls: syscall_fields[3].parse().unwrap_or(0),\n          errors: if len < 6 {\n            0\n          } else {\n            syscall_fields[4].parse().unwrap_or(0)\n          },\n        };\n        summary.insert(syscall_name.to_string(), output);\n      }\n    }\n  }\n\n  let total_fields: Vec<&str> = total_line.split_whitespace().collect();\n  let total = match total_fields.len() {\n    5 => StraceOutput {\n      percent_time: total_fields[0].parse().unwrap_or(0.0),\n      seconds: total_fields[1].parse().unwrap_or(0.0),\n      usecs_per_call: None,\n      calls: total_fields[2].parse().unwrap_or(0),\n      errors: total_fields[3].parse().unwrap_or(0),\n    },\n    6 => StraceOutput {\n      percent_time: total_fields[0].parse().unwrap_or(0.0),\n      seconds: total_fields[1].parse().unwrap_or(0.0),\n      usecs_per_call: total_fields[2].parse().ok(),\n      calls: total_fields[3].parse().unwrap_or(0),\n      errors: total_fields[4].parse().unwrap_or(0),\n    },\n    _ => {\n      panic!(\"Unexpected total field count: {}\", total_fields.len());\n    }\n  };\n\n  summary.insert(\"total\".to_string(), total);\n  summary\n}\n\n/// Run a command and wait for completion.\n/// Returns an error if the command fails.\npub fn run(cmd: &[&str]) -> Result<()> {\n  let status = Command::new(cmd[0])\n    .args(&cmd[1..])\n    .stdin(Stdio::piped())\n    .status()\n    .with_context(|| format!(\"failed to execute command: {cmd:?}\"))?;\n\n  if !status.success() {\n    bail!(\"Command {:?} exited with {:?}\", cmd, status.code());\n  }\n  Ok(())\n}\n\n/// Read a JSON file into a [`serde_json::Value`].\npub fn read_json(filename: &str) -> Result<Value> {\n  let f =\n    fs::File::open(filename).with_context(|| format!(\"failed to open JSON file {filename}\"))?;\n  Ok(serde_json::from_reader(f)?)\n}\n\n/// Write a [`serde_json::Value`] into a JSON file.\npub fn write_json(filename: &str, value: &Value) -> Result<()> {\n  let f =\n    fs::File::create(filename).with_context(|| format!(\"failed to create JSON file {filename}\"))?;\n  serde_json::to_writer(f, value)?;\n  Ok(())\n}\n\n/// Download a file from either a local path or an HTTP/HTTPS URL.\n/// Falls back to copying the file if the URL does not start with http/https.\npub fn download_file(url: &str, filename: PathBuf) -> Result<()> {\n  if !url.starts_with(\"http:\") && !url.starts_with(\"https:\") {\n    fs::copy(url, &filename).with_context(|| format!(\"failed to copy from {url}\"))?;\n    return Ok(());\n  }\n\n  println!(\"Downloading {url}\");\n  let status = Command::new(\"curl\")\n    .arg(\"-L\")\n    .arg(\"-s\")\n    .arg(\"-o\")\n    .arg(&filename)\n    .arg(url)\n    .status()\n    .with_context(|| format!(\"failed to execute curl for {url}\"))?;\n\n  if !status.success() {\n    bail!(\"curl failed with exit code {:?}\", status.code());\n  }\n  if !filename.exists() {\n    bail!(\"expected file {:?} to exist after download\", filename);\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "bench/tests/cpu_intensive/public/index.css",
    "content": "body {\n  font-family:\n    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,\n    sans-serif;\n  margin: auto;\n  max-width: 38rem;\n  padding: 2rem;\n}\n"
  },
  {
    "path": "bench/tests/cpu_intensive/public/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Hello World!</title>\n    <link rel=\"stylesheet\" href=\"index.css\" />\n  </head>\n  <body>\n    <h1>Calculate prime numbers</h1>\n    <p></p>\n    <button type=\"button\" id=\"start\">Start</button>\n    <p id=\"status\"></p>\n    <p id=\"results\"></p>\n    <script src=\"site.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "bench/tests/cpu_intensive/public/site.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// Create web worker\nconst THRESHOLD = 10000000\nconst worker = new Worker('worker.js')\n/** @type {HTMLButtonElement} */\nconst start = document.getElementById('start')\n/** @type {HTMLParagraphElement} */\nconst status = document.getElementById('status')\nconst results = document.getElementById('results')\n\nconst ITERATIONS = 1\n\nlet resolver\n\nconst onMessage = (message) => {\n  // Update the UI\n  let prefix = '[Calculating]'\n\n  if (message.data.status === 'done') {\n    // tell tauri that we are done\n    window.__TAURI__.core.invoke('app_completed_successfully')\n  }\n\n  status.innerHTML = `${prefix} Found <code>${message.data.count}</code> prime numbers in <code>${message.data.time}ms</code>`\n\n  if (message.data.status === 'done') {\n    resolver(message.data.time)\n  }\n}\n\nworker.addEventListener('message', onMessage)\n\nconst benchmark = () => {\n  return new Promise((resolve) => {\n    const startTime = Date.now()\n    resolver = resolve\n    worker.postMessage({ value: THRESHOLD, startTime })\n  })\n}\n\nconst calculate = async () => {\n  let total = 0\n\n  for (let i = 0; i < ITERATIONS; i++) {\n    const result = await benchmark()\n    total += result\n  }\n\n  const average = total / ITERATIONS\n\n  results.innerText = `Average time: ${average}ms`\n}\n\nwindow.addEventListener('DOMContentLoaded', calculate)\n"
  },
  {
    "path": "bench/tests/cpu_intensive/public/worker.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nconst isPrime = (number) => {\n  if (number % 2 === 0 && number > 2) {\n    return false\n  }\n\n  let start = 2\n  const limit = Math.sqrt(number)\n  while (start <= limit) {\n    if (number % start++ < 1) {\n      return false\n    }\n  }\n  return number > 1\n}\n\naddEventListener('message', (e) => {\n  const { startTime } = e.data\n\n  let n = 0\n  let total = 0\n  const THRESHOLD = e.data.value\n  const primes = []\n\n  let previous = startTime\n\n  while (++n <= THRESHOLD) {\n    if (isPrime(n)) {\n      primes.push(n)\n      total++\n\n      const now = Date.now()\n\n      if (now - previous > 250) {\n        previous = now\n        postMessage({\n          status: 'calculating',\n          count: total,\n          time: Date.now() - startTime\n        })\n      }\n    }\n  }\n\n  postMessage({ status: 'done', count: total, time: Date.now() - startTime })\n})\n"
  },
  {
    "path": "bench/tests/cpu_intensive/src-tauri/.gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n"
  },
  {
    "path": "bench/tests/cpu_intensive/src-tauri/Cargo.toml",
    "content": "[package]\nname = \"bench_cpu_intensive\"\nversion = \"0.1.0\"\ndescription = \"A very simple Tauri Application\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\n\n[build-dependencies]\ntauri-build = { path = \"../../../../crates/tauri-build\", features = [\n  \"codegen\",\n] }\n\n[dependencies]\nserde_json = \"1\"\nserde = { version = \"1\", features = [\"derive\"] }\ntauri = { path = \"../../../../crates/tauri\", features = [] }\n"
  },
  {
    "path": "bench/tests/cpu_intensive/src-tauri/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nfn main() {\n  tauri_build::build()\n}\n"
  },
  {
    "path": "bench/tests/cpu_intensive/src-tauri/src/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\n#[tauri::command]\nfn app_completed_successfully() {\n  std::process::exit(0);\n}\n\nfn main() {\n  tauri::Builder::default()\n    .invoke_handler(tauri::generate_handler![app_completed_successfully])\n    .run(tauri::generate_context!())\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "bench/tests/cpu_intensive/src-tauri/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"frontendDist\": \"../public\"\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n    \"windows\": [\n      {\n        \"title\": \"Welcome to Tauri!\",\n        \"width\": 800,\n        \"height\": 600,\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; connect-src ipc: http://ipc.localhost\"\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../../../../examples/.icons/32x32.png\",\n      \"../../../../examples/.icons/128x128.png\",\n      \"../../../../examples/.icons/128x128@2x.png\",\n      \"../../../../examples/.icons/icon.icns\",\n      \"../../../../examples/.icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "bench/tests/files_transfer/public/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Welcome to Tauri!</title>\n  </head>\n  <body>\n    <h1>Welcome to Tauri!</h1>\n\n    <script>\n      window.addEventListener('DOMContentLoaded', (event) => {\n        window.__TAURI__.core\n          .invoke('read_file')\n          .then((_data) => {\n            // success\n            window.__TAURI__.core.invoke('app_should_close', {\n              exitCode: 0\n            })\n          })\n          .catch((_error) => {\n            // error\n            window.__TAURI__.core.invoke('app_should_close', {\n              exitCode: 1\n            })\n          })\n      })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "bench/tests/files_transfer/src-tauri/.gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n"
  },
  {
    "path": "bench/tests/files_transfer/src-tauri/Cargo.toml",
    "content": "[package]\nname = \"bench_files_transfer\"\nversion = \"0.1.0\"\ndescription = \"A very simple Tauri Application\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\n\n[build-dependencies]\ntauri-build = { path = \"../../../../crates/tauri-build\", features = [\n  \"codegen\",\n] }\n\n[dependencies]\nserde_json = \"1\"\nserde = { version = \"1\", features = [\"derive\"] }\ntauri = { path = \"../../../../crates/tauri\", features = [] }\n"
  },
  {
    "path": "bench/tests/files_transfer/src-tauri/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nfn main() {\n  tauri_build::build()\n}\n"
  },
  {
    "path": "bench/tests/files_transfer/src-tauri/src/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nuse std::fs::read;\nuse tauri::{command, ipc::Response, path::BaseDirectory, AppHandle, Manager, Runtime};\n\n#[command]\nfn app_should_close(exit_code: i32) {\n  std::process::exit(exit_code);\n}\n\n#[command]\nasync fn read_file<R: Runtime>(app: AppHandle<R>) -> Result<Response, String> {\n  let path = app\n    .path()\n    .resolve(\".tauri_3mb.json\", BaseDirectory::Home)\n    .map_err(|e| e.to_string())?;\n  let contents = read(path).map_err(|e| e.to_string())?;\n  Ok(Response::new(contents))\n}\n\nfn main() {\n  tauri::Builder::default()\n    .invoke_handler(tauri::generate_handler![app_should_close, read_file])\n    .run(tauri::generate_context!())\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "bench/tests/files_transfer/src-tauri/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"frontendDist\": \"../public\"\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n    \"windows\": [\n      {\n        \"title\": \"Welcome to Tauri!\",\n        \"width\": 800,\n        \"height\": 600,\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; connect-src ipc: http://ipc.localhost\"\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../../../../examples/.icons/32x32.png\",\n      \"../../../../examples/.icons/128x128.png\",\n      \"../../../../examples/.icons/128x128@2x.png\",\n      \"../../../../examples/.icons/icon.icns\",\n      \"../../../../examples/.icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "bench/tests/helloworld/public/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Welcome to Tauri!</title>\n  </head>\n  <body>\n    <h1>Welcome to Tauri!</h1>\n\n    <script>\n      window.addEventListener('DOMContentLoaded', (event) =>\n        window.__TAURI__.core.invoke('app_loaded_successfully')\n      )\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "bench/tests/helloworld/src-tauri/.gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n"
  },
  {
    "path": "bench/tests/helloworld/src-tauri/Cargo.toml",
    "content": "[package]\nname = \"bench_helloworld\"\nversion = \"0.1.0\"\ndescription = \"A very simple Tauri Application\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\n\n[build-dependencies]\ntauri-build = { path = \"../../../../crates/tauri-build\", features = [\n  \"codegen\",\n] }\n\n[dependencies]\nserde_json = \"1\"\nserde = { version = \"1\", features = [\"derive\"] }\ntauri = { path = \"../../../../crates/tauri\", features = [] }\n"
  },
  {
    "path": "bench/tests/helloworld/src-tauri/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nfn main() {\n  tauri_build::build()\n}\n"
  },
  {
    "path": "bench/tests/helloworld/src-tauri/src/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\n#[tauri::command]\nfn app_loaded_successfully() {\n  std::process::exit(0);\n}\n\nfn main() {\n  tauri::Builder::default()\n    .invoke_handler(tauri::generate_handler![app_loaded_successfully])\n    .run(tauri::generate_context!())\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "bench/tests/helloworld/src-tauri/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"frontendDist\": \"../public\",\n    \"beforeDevCommand\": \"\",\n    \"beforeBuildCommand\": \"\"\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n    \"windows\": [\n      {\n        \"title\": \"Welcome to Tauri!\",\n        \"width\": 800,\n        \"height\": 600,\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; connect-src ipc: http://ipc.localhost\"\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../../../../examples/.icons/32x32.png\",\n      \"../../../../examples/.icons/128x128.png\",\n      \"../../../../examples/.icons/128x128@2x.png\",\n      \"../../../../examples/.icons/icon.icns\",\n      \"../../../../examples/.icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "crates/tauri/.scripts/loop_qc.sh",
    "content": "#!/bin/bash\n# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\n\n# Loop all quickcheck tests for tauri.\nwhile true\ndo\n    cargo test qc_\n    if [[ x$? != x0 ]] ; then\n        exit $?\n    fi\ndone\n"
  },
  {
    "path": "crates/tauri/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.10.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.3`\n- Upgraded to `tauri-runtime@2.10.1`\n- Upgraded to `tauri-runtime-wry@2.10.1`\n- Upgraded to `tauri-macros@2.5.5`\n- Upgraded to `tauri-build@2.5.6`\n\n## \\[2.10.2]\n\n### Dependencies\n\n- Upgraded to `tauri-macros@2.5.4`\n- Upgraded to `tauri-build@2.5.5`\n\n## \\[2.10.1]\n\n### Dependencies\n\n- [`ce8fddb46`](https://www.github.com/tauri-apps/tauri/commit/ce8fddb4648d6421579d43c7dd44959bc57a74e0) ([#14873](https://www.github.com/tauri-apps/tauri/pull/14873)) Unlocked version range for webkit2gtk-rs dependency.\n\n## \\[2.10.0]\n\n### New Features\n\n- [`e919a760e`](https://www.github.com/tauri-apps/tauri/commit/e919a760edfc115f9e4b5d841e29cc38d5535ed1) ([#14619](https://www.github.com/tauri-apps/tauri/pull/14619) by [@NaamuKim](https://www.github.com/tauri-apps/tauri/../../NaamuKim)) Add `set_simple_fullscreen` method to `WebviewWindow`.\n\n  This method was already available on the `Window` type and is now also available on `WebviewWindow` for consistency. On macOS, it toggles fullscreen mode without creating a new macOS Space. On other platforms, it falls back to regular fullscreen.\n\n### Bug Fixes\n\n- [`853ed4642`](https://www.github.com/tauri-apps/tauri/commit/853ed4642ff77154ccd380dd9289d90815d42691) ([#14442](https://www.github.com/tauri-apps/tauri/pull/14442) by [@ish1416](https://www.github.com/tauri-apps/tauri/../../ish1416)) Fixed 500 error when accessing local video files in Android external storage directory via `convertFileSrc`. Added better error handling and logging for Android external storage access to help diagnose permission and accessibility issues.\n- [`4d5d78daf`](https://www.github.com/tauri-apps/tauri/commit/4d5d78daf636feaac20c5bc48a6071491c4291ee) ([#14812](https://www.github.com/tauri-apps/tauri/pull/14812) by [@oscartbeaumont](https://www.github.com/tauri-apps/tauri/../../oscartbeaumont)) fix(specta): don't use `#[specta(rename = ...)]` with `tauri::ipc::Channel`\n- [`ff5d76ca2`](https://www.github.com/tauri-apps/tauri/commit/ff5d76ca214b94a7b6e88aa4f0f797bbf747824d) ([#14653](https://www.github.com/tauri-apps/tauri/pull/14653) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) `WindowConfig::focus` is set to `false` in `WindowConfig::default()`\n\n### What's Changed\n\n- [`0575dd287`](https://www.github.com/tauri-apps/tauri/commit/0575dd287e021b61d2aedf64d62ae84a2c925fb4) ([#14521](https://www.github.com/tauri-apps/tauri/pull/14521) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Change the way bundle type information is added to binary files. Instead of looking up the value of a variable we simply look for the default value.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.2`\n- Upgraded to `tauri-build@2.5.4`\n- Upgraded to `tauri-runtime-wry@2.10.0`\n- Upgraded to `tauri-runtime@2.10.0`\n- Upgraded to `tauri-macros@2.5.3`\n- [`75057c7c0`](https://www.github.com/tauri-apps/tauri/commit/75057c7c08f0d4d3dd8d10cea4e2217e5d61fe1a) ([#14778](https://www.github.com/tauri-apps/tauri/pull/14778) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) **Breaking Change** for `with_webview` users: Updated webkit2gtk-rs crates to `v2.0.2`.\n- [`75057c7c0`](https://www.github.com/tauri-apps/tauri/commit/75057c7c08f0d4d3dd8d10cea4e2217e5d61fe1a) ([#14778](https://www.github.com/tauri-apps/tauri/pull/14778) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Update wry to `v0.54`.\n\n## \\[2.9.5]\n\n### Bug Fixes\n\n- [`251203b89`](https://www.github.com/tauri-apps/tauri/commit/251203b8963419cb3b40741767393e8f3c909ef9) ([#14637](https://www.github.com/tauri-apps/tauri/pull/14637) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `Monitor::work_area` returns logical position and size inside the `PhysicalRect` on Linux\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@2.9.3`\n\n## \\[2.9.4]\n\n### Performance Improvements\n\n- [`ce98d87ce`](https://www.github.com/tauri-apps/tauri/commit/ce98d87ce0aaa907285852eb80691197424e03c3) ([#14474](https://www.github.com/tauri-apps/tauri/pull/14474) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) refactor: remove needless collect. No user facing changes.\n- [`ee3cc4a91`](https://www.github.com/tauri-apps/tauri/commit/ee3cc4a91bf1315ecaefe90f423ffd55ef6c40db) ([#14475](https://www.github.com/tauri-apps/tauri/pull/14475) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) perf: remove needless clones in various files for improved performance. No user facing changes.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.1`\n- Upgraded to `tauri-runtime@2.9.2`\n- Upgraded to `tauri-runtime-wry@2.9.2`\n- Upgraded to `tauri-macros@2.5.2`\n- Upgraded to `tauri-build@2.5.3`\n\n## \\[2.9.3]\n\n### Bug Fixes\n\n- [`4b00130b8`](https://www.github.com/tauri-apps/tauri/commit/4b00130b86a27b6f121bf57897b5e92d83bcc0fc) ([#14385](https://www.github.com/tauri-apps/tauri/pull/14385) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS deadlock when running on the simulator from Xcode by properly piping stdout/stderr messages through the Xcode console and OSLog.\n\n### Dependencies\n\n- Upgraded to `tauri-macros@2.5.1`\n- Upgraded to `tauri-build@2.5.2`\n\n## \\[2.9.2]\n\n### Bug Fixes\n\n- [`28b9e7c7b`](https://www.github.com/tauri-apps/tauri/commit/28b9e7c7b83845c35fe46c37e8ed8e9022b4634e) ([#14377](https://www.github.com/tauri-apps/tauri/pull/14377) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `undefined is not an object (evaluating '[callbackId, data]')` error on custom protocol IPC fails\n\n## \\[2.9.1]\n\n### Bug Fixes\n\n- [`4b6b8690a`](https://www.github.com/tauri-apps/tauri/commit/4b6b8690ab886ebdf1307951cffbe03e31280baa) ([#14347](https://www.github.com/tauri-apps/tauri/pull/14347) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused docs.rs builds to fail. No user facing changes.\n\n### Dependencies\n\n- Upgraded to `tauri-build@2.5.1`\n- Upgraded to `tauri-runtime@2.9.1`\n- Upgraded to `tauri-runtime-wry@2.9.1`\n\n## \\[2.9.0]\n\n### New Features\n\n- [`f5851ee00`](https://www.github.com/tauri-apps/tauri/commit/f5851ee00d6d1f4d560a220ca5a728fedd525092) ([#14089](https://www.github.com/tauri-apps/tauri/pull/14089)) Adds the `scroll_bar_style` option to the Webview and WebviewWindow builders.\n  The possible values for this option are gated behind conditional compilation\n  flags, and will need to be applied using conditional compilation if customised.\n- [`3397fd9bf`](https://www.github.com/tauri-apps/tauri/commit/3397fd9bfe5f6b1337110149f6c34731b8a44bb3) ([#14133](https://www.github.com/tauri-apps/tauri/pull/14133)) Added mobile app plugin to support exit and back button press event.\n- [`68cb31897`](https://www.github.com/tauri-apps/tauri/commit/68cb318979317c09f401825150e007d60377e75e) ([#14328](https://www.github.com/tauri-apps/tauri/pull/14328)) Added `onStop`, `onDestroy`, `onRestart`, `onConfigurationChanged` Android plugin hooks.\n- [`2e089f6ac`](https://www.github.com/tauri-apps/tauri/commit/2e089f6acb854e4d7f8eafb9b2f8242b1c9fa491) ([#14148](https://www.github.com/tauri-apps/tauri/pull/14148)) Support async Swift plugin methods (`completionHandler:`) in PluginManager\n\n### Bug Fixes\n\n- [`006d59283`](https://www.github.com/tauri-apps/tauri/commit/006d592837259cac87f15cf3ffc99e7fce97685e) ([#14260](https://www.github.com/tauri-apps/tauri/pull/14260)) Properly deserialize Android plugin args with key starting with `is` (previously treated as a getter instead of a field name).\n- [`69476d8e2`](https://www.github.com/tauri-apps/tauri/commit/69476d8e2314b85bf46046140bc5495fe29b7d29) ([#14170](https://www.github.com/tauri-apps/tauri/pull/14170)) Fix the stack overflow when having too many commands in a single invoke handler in release build\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.0`\n- Upgraded to `tauri-runtime-wry@2.9.0`\n- Upgraded to `tauri-runtime@2.9.0`\n- Upgraded to `tauri-build@2.5.0`\n- Upgraded to `tauri-macros@2.5.0`\n\n## \\[2.8.5]\n\n### Enhancements\n\n- [`07e134f70`](https://www.github.com/tauri-apps/tauri/commit/07e134f70e3a65424641f1b384a26bf059fd9c56) ([#14107](https://www.github.com/tauri-apps/tauri/pull/14107) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Improve error message for request errors on iOS when local network permission has been denied and the app tries to reach the development server.\n\n### Dependencies\n\n- Upgraded to `tauri-build@2.4.1`\n\n## \\[2.8.4]\n\n### Bug Fixes\n\n- [`03e7c1193`](https://www.github.com/tauri-apps/tauri/commit/03e7c1193208716170f120a1d4a39cea0bc21064) ([#14080](https://www.github.com/tauri-apps/tauri/pull/14080) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ignore initial navigation to `about:blank` so `on_new_window` does not give a warning on first navigation on macOS.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@2.8.1`\n\n## \\[2.8.3]\n\n### Bug Fixes\n\n- [`534998406`](https://www.github.com/tauri-apps/tauri/commit/534998406433a1be52caa9792d120763ab8339ac) ([#14054](https://www.github.com/tauri-apps/tauri/pull/14054) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the runtime WebView2 detection to fail for FixedRuntime installations.\n\n## \\[2.8.2]\n\n### Bug Fixes\n\n- [`5075b67d3`](https://www.github.com/tauri-apps/tauri/commit/5075b67d368e63e07df5bac5e43c24396460692d) ([#14039](https://www.github.com/tauri-apps/tauri/pull/14039) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix compilation when the `wry` Cargo feature is disabled.\n\n## \\[2.8.1]\n\n### Bug Fixes\n\n- [`902727`](https://www.github.com/tauri-apps/tauri/commit/902727a6acea0bd9569b62ca243ae9563b4ed795) Move `WebviewWindowBuilder::with_related_view` behind the `wry` feature flag.\n\n## \\[2.8.0]\n\n### New Features\n\n- [`68874c68c`](https://www.github.com/tauri-apps/tauri/commit/68874c68c566638b4c21a3aa67844d1bdaeb6dab) ([#13564](https://www.github.com/tauri-apps/tauri/pull/13564) by [@robertrpf](https://www.github.com/tauri-apps/tauri/../../robertrpf)) Add window focusable attribute and set_focusable API.\n- [`22d6bcacb`](https://www.github.com/tauri-apps/tauri/commit/22d6bcacbb2001eb292ebd8c5d021447700f9512) ([#14008](https://www.github.com/tauri-apps/tauri/pull/14008) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Implement `App::set_device_event_filter` for `AppHandle` also.\n- [`d6d5f3707`](https://www.github.com/tauri-apps/tauri/commit/d6d5f3707768a094ff7e961ae75ba0398d772655) ([#13358](https://www.github.com/tauri-apps/tauri/pull/13358) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Load root certificate from CLI-set environment variable and use it on the mobile dev server proxy.\n- [`33d0b3f0c`](https://www.github.com/tauri-apps/tauri/commit/33d0b3f0c133edebb1c716e2f5942d70509ae347) ([#13876](https://www.github.com/tauri-apps/tauri/pull/13876) by [@thlstsul](https://www.github.com/tauri-apps/tauri/../../thlstsul)) Added `WebviewBuilder::on_document_title_changed` and `WebviewWindowBuilder::on_document_title_changed`.\n- [`33d0b3f0c`](https://www.github.com/tauri-apps/tauri/commit/33d0b3f0c133edebb1c716e2f5942d70509ae347) ([#13876](https://www.github.com/tauri-apps/tauri/pull/13876) by [@thlstsul](https://www.github.com/tauri-apps/tauri/../../thlstsul)) Added `WebviewBuilder::on_new_window` and `WebviewWindowBuilder::on_new_window`.\n- [`7c2eb31c8`](https://www.github.com/tauri-apps/tauri/commit/7c2eb31c83a202b3481a6d560ae2048bcbe4157b) ([#13895](https://www.github.com/tauri-apps/tauri/pull/13895) by [@ahqsoftwares](https://www.github.com/tauri-apps/tauri/../../ahqsoftwares)) Introduces `PluginHandle::run_mobile_plugin_async` as an async alternative to `run_mobile_plugin`\n- [`dfadcb764`](https://www.github.com/tauri-apps/tauri/commit/dfadcb764bdf84089a5487005a7b4f3b7cf09494) ([#13661](https://www.github.com/tauri-apps/tauri/pull/13661) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Added `Webview::set_cookie()`, `Webview::delete_cookie()`, `WebviewWindow::set_cookie()` and `WebviewWindow::delete_cookie()`.\n- [`5110a762e`](https://www.github.com/tauri-apps/tauri/commit/5110a762e9db978a28a15400bf76e3c864da2a86) ([#13830](https://www.github.com/tauri-apps/tauri/pull/13830) by [@Sky-walkerX](https://www.github.com/tauri-apps/tauri/../../Sky-walkerX)) Added `Window::set_simple_fullscreen`.\n\n### Enhancements\n\n- [`7261a1436`](https://www.github.com/tauri-apps/tauri/commit/7261a14368eeef040aee3350bb39183558d18bf0) ([#14012](https://www.github.com/tauri-apps/tauri/pull/14012) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Implemented `Webview::on_webview_event` for `WebviewWindow` as well\n- [`0e6b5cbe5`](https://www.github.com/tauri-apps/tauri/commit/0e6b5cbe5f44b53aca7aff22bc3ea1a9444b3209) ([#14009](https://www.github.com/tauri-apps/tauri/pull/14009) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) re-export `PixelUnit`, `PhysicalUnit`, `LogicalUnit`\n- [`a3dc42477`](https://www.github.com/tauri-apps/tauri/commit/a3dc42477a9aae0471ecf3caa5812e9537532bbf) ([#14013](https://www.github.com/tauri-apps/tauri/pull/14013) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) export `TitleBarStyle` for all platforms.\n- [`f1232671a`](https://www.github.com/tauri-apps/tauri/commit/f1232671abc15f03118a35da1883ce9aca88ff2a) ([#13959](https://www.github.com/tauri-apps/tauri/pull/13959) by [@petersamokhin](https://www.github.com/tauri-apps/tauri/../../petersamokhin)) Introduce `with_inner_tray_icon` for Tauri `TrayIcon` to access the inner platform-specific tray icon.\n\n  Note that `tray-icon` crate may be updated in minor releases of Tauri.\n  Therefore, it's recommended to pin Tauri to at least a minor version when you're using `with_inner_tray_icon`.\n- [`72b4226ee`](https://www.github.com/tauri-apps/tauri/commit/72b4226ee9932b4dafa4837a49420b2c02d14bb7) ([#13809](https://www.github.com/tauri-apps/tauri/pull/13809) by [@Beanow](https://www.github.com/tauri-apps/tauri/../../Beanow)) Reduced `Debug` format size for binary buffers.\n- [`21ebc6e82`](https://www.github.com/tauri-apps/tauri/commit/21ebc6e82062b55a12f3a360d9a979daf5ae7e66) ([#14007](https://www.github.com/tauri-apps/tauri/pull/14007) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Changed the parameter type of `AppHandle::remove_plugin` from `&'static str` to `&str`.\n- [`5ba1c3faa`](https://www.github.com/tauri-apps/tauri/commit/5ba1c3faa468073512bdb5035a01f7f99720fcf0) ([#13722](https://www.github.com/tauri-apps/tauri/pull/13722) by [@s00d](https://www.github.com/tauri-apps/tauri/../../s00d)) Added icon (icon and nativeIcon) support for Submenu:\n\n  - In the Rust API (`tauri`), you can now set an icon for submenus via the builder and dedicated methods.\n  - In the JS/TS API (`@tauri-apps/api`), `SubmenuOptions` now has an `icon` field, and the `Submenu` class provides `setIcon` and `setNativeIcon` methods.\n  - Usage examples are added to the documentation and demo app.\n\n  This is a backwards-compatible feature. Submenus can now display icons just like regular menu items.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.7.0`\n- Upgraded to `tauri-runtime-wry@2.8.0`\n- Upgraded to `tauri-runtime@2.8.0`\n- Upgraded to `tauri-macros@2.3.3`\n- Upgraded to `tauri-build@2.3.2`\n\n## \\[2.7.0]\n\n### New Features\n\n- [`7bc77a038`](https://www.github.com/tauri-apps/tauri/commit/7bc77a038af062a02aabeaf9b228577447bad5e5) ([#13609](https://www.github.com/tauri-apps/tauri/pull/13609) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Added `tauri::plugin::Builder::js_init_script_on_all_frames` that allows plugins to add initialization scripts that runs on all frames\n\n### Enhancements\n\n- [`7f3c98911`](https://www.github.com/tauri-apps/tauri/commit/7f3c989111e007d7eeb5da118421214848e4bfcd) ([#13837](https://www.github.com/tauri-apps/tauri/pull/13837) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Add `AppHandle::plugin_boxed` and `Builder::plugin_boxed` methods to allow adding plugins in the form of boxed trait objects.\n- [`7bc77a038`](https://www.github.com/tauri-apps/tauri/commit/7bc77a038af062a02aabeaf9b228577447bad5e5) ([#13609](https://www.github.com/tauri-apps/tauri/pull/13609) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) `tauri::plugin::Builder::js_init_script` now takes `impl Into<String>` instead of `String`\n\n### Bug Fixes\n\n- [`6a4451bcd`](https://www.github.com/tauri-apps/tauri/commit/6a4451bcd9cf5a2428857d2e47ea25e3d74712ae) ([#13849](https://www.github.com/tauri-apps/tauri/pull/13849) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix isolation pattern creates iframes within iframes on Windows\n- [`4ba871c5d`](https://www.github.com/tauri-apps/tauri/commit/4ba871c5d2eb3fbb8db56c8d8f9916e65d3e34ac) ([#13782](https://www.github.com/tauri-apps/tauri/pull/13782) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes loading external URLs in mobile development mode.\n- [`1c5df96fe`](https://www.github.com/tauri-apps/tauri/commit/1c5df96fe8542e815cd887e66c29efb268add710) ([#13773](https://www.github.com/tauri-apps/tauri/pull/13773) by [@tasgon](https://www.github.com/tauri-apps/tauri/../../tasgon)) Forward request body on the mobile frontend proxy.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@2.7.2`\n- Upgraded to `tauri-utils@2.6.0`\n- Upgraded to `tauri-runtime@2.7.1`\n- Upgraded to `tauri-macros@2.3.2`\n- Upgraded to `tauri-build@2.3.1`\n\n## \\[2.6.2]\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@2.7.1`\n\n## \\[2.6.1]\n\n### Bug Fixes\n\n- [`5bbcaaec8`](https://www.github.com/tauri-apps/tauri/commit/5bbcaaec891c3011b147caed8908a5d043a34f48) ([#13707](https://www.github.com/tauri-apps/tauri/pull/13707) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix a regression that the JavaScript API can no longer set menus and icons for tray icons\n\n### Dependencies\n\n- Upgraded to `tauri-macros@2.3.1`\n\n## \\[2.6.0]\n\n### New Features\n\n- [`50ebddaa2`](https://www.github.com/tauri-apps/tauri/commit/50ebddaa2d83033a393a176ba07ef28352b98210) ([#13319](https://www.github.com/tauri-apps/tauri/pull/13319) by [@kingsword09](https://www.github.com/tauri-apps/tauri/../../kingsword09)) Expose the `setAutoResize` API for webviews in `@tauri-apps/api`.\n- [`267368fd4`](https://www.github.com/tauri-apps/tauri/commit/267368fd4f83e0a71dfb1b72a66d56592a2066bc) ([#13276](https://www.github.com/tauri-apps/tauri/pull/13276) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `Monitor::work_area` getter\n- [`267368fd4`](https://www.github.com/tauri-apps/tauri/commit/267368fd4f83e0a71dfb1b72a66d56592a2066bc) ([#13276](https://www.github.com/tauri-apps/tauri/pull/13276) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `tauri::PhysicalRect` and `tauri::LogicalRect` types.\n- [`09c19932d`](https://www.github.com/tauri-apps/tauri/commit/09c19932d2ddf05f28bcdc73796a966532e7ca1c) ([#13304](https://www.github.com/tauri-apps/tauri/pull/13304) by [@39zde](https://www.github.com/tauri-apps/tauri/../../39zde)) Adds the option to configure the HTTP `Service-Worker-Allowed` response header in `app > security > headers`\n- [`9c16eefa3`](https://www.github.com/tauri-apps/tauri/commit/9c16eefa319b4697bac1d1019bbb5f93eca63173) ([#13629](https://www.github.com/tauri-apps/tauri/pull/13629) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Added `x11` Cargo feature (enabled by default). Disabling it is useful for apps that only support Wayland, reducing its size.\n  **NOTE**: When manually disabling tauri default features, you must enable the `x11` feature to support it.\n\n### Enhancements\n\n- [`96ecfca42`](https://www.github.com/tauri-apps/tauri/commit/96ecfca428e4e5d9ff5d5eeed3f94a06a466ed02) ([#13406](https://www.github.com/tauri-apps/tauri/pull/13406) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Check if the webview runtime is accessible when creating a webview, returning an error if it doesn't.\n\n### Bug Fixes\n\n- [`94b77b36e`](https://www.github.com/tauri-apps/tauri/commit/94b77b36e35cd8396a5589fbcce26cf44f43d938) ([#13288](https://www.github.com/tauri-apps/tauri/pull/13288) by [@oscartbeaumont](https://www.github.com/tauri-apps/tauri/../../oscartbeaumont)) Prevent the JavaScript runtime crashing when channel events fire in a webview that no longer has callbacks for the channel.\n- [`bc2f0e48a`](https://www.github.com/tauri-apps/tauri/commit/bc2f0e48acba5c1c99b9fceb1000863c47df89ef) ([#13401](https://www.github.com/tauri-apps/tauri/pull/13401) by [@oscartbeaumont](https://www.github.com/tauri-apps/tauri/../../oscartbeaumont)) fix(macOS): caculation for work area\n- [`dfacb656d`](https://www.github.com/tauri-apps/tauri/commit/dfacb656d266de5d99656b1513eacc0f498f0b0a) ([#13360](https://www.github.com/tauri-apps/tauri/pull/13360) by [@velocitysystems](https://www.github.com/tauri-apps/tauri/../../velocitysystems)) Fixes multiple event listeners registration for iOS plugins.\n- [`23b9da75b`](https://www.github.com/tauri-apps/tauri/commit/23b9da75b91379cca9520bc53b10fdf39ebae241) ([#13324](https://www.github.com/tauri-apps/tauri/pull/13324) by [@kingsword09](https://www.github.com/tauri-apps/tauri/../../kingsword09)) Fixed path joining behavior where `path.join('', 'a')` incorrectly returns \"/a\" instead of \"a\".\n- [`638804e9c`](https://www.github.com/tauri-apps/tauri/commit/638804e9c488afdcd51bff8f329a321903337263) ([#13423](https://www.github.com/tauri-apps/tauri/pull/13423) by [@kingsword09](https://www.github.com/tauri-apps/tauri/../../kingsword09)) Fixed set_window_effects not runs on main thread in WindowBuilder.\n- [`039f44b7b`](https://www.github.com/tauri-apps/tauri/commit/039f44b7b1ecd411e3b3406aa28ccb8e8a0ec63a) ([#13307](https://www.github.com/tauri-apps/tauri/pull/13307) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `TrayIcon.getById` returning a new resource ID instead of reusing a previously created id from `TrayIcon.new`.\n- [`76cbeef20`](https://www.github.com/tauri-apps/tauri/commit/76cbeef20848d9adf95c0e95ca17058dbf76fe1e) ([#13278](https://www.github.com/tauri-apps/tauri/pull/13278) by [@situ2001](https://www.github.com/tauri-apps/tauri/../../situ2001)) Fix JavaScript API `Webview.proxyUrl` had no effect when used in the `Webview` constructor\n- [`b985eaf0a`](https://www.github.com/tauri-apps/tauri/commit/b985eaf0a231ea570e36d686c665cddbc76ab4f6) ([#13306](https://www.github.com/tauri-apps/tauri/pull/13306) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Immediately unregister event listener when the unlisten function is called.\n\n### Performance Improvements\n\n- [`6a39f4999`](https://www.github.com/tauri-apps/tauri/commit/6a39f49991e613e8f3befe0e8dff288482ccdd89) ([#13464](https://www.github.com/tauri-apps/tauri/pull/13464) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Use dynamic dispatch for async commands in dev, this should speed up the compilation time by quite a bit, and significantly reduces the incremental compilation time\n\n### What's Changed\n\n- [`168629646`](https://www.github.com/tauri-apps/tauri/commit/168629646335f24cc7f1c4a61df22688b2198f98) ([#13418](https://www.github.com/tauri-apps/tauri/pull/13418) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Put dynamic ACL into a feature `dynamic-acl`, this is currently enabled by default to align with the previous behaviors, you can disable it through `default-features = false` to reduce the final binary size by not including the ACL references\n- [`b5c549d18`](https://www.github.com/tauri-apps/tauri/commit/b5c549d1898ecdb712822c02dc665cc6771fbd07) ([#13325](https://www.github.com/tauri-apps/tauri/pull/13325) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) `transformCallback` now registers the callbacks inside `window.__TAURI_INTERNALS__.callbacks` instead of directly on `window['_{id}']`\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.5.0`\n- Upgraded to `tauri-runtime-wry@2.7.0`\n- Upgraded to `tauri-macros@2.3.0`\n- Upgraded to `tauri-build@2.3.0`\n- Upgraded to `tauri-runtime@2.7.0`\n- [`9c16eefa3`](https://www.github.com/tauri-apps/tauri/commit/9c16eefa319b4697bac1d1019bbb5f93eca63173) ([#13629](https://www.github.com/tauri-apps/tauri/pull/13629) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Updated tao to 0.34, wry to 0.52 and webview2-com to 0.38.\n\n### Breaking Changes\n\n- [`b7cdb3b39`](https://www.github.com/tauri-apps/tauri/commit/b7cdb3b39ef7e84773ce9312535825801350fa20) ([#13410](https://www.github.com/tauri-apps/tauri/pull/13410) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Feature gated the HTML manipulation code in `tauri-utils` behined a flag to reduce compile time\n\n## \\[2.5.1]\n\n### Enhancements\n\n- [`31becbd1d`](https://www.github.com/tauri-apps/tauri/commit/31becbd1d19ae75d82854d7d28e7b204be0d9a4c) ([#13269](https://www.github.com/tauri-apps/tauri/pull/13269) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Respect `data-tauri-drag-region=\"false\"`, it will no longer start dragging. This is useful when binding the attribute to a state using React, or another framework.\n\n### Bug Fixes\n\n- [`da2a6ae5e`](https://www.github.com/tauri-apps/tauri/commit/da2a6ae5e396aace7d90d15f150294241a6ba0e2) ([#13268](https://www.github.com/tauri-apps/tauri/pull/13268) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix a regression that made the raw type messages received from `Channel.onmessage` became `number[]` instead of `ArrayBuffer` when that message is small\n- [`85b191252`](https://www.github.com/tauri-apps/tauri/commit/85b19125294917e10e89fc9e09722eaaa4f69962) ([#13241](https://www.github.com/tauri-apps/tauri/pull/13241) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Make `tauri-runtime-wry` optional even with features like `macos-private-api`\n\n## \\[2.5.0]\n\n### New Features\n\n- [`dd4f13ce4`](https://www.github.com/tauri-apps/tauri/commit/dd4f13ce4b3cd89cde2fa3f18a063c272f215621) ([#13185](https://www.github.com/tauri-apps/tauri/pull/13185)) MacOS: Add `set_dock_visibility` method to support setting the visibility of the application in the dock.\n- [`8cf662e34`](https://www.github.com/tauri-apps/tauri/commit/8cf662e34bf738a0d16bb7b9aeb35667e2e4984b) ([#13076](https://www.github.com/tauri-apps/tauri/pull/13076)) -   add API to run initialization scripts on all frames\n  \\-   `WebviewBuilder::initialization_script_on_all_frames`\n  \\-   `WebviewWindowBuilder::initialization_script_on_all_frames`\n  \\-   `WebviewAttributes::initialization_script_on_all_frames`\n- [`ea36294cb`](https://www.github.com/tauri-apps/tauri/commit/ea36294cbca98f7725c91d1464fd92e77c89698a) ([#13208](https://www.github.com/tauri-apps/tauri/pull/13208)) Added `WebviewWindowBuilder::with_input_accessory_view_builder` and `WebviewBuilder::with_input_accessory_view_builder` on iOS.\n- [`c1cd0a2dd`](https://www.github.com/tauri-apps/tauri/commit/c1cd0a2ddb5bc3e99451cbe399b5fc9f0035f571) ([#13090](https://www.github.com/tauri-apps/tauri/pull/13090)) macOS/iOS: add option to disable or enable link previews when building a webview (the webkit api has it enabled by default)\n\n  - `WebViewBuilder.allow_link_preview(allow_link_preview: bool)`\n  - `WebviewWindowBuilder.allow_link_preview(allow_link_preview: bool)`\n- [`b072e2b29`](https://www.github.com/tauri-apps/tauri/commit/b072e2b2967640ae4fa1af466ae878c156551edd) ([#9687](https://www.github.com/tauri-apps/tauri/pull/9687)) Add `preventOverflow` config option to prevent the window from overflowing the monitor size on creation\n- [`b072e2b29`](https://www.github.com/tauri-apps/tauri/commit/b072e2b2967640ae4fa1af466ae878c156551edd) ([#9687](https://www.github.com/tauri-apps/tauri/pull/9687)) Add `WindowBuilder::prevent_overflow`, `WebviewWindowBuilder::prevent_overflow`, `WindowBuilder::prevent_overflow_with_margin` and `WebviewWindowBuilder::prevent_overflow_with_margin` APIs to prevent the window from overflowing the monitor size on creation.\n\n### Enhancements\n\n- [`9356fa15d`](https://www.github.com/tauri-apps/tauri/commit/9356fa15d87e14b4512fe1b86383a597e6e641d4) ([#13239](https://www.github.com/tauri-apps/tauri/pull/13239)) Enhance panic message when fetching unmanaged state.\n- [`ebd3dcb92`](https://www.github.com/tauri-apps/tauri/commit/ebd3dcb92f8c0381daf6f5fdb2eaeef05f11bb6c) ([#13135](https://www.github.com/tauri-apps/tauri/pull/13135)) `Webview::eval` and `WebviewWindow::eval` now takes `impl Into<String>` instead of `&str` to allow passing the scripts more flexible and efficiently\n- [`fbd57a1af`](https://www.github.com/tauri-apps/tauri/commit/fbd57a1afd94cc4aadff0b252724fe44060c67e5) ([#13175](https://www.github.com/tauri-apps/tauri/pull/13175)) `Builder::invoke_system` takes `AsRef<str>` now\n\n### Bug Fixes\n\n- [`66e6325f4`](https://www.github.com/tauri-apps/tauri/commit/66e6325f43efa49ec2165c45afec911a1a14ecfb) ([#13136](https://www.github.com/tauri-apps/tauri/pull/13136)) Fix `Channel`'s callback attached to `window` never cleaned up\n- [`0d39ff6b0`](https://www.github.com/tauri-apps/tauri/commit/0d39ff6b09e0a58a2e031d60f7bdc92b48d3cdf0) ([#13150](https://www.github.com/tauri-apps/tauri/pull/13150)) Fix missing `core:` in referenced commands in ACL error message\n- [`690146e31`](https://www.github.com/tauri-apps/tauri/commit/690146e3115f615818ec6927eb56fab157221504) ([#13217](https://www.github.com/tauri-apps/tauri/pull/13217)) Fix large number of commands with large structs as parameters causing stack overflow on debug build on Windows\n- [`f888502fd`](https://www.github.com/tauri-apps/tauri/commit/f888502fd228ad96b105e1e66f01c20c9f109983) ([#13227](https://www.github.com/tauri-apps/tauri/pull/13227)) `invoke` will now properly throw when `options.headers` contains non-ascii characters instead of silently replacing them\n- [`f888502fd`](https://www.github.com/tauri-apps/tauri/commit/f888502fd228ad96b105e1e66f01c20c9f109983) ([#13227](https://www.github.com/tauri-apps/tauri/pull/13227)) Fix `invoke` ignores the headers option if it's an `Headers`\n- [`b8c0d7e40`](https://www.github.com/tauri-apps/tauri/commit/b8c0d7e402b2ea8114bfa0f9322c986bd3eb7845) ([#13040](https://www.github.com/tauri-apps/tauri/pull/13040)) Fix `run_return` not responding to `restart` and `request_restart`\n\n### Performance Improvements\n\n- [`66e6325f4`](https://www.github.com/tauri-apps/tauri/commit/66e6325f43efa49ec2165c45afec911a1a14ecfb) ([#13136](https://www.github.com/tauri-apps/tauri/pull/13136)) Improve `Channel`'s performance when sending small amount of data (e.g. sending a number)\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.4.0`\n- Upgraded to `tauri-runtime@2.6.0`\n- Upgraded to `tauri-runtime-wry@2.6.0`\n- Upgraded to `tauri-macros@2.2.0`\n- Upgraded to `tauri-build@2.2.0`\n- [`bb5faa21f`](https://www.github.com/tauri-apps/tauri/commit/bb5faa21f418dd765ce81b495b56e9c519251b6d) ([#13163](https://www.github.com/tauri-apps/tauri/pull/13163)) Update webview2-com to 0.37.\n- [`bb5faa21f`](https://www.github.com/tauri-apps/tauri/commit/bb5faa21f418dd765ce81b495b56e9c519251b6d) ([#13163](https://www.github.com/tauri-apps/tauri/pull/13163)) Update windows to 0.61.\n\n### Breaking Changes\n\n- [`fca5154e7`](https://www.github.com/tauri-apps/tauri/commit/fca5154e7ab57bb1bc8c6f4c3c6e4b5650d170d9) ([#13130](https://www.github.com/tauri-apps/tauri/pull/13130)) Removed re-exported `WebviewAttributes` from `tauri-runtime` which is exposed by accident in `tauri` and not used by any public facing APIs\n\n## \\[2.4.1]\n\n### Enhancements\n\n- [`a851b6597`](https://www.github.com/tauri-apps/tauri/commit/a851b6597f7e37d12f9e4632945e8466800eb5ff) ([#13057](https://www.github.com/tauri-apps/tauri/pull/13057) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Enhanced the description of generated docs and schema for permission sets to include list of permissions within.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.1`\n- Upgraded to `tauri-runtime@2.5.1`\n- Upgraded to `tauri-runtime-wry@2.5.1`\n- Upgraded to `tauri-macros@2.1.1`\n- Upgraded to `tauri-build@2.1.1`\n\n## \\[2.4.0]\n\n### New Features\n\n- [`be2e6b85f`](https://www.github.com/tauri-apps/tauri/commit/be2e6b85fed226732b4a98f68cc5d72b4f8f5a13) ([#12944](https://www.github.com/tauri-apps/tauri/pull/12944) by [@Simon-Laux](https://www.github.com/tauri-apps/tauri/../../Simon-Laux)) add `Window.is_always_on_top()` and `WebviewWindow.is_always_on_top()`\n- [`c10802425`](https://www.github.com/tauri-apps/tauri/commit/c10802425781d6ce7aac0a8beeddf2b51120f69c) ([#12710](https://www.github.com/tauri-apps/tauri/pull/12710) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) derive `Clone` for `tauri::ExitRequestApi`.\n- [`cedb24d49`](https://www.github.com/tauri-apps/tauri/commit/cedb24d494b84111daa3206c05196c8b89f1e994) ([#12665](https://www.github.com/tauri-apps/tauri/pull/12665) by [@charrondev](https://www.github.com/tauri-apps/tauri/../../charrondev)) Added `Webview::cookies()`, `Webview::cookies_for_url()`, `WebviewWindow::cookies()` and `Webview::cookies_for_url()`.\n- [`20c190691`](https://www.github.com/tauri-apps/tauri/commit/20c19069125c89b2d45a2127278c9ffc2df35fc2) ([#12821](https://www.github.com/tauri-apps/tauri/pull/12821) by [@Simon-Laux](https://www.github.com/tauri-apps/tauri/../../Simon-Laux)) Add `WebviewBuilder.disable_javascript` and `WebviewWindowBuilder.disable_javascript` api to disable JavaScript.\n- [`060de5bbd`](https://www.github.com/tauri-apps/tauri/commit/060de5bbdddca384e3965a8938d89840f27c581d) ([#12837](https://www.github.com/tauri-apps/tauri/pull/12837) by [@niladrix719](https://www.github.com/tauri-apps/tauri/../../niladrix719)) Added `getIdentifier()` function to get the application identifier configured in tauri.conf.json\n- [`658e5f5d1`](https://www.github.com/tauri-apps/tauri/commit/658e5f5d1dc1bd970ae572a42447448d064a7fee) ([#12668](https://www.github.com/tauri-apps/tauri/pull/12668) by [@thomaseizinger](https://www.github.com/tauri-apps/tauri/../../thomaseizinger)) Add `App::run_return` function. Contrary to `App::run`, this will **not** exit the process but instead return the requested exit-code. This allows the host app to perform further cleanup after Tauri has exited. `App::run_return` is not available on iOS and fallbacks to the regular `App::run` functionality.\n\n  The `App::run_iteration` function is deprecated as part of this because calling it in a loop - as suggested by the name - will cause a busy-loop.\n- [`5591a4f0b`](https://www.github.com/tauri-apps/tauri/commit/5591a4f0b41afb175acf188a30e86b7c09e51328) ([#12912](https://www.github.com/tauri-apps/tauri/pull/12912) by [@Daedaluz](https://www.github.com/tauri-apps/tauri/../../Daedaluz)) Change webview zoom on mousewheel when the `zoom_hotkeys_enabled` configuration is set to `true`.\n- [`bcdd51025`](https://www.github.com/tauri-apps/tauri/commit/bcdd510254ebe37827e22a5ffeb944321361e97c) ([#13012](https://www.github.com/tauri-apps/tauri/pull/13012) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) The `path` basename and extname APIs now accept Android content URIs, such as the paths returned by the dialog plugin.\n- [`bcdd51025`](https://www.github.com/tauri-apps/tauri/commit/bcdd510254ebe37827e22a5ffeb944321361e97c) ([#13012](https://www.github.com/tauri-apps/tauri/pull/13012) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `PathResolver::file_name` to resolve file names from content URIs on Android (leverating `std::path::Path::file_name` on other platforms).\n- [`c698a6d6f`](https://www.github.com/tauri-apps/tauri/commit/c698a6d6f3e02548444a4aa0e5220bbc6fc05c74) ([#12818](https://www.github.com/tauri-apps/tauri/pull/12818) by [@Simon-Laux](https://www.github.com/tauri-apps/tauri/../../Simon-Laux)) feat: add `Webview.reload` and `WebviewWindow.reload`\n- [`013f8f652`](https://www.github.com/tauri-apps/tauri/commit/013f8f652302f2d49c5ec0a075582033d8b074fb) ([#12890](https://www.github.com/tauri-apps/tauri/pull/12890) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Added `build > removeUnusedCommands` to trigger the build scripts and macros to remove unused commands based on the capabilities you defined. Note this won't be accounting for dynamically added ACLs so make sure to check it when using this.\n- [`35018eed0`](https://www.github.com/tauri-apps/tauri/commit/35018eed026d101273d758fca049ba91bfc891fa) ([#12996](https://www.github.com/tauri-apps/tauri/pull/12996) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Introduce `AppHandle::request_restart()` as an alternative to `AppHandle::restart()` to trigger exit event reliably\n- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `WebviewWindowBuilder::traffic_light_position` to set the traffic light buttons position on macOS.\n- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `trafficLightPosition` window configuration to set the traffic light buttons position on macOS.\n\n### Enhancements\n\n- [`55ffa23c9`](https://www.github.com/tauri-apps/tauri/commit/55ffa23c9e4075ec3b3e64f808398b323db79e6a) ([#12855](https://www.github.com/tauri-apps/tauri/pull/12855) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix docs.rs build for mobile targets.\n- [`f2c94aaca`](https://www.github.com/tauri-apps/tauri/commit/f2c94aaca074bf15164d08e959b67f67f4c934ed) ([#12682](https://www.github.com/tauri-apps/tauri/pull/12682) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Add `Plugin#startIntentSenderForResult` Android API for mobile plugins.\n\n### Bug Fixes\n\n- [`755533c51`](https://www.github.com/tauri-apps/tauri/commit/755533c518391824c7393a16577d10db3aa91d19) ([#12876](https://www.github.com/tauri-apps/tauri/pull/12876) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Removed `TSend: Clone` requirement for `Channel<TSend>` by implementing `Clone` manually instead of driving it.\n- [`f98598817`](https://www.github.com/tauri-apps/tauri/commit/f98598817ca5b481d9de7a661ad00d14fd6b3b72) ([#12870](https://www.github.com/tauri-apps/tauri/pull/12870) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Update path plugin to use older dataDir API on SDK < 24.\n- [`f67a4a6bf`](https://www.github.com/tauri-apps/tauri/commit/f67a4a6bfec8ba21ae75f58c6fc74f12a07d4abf) ([#12971](https://www.github.com/tauri-apps/tauri/pull/12971) by [@WofWca](https://www.github.com/tauri-apps/tauri/../../WofWca)) Fix `tauri::AssetResolver::get` and `tauri::AssetResolver::get_for_scheme`\n  skipping the first character of the `path` even if it's not a slash (/).\n- [`dc90cd391`](https://www.github.com/tauri-apps/tauri/commit/dc90cd3919e6760ab28c8b781e5c864bf836240e) ([#12911](https://www.github.com/tauri-apps/tauri/pull/12911) by [@Daedaluz](https://www.github.com/tauri-apps/tauri/../../Daedaluz)) Listen for `Ctrl +` or `Cmd +` to support zoom functionality in swedish keyboard layouts.\n- [`b05f82d35`](https://www.github.com/tauri-apps/tauri/commit/b05f82d35ba068bfeb44193b204fbfe365415a25) ([#12313](https://www.github.com/tauri-apps/tauri/pull/12313) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) `AppHandle::restart()` now waits for `RunEvent::Exit` to be delivered before restarting the application.\n\n### Performance Improvements\n\n- [`1cd8f55ee`](https://www.github.com/tauri-apps/tauri/commit/1cd8f55eed326d61860fee62ba2d2f4464bdcfcc) ([#13033](https://www.github.com/tauri-apps/tauri/pull/13033) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Don't ship global `bundle.global.js` if `app > withGlobalTauri` is set to false\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.5.0`\n- Upgraded to `tauri-runtime-wry@2.5.0`\n- Upgraded to `tauri-utils@2.3.0`\n- Upgraded to `tauri-build@2.1.0`\n- Upgraded to `tauri-macros@2.1.0`\n\n## \\[2.3.1]\n\n### Bug Fixes\n\n- [`4f26dcf30`](https://www.github.com/tauri-apps/tauri/commit/4f26dcf3090f230d2996626423c8b9d58cc2e8aa) ([#12833](https://www.github.com/tauri-apps/tauri/pull/12833) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix OS webviews (`webview2` and `webkit2gtk`) are always compiled with tauri even without `wry` feature\n- [`e103e87f1`](https://www.github.com/tauri-apps/tauri/commit/e103e87f155cf7fa51baa0a48a476463216c0d62) ([#12848](https://www.github.com/tauri-apps/tauri/pull/12848) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix crash on Windows because of missing functions on older Windows systems, regression in 2.3.0\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@2.4.1`\n\n## \\[2.3.0]\n\n### New Features\n\n- [`abdd55807`](https://www.github.com/tauri-apps/tauri/commit/abdd55807587f1bb41b95d0b129ba24b3c6e1d28) ([#12460](https://www.github.com/tauri-apps/tauri/pull/12460) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Add `emit_str*` methods to `Emitter` trait to allow emitting JSON serialized data directly.\n- [`7d8252679`](https://www.github.com/tauri-apps/tauri/commit/7d8252679d7c28b948d94ccd8130a5c9feaa3d27) ([#12701](https://www.github.com/tauri-apps/tauri/pull/12701) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Export `struct tauri::ExitRequestApi`.\n\n### Enhancements\n\n- [`a2d36b8c3`](https://www.github.com/tauri-apps/tauri/commit/a2d36b8c34a8dcfc6736797ca5cd4665faf75e7e) ([#12181](https://www.github.com/tauri-apps/tauri/pull/12181) by [@bastiankistner](https://www.github.com/tauri-apps/tauri/../../bastiankistner)) Add an option to change the default background throttling policy (currently for WebKit only).\n- [`e9c9c4d6f`](https://www.github.com/tauri-apps/tauri/commit/e9c9c4d6f6f9c39f848183bc432790b1f9cb74fc) ([#12529](https://www.github.com/tauri-apps/tauri/pull/12529) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Derive `Clone` for `PathResolver` struct.\n- [`385a41dea`](https://www.github.com/tauri-apps/tauri/commit/385a41dea27330b42ae21419815c458afab47f94) ([#12817](https://www.github.com/tauri-apps/tauri/pull/12817) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Windows, undecorated window with shadows, now have native resize handles outside of the window client area.\n- [`d6520a21c`](https://www.github.com/tauri-apps/tauri/commit/d6520a21ce02c3e2be2955999946c2cb7bdb07aa) ([#12541](https://www.github.com/tauri-apps/tauri/pull/12541) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Updated `wry` to 0.50, `windows` to 0.60, `webview2-com` to 0.36, and `objc2` to 0.6. This can be a **breaking change** if you use the `with_webview` API!\n\n### Bug Fixes\n\n- [`d7b998fe7`](https://www.github.com/tauri-apps/tauri/commit/d7b998fe71eca4d5471d73900f7694c043a17256) ([#12723](https://www.github.com/tauri-apps/tauri/pull/12723) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Deprecate `Manager::unmanage` to fix `use-after-free` unsoundness, see tauri-apps/tauri#12721 for details.\n- [`3dbcbe768`](https://www.github.com/tauri-apps/tauri/commit/3dbcbe7685319724c41e66d912b5daaec7f99868) ([#12461](https://www.github.com/tauri-apps/tauri/pull/12461) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) `Webview::navigate` and `WebviewWindow::navigate` borrows `&self` instead of unnecessarily borrowing `&mut self`.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.4.0`\n- Upgraded to `tauri-runtime-wry@2.4.0`\n- Upgraded to `tauri-utils@2.2.0`\n- Upgraded to `tauri-macros@2.0.5`\n- Upgraded to `tauri-build@2.0.6`\n\n## \\[2.2.5]\n\n### Bug Fixes\n\n- [`477e9c049`](https://www.github.com/tauri-apps/tauri/commit/477e9c0496ff75ef8ef7aedc5430c77e213cd740) ([#12514](https://www.github.com/tauri-apps/tauri/pull/12514) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused iOS apps to panic when using an async function for `tauri::mobile_entry_point`.\n\n## \\[2.2.4]\n\n### Bug Fixes\n\n- [`27096cdc0`](https://www.github.com/tauri-apps/tauri/commit/27096cdc05d89b61b2372b4e4a3018c87f240ab8) ([#12445](https://www.github.com/tauri-apps/tauri/pull/12445) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused Tauri's CLI to enable tauri's `native-tls` feature even though it wasn't needed. Moved `reqwest` to a mobile-only dependency in `tauri` and enabled its `rustls-tls` feature flag.\n\n## \\[2.2.3]\n\n### Bug Fixes\n\n- [`d2c8f0eb5`](https://www.github.com/tauri-apps/tauri/commit/d2c8f0eb5ce2a5ebacd857614b7c89bbd5c9dca4) ([#12424](https://www.github.com/tauri-apps/tauri/pull/12424) by [@mattyg](https://www.github.com/tauri-apps/tauri/../../mattyg)) Ensure that tauri's builtin initialization scripts and plugin initialization scripts are executed before any user-added initialization scripts in a webview.\n\n## \\[2.2.2]\n\n### Bug Fixes\n\n- [`61e69db9e`](https://www.github.com/tauri-apps/tauri/commit/61e69db9e48f6a652ed3ecb05567c648c6e788d0) ([#12376](https://www.github.com/tauri-apps/tauri/pull/12376) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Widen `specta` version range to `^2.0.0-rc.16` again.\n\n## \\[2.2.1]\n\n### Bug Fixes\n\n- [`cd1d026f9`](https://www.github.com/tauri-apps/tauri/commit/cd1d026f9799c26b04acb64f49e7ee0a8b193049) ([#11961](https://www.github.com/tauri-apps/tauri/pull/11961) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix tauri fails to build if the project path contains glob characters\n\n### Dependencies\n\n- Upgraded to `tauri-build@2.0.5`\n\n## \\[2.2.0]\n\n### New Features\n\n- [`f884bae75`](https://www.github.com/tauri-apps/tauri/commit/f884bae75b3ad8f8debfd29bb05ac9cbc9748c89) ([#11742](https://www.github.com/tauri-apps/tauri/pull/11742) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `tauri::Builder::on_tray_icon_event` handler.\n- [`5188c0fae`](https://www.github.com/tauri-apps/tauri/commit/5188c0fae2bb47e40d09f70bf308c300045a2e2b) ([#11767](https://www.github.com/tauri-apps/tauri/pull/11767) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `Scope::is_forbidden` to check if a path was explicitly forbidden.\n- [`18bd639f6`](https://www.github.com/tauri-apps/tauri/commit/18bd639f6e22c0188aa219739f367b5bf5ab0398) ([#11798](https://www.github.com/tauri-apps/tauri/pull/11798) by [@lars-berger](https://www.github.com/tauri-apps/tauri/../../lars-berger)) Add `WebviewWindowBuilder/WebviewBuilder::data_store_identifier` on macOS.\n- [`dc4d79477`](https://www.github.com/tauri-apps/tauri/commit/dc4d79477665bc3bfefb4048772414cf5d78e3df) ([#11628](https://www.github.com/tauri-apps/tauri/pull/11628) by [@SpikeHD](https://www.github.com/tauri-apps/tauri/../../SpikeHD)) Add `WebviewWindowBuilder/WebviewBuilder::extensions_path` on Linux and Windows.\n- [`020ea0556`](https://www.github.com/tauri-apps/tauri/commit/020ea05561348dcd6d2a7df358f8a5190f661ba2) ([#11661](https://www.github.com/tauri-apps/tauri/pull/11661) by [@ahqsoftwares](https://www.github.com/tauri-apps/tauri/../../ahqsoftwares)) Add badging APIs:\n\n  - `Window/WebviewWindow::set_badge_count` for Linux, macOS and IOS.\n  - `Window/WebviewWindow::set_overlay_icon` for Windows Only.\n  - `Window/WebviewWindow::set_badge_label`for macOS Only.\n- [`fc30b20be`](https://www.github.com/tauri-apps/tauri/commit/fc30b20bea125f647db00ca824663f8e1da4d61f) ([#11726](https://www.github.com/tauri-apps/tauri/pull/11726) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `TrayIconBuilder::show_menu_on_left_click` method and deprecate `TrayIconBuilder::menu_on_left_click` for consistent naming and clarity.\n\n### Enhancements\n\n- [`d86aaccb0`](https://www.github.com/tauri-apps/tauri/commit/d86aaccb0b42760bb59bc04d74a6ea234bb64229) ([#11729](https://www.github.com/tauri-apps/tauri/pull/11729) by [@sandercox](https://www.github.com/tauri-apps/tauri/../../sandercox)) Add support for `TrayIconBuilder::menu_on_left_click` and `TrayIcon::set_show_menu_on_left_click` on Windows.\n- [`b0ddee899`](https://www.github.com/tauri-apps/tauri/commit/b0ddee8992ac3c7d47e2cfc9714f5725fadca7cf) ([#12101](https://www.github.com/tauri-apps/tauri/pull/12101) by [@renovate](https://www.github.com/tauri-apps/tauri/../../renovate)) **Breaking change:** Updated `webview2-com` to `0.34`. This may be a breaking change if you use the `with_webview` method.\n\n### Bug Fixes\n\n- [`b50a1ac0e`](https://www.github.com/tauri-apps/tauri/commit/b50a1ac0ef4c7686c3537512f1ba607b9ba6589c) ([#11850](https://www.github.com/tauri-apps/tauri/pull/11850) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Add webview and window color setters to autogenerated permissions.\n- [`ba6f37014`](https://www.github.com/tauri-apps/tauri/commit/ba6f3701472daa9295a39709761ea85c128881f1) ([#11785](https://www.github.com/tauri-apps/tauri/pull/11785) by [@lars-berger](https://www.github.com/tauri-apps/tauri/../../lars-berger)) Fix panic when a plugin command is run with a capability added at runtime (via `Manager::add_capability`).\n- [`e349dfe57`](https://www.github.com/tauri-apps/tauri/commit/e349dfe5722c0b47620582bd03f8c184ffeb6979) ([#12000](https://www.github.com/tauri-apps/tauri/pull/12000) by [@stringhandler](https://www.github.com/tauri-apps/tauri/../../stringhandler)) Fixed a panic caused by an assert when the resource random id has been used already.\n- [`46935212b`](https://www.github.com/tauri-apps/tauri/commit/46935212b61da44dc82dfeb803fceebf5659f7b7) ([#11658](https://www.github.com/tauri-apps/tauri/pull/11658) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `.json5` capability files not recognized even with `config-json5` feature enabled\n- [`b37741da6`](https://www.github.com/tauri-apps/tauri/commit/b37741da6a2d3dad71490c910a64eeedda2ba9ca) ([#11871](https://www.github.com/tauri-apps/tauri/pull/11871) by [@johncarmack1984](https://www.github.com/tauri-apps/tauri/../../johncarmack1984)) Fix `specta-util` dependency not found error when using `specta` feature\n- [`ca7f025fd`](https://www.github.com/tauri-apps/tauri/commit/ca7f025fd8666f8fce6894bb5e16cf2d4fc81e0c) ([#11958](https://www.github.com/tauri-apps/tauri/pull/11958) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix panic when invoking a command with an unmanaged state, an error will be returned instead.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.3.0`\n- Upgraded to `tauri-runtime-wry@2.3.0`\n- Upgraded to `tauri-utils@2.1.1`\n- Upgraded to `tauri-macros@2.0.4`\n- Upgraded to `tauri-build@2.0.4`\n\n## \\[2.1.1]\n\n### Bug Fixes\n\n- [`e8a50f6d7`](https://www.github.com/tauri-apps/tauri/commit/e8a50f6d760fad4529e7abb400302a1b487f11dd) ([#11645](https://www.github.com/tauri-apps/tauri/pull/11645)) Fix integer values of `BasDirectory.Home` and `BaseDirectory.Font` regression which broke path APIs in JS.\n\n## \\[2.1.0]\n\n### New Features\n\n- [`fabc2f283`](https://www.github.com/tauri-apps/tauri/commit/fabc2f283e38b62c721326e44645d47138418cbc) ([#11485](https://www.github.com/tauri-apps/tauri/pull/11485) by [@39zde](https://www.github.com/tauri-apps/tauri/../../39zde)) Adds a new configuration option `app > security > headers` to define headers that will be added to every http response from tauri to the web view. This doesn't include IPC messages and error responses.\n- [`8036c78e0`](https://www.github.com/tauri-apps/tauri/commit/8036c78e08715b1bc6b9fcb0c59a570eec98014f) ([#11455](https://www.github.com/tauri-apps/tauri/pull/11455) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `PathResolver::home_dir()` method on Android.\n- [`5c4b83084`](https://www.github.com/tauri-apps/tauri/commit/5c4b830843ab085f8ff9db9e08d832223b027e4e) ([#11191](https://www.github.com/tauri-apps/tauri/pull/11191) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Improved support for `dpi` module types to allow these types to be used without manual conversions with `invoke`:\n\n  - Added `SERIALIZE_TO_IPC_FN` const in `core` module which can be used to implement custom IPC serialization for types passed to `invoke`.\n  - Added `Size` and `Position` classes in `dpi` module.\n  - Implementd `SERIALIZE_TO_IPC_FN` method on `PhysicalSize`, `PhysicalPosition`, `LogicalSize` and `LogicalPosition` to convert it into a valid IPC-compatible value that can be deserialized correctly on the Rust side into its equivalent struct.\n- [`4d545ab3c`](https://www.github.com/tauri-apps/tauri/commit/4d545ab3ca228c8a21b966b709f84a0da2864479) ([#11486](https://www.github.com/tauri-apps/tauri/pull/11486) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Window::set_background_color` and `WindowBuilder::background_color`.\n- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > devtools` config option and when creating the webview from JS, to enable or disable devtools for a specific webview.\n- [`f0da0bde8`](https://www.github.com/tauri-apps/tauri/commit/f0da0bde87a80fdca20c588cefcad86e03b9627c) ([#11439](https://www.github.com/tauri-apps/tauri/pull/11439) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `WebviewWindow::resolve_command_scope` to check a command scope at runtime.\n- [\\`\\`](https://www.github.com/tauri-apps/tauri/commit/undefined) Detect if `SERIALIZE_TO_IPC_FN`, const from the JS `core` module, is implemented on objects when serializing over IPC and use it.\n- [`f37e97d41`](https://www.github.com/tauri-apps/tauri/commit/f37e97d410c4a219e99f97692da05ca9d8e0ba3a) ([#11477](https://www.github.com/tauri-apps/tauri/pull/11477) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > useHttpsScheme` config option to choose whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android\n- [`f37e97d41`](https://www.github.com/tauri-apps/tauri/commit/f37e97d410c4a219e99f97692da05ca9d8e0ba3a) ([#11477](https://www.github.com/tauri-apps/tauri/pull/11477) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder/WebviewBuilder::use_https_scheme` to choose whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android\n- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder::devtools` and `WebviewBuilder::devtools` to enable or disable devtools for a specific webview.\n- [`129414faa`](https://www.github.com/tauri-apps/tauri/commit/129414faa4e027c9035d56614682cacc0335a6a0) ([#11569](https://www.github.com/tauri-apps/tauri/pull/11569) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewBuilder::focused` method to choose whether to focus webview or not on creation.\n- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `app > windows > windowClassname` config option to specify the name of the window class on Windows.\n- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `WindowBuilder/WebviewWindowBuilder::window_classname` method to specify the name of the window class on Windows.\n\n### Enhancements\n\n- [`17c6952ae`](https://www.github.com/tauri-apps/tauri/commit/17c6952aec965fa41e6695ad68461a218afc20f1) ([#11522](https://www.github.com/tauri-apps/tauri/pull/11522) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Enhance the error message when using `async` commands with a reference.\n- [`c33bbf457`](https://www.github.com/tauri-apps/tauri/commit/c33bbf45740274b6918ea6c647f366fb6008e459) ([#11575](https://www.github.com/tauri-apps/tauri/pull/11575) by [@kornelski](https://www.github.com/tauri-apps/tauri/../../kornelski)) Include the path in ACL I/O errors.\n\n### Bug Fixes\n\n- [`229d7f8e2`](https://www.github.com/tauri-apps/tauri/commit/229d7f8e220cc8d5ca06eff1ed85cb7d047c1d6c) ([#11616](https://www.github.com/tauri-apps/tauri/pull/11616) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix regression in creating child webviews on macOS and Windows, covering the whole window.\n- [`8c6d1e8e6`](https://www.github.com/tauri-apps/tauri/commit/8c6d1e8e6c852667bb223b5f4823948868c26d98) ([#11401](https://www.github.com/tauri-apps/tauri/pull/11401) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `App/AppHandle/Window/Webview/WebviewWindow::cursor_position` getter method failing on Linux with `GDK may only be used from the main thread`.\n- [`f8994b214`](https://www.github.com/tauri-apps/tauri/commit/f8994b214e89acc99ab5ce8dcca8485f43a62dbb) ([#11581](https://www.github.com/tauri-apps/tauri/pull/11581) by [@Mikkel-T](https://www.github.com/tauri-apps/tauri/../../Mikkel-T)) Fix listeners created with `EventTarget::AnyLabel` never receiving events.\n- [`4191a7a53`](https://www.github.com/tauri-apps/tauri/commit/4191a7a53d941b179780a550638f1b4a09d17fd1) ([#11583](https://www.github.com/tauri-apps/tauri/pull/11583) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix tray events not fired for tray icons created inside an async command.\n- [`129414faa`](https://www.github.com/tauri-apps/tauri/commit/129414faa4e027c9035d56614682cacc0335a6a0) ([#11569](https://www.github.com/tauri-apps/tauri/pull/11569) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix webview not focused by default.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.0`\n- Upgraded to `tauri-runtime@2.2.0`\n- Upgraded to `tauri-runtime-wry@2.2.0`\n- Upgraded to `tauri-macros@2.0.3`\n- Upgraded to `tauri-build@2.0.3`\n\n## \\[2.0.6]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.2`\n\n## \\[2.0.5]\n\n### New Features\n\n- [`6cd917c22`](https://www.github.com/tauri-apps/tauri/commit/6cd917c227596e4e557496347ccae8ef579f6ea0) ([#11390](https://www.github.com/tauri-apps/tauri/pull/11390) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add new methods on `tauri::menu::MenuBuilder` and `tauri::menu::SubmenuBuilder` to create predefined menu item with specific text.\n\n### Enhancements\n\n- [`eb61d44f9`](https://www.github.com/tauri-apps/tauri/commit/eb61d44f9fc1be591c3d10a6ac1451aa39e6a77b) ([#11398](https://www.github.com/tauri-apps/tauri/pull/11398) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fallback to the Window and AppHandle resource table when closing a resource by ID.\n\n### Bug Fixes\n\n- [`e1bf6ef8c`](https://www.github.com/tauri-apps/tauri/commit/e1bf6ef8cbe3421eeaec47a222446121bcc28354) ([#11374](https://www.github.com/tauri-apps/tauri/pull/11374) by [@chrox](https://www.github.com/tauri-apps/tauri/../../chrox)) Expose `content-range` header in `range` response of `asset` protocol\n\n### What's Changed\n\n- [`2e88633ba`](https://www.github.com/tauri-apps/tauri/commit/2e88633ba4da8fc289c6d8a29c36f3327f9b576e) ([#11369](https://www.github.com/tauri-apps/tauri/pull/11369) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Remove references to no longer used `__TAURI_INTERNALS__.metadata.windows` and `__TAURI_INTERNALS__.metadata.webviews`.\n\n## \\[2.0.4]\n\n### New Features\n\n- [`bcf279278`](https://www.github.com/tauri-apps/tauri/commit/bcf279278dd36e05836be9568c432a679143258c) ([#11354](https://www.github.com/tauri-apps/tauri/pull/11354)) On Windows, Add `ContextMenu::hpopupmenu` method to get the [`HMENU`](https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types#HMENU) used for popups and tray icon menu.\n\n### Enhancements\n\n- [`e3b09be7f`](https://www.github.com/tauri-apps/tauri/commit/e3b09be7f0b7d47407cf51d6c2aafed741a96efe) ([#11362](https://www.github.com/tauri-apps/tauri/pull/11362)) Added `Builder::channel_interceptor` to intercept messages to be sent to the frontend, complemeting the `Builder::invoke_system` interface.\n- [`3cb73d08c`](https://www.github.com/tauri-apps/tauri/commit/3cb73d08c6d9b1e9f8a60c6ef6c492415cb41029) ([#11355](https://www.github.com/tauri-apps/tauri/pull/11355)) Mark the event commands as async so they do not block the main thread.\n\n### Bug Fixes\n\n- [`f3f521f03`](https://www.github.com/tauri-apps/tauri/commit/f3f521f038fa94ad583392092efe5bf1098fc94a) ([#11348](https://www.github.com/tauri-apps/tauri/pull/11348)) Fix `TAURI_ANDROID_PACKAGE_UNESCAPED not set` panic during compilation for Android when using an older tauri cli.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@2.1.1`\n\n## \\[2.0.3]\n\n### New Features\n\n- [`1d3f51e10`](https://www.github.com/tauri-apps/tauri/commit/1d3f51e100b0efc0e4ce164796460e9acdc458da) ([#11228](https://www.github.com/tauri-apps/tauri/pull/11228) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `tauri::Builder::on_menu_event`.\n\n### Bug Fixes\n\n- [`d609bef9f`](https://www.github.com/tauri-apps/tauri/commit/d609bef9fd7cd6eeb2bd701558100bd9cfb6e6f6) ([#11314](https://www.github.com/tauri-apps/tauri/pull/11314) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix android invalid proguard file when using an `identifier` that contains a component that is a reserved kotlin keyword, like `in`, `class`, etc\n- [`04fd3a7db`](https://www.github.com/tauri-apps/tauri/commit/04fd3a7db556a5d83989c9de2a03095061996c9d) ([#11264](https://www.github.com/tauri-apps/tauri/pull/11264) by [@chrox](https://www.github.com/tauri-apps/tauri/../../chrox)) Respond with empty body for `HEAD` requests to `asset` protocol\n- [`4731f0cf3`](https://www.github.com/tauri-apps/tauri/commit/4731f0cf31fc99876f17a9b0e8170c1ef759443b) ([#11290](https://www.github.com/tauri-apps/tauri/pull/11290) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Export the `ipc::Invoke` struct.\n- [`2d087ee4b`](https://www.github.com/tauri-apps/tauri/commit/2d087ee4b7d3e8849933f81284e4f5ed1aaa6455) ([#11268](https://www.github.com/tauri-apps/tauri/pull/11268) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Linux, fix commands, that use `Webview` or `WebviewWindow` as an argument, receiving an incorrect webview when using multi webviews.\n- [`2d087ee4b`](https://www.github.com/tauri-apps/tauri/commit/2d087ee4b7d3e8849933f81284e4f5ed1aaa6455) ([#11268](https://www.github.com/tauri-apps/tauri/pull/11268) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Linux, fix events only emitted to first webview only when using multi webviews.\n- [`2d087ee4b`](https://www.github.com/tauri-apps/tauri/commit/2d087ee4b7d3e8849933f81284e4f5ed1aaa6455) ([#11268](https://www.github.com/tauri-apps/tauri/pull/11268) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Linux, fix custom protocols receiving an incorrect webview label when using multi webviews\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.1.0`\n- Upgraded to `tauri-runtime-wry@2.1.0`\n\n## \\[2.0.2]\n\n### Enhancements\n\n- [`03e759042`](https://www.github.com/tauri-apps/tauri/commit/03e759042913e2ae9d45f299d6b6ad4b64ac3d2c) ([#11235](https://www.github.com/tauri-apps/tauri/pull/11235) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `App::invoke_key` and `AppHandle::invoke_key` for custom invoke systems that rely on manual `Webview::on_message` calls.\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.1`\n- Upgraded to `tauri-runtime@2.0.1`\n- Upgraded to `tauri-runtime-wry@2.0.1`\n- Upgraded to `tauri-macros@2.0.1`\n- Upgraded to `tauri-build@2.0.1`\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78.\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0`\n- Upgraded to `tauri-runtime@2.0.0`\n- Upgraded to `tauri-runtime-wry@2.0.0`\n- Upgraded to `tauri-macros@2.0.0`\n- Upgraded to `tauri-build@2.0.0`\n\n## \\[2.0.0-rc.17]\n\n### Breaking Changes\n\n- [`354be36d4`](https://www.github.com/tauri-apps/tauri/commit/354be36d4efed6c0c53639af44607f7b050adfd2) ([#11163](https://www.github.com/tauri-apps/tauri/pull/11163) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Changed uri scheme protocol handler to take `UriSchemeContext` as first argument instead of `AppHandle`. `UriSchemeContext` can be used to access an app handle or the webview label that made the request. The following methods are affected:\n\n  - `tauri::Builder::register_uri_scheme_protocol`\n  - `tauri::Builder::register_asynchronous_uri_scheme_protocol`\n  - `tauri::plugin::Builder::register_uri_scheme_protocol`\n  - `tauri::plugin::Builder::register_asynchronous_uri_scheme_protocol`\n\n## \\[2.0.0-rc.16]\n\n### New Features\n\n- [`a247170e1`](https://www.github.com/tauri-apps/tauri/commit/a247170e1f620a9b012274b11cfe51e90327d6e9) ([#11056](https://www.github.com/tauri-apps/tauri/pull/11056) by [@SpikeHD](https://www.github.com/tauri-apps/tauri/../../SpikeHD)) Expose the ability to enabled browser extensions in WebView2 on Windows.\n- [`9014a3f17`](https://www.github.com/tauri-apps/tauri/commit/9014a3f1765ca406ea5c3e5224267a79c52cd53d) ([#11066](https://www.github.com/tauri-apps/tauri/pull/11066) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindow::clear_all_browsing_data` and `Webview::clear_all_browsing_data` to clear the webview browsing data.\n- [`0ddfc59d6`](https://www.github.com/tauri-apps/tauri/commit/0ddfc59d6785e3b6a85a674a2f80d4c0affd2898) ([#11071](https://www.github.com/tauri-apps/tauri/pull/11071) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `Manager::unmanage` to remove previously managed state.\n- [`1d8b67b29`](https://www.github.com/tauri-apps/tauri/commit/1d8b67b2970a09ec478093e127612fac823de805) ([#11162](https://www.github.com/tauri-apps/tauri/pull/11162) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Support async functions for `mobile_entry_point` macro\n- [`5621174b0`](https://www.github.com/tauri-apps/tauri/commit/5621174b05f615e1589292ccd3954dc7e6b5569f) ([#11132](https://www.github.com/tauri-apps/tauri/pull/11132) by [@chippers](https://www.github.com/tauri-apps/tauri/../../chippers)) Add `ScopeObjectMatch` for easy scope validation those that can be represented by a boolean return value.\n- [`95df53a2e`](https://www.github.com/tauri-apps/tauri/commit/95df53a2ed96873cd35a4b14a5e312d07e4e3004) ([#11143](https://www.github.com/tauri-apps/tauri/pull/11143) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Add the ability to set theme dynamically using `Window::set_theme`, `App::set_theme`\n- [`d9d2502b4`](https://www.github.com/tauri-apps/tauri/commit/d9d2502b41e39efde679e30c8955006e2ba9ea64) ([#11140](https://www.github.com/tauri-apps/tauri/pull/11140) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `Webview::hide` and `Webview::show` methods.\n- [`de7414aab`](https://www.github.com/tauri-apps/tauri/commit/de7414aab935e45540594ea930eb60bae4dbc979) ([#11154](https://www.github.com/tauri-apps/tauri/pull/11154) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `Window::set_enabled` and `Window::is_enabled` methods\n\n### Bug Fixes\n\n- [`948772a65`](https://www.github.com/tauri-apps/tauri/commit/948772a657eb3caf20843628abac9109e3b67d41) ([#11114](https://www.github.com/tauri-apps/tauri/pull/11114) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Change the `button_state` tray event field to camelCase `buttonState`.\n- [`a49fc999f`](https://www.github.com/tauri-apps/tauri/commit/a49fc999fc3eba3bfd47480b0a8c68c0b45e3127) ([#11161](https://www.github.com/tauri-apps/tauri/pull/11161) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix internal crash when trying to close the same window multiple times.\n- [`62b3a5cd1`](https://www.github.com/tauri-apps/tauri/commit/62b3a5cd1c804440c2130ab36cc3eadb3baf61cb) ([#11043](https://www.github.com/tauri-apps/tauri/pull/11043) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `localStorage` not shared between webviews that use the same data directory.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.14`\n- Upgraded to `tauri-runtime@2.0.0-rc.13`\n- Upgraded to `tauri-utils@2.0.0-rc.13`\n- Upgraded to `tauri-macros@2.0.0-rc.12`\n- Upgraded to `tauri-build@2.0.0-rc.13`\n\n### Breaking Changes\n\n- [`0b4495996`](https://www.github.com/tauri-apps/tauri/commit/0b4495996d3131a5ee80fbb2c71a28203e491ee7) ([#11121](https://www.github.com/tauri-apps/tauri/pull/11121) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Simplified emitted tray event JS value and updated `TrayIconEvent` type definition to match it.\n\n## \\[2.0.0-rc.15]\n\n### New Features\n\n- [`ad294d274`](https://www.github.com/tauri-apps/tauri/commit/ad294d274dd81d2ef91ed73af9163b6e9b8eb964) ([#11032](https://www.github.com/tauri-apps/tauri/pull/11032) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > create` option to choose whether to create this window at app startup or not.\n\n### Enhancements\n\n- [`e7fd676c2`](https://www.github.com/tauri-apps/tauri/commit/e7fd676c2741929727e3e25bd81cd6ea45e4da7b) ([#11025](https://www.github.com/tauri-apps/tauri/pull/11025) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Inject `__INVOKE_KEY__` into custom invoke systems so their implementations can properly construct `tauri::webview::InvokeRequest`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.12`\n- Upgraded to `tauri-runtime@2.0.0-rc.12`\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.13`\n- Upgraded to `tauri-macros@2.0.0-rc.11`\n- Upgraded to `tauri-build@2.0.0-rc.12`\n\n### Breaking Changes\n\n- [`551e0624a`](https://www.github.com/tauri-apps/tauri/commit/551e0624a903ed6cf8390add7868c655c7778ce4) ([#11027](https://www.github.com/tauri-apps/tauri/pull/11027) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Remove the `responder` part of a custom invoke system now that the responder can be set directly in the `tauri::WebviewWindow::on_message` function.\n\n## \\[2.0.0-rc.14]\n\n### Bug Fixes\n\n- [`e5f037277`](https://www.github.com/tauri-apps/tauri/commit/e5f037277505c477b8d563bd77b7bd6e23b46296) ([#11018](https://www.github.com/tauri-apps/tauri/pull/11018) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix compilation error on macOS due to a missing feature for `NSImage`.\n\n## \\[2.0.0-rc.13]\n\n### Enhancements\n\n- [`bc4804d48`](https://www.github.com/tauri-apps/tauri/commit/bc4804d4841efefd57fd1f3e147550a3340e2b31) ([#10924](https://www.github.com/tauri-apps/tauri/pull/10924) by [@madsmtm](https://www.github.com/tauri-apps/tauri/../../madsmtm)) Use `objc2` internally and in examples, leading to better memory safety.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.12`\n\n### Breaking Changes\n\n- [`bc4804d48`](https://www.github.com/tauri-apps/tauri/commit/bc4804d4841efefd57fd1f3e147550a3340e2b31) ([#10924](https://www.github.com/tauri-apps/tauri/pull/10924) by [@madsmtm](https://www.github.com/tauri-apps/tauri/../../madsmtm)) Change the pointer type of `PlatformWebview`'s `inner`, `controller`, `ns_window` and `view_controller` to `c_void`, to avoid publically depending on `objc`.\n\n## \\[2.0.0-rc.12]\n\n### New Features\n\n- [`35bd9dd3d`](https://www.github.com/tauri-apps/tauri/commit/35bd9dd3dc3d8972bbc4aa5f4a6c6fa14354e9bf) ([#10977](https://www.github.com/tauri-apps/tauri/pull/10977) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `mainBinaryName` config option to set the file name for the main binary.\n\n### Enhancements\n\n- [`5eb036f33`](https://www.github.com/tauri-apps/tauri/commit/5eb036f33951a9946f25ce5fa7fc47ae4469aa60) ([#11002](https://www.github.com/tauri-apps/tauri/pull/11002) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Handle macOS binary name change on the `process::restart` function.\n\n### Bug Fixes\n\n- [`63649d82d`](https://www.github.com/tauri-apps/tauri/commit/63649d82d20b8f69d973b41bd0c157997770d6a0) ([#10971](https://www.github.com/tauri-apps/tauri/pull/10971) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix schema generation for `core:default` set.\n- [`be18ed50d`](https://www.github.com/tauri-apps/tauri/commit/be18ed50d8e04261da1553662a768e7ce0f1dd8f) ([#10982](https://www.github.com/tauri-apps/tauri/pull/10982) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Add a Proguard rule to prevent custom JSON deserializer and serializer classes from being optimized away.\n- [`00182ebf8`](https://www.github.com/tauri-apps/tauri/commit/00182ebf894b83302179ccb7f415f97d04600c77) ([#10988](https://www.github.com/tauri-apps/tauri/pull/10988) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `requestPermissions` not resolving on Android.\n\n### Dependencies\n\n- Upgraded to `tauri-build@2.0.0-rc.11`\n- Upgraded to `tauri-utils@2.0.0-rc.11`\n- Upgraded to `tauri-runtime@2.0.0-rc.11`\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.11`\n- Upgraded to `tauri-macros@2.0.0-rc.10`\n\n### Breaking Changes\n\n- [`fe5ff1228`](https://www.github.com/tauri-apps/tauri/commit/fe5ff1228c34cf12929d861454ab9716da9480da) ([#10978](https://www.github.com/tauri-apps/tauri/pull/10978) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Rename `PermissionState::Unknown` to `PermissionState::Prompt`.\n\n## \\[2.0.0-rc.11]\n\n### Bug Fixes\n\n- [`fafceec30`](https://www.github.com/tauri-apps/tauri/commit/fafceec3092f405fbc6642d331e5440b90d9fd62) ([#10943](https://www.github.com/tauri-apps/tauri/pull/10943) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes mobile dev server proxy request URL with trailing slashes.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.10`\n- Upgraded to `tauri-runtime@2.0.0-rc.10`\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.10`\n- Upgraded to `tauri-macros@2.0.0-rc.9`\n- Upgraded to `tauri-build@2.0.0-rc.10`\n\n## \\[2.0.0-rc.10]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.9`\n- Upgraded to `tauri-runtime@2.0.0-rc.9`\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.9`\n- Upgraded to `tauri-macros@2.0.0-rc.8`\n- Upgraded to `tauri-build@2.0.0-rc.9`\n- [`d9c8d3cc8`](https://www.github.com/tauri-apps/tauri/commit/d9c8d3cc8d5ca67cd767ffc7a521f801b41ce201) ([#10902](https://www.github.com/tauri-apps/tauri/pull/10902) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Update infer to 0.16, tray icon to 0.17, urlpattern to 0.3, image to 0.25\n\n### Breaking Changes\n\n- [`faa259bac`](https://www.github.com/tauri-apps/tauri/commit/faa259bacf1ace670af763982c6903190faf163a) ([#10907](https://www.github.com/tauri-apps/tauri/pull/10907) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) The `Assets::iter` function now must return a iterator with `Item = (Cow<'_, str>, Cow<'_, [u8]>)` to be more flexible on contexts where the assets are not `'static`.\n\n## \\[2.0.0-rc.9]\n\n### New Features\n\n- [`0899e5083`](https://www.github.com/tauri-apps/tauri/commit/0899e5083104dfcf924a0530ba73ead2963ee421) ([#10884](https://www.github.com/tauri-apps/tauri/pull/10884) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Derive serde `Serialize` for `SafePathBuf`\n- [`431ca2c77`](https://www.github.com/tauri-apps/tauri/commit/431ca2c7763f7e31ad533c49576ab658569ddd29) ([#10870](https://www.github.com/tauri-apps/tauri/pull/10870) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Add `FromStr` impl for `SafePathBuf`\n\n### Bug Fixes\n\n- [`79de4332b`](https://www.github.com/tauri-apps/tauri/commit/79de4332b6fe01e848c286cedf9ceea773cf6190) ([#10841](https://www.github.com/tauri-apps/tauri/pull/10841) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes IPC postMessage raw body processing when using the isolation pattern.\n- [`6696e4880`](https://www.github.com/tauri-apps/tauri/commit/6696e48800576e124066388156f1d083376eec30) ([#10842](https://www.github.com/tauri-apps/tauri/pull/10842) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes a warning when using a null value on the `invoke.resolve()` iOS plugin API.\n\n### What's Changed\n\n- [`27d018343`](https://www.github.com/tauri-apps/tauri/commit/27d01834312ee7953b6ccd5b0a368e7a69b225e9) ([#10844](https://www.github.com/tauri-apps/tauri/pull/10844) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Changes how the Info.plist is embedded on macOS development to avoid a clippy warning.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.8`\n- Upgraded to `tauri-utils@2.0.0-rc.8`\n- Upgraded to `tauri-runtime@2.0.0-rc.8`\n- Upgraded to `tauri-macros@2.0.0-rc.7`\n- Upgraded to `tauri-build@2.0.0-rc.8`\n\n### Breaking Changes\n\n- [`5048a7293`](https://www.github.com/tauri-apps/tauri/commit/5048a7293b87b5b93aaefd42dedc0e551e08086c) ([#10840](https://www.github.com/tauri-apps/tauri/pull/10840) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) The `linux-ipc-protocol` feature is now always enabled, so the Cargo feature flag was removed.\n  This increases the minimum webkit2gtk version to a release that does not affect the minimum target Linux distros for Tauri apps.\n\n## \\[2.0.0-rc.8]\n\n### Enhancements\n\n- [`d7e5c00e9`](https://www.github.com/tauri-apps/tauri/commit/d7e5c00e94938f6be94e693d3f21f1f8f431c4f9) ([#10817](https://www.github.com/tauri-apps/tauri/pull/10817) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `plugin:::PermissionState` enum.\n\n## \\[2.0.0-rc.7]\n\n### New Features\n\n- [`1e441811e`](https://www.github.com/tauri-apps/tauri/commit/1e441811ee16c687343760f555c86d52ebfe8f87) ([#10786](https://www.github.com/tauri-apps/tauri/pull/10786) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Windows, Add and emit `DoubleClick` variant for `TrayIconEvent`.\n\n### Enhancements\n\n- [`f86a8146a`](https://www.github.com/tauri-apps/tauri/commit/f86a8146addd8a25bc44c492300fe0563104b83d) ([#10761](https://www.github.com/tauri-apps/tauri/pull/10761) by [@rdlabo](https://www.github.com/tauri-apps/tauri/../../rdlabo)) Added `getArgs` and `getRawArgs` methods to the plugin `Invoke` class (Kotlin and Swift),\n  which lets you parse the arguments manually instead of through the `parseArgs` method.\n\n### Bug Fixes\n\n- [`03f2a5098`](https://www.github.com/tauri-apps/tauri/commit/03f2a50981b8c01b1c196811fce9d93f1bf0820d) ([#10718](https://www.github.com/tauri-apps/tauri/pull/10718) by [@rdlabo](https://www.github.com/tauri-apps/tauri/../../rdlabo)) Update swift-rs fixing a plugin build when native dependencies are used.\n- [`22d2afa89`](https://www.github.com/tauri-apps/tauri/commit/22d2afa89bfe626bf952c2bb4b1f37935c1a2f71) ([#10800](https://www.github.com/tauri-apps/tauri/pull/10800) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Change the Android Proguard rules to keep custom JSON deserializers.\n- [`fbe76a955`](https://www.github.com/tauri-apps/tauri/commit/fbe76a955a63af9fb33f66d5f747caf858cf179b) ([#10797](https://www.github.com/tauri-apps/tauri/pull/10797) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Uint8Arrays and ArrayBuffers are now properly serialized as an array of numbers.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.7`\n- Upgraded to `tauri-build@2.0.0-rc.7`\n- Upgraded to `tauri-runtime@2.0.0-rc.7`\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.7`\n- Upgraded to `tauri-macros@2.0.0-rc.6`\n\n## \\[2.0.0-rc.6]\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.6`\n- Upgraded to `tauri-build@2.0.0-rc.6`\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.6`\n- Upgraded to `tauri-runtime@2.0.0-rc.6`\n- Upgraded to `tauri-macros@2.0.0-rc.5`\n\n## \\[2.0.0-rc.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.5`\n- Upgraded to `tauri-runtime@2.0.0-rc.5`\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.5`\n- Upgraded to `tauri-macros@2.0.0-rc.5`\n- Upgraded to `tauri-build@2.0.0-rc.5`\n\n## \\[2.0.0-rc.4]\n\n### Enhancements\n\n- [`30c7685eb`](https://www.github.com/tauri-apps/tauri/commit/30c7685eb82c7a1a9af53abdca7d75b1a886cc6e) ([#10295](https://www.github.com/tauri-apps/tauri/pull/10295) by [@liesauer](https://www.github.com/tauri-apps/tauri/../../liesauer)) Added `Builder::append_invoke_initialization_script`.\n- [`ed04cc3d3`](https://www.github.com/tauri-apps/tauri/commit/ed04cc3d36205b277517d052dfd997b6c3cb673d) ([#10664](https://www.github.com/tauri-apps/tauri/pull/10664) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Include more information in the IPC permission error message.\n\n### Bug Fixes\n\n- [`5c335ae9a`](https://www.github.com/tauri-apps/tauri/commit/5c335ae9ad88e46c2135a557390f6e808c9a6088) ([#10648](https://www.github.com/tauri-apps/tauri/pull/10648) by [@Flakebi](https://www.github.com/tauri-apps/tauri/../../Flakebi)) Prevent build script from rerunning unnecessarily by only writing files when the content changes.\n\n### Dependencies\n\n- Upgraded to `tauri-build@2.0.0-rc.4`\n- Upgraded to `tauri-utils@2.0.0-rc.4`\n- Upgraded to `tauri-runtime@2.0.0-rc.4`\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.4`\n- Upgraded to `tauri-macros@2.0.0-rc.4`\n\n## \\[2.0.0-rc.3]\n\n### Bug Fixes\n\n- [`b1d9ffa1a`](https://www.github.com/tauri-apps/tauri/commit/b1d9ffa1abc9eff65acf16792b4fb33d9c45ba8a) ([#10582](https://www.github.com/tauri-apps/tauri/pull/10582) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix IPC fallback (postMessage implementation when custom protocol fails) hanging when sending responses.\n\n### What's Changed\n\n- [`bfc49cc7a`](https://www.github.com/tauri-apps/tauri/commit/bfc49cc7a1d43e3378e93865b9b37ce4bddfa6e6) ([#10558](https://www.github.com/tauri-apps/tauri/pull/10558) by [@ahqsoftwares](https://www.github.com/tauri-apps/tauri/../../ahqsoftwares)) Remove targetSdk from gradle files\n- [`fedf93eb7`](https://www.github.com/tauri-apps/tauri/commit/fedf93eb7e09c161997f6ba96a17fc29e727af69) ([#10585](https://www.github.com/tauri-apps/tauri/pull/10585) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Change how IPC handles errors to simplify what's logged in the console.\n\n### Dependencies\n\n- Upgraded to `tauri-build@2.0.0-rc.3`\n- Upgraded to `tauri-utils@2.0.0-rc.3`\n- Upgraded to `tauri-runtime@2.0.0-rc.3`\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.3`\n- Upgraded to `tauri-macros@2.0.0-rc.3`\n- [`d39c392b7`](https://www.github.com/tauri-apps/tauri/commit/d39c392b7cec746da423211f9c74632abe4b6af5) ([#10655](https://www.github.com/tauri-apps/tauri/pull/10655) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Update `tao` to 0.29 and `wry` to 0.42.\n\n### Breaking Changes\n\n- [`d0510f52e`](https://www.github.com/tauri-apps/tauri/commit/d0510f52eb7efeabe00df5030cf10be16f99e178) ([#10641](https://www.github.com/tauri-apps/tauri/pull/10641) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added a dedicated type for IPC response body `InvokeResponseBody` for performance reasons.\n  This is only a breaking change if you are directly using types from `tauri::ipc`.\n\n## \\[2.0.0-rc.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.2`\n- Upgraded to `tauri-runtime@2.0.0-rc.2`\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.2`\n- Upgraded to `tauri-macros@2.0.0-rc.2`\n- Upgraded to `tauri-build@2.0.0-rc.2`\n\n## \\[2.0.0-rc.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.1`\n- Upgraded to `tauri-runtime@2.0.0-rc.1`\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.1`\n- Upgraded to `tauri-macros@2.0.0-rc.1`\n- Upgraded to `tauri-build@2.0.0-rc.1`\n\n## \\[2.0.0-rc.0]\n\n### Bug Fixes\n\n- [`6755af230`](https://www.github.com/tauri-apps/tauri/commit/6755af23021a254cff98c07aa7711545771097a6)([#10435](https://www.github.com/tauri-apps/tauri/pull/10435)) Fix Specta remote implementation target for `Channel`.\n- [`24445d71d`](https://www.github.com/tauri-apps/tauri/commit/24445d71de92d526d0ccaecb54f13003ddc6f6b4)([#10432](https://www.github.com/tauri-apps/tauri/pull/10432)) Fixes asset resolving when not using the `compression` feature.\n\n### Enhancements\n\n- [`1e0793b68`](https://www.github.com/tauri-apps/tauri/commit/1e0793b6821799829e380c88066b3415cc9006df) ([#10357](https://www.github.com/tauri-apps/tauri/pull/10357)) Enhance `AssetResolver::get` in development mode by reading distDir directly as a fallback to the embedded assets.\n- [`7aeac39e7`](https://www.github.com/tauri-apps/tauri/commit/7aeac39e7fb97dc57ca278f1c097058275c20aa2) ([#10397](https://www.github.com/tauri-apps/tauri/pull/10397)) Make the set of gtk application id optional, to allow more then one instance of the app running at the same time.\n- [`cf994a6bb`](https://www.github.com/tauri-apps/tauri/commit/cf994a6bb064a50d3e5aef67e9a25903ee17a1e2) ([#10405](https://www.github.com/tauri-apps/tauri/pull/10405)) Add `tauri::plugin::Builder::try_build` to allow plugins to check if their `TauriPlugin` initialization is valid.\n\n### Security fixes\n\n- [`426d14bb4`](https://www.github.com/tauri-apps/tauri/commit/426d14bb4164290d93b5a0f61e925cb2dfc4aafa) ([#10423](https://www.github.com/tauri-apps/tauri/pull/10423)) Explicitly check that the main frame's origin is the sender of Isolation Payloads\n- [`289ae5555`](https://www.github.com/tauri-apps/tauri/commit/289ae5555da3802741018015bfe4927729a2eb33) ([#10386](https://www.github.com/tauri-apps/tauri/pull/10386)) Re-enable TLS checks that were previously disabled to support an insecure HTTPS custom protocol on Android which is no longer used.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.0`\n- Upgraded to `tauri-macros@2.0.0-rc.0`\n- Upgraded to `tauri-build@2.0.0-rc.0`\n- Upgraded to `tauri-runtime@2.0.0-rc.0`\n- Upgraded to `tauri-runtime-wry@2.0.0-rc.0`\n\n### Breaking Changes\n\n- [`758d28c8a`](https://www.github.com/tauri-apps/tauri/commit/758d28c8a2d5c9567158e339326b765f72da983e) ([#10390](https://www.github.com/tauri-apps/tauri/pull/10390)) Core plugin permissions are now prefixed with `core:`, the `core:default` permission set can now be used and the `core` plugin name is reserved.\n  The `tauri migrate` tool will automate the migration process, which involves prefixing all `app`, `event`, `image`, `menu`, `path`, `resources`, `tray`, `webview` and `window` permissions with `core:`.\n\n## \\[2.0.0-beta.25]\n\n### New Features\n\n- [`da25f7353`](https://www.github.com/tauri-apps/tauri/commit/da25f7353070477ba969851e974379d7666d6806) ([#10242](https://www.github.com/tauri-apps/tauri/pull/10242) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add APIs to enable setting window size constraints separately:\n\n  - Added `WindowBuilder::inner_size_constraints` and `WebviewWindowBuilder::inner_size_constraints` which can be used for setting granular constraints.\n  - Added `WindowSizeConstraints` struct\n  - Added `Window::set_size_constraints` and `WebviewWindow::set_size_constraints`\n\n### Bug Fixes\n\n- [`e1776946a`](https://www.github.com/tauri-apps/tauri/commit/e1776946ad034d7a6e005834a754773671d9f7ef) ([#10362](https://www.github.com/tauri-apps/tauri/pull/10362) by [@Brendonovich](https://www.github.com/tauri-apps/tauri/../../Brendonovich)) Use ` specta rc.15's  `derive\\` feature which fixes build issues in docs.rs.\n- [`da25f7353`](https://www.github.com/tauri-apps/tauri/commit/da25f7353070477ba969851e974379d7666d6806) ([#10242](https://www.github.com/tauri-apps/tauri/pull/10242) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Apply `minWidth`, `minHieght`, `maxWidth` and `maxHeight` constraints separately, which fixes a long standing bug where these constraints were never applied unless width and height were constrained together.\n\n### What's Changed\n\n- [`9546548ec`](https://www.github.com/tauri-apps/tauri/commit/9546548ec0c83ba620b1bc9d1d424a7009d0b423) ([#10297](https://www.github.com/tauri-apps/tauri/pull/10297) by [@pewsheen](https://www.github.com/tauri-apps/tauri/../../pewsheen)) On macOS, set default titlebar style to `Visible` to prevent webview move out of the view.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.21`\n- Upgraded to `tauri-runtime@2.0.0-beta.21`\n\n## \\[2.0.0-beta.24]\n\n### New Features\n\n- [`7bc6a2a1d`](https://www.github.com/tauri-apps/tauri/commit/7bc6a2a1d6d2c5406d91cac94d33bce76443c28f) ([#9788](https://www.github.com/tauri-apps/tauri/pull/9788) by [@pewsheen](https://www.github.com/tauri-apps/tauri/../../pewsheen)) Add a new method to set title bar style dynamically on macOS.\n\n### Enhancements\n\n- [`a7354f9a8`](https://www.github.com/tauri-apps/tauri/commit/a7354f9a81d4db83ff3d34b29617717117ad64d2) ([#10171](https://www.github.com/tauri-apps/tauri/pull/10171) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Mark `AppHandle::restart` and `process::restart` as [diverging functions](https://doc.rust-lang.org/rust-by-example/fn/diverging.html).\n\n### Bug Fixes\n\n- [`4c239729c`](https://www.github.com/tauri-apps/tauri/commit/4c239729c3e1b899ecbc6793c3682848e8de1729) ([#10167](https://www.github.com/tauri-apps/tauri/pull/10167) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix deserialization of raw invoke requests when using `isolation` pattern.\n- [`55733aba9`](https://www.github.com/tauri-apps/tauri/commit/55733aba9c5a5c8d664afea0c83d9337bc99387d) ([#10176](https://www.github.com/tauri-apps/tauri/pull/10176) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Move `PluginApi::register_ios_plugin` behind the `wry` Cargo feature as `Webview::with_webview` is only available when that feature is enabled.\n\n### Dependencies\n\n- Upgraded to `tauri-macros@2.0.0-beta.19`\n- Upgraded to `tauri-build@2.0.0-beta.19`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.20`\n- Upgraded to `tauri-utils@2.0.0-beta.19`\n- Upgraded to `tauri-runtime@2.0.0-beta.20`\n\n### Breaking Changes\n\n- [`ba9590aa9`](https://www.github.com/tauri-apps/tauri/commit/ba9590aa92a67a11fa1e559e506d87b7e643cc24) ([#9640](https://www.github.com/tauri-apps/tauri/pull/9640) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Emitter` and `Listener` traits that defines what an emitter or a listener can do, this however comes with a few breaking changes:\n\n  - Removed `Manager::listen_any`, use `Listener::listen_any` instead.\n  - Removed `Manager::once_any`, use `Listener::once_any` instead.\n  - Removed `Manager::unlisten`, use `Listener::unlisten` instead.\n  - Removed `Manager::emit`, use `Emitter::emit` instead.\n  - Removed `Manager::emit_to`, use `Emitter::emit_to` instead.\n  - Removed `Manager::emit_filter`, use `Emitter::emit_filter` instead.\n  - Removed `App/AppHandle::listen`, `WebviewWindow::listen`, `Window::listen` and `Webview::listen`, use `Listener::listen` instead.\n  - Removed `App/AppHandle::once`, `WebviewWindow::once`, `Window::once` and `Webview::once`, use `Listener::once` instead.\n  - Removed `App/AppHandle::unlisten`, `WebviewWindow::unlisten`, `Window::unlisten` and `Webview::unlisten`, use `Listener::unlisten` instead.\n- [`261c9f942`](https://www.github.com/tauri-apps/tauri/commit/261c9f942de9a598b5c6cc504de6bddd1306113b) ([#10170](https://www.github.com/tauri-apps/tauri/pull/10170) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Renamed `DragDropEvent` enum variants to better convey when they are triggered:\n\n  - `DragDropEvent::Dragged` -> `DragDropEvent::Enter`\n  - `DragDropEvent::DragOver` -> `DragDropEvent::Over`\n  - `DragDropEvent::Dropped` -> `DragDropEvent::Drop`\n  - `DragDropEvent::Cancelled` -> `DragDropEvent::Leave`\n\n  This also comes with a change in the events being emitted to JS and Rust event listeners:\n\n  - `tauri://drag` -> `tauri://drag-enter`\n  - `tauri://drop-over` -> `tauri://drag-over`\n  - `tauri://drop` -> `tauri://drag-drop`\n  - `tauri://drag-cancelled` -> `tauri://drag-leave`\n- [`2b1ceb40d`](https://www.github.com/tauri-apps/tauri/commit/2b1ceb40d345aef42dd79438fa69ca7989ee0194) ([#10229](https://www.github.com/tauri-apps/tauri/pull/10229) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Renamed the JS `getCurrent` and `getAll` functions to a clearer name to avoid ambiguity:\n\n  - `getCurrent` in `window` module has been renamed to `getCurrentWindow`\n  - `getCurrent` in `webview` module has been renamed to `getCurrentWebview`\n  - `getCurrent` in `webviewWindow` module has been renamed to `getCurrentWebviewWindow`\n  - `getAll` in `window` module has been renamed to `getAllWindows`\n  - `getAll` in `webview` module has been renamed to `getAllWebviews`\n  - `getAll` in `webviewWindow` module has been renamed to `getAllWebviewWindows`\n- [`57612ab24`](https://www.github.com/tauri-apps/tauri/commit/57612ab24963b02d769ce408b0283ef552fb7b0d) ([#10139](https://www.github.com/tauri-apps/tauri/pull/10139) by [@Brendonovich](https://www.github.com/tauri-apps/tauri/../../Brendonovich)) Add `TSend` generic to `ipc::Channel` for typesafe `send` calls and type inspection in `tauri-specta`\n\n## \\[2.0.0-beta.23]\n\n### New Features\n\n- [`148f04887`](https://www.github.com/tauri-apps/tauri/commit/148f048871caee21498b236c058b8890f2b66cc7) ([#9979](https://www.github.com/tauri-apps/tauri/pull/9979)) Add `defaultWindowIcon` to the JS `app` module to retrieve the default window icon in JS.\n- [`5b769948a`](https://www.github.com/tauri-apps/tauri/commit/5b769948a81cac333f64c870a470ba6525bd5cd3) ([#9959](https://www.github.com/tauri-apps/tauri/pull/9959)) Add `include_image` macro to help embedding instances of `Image` struct at compile-time in rust to be used with window, menu or tray icons.\n- [`ddaabda36`](https://www.github.com/tauri-apps/tauri/commit/ddaabda365ed5dc0780925049473989cbd1d7ea3) ([#9922](https://www.github.com/tauri-apps/tauri/pull/9922)) Add `WebviewWindowBuilder::on_download`.\n\n### Enhancements\n\n- [`cee0bfcd6`](https://www.github.com/tauri-apps/tauri/commit/cee0bfcd6c03c2a6794abca8f4fde700f3f818ba) ([#10092](https://www.github.com/tauri-apps/tauri/pull/10092)) Make `tray:default` and `menu:default` include all tray and menu permissions\n\n### Bug Fixes\n\n- [`e93ca1df3`](https://www.github.com/tauri-apps/tauri/commit/e93ca1df3b3948647f501f9f958e894ade6a27fb) ([#10138](https://www.github.com/tauri-apps/tauri/pull/10138)) Fix `InvokeBody::deserialize` method deserialization for `InvokeBody::Raw` variant\n- [`e6e17ad1c`](https://www.github.com/tauri-apps/tauri/commit/e6e17ad1c8a6b53463946c407a354c250bd7e701) ([#9954](https://www.github.com/tauri-apps/tauri/pull/9954)) Add `std` feature to `raw-window-handle` crate so that using `default-features = false` on `tauri` crate can work\n- [`f29b78811`](https://www.github.com/tauri-apps/tauri/commit/f29b78811080bc8313459f34545152d939c62bf6) ([#9862](https://www.github.com/tauri-apps/tauri/pull/9862)) On Windows, handle resizing undecorated windows natively which improves performance and fixes a couple of annoyances with previous JS implementation:\n\n  - No more cursor flickering when moving the cursor across an edge.\n  - Can resize from top even when `data-tauri-drag-region` element exists there.\n  - Upon starting rezing, clicks don't go through elements behind it so no longer accidental clicks.\n\n### What's Changed\n\n- [`669b9c6b5`](https://www.github.com/tauri-apps/tauri/commit/669b9c6b5af791129b77ee440dacaa98288c906b) ([#9621](https://www.github.com/tauri-apps/tauri/pull/9621)) Set the gtk application to the identifier defined in `tauri.conf.json` to ensure the app uniqueness.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.18`\n- Upgraded to `tauri-build@2.0.0-beta.18`\n- Upgraded to `tauri-macros@2.0.0-beta.18`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.19`\n- Upgraded to `tauri-runtime@2.0.0-beta.19`\n- [`f955f7b49`](https://www.github.com/tauri-apps/tauri/commit/f955f7b4903bcea376c0a8b430736f66c8cebf56) ([#9929](https://www.github.com/tauri-apps/tauri/pull/9929)) Switch from `dirs_next` to `dirs` as `dirs_next` is now unmaintained while `dirs` is\n- [`d4c908cfb`](https://www.github.com/tauri-apps/tauri/commit/d4c908cfb8c567abdaf99b85f65f482ea81967e5) ([#10048](https://www.github.com/tauri-apps/tauri/pull/10048)) Update `windows` crate to version `0.57` and `webview2-com` crate to version `0.31`\n\n### Breaking Changes\n\n- [`3afe82894`](https://www.github.com/tauri-apps/tauri/commit/3afe8289407b53791e761764964a42207a7f7881) ([#10134](https://www.github.com/tauri-apps/tauri/pull/10134)) Changed `WebviewWindow::navigate` and `Webview::navigate` method signature to return a `Result`\n\n## \\[2.0.0-beta.22]\n\n### Bug Fixes\n\n- [`dfd05441c`](https://www.github.com/tauri-apps/tauri/commit/dfd05441c761b1737e29794ab1f02e41e5d7cc12)([#9860](https://www.github.com/tauri-apps/tauri/pull/9860)) Revert adding `app-region: drag` to HTML elements with `data-tauri-drag-region` on Windows as it has a few issues:\n\n  - Doesn't allow right click, as it will always show the system context menu on right click.\n  - `data-tauri-drag-region` works only if the click was on an element that has it, this allows buttons in the custom titlebar to work, however `app-region: drag` will treat the whole area as a titlebar won't even allow clicks on buttons.\n\n## \\[2.0.0-beta.21]\n\n### New Features\n\n- [`8a1ae2dea`](https://www.github.com/tauri-apps/tauri/commit/8a1ae2deaf3086e531ada25b1627f900e2e421fb)([#9843](https://www.github.com/tauri-apps/tauri/pull/9843)) Added an option to use a Xcode project for the iOS plugin instead of a plain SwiftPM project.\n\n### Bug Fixes\n\n- [`276c4b143`](https://www.github.com/tauri-apps/tauri/commit/276c4b14385e17cff15a2e5b57fd2a7cddef9f08)([#9832](https://www.github.com/tauri-apps/tauri/pull/9832)) On Windows, fix wrong menubar theme when window is using an explicit theme.\n- [`ccc3ea729`](https://www.github.com/tauri-apps/tauri/commit/ccc3ea729de205ef467f737f1feeb5bf02d9cd72)([#9646](https://www.github.com/tauri-apps/tauri/pull/9646)) Parse the correct platform `tauri.<platform>.conf.json` config file when building or developing for mobile.\n- [`aa55e0335`](https://www.github.com/tauri-apps/tauri/commit/aa55e033540cc77c3fb159b9230337d9dd33034e)([#9899](https://www.github.com/tauri-apps/tauri/pull/9899)) Set default window origin to `null`. Prevent window crash when loading `about:blank`.\n\n### What's Changed\n\n- [`9ac930380`](https://www.github.com/tauri-apps/tauri/commit/9ac930380a5df3fe700e68e75df8684d261ca292)([#9850](https://www.github.com/tauri-apps/tauri/pull/9850)) Emit `cargo:rustc-check-cfg` instruction so Cargo validates custom cfg attributes on Rust 1.80 (or nightly-2024-05-05).\n- [`80aa50498`](https://www.github.com/tauri-apps/tauri/commit/80aa504987dd9cfa59aa5848c4d7960e1d58d0e6)([#9870](https://www.github.com/tauri-apps/tauri/pull/9870)) Updated Android target SDK to 34.\n\n### Dependencies\n\n- Upgraded to `tauri-build@2.0.0-beta.17`\n- Upgraded to `tauri-macros@2.0.0-beta.17`\n- Upgraded to `tauri-utils@2.0.0-beta.17`\n- Upgraded to `tauri-runtime@2.0.0-beta.18`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.18`\n\n### Breaking Changes\n\n- [`e8f6eb59a`](https://www.github.com/tauri-apps/tauri/commit/e8f6eb59a5eaad26ae0314ac9e4c0061e6bd25fe)([#9552](https://www.github.com/tauri-apps/tauri/pull/9552)) Include binary path in `Env.args_os`, previously it was skipped.\n- [`1df5cdeb0`](https://www.github.com/tauri-apps/tauri/commit/1df5cdeb06f5464e0eec4055e21b7b7bc8739eed)([#9858](https://www.github.com/tauri-apps/tauri/pull/9858)) Use `tauri.conf.json > identifier` to set the `PackageName` in Android and `BundleId` in iOS.\n- [`aaecb6a72`](https://www.github.com/tauri-apps/tauri/commit/aaecb6a72e5d1462967cc910c2628999997742d0)([#9890](https://www.github.com/tauri-apps/tauri/pull/9890)) Renamed `dev` function to `is_dev` and marked it as `const fn`\n- [`c4410daa8`](https://www.github.com/tauri-apps/tauri/commit/c4410daa85616340e911c8243fdaa69e6906fd49)([#9777](https://www.github.com/tauri-apps/tauri/pull/9777)) This release contains breaking changes to the tray event structure because of newly added events:\n\n  - Changed `TrayIconEvent` to be an enum instead of a struct.\n  - Added `MouseButtonState` and `MouseButton` enums.\n  - Removed `ClickType` enum and replaced it with `MouseButton` enum.\n  - Added `MouseButtonState` enum.\n\n## \\[2.0.0-beta.20]\n\n### New Features\n\n- [`ae6b13dfc`](https://www.github.com/tauri-apps/tauri/commit/ae6b13dfc0590dcaedbdb619c148072f072df050)([#9789](https://www.github.com/tauri-apps/tauri/pull/9789)) Add `app-region: drag` to HTML elements with `data-tauri-drag-region` on Windows, only WebView2 123+, which should fix dragging using touch.\n- [`ec0e092ec`](https://www.github.com/tauri-apps/tauri/commit/ec0e092ecd23b547c756c7476f23a0d95be6db80)([#9770](https://www.github.com/tauri-apps/tauri/pull/9770)) Add `App/AppHandle/Window/Webview/WebviewWindow::monitor_from_point(x, y)` getter to get the monitor from a given point.\n\n### Enhancements\n\n- [`5d20530c9`](https://www.github.com/tauri-apps/tauri/commit/5d20530c91495e548ecc9cb4369da09977a9a962)([#9842](https://www.github.com/tauri-apps/tauri/pull/9842)) Added `AppHandle::set_activation_policy` for macOS.\n\n### Bug Fixes\n\n- [`0b690f242`](https://www.github.com/tauri-apps/tauri/commit/0b690f242f3a9fdffc268ee66464151b3466d00b)([#9845](https://www.github.com/tauri-apps/tauri/pull/9845)) Export `tauri::UriSchemeResponder`.\n\n### Security fixes\n\n- [`d950ac123`](https://www.github.com/tauri-apps/tauri/commit/d950ac1239817d17324c035e5c4769ee71fc197d) Only process IPC commands from the main frame.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.16`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.17`\n- Upgraded to `tauri-runtime@2.0.0-beta.17`\n- Upgraded to `tauri-macros@2.0.0-beta.16`\n- Upgraded to `tauri-build@2.0.0-beta.16`\n\n## \\[2.0.0-beta.19]\n\n### New Features\n\n- [`78839b6d2`](https://www.github.com/tauri-apps/tauri/commit/78839b6d2f1005a5e6e1a54b0305136bae0c3a7c)([#4865](https://www.github.com/tauri-apps/tauri/pull/4865)) Add `RunEvent::Reopen` for handle click on dock icon on macOS.\n\n### Bug Fixes\n\n- [`fedca7386`](https://www.github.com/tauri-apps/tauri/commit/fedca7386079fe639c629d6084cc13031150baf6)([#9720](https://www.github.com/tauri-apps/tauri/pull/9720)) Fix IPC tracing format incompatible between the custom protocol and the postMessage implementations.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.0.0-beta.16`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.16`\n- Upgraded to `tauri-utils@2.0.0-beta.15`\n- Upgraded to `tauri-macros@2.0.0-beta.15`\n- Upgraded to `tauri-build@2.0.0-beta.15`\n\n### Breaking Changes\n\n- [`783ef0f2d`](https://www.github.com/tauri-apps/tauri/commit/783ef0f2d331f520fa827c3112f36c0b519b9292)([#9647](https://www.github.com/tauri-apps/tauri/pull/9647)) Changed `WebviewWindow::url` and `Webview::url` getter to return a result.\n\n## \\[2.0.0-beta.18]\n\n### New Features\n\n- [`07ff78c2d`](https://www.github.com/tauri-apps/tauri/commit/07ff78c2de74d3bd85328ce6536f8a858be89128)([#9615](https://www.github.com/tauri-apps/tauri/pull/9615)) Add `TrayIcon::rect` method to retrieve the tray icon rectangle\n\n### Enhancements\n\n- [`7f6d2698c`](https://www.github.com/tauri-apps/tauri/commit/7f6d2698c923019e4ce53f794e68aa7e56fd7b86)([#9631](https://www.github.com/tauri-apps/tauri/pull/9631)) Improve the error message that is shown when deserializing the Tauri plugin config.\n- [`8a71858eb`](https://www.github.com/tauri-apps/tauri/commit/8a71858eb2a9dc12a43d8fb56f803cdcae072b68)([#9630](https://www.github.com/tauri-apps/tauri/pull/9630)) Provide a default for the runtime generic on `Menu`, `MenuItem`, `Submenu`, `PredefinedMenuItem`, `CheckMenuItem` and `IconMenuItem`.\n- [`8a71858eb`](https://www.github.com/tauri-apps/tauri/commit/8a71858eb2a9dc12a43d8fb56f803cdcae072b68)([#9630](https://www.github.com/tauri-apps/tauri/pull/9630)) Provide a default for the runtime generic on `TrayIcon`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.14`\n- Upgraded to `tauri-runtime@2.0.0-beta.15`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.15`\n- Upgraded to `tauri-macros@2.0.0-beta.14`\n- Upgraded to `tauri-build@2.0.0-beta.14`\n\n## \\[2.0.0-beta.17]\n\n### New Features\n\n- [`12b4159bd`](https://www.github.com/tauri-apps/tauri/commit/12b4159bdaf6e1f8d7b58ed8ff96345fa69c2ef0)([#9392](https://www.github.com/tauri-apps/tauri/pull/9392)) Add `specta` feature flag which adds `specta` support for `AppHandle`, `State`, `Window`, `Webview` and `WebviewWindow` types.\n- [`477bb8cd4`](https://www.github.com/tauri-apps/tauri/commit/477bb8cd4ea88ade3f6c1f268ad1701a68150161)([#9297](https://www.github.com/tauri-apps/tauri/pull/9297)) Add `App/AppHandle/Window/Webview/WebviewWindow::cursor_position` getter to get the current cursor position.\n\n### Enhancements\n\n- [`eff778b8f`](https://www.github.com/tauri-apps/tauri/commit/eff778b8f0c675fda3f6f6d1041bc94afd765d1c)([#9571](https://www.github.com/tauri-apps/tauri/pull/9571)) Run each plugin initialization script on its own context so they do not interfere with each other or the Tauri init script.\n\n### Bug Fixes\n\n- [`6c047aee1`](https://www.github.com/tauri-apps/tauri/commit/6c047aee14fcae86b341e4fcefdbbf8f8378ac20)([#9612](https://www.github.com/tauri-apps/tauri/pull/9612)) Fix window white flashing on exit on Windows\n- [`98101cb17`](https://www.github.com/tauri-apps/tauri/commit/98101cb17fe49f305a75fcb4267f82d89f7ac0c3)([#9561](https://www.github.com/tauri-apps/tauri/pull/9561)) Allow any headers on the IPC custom protocol.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.0.0-beta.14`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.14`\n\n## \\[2.0.0-beta.16]\n\n### New Features\n\n- [`70c51371e`](https://www.github.com/tauri-apps/tauri/commit/70c51371e01184223312de3dba8030394a5a9406)([#9539](https://www.github.com/tauri-apps/tauri/pull/9539)) Add `window.isTauri` to check whether running inside tauri or not.\n\n### Bug Fixes\n\n- [`daf018e4f`](https://www.github.com/tauri-apps/tauri/commit/daf018e4f5d5f6dcde51c5de42d73ab15287ec7e)([#9505](https://www.github.com/tauri-apps/tauri/pull/9505)) Fix resource tables not cleaned up on exit which causes tray icon inside resource tables not cleaned up on exit\n- [`a07b51320`](https://www.github.com/tauri-apps/tauri/commit/a07b5132019faa7695c573a6610d2def0ff9c40a)([#9490](https://www.github.com/tauri-apps/tauri/pull/9490)) Add missing permission for `window.start_resize_dragging`\n- [`35b25f7e5`](https://www.github.com/tauri-apps/tauri/commit/35b25f7e5c0fe03af4ed3582e22a626863f035f0)([#9530](https://www.github.com/tauri-apps/tauri/pull/9530)) Do not use JS optional chaining to prevent script errors on older webviews such as macOS 10.14.\n\n### What's Changed\n\n- [`005fe8ce1`](https://www.github.com/tauri-apps/tauri/commit/005fe8ce1ef71ea46a7d86f98bdf397ca81eb920)([#9410](https://www.github.com/tauri-apps/tauri/pull/9410)) Fix `closable`, `maximizable` and `minimizable` options not taking effect when used in tauri.conf.json or from JS APIs.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.13`\n- Upgraded to `tauri-build@2.0.0-beta.13`\n- Upgraded to `tauri-utils@2.0.0-beta.13`\n- Upgraded to `tauri-runtime@2.0.0-beta.13`\n- Upgraded to `tauri-macros@2.0.0-beta.13`\n\n## \\[2.0.0-beta.15]\n\n### New Features\n\n- [`58a7a552d`](https://www.github.com/tauri-apps/tauri/commit/58a7a552d739b77b71d61af11c53f7f2dc7a6e7e)([#9378](https://www.github.com/tauri-apps/tauri/pull/9378)) Added the `set_zoom` function to the webview API.\n- [`58a7a552d`](https://www.github.com/tauri-apps/tauri/commit/58a7a552d739b77b71d61af11c53f7f2dc7a6e7e)([#9378](https://www.github.com/tauri-apps/tauri/pull/9378)) Add `zoom_hotkeys_enabled` to enable browser native zoom controls on creating webviews.\n- [`4973d73a2`](https://www.github.com/tauri-apps/tauri/commit/4973d73a237dc5c60618c1011e202278e7a29b5c)([#9386](https://www.github.com/tauri-apps/tauri/pull/9386)) Provide a basic zoom hotkey polyfill for non-Windows platforms\n\n### Enhancements\n\n- [`f1674fce6`](https://www.github.com/tauri-apps/tauri/commit/f1674fce6dfb1cf0378a85165bb62c270715211b)([#9420](https://www.github.com/tauri-apps/tauri/pull/9420)) Tauri's built-in commands for the JS api will now return simplified paths on Windows, removing the `\\\\?\\` prefix.\n\n### Bug Fixes\n\n- [`c8a82ad22`](https://www.github.com/tauri-apps/tauri/commit/c8a82ad2236ee1def621b5930bdb136f01dd07e4)([#9379](https://www.github.com/tauri-apps/tauri/pull/9379)) Fix deadlock when using the menu/tray/image JS APIs.\n- [`6251645ac`](https://www.github.com/tauri-apps/tauri/commit/6251645acfe2df2da726f38a09373d7370bfcc86)([#9360](https://www.github.com/tauri-apps/tauri/pull/9360)) Fixes an issue causing `getAll()` to list webviews that were already destroyed.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.12`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.12`\n- Upgraded to `tauri-runtime@2.0.0-beta.12`\n- Upgraded to `tauri-macros@2.0.0-beta.12`\n- Upgraded to `tauri-build@2.0.0-beta.12`\n\n### Breaking Changes\n\n- [`c8a82ad22`](https://www.github.com/tauri-apps/tauri/commit/c8a82ad2236ee1def621b5930bdb136f01dd07e4)([#9379](https://www.github.com/tauri-apps/tauri/pull/9379)) Changed `JsImage::into_img` to take a reference to a `ResourceTable` instead of a `Manager`.\n\n## \\[2.0.0-beta.14]\n\n### New Features\n\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Added `Rect` struct.\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Add `Webview::bounds` and `Webview::set_bounds` APIs.\n\n### Enhancements\n\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Enhance the IPC URL check by using the Origin header on the custom protocol IPC and the new request URI field on the postMessage IPC instead of using `Webview::url()` which only returns the URL of the main frame and is not suitable for iframes (iframe URL fetch is still not supported on Android and on Linux when using the postMessage IPC).\n\n### Bug Fixes\n\n- [`c33f6e6cf`](https://www.github.com/tauri-apps/tauri/commit/c33f6e6cf35a0d34b5598875a2e5b642a01c8b38)([#9211](https://www.github.com/tauri-apps/tauri/pull/9211)) Fixed an issue preventing webview/window creation events to not be emitted. This also fixed the `getByLabel` and `getAll` JavaScript functions.\n\n### What's Changed\n\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Updated `http` crate to `1.1`\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.11`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.11`\n- Upgraded to `tauri-runtime@2.0.0-beta.11`\n- Upgraded to `tauri-macros@2.0.0-beta.11`\n- Upgraded to `tauri-build@2.0.0-beta.11`\n\n### Breaking Changes\n\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Rename `FileDrop` to `DragDrop` on structs, enums and enum variants. Also renamed `file_drop` to `drag_drop` on fields and function names.\n- [`284eca9ef`](https://www.github.com/tauri-apps/tauri/commit/284eca9ef2396b76ce3df6f32fb3b2d2c40044ad)([#9272](https://www.github.com/tauri-apps/tauri/pull/9272)) `Manager::resources_table` is now scoped so each `App/AppHandle/Window/Webview/WebviewWindow` has its own resource collection.\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Refactored the tray icon event struct:\n\n  - Changed `TrayIconEvent.icon_rect` type to use the new `tauri::Rect` type.\n  - Removed `TrayIconEvent.x` and `TrayIconEvent.y` fields and combined them into `TrayIconEvent.position` field.\n  - Removed `tauri::tray::Rectangle` struct.\n\n## \\[2.0.0-beta.13]\n\n### Enhancements\n\n- [`75f5cb401`](https://www.github.com/tauri-apps/tauri/commit/75f5cb4015f72745161110ad0076cf4945411a6d)([#9214](https://www.github.com/tauri-apps/tauri/pull/9214)) `tauri::Window` and `tauri::WebviewWindow` now implement `raw_window_handle::HasDisplayHandle`.\n\n### Bug Fixes\n\n- [`81b853bc8`](https://www.github.com/tauri-apps/tauri/commit/81b853bc875ce2da4e300614ca234f10d54966a6)([#9213](https://www.github.com/tauri-apps/tauri/pull/9213)) Fixed an issue where errors where returned as strings instead of objects from commands.\n- [`43230cb6b`](https://www.github.com/tauri-apps/tauri/commit/43230cb6b7a4b14a23ea8f05636ae06f03c718e9)([#9219](https://www.github.com/tauri-apps/tauri/pull/9219)) Fixes the menu plugin `remove` command signature.\n\n## \\[2.0.0-beta.12]\n\n### New Features\n\n- [`e227fe02f`](https://www.github.com/tauri-apps/tauri/commit/e227fe02f986e145c0731a64693e1c830a9eb5b0)([#9156](https://www.github.com/tauri-apps/tauri/pull/9156)) Allow plugins to define (at compile time) JavaScript that are initialized when `withGlobalTauri` is true.\n\n### Enhancements\n\n- [`79b8a3514`](https://www.github.com/tauri-apps/tauri/commit/79b8a3514baedcd9c35e777d2b6d89a7a086ddec)([#9151](https://www.github.com/tauri-apps/tauri/pull/9151)) Improve and optimize event emit calls.\n\n### Bug Fixes\n\n- [`379cc2b35`](https://www.github.com/tauri-apps/tauri/commit/379cc2b3547395474d4b66b4222679cf4538428d)([#9165](https://www.github.com/tauri-apps/tauri/pull/9165)) Fix `basename(path, 'ext')` JS API when removing all occurances of `ext` where it should only remove the last one.\n\n### Dependencies\n\n- Upgraded to `tauri-build@2.0.0-beta.10`\n- Upgraded to `tauri-utils@2.0.0-beta.10`\n- Upgraded to `tauri-runtime@2.0.0-beta.10`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.10`\n- Upgraded to `tauri-macros@2.0.0-beta.10`\n\n### Breaking Changes\n\n- [`acdd76833`](https://www.github.com/tauri-apps/tauri/commit/acdd76833db6d81f4012418133d0042220de100b)([#9155](https://www.github.com/tauri-apps/tauri/pull/9155)) Removed `App/AppHandle::tray` and `App/AppHandle::remove_tray`, use `App/AppHandle::tray_by_id` and `App/AppHandle::remove_tray_by_id` instead. If these APIs were used to access tray icon configured in `tauri.conf.json`, you can use `App/AppHandle::tray_by_id` with ID `main` or the configured value.\n- [`ea0242db4`](https://www.github.com/tauri-apps/tauri/commit/ea0242db4aa6c127d2bb4a2e275000ba47c9e68c)([#9179](https://www.github.com/tauri-apps/tauri/pull/9179)) Removed `width` and `height` methods on the JS `Image` class, use `size` instead.\n\n## \\[2.0.0-beta.11]\n\n### New Features\n\n- [`490a6b424`](https://www.github.com/tauri-apps/tauri/commit/490a6b424e81714524150aef96fbf6cf7004b940)([#9147](https://www.github.com/tauri-apps/tauri/pull/9147)) The `Assets` trait now include a `setup` method that lets you run initialization code for your custom asset provider.\n\n### Bug Fixes\n\n- [`85de230f3`](https://www.github.com/tauri-apps/tauri/commit/85de230f313da81cbbd061e66e8de64e5b33104c)([#9144](https://www.github.com/tauri-apps/tauri/pull/9144)) Fix old JS listeners being dropped on page load after it was possible to create new listeners.\n- [`e673854c8`](https://www.github.com/tauri-apps/tauri/commit/e673854c8333cb8a8d298471737293f17ec5a3ee)([#9133](https://www.github.com/tauri-apps/tauri/pull/9133)) Fixes capability remote domain not allowing subpaths, query parameters and hash when those values are empty.\n\n### Dependencies\n\n- Upgraded to `tauri-macros@2.0.0-beta.9`\n- Upgraded to `tauri-utils@2.0.0-beta.9`\n- Upgraded to `tauri-build@2.0.0-beta.9`\n- Upgraded to `tauri-runtime@2.0.0-beta.9`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.9`\n\n### Breaking Changes\n\n- [`490a6b424`](https://www.github.com/tauri-apps/tauri/commit/490a6b424e81714524150aef96fbf6cf7004b940)([#9147](https://www.github.com/tauri-apps/tauri/pull/9147)) The `Context` struct and the `Assets` trait now takes a `R: Runtime` generic.\n- [`ba0206d8a`](https://www.github.com/tauri-apps/tauri/commit/ba0206d8a30a9b43ec5090dcaabd1a23baa1420c)([#9141](https://www.github.com/tauri-apps/tauri/pull/9141)) `Context::assets` now returns `&dyn Assets` instead of `&A` generic.\n- [`ba0206d8a`](https://www.github.com/tauri-apps/tauri/commit/ba0206d8a30a9b43ec5090dcaabd1a23baa1420c)([#9141](https://www.github.com/tauri-apps/tauri/pull/9141)) The `Context` type no longer uses the `<A: Assets>` generic so the assets implementation can be swapped with `Context::assets_mut`.\n- [`490a6b424`](https://www.github.com/tauri-apps/tauri/commit/490a6b424e81714524150aef96fbf6cf7004b940)([#9147](https://www.github.com/tauri-apps/tauri/pull/9147)) Removed `Context::assets_mut` and added `Context::set_assets`.\n- [`db0a24a97`](https://www.github.com/tauri-apps/tauri/commit/db0a24a973191752aeecfbd556faa254b0f17e79)([#9132](https://www.github.com/tauri-apps/tauri/pull/9132)) Use the image crate for `tauri::image::Image` and remove the `from_png_bytes` and `from_ico_bytes` APIs.\n\n## \\[2.0.0-beta.10]\n\n### New Features\n\n- [`3e472d0af`](https://www.github.com/tauri-apps/tauri/commit/3e472d0afcd67545dd6d9f18d304580a3b2759a8)([#9115](https://www.github.com/tauri-apps/tauri/pull/9115)) Added `CapabilityBuilder::platform` to link the runtime capability with a specific platform.\n\n### Enhancements\n\n- [`3e472d0af`](https://www.github.com/tauri-apps/tauri/commit/3e472d0afcd67545dd6d9f18d304580a3b2759a8)([#9115](https://www.github.com/tauri-apps/tauri/pull/9115)) Changed the permission and capability platforms to be optional.\n- [`9dc9ca6e3`](https://www.github.com/tauri-apps/tauri/commit/9dc9ca6e38be62ef2746c7a4c2b77b2d67c0d998)([#9113](https://www.github.com/tauri-apps/tauri/pull/9113)) Added `tauri::dev()` to determine whether we are running in development mode or not.\n\n### Bug Fixes\n\n- [`5541aafef`](https://www.github.com/tauri-apps/tauri/commit/5541aafef33113bc292558ba125e685135aabab4)([#9107](https://www.github.com/tauri-apps/tauri/pull/9107)) Fix `emit` and `emit_to` (when used with `EventTarget::Any`) always skipping the webview listeners.\n- [`80c12ead4`](https://www.github.com/tauri-apps/tauri/commit/80c12ead4655af91f08046f19c2d478a4cbf94cd)([#9121](https://www.github.com/tauri-apps/tauri/pull/9121)) Fix regression on IPC response when using a channel to return objects.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.8`\n- Upgraded to `tauri-runtime@2.0.0-beta.8`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.8`\n- Upgraded to `tauri-macros@2.0.0-beta.8`\n- Upgraded to `tauri-build@2.0.0-beta.8`\n\n### Breaking Changes\n\n- [`4ef17d083`](https://www.github.com/tauri-apps/tauri/commit/4ef17d08336a2e0df4a7ef9adea746d7419710b6)([#9116](https://www.github.com/tauri-apps/tauri/pull/9116)) The ACL configuration for remote URLs now uses the URLPattern standard instead of glob patterns.\n- [`ed48e2b3c`](https://www.github.com/tauri-apps/tauri/commit/ed48e2b3c7fa914e4c9af432c02b8154f872c68a)([#9122](https://www.github.com/tauri-apps/tauri/pull/9122)) Expose `tauri::image` module to export the `JsImage` type and removed the `Image` root re-export.\n\n## \\[2.0.0-beta.9]\n\n### New Features\n\n- [`46de49aaa`](https://www.github.com/tauri-apps/tauri/commit/46de49aaad4a148fafc31d591be0e2ed12256507)([#9059](https://www.github.com/tauri-apps/tauri/pull/9059)) Added `set_auto_resize` method for the webview.\n- [`d1e77acd8`](https://www.github.com/tauri-apps/tauri/commit/d1e77acd8dfdf554b90b542513a58a2de1ef2360)([#9011](https://www.github.com/tauri-apps/tauri/pull/9011)) Add a new `Image` type in Rust and JS.\n\n### Enhancements\n\n- [`a77be9747`](https://www.github.com/tauri-apps/tauri/commit/a77be9747443ffc29c34160b55893483bb5f0d74)([#9038](https://www.github.com/tauri-apps/tauri/pull/9038)) Fallback to the postMessage IPC interface if we cannot reach the IPC custom protocol.\n- [`e62ca4ee9`](https://www.github.com/tauri-apps/tauri/commit/e62ca4ee95f4308a6ad128d0f100c85634e28223)([#9070](https://www.github.com/tauri-apps/tauri/pull/9070)) Added a mechanism to preserve channel message order.\n- [`03098b531`](https://www.github.com/tauri-apps/tauri/commit/03098b531562e4d58ab12ad9da2acb1eb3480497)([#9036](https://www.github.com/tauri-apps/tauri/pull/9036)) `Manager::add_capability` now allows adding a dynamically defined capability instead of only relying on static strings.\n- [`b5c743276`](https://www.github.com/tauri-apps/tauri/commit/b5c7432769b84ffe22db721dcfc6af218026f5d4)([#9086](https://www.github.com/tauri-apps/tauri/pull/9086)) Use a strict content security policy on the isolation pattern iframe.\n- [`46de49aaa`](https://www.github.com/tauri-apps/tauri/commit/46de49aaad4a148fafc31d591be0e2ed12256507)([#9059](https://www.github.com/tauri-apps/tauri/pull/9059)) When using the `unstable` feature flag, `WebviewWindow` will internally use the child webview interface for flexibility.\n\n### Bug Fixes\n\n- [`86fa339de`](https://www.github.com/tauri-apps/tauri/commit/86fa339de7b176efafa9b3e89f94dcad5fcd03da)([#9071](https://www.github.com/tauri-apps/tauri/pull/9071)) Fix compile time error in context generation when using `app.windows.windowEffects.color`\n- [`947a50b8e`](https://www.github.com/tauri-apps/tauri/commit/947a50b8e28379c452c32eddc3e0101870e50055)([#9049](https://www.github.com/tauri-apps/tauri/pull/9049)) Fix `tauri migrate` for http plugin ACL.\n- [`fe18012d3`](https://www.github.com/tauri-apps/tauri/commit/fe18012d30d1d8b3ffa10c8e321710eba644ef94)([#9072](https://www.github.com/tauri-apps/tauri/pull/9072)) Resolve symlinks on the filesystem scope check.\n- [`6c0683224`](https://www.github.com/tauri-apps/tauri/commit/6c068322460300e9d56a4fac5b018d4c437daa9e)([#9068](https://www.github.com/tauri-apps/tauri/pull/9068)) Fixes scope resolution grouping scopes for all windows.\n\n### Dependencies\n\n- Upgraded to `tauri-build@2.0.0-beta.7`\n- Upgraded to `tauri-utils@2.0.0-beta.7`\n- Upgraded to `tauri-runtime@2.0.0-beta.7`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.7`\n- Upgraded to `tauri-macros@2.0.0-beta.7`\n\n### Breaking Changes\n\n- [`d1e77acd8`](https://www.github.com/tauri-apps/tauri/commit/d1e77acd8dfdf554b90b542513a58a2de1ef2360)([#9011](https://www.github.com/tauri-apps/tauri/pull/9011)) Renamed `icon-ico` and `icon-png` feature flags to `image-ico` and `image-png` respectively\n- [`720357fd5`](https://www.github.com/tauri-apps/tauri/commit/720357fd5cd1fefef8485077dfb116ee39ef4ab4)([#9104](https://www.github.com/tauri-apps/tauri/pull/9104)) Removed `tauri::path::Result` and `tauri::path::Error` which were merely an unintentional re-export of `tauri::Result` and `tauri::Error` so use those instead.\n- [`6c0683224`](https://www.github.com/tauri-apps/tauri/commit/6c068322460300e9d56a4fac5b018d4c437daa9e)([#9068](https://www.github.com/tauri-apps/tauri/pull/9068)) The `allows` and `denies` methods from `ipc::ScopeValue`, `ipc::CommandScope` and `ipc::GlobalScope` now returns `&Vec<Arc<T>>` instead of `&Vec<T>`.\n- [`d1e77acd8`](https://www.github.com/tauri-apps/tauri/commit/d1e77acd8dfdf554b90b542513a58a2de1ef2360)([#9011](https://www.github.com/tauri-apps/tauri/pull/9011)) Removed `Context::default_window_icon_mut` and `Context::tray_icon_mut`, use `Context::set_default_window_icon` and `Context::set_tray_icon` instead. Also changed `Context::set_tray_icon` to accept `Option<T>`.\n- [`d1e77acd8`](https://www.github.com/tauri-apps/tauri/commit/d1e77acd8dfdf554b90b542513a58a2de1ef2360)([#9011](https://www.github.com/tauri-apps/tauri/pull/9011)) Removed `Icon` enum, use the new `Image` type instead. All APIs that previously accepted `Icon` have changed to accept `Image` instead.\n\n## \\[2.0.0-beta.8]\n\n### New Features\n\n- [`d7f56fef`](https://www.github.com/tauri-apps/tauri/commit/d7f56fef85cac3af4e2dbac1eac40e5567b1f160)([#9014](https://www.github.com/tauri-apps/tauri/pull/9014)) Allow defining a permission that only applies to a set of target platforms via the `platforms` configuration option.\n\n### Bug Fixes\n\n- [`e1d5b790`](https://www.github.com/tauri-apps/tauri/commit/e1d5b7906369a40df19e8ee86c56f90a27d6357c)([#8995](https://www.github.com/tauri-apps/tauri/pull/8995)) Fixes capability webview label check.\n- [`222a96b7`](https://www.github.com/tauri-apps/tauri/commit/222a96b74b145fb48d3f0c109897962d56fae57a)([#8999](https://www.github.com/tauri-apps/tauri/pull/8999)) Fixes `Window::add_child` deadlock.\n- [`e4463f08`](https://www.github.com/tauri-apps/tauri/commit/e4463f08145c044bd37dc1c6f5f39e6a572ace3e)([#8930](https://www.github.com/tauri-apps/tauri/pull/8930)) Clear JS event listeneres on page load, which fixes zombie listeners when the page reloads.\n- [`222a96b7`](https://www.github.com/tauri-apps/tauri/commit/222a96b74b145fb48d3f0c109897962d56fae57a)([#8999](https://www.github.com/tauri-apps/tauri/pull/8999)) Fixes `Webview::reparent` not updating the webview parent window reference.\n\n### Dependencies\n\n- Upgraded to `tauri-build@2.0.0-beta.6`\n- Upgraded to `tauri-utils@2.0.0-beta.6`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.6`\n- Upgraded to `tauri-runtime@2.0.0-beta.6`\n- Upgraded to `tauri-macros@2.0.0-beta.6`\n\n### Breaking Changes\n\n- [`3657ad82`](https://www.github.com/tauri-apps/tauri/commit/3657ad82f88ce528551d032d521c52eed3f396b4)([#9008](https://www.github.com/tauri-apps/tauri/pull/9008)) Allow defining permissions for the application commands via `tauri_build::Attributes::app_manifest`.\n\n### Breaking Changes\n\n- [`b9e6a018`](https://www.github.com/tauri-apps/tauri/commit/b9e6a01879d9233040f3d3fab11c59e70563da7e)([#8937](https://www.github.com/tauri-apps/tauri/pull/8937)) The `custom-protocol` Cargo feature is no longer required on your application and is now ignored. To check if running on production, use `#[cfg(not(dev))]` instead of `#[cfg(feature = \"custom-protocol\")]`.\n\n## \\[2.0.0-beta.7]\n\n### Enhancements\n\n- [`bc5b5e67`](https://www.github.com/tauri-apps/tauri/commit/bc5b5e671a546512f823f1c157421b4c3311dfc0)([#8984](https://www.github.com/tauri-apps/tauri/pull/8984)) Do not include a CSP tag in the application HTML and rely on the custom protocol response header instead.\n\n### Bug Fixes\n\n- [`6cb601d4`](https://www.github.com/tauri-apps/tauri/commit/6cb601d42e2af75aa818371b8b8f7d5b2e77dc90)([#8983](https://www.github.com/tauri-apps/tauri/pull/8983)) Convert the command name to camelCase when executing a mobile plugin command.\n- [`60bf11ab`](https://www.github.com/tauri-apps/tauri/commit/60bf11abcbec8d0362aa256e2293985bfd62620f)([#8986](https://www.github.com/tauri-apps/tauri/pull/8986)) Export `ProgressBarStatus`, regression introduced in `2.0.0-beta.4`\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.5`\n- Upgraded to `tauri-runtime@2.0.0-beta.5`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.5`\n- Upgraded to `tauri-macros@2.0.0-beta.5`\n- Upgraded to `tauri-build@2.0.0-beta.5`\n\n## \\[2.0.0-beta.6]\n\n### Bug Fixes\n\n- [`6edc563c`](https://www.github.com/tauri-apps/tauri/commit/6edc563cf9ca26b4622c3135d92e493a5d5bd6e8)([#8953](https://www.github.com/tauri-apps/tauri/pull/8953)) Fixes a deadlock when the window is destroyed.\n\n## \\[2.0.0-beta.5]\n\n### New Features\n\n- [`fdcaf935`](https://www.github.com/tauri-apps/tauri/commit/fdcaf935fa75ecfa2806939c4faad4fe9e880386)([#8939](https://www.github.com/tauri-apps/tauri/pull/8939)) Added the `reparent` function to the webview API.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.4`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.4`\n- Upgraded to `tauri-build@2.0.0-beta.4`\n- Upgraded to `tauri-runtime@2.0.0-beta.4`\n- Upgraded to `tauri-macros@2.0.0-beta.4`\n\n## \\[2.0.0-beta.4]\n\n### Enhancements\n\n- [`3fb414b6`](https://www.github.com/tauri-apps/tauri/commit/3fb414b61ad7cfce67751230826fddfb39effec5)([#8914](https://www.github.com/tauri-apps/tauri/pull/8914)) Return an id when using from `Manager::once_any`, `App::once`, `Window::once`, `Webview::once`, `WebviewWindow::once` and `fs::Scope::once`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.3`\n- Upgraded to `tauri-runtime@2.0.0-beta.3`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.3`\n- Upgraded to `tauri-macros@2.0.0-beta.3`\n- Upgraded to `tauri-build@2.0.0-beta.3`\n\n### Breaking Changes\n\n- [`361ec37f`](https://www.github.com/tauri-apps/tauri/commit/361ec37fd4a5caa5b6630b9563ef079f53c6c336)([#8932](https://www.github.com/tauri-apps/tauri/pull/8932)) Moved `ProgressBarState` from `tauri-utils` to the `tauri::window` module and removed the `unity_uri` field.\n\n## \\[2.0.0-beta.3]\n\n### New Features\n\n- [`16e550ec`](https://www.github.com/tauri-apps/tauri/commit/16e550ec1503765158cdc3bb2a20e70ec710e981)([#8844](https://www.github.com/tauri-apps/tauri/pull/8844)) Add webview-specific events for multi-webview windows:\n\n  - Add `WebviewEvent` enum\n  - Add `RunEvent::WebviewEvent` variant.\n  - Add `Builder::on_webview_event` and `Webview::on_webview_event` methods.\n\n### Enhancements\n\n- [`11a5816b`](https://www.github.com/tauri-apps/tauri/commit/11a5816bdffcbaa20df936dee43751de2cf67530)([#8864](https://www.github.com/tauri-apps/tauri/pull/8864)) A file-drop now allows sub-directories recursively when the path is a directory.\n- [`0cb0a15c`](https://www.github.com/tauri-apps/tauri/commit/0cb0a15ce22af3d649cf219ac04188c14c5f4905)([#8789](https://www.github.com/tauri-apps/tauri/pull/8789)) Add `webviews` array on the capability for usage on multiwebview contexts.\n- [`258494bd`](https://www.github.com/tauri-apps/tauri/commit/258494bd247b6d36485bf16bf7184b93fd299da9)([#8806](https://www.github.com/tauri-apps/tauri/pull/8806)) Added `Manager::add_capability` to add a capability file at runtime.\n- [`5618f6d2`](https://www.github.com/tauri-apps/tauri/commit/5618f6d2ffc9ebf40710145538b06bebfa55f878)([#8856](https://www.github.com/tauri-apps/tauri/pull/8856)) Relax requirements on plugin's identifiers to be alphanumeric and `-` instead of only lower alpha and `-`.\n\n### Bug Fixes\n\n- [`16e550ec`](https://www.github.com/tauri-apps/tauri/commit/16e550ec1503765158cdc3bb2a20e70ec710e981)([#8844](https://www.github.com/tauri-apps/tauri/pull/8844)) Fix JS event listeners registered using JS `listen` api with `EventTarget::Any` never fired.\n- [`8751c329`](https://www.github.com/tauri-apps/tauri/commit/8751c3299f2b7229c6108aa37dedf1dc5edb3e5c)([#8793](https://www.github.com/tauri-apps/tauri/pull/8793)) Fix invoking toggle devtools by hotkey.\n- [`bd73ab0a`](https://www.github.com/tauri-apps/tauri/commit/bd73ab0a1adcf648e38d579b92515dababf34993)([#8766](https://www.github.com/tauri-apps/tauri/pull/8766)) When using the multiwebview mode, properly remove the webview from memory on `Webview::close`.\n- [`46b6598a`](https://www.github.com/tauri-apps/tauri/commit/46b6598a94cd0c6fa4a1654ac67432d94ea20ebf)([#8826](https://www.github.com/tauri-apps/tauri/pull/8826)) Fix JS `onCloseRequested` catching close event from other windows.\n- [`2e6db908`](https://www.github.com/tauri-apps/tauri/commit/2e6db908d7b3a2c928c46b0ad9ccf9ec55a29480)([#8777](https://www.github.com/tauri-apps/tauri/pull/8777)) Fix regression in `tauri::Error` not being `Sync`.\n\n### What's Changed\n\n- [`76ce9f61`](https://www.github.com/tauri-apps/tauri/commit/76ce9f61dd3c5bdd589c7557543894e1f770dd16)([#3002](https://www.github.com/tauri-apps/tauri/pull/3002)) Enhance centering a newly created window, it will no longer jump to center after being visible.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.2`\n- Upgraded to `tauri-build@2.0.0-beta.2`\n- Upgraded to `tauri-macros@2.0.0-beta.2`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.2`\n- Upgraded to `tauri-runtime@2.0.0-beta.2`\n\n### Breaking Changes\n\n- [`258494bd`](https://www.github.com/tauri-apps/tauri/commit/258494bd247b6d36485bf16bf7184b93fd299da9)([#8806](https://www.github.com/tauri-apps/tauri/pull/8806)) Removed the lifetime parameter from `ipc::GlobalScope` and `ipc::CommandScope`.\n- [`f284f9c5`](https://www.github.com/tauri-apps/tauri/commit/f284f9c545deeb77d15b6e8b1d0d05f49c40634c)([#8898](https://www.github.com/tauri-apps/tauri/pull/8898)) Changed the capability `remote` configuration to take a list of `urls` instead of `domains` for more flexibility.\n- [`2f55bfec`](https://www.github.com/tauri-apps/tauri/commit/2f55bfecbf0244f3b5aa1ad7622182fca3fcdcbb)([#8795](https://www.github.com/tauri-apps/tauri/pull/8795)) Update raw-window-handle to 0.6.\n- [`2e6db908`](https://www.github.com/tauri-apps/tauri/commit/2e6db908d7b3a2c928c46b0ad9ccf9ec55a29480)([#8777](https://www.github.com/tauri-apps/tauri/pull/8777)) Require `ScopeObject::Error` to be `Sync` as well.\n\n## \\[2.0.0-beta.2]\n\n### Bug Fixes\n\n- [`fe67ab7f`](https://www.github.com/tauri-apps/tauri/commit/fe67ab7f2532c668b35f15415d876e576b3fb74e)([#8761](https://www.github.com/tauri-apps/tauri/pull/8761)) Workaround for zbus not enabling the proper Cargo features for its nix dependency.\n\n### Dependencies\n\n- Upgraded to `tauri-build@2.0.0-beta.1`\n- Upgraded to `tauri-utils@2.0.0-beta.1`\n- Upgraded to `tauri-runtime@2.0.0-beta.1`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.1`\n- Upgraded to `tauri-macros@2.0.0-beta.1`\n\n## \\[2.0.0-beta.1]\n\n### Bug Fixes\n\n- [`863bc9e5`](https://www.github.com/tauri-apps/tauri/commit/863bc9e55f9099207403e34cb5c218231265b2c1)([#8749](https://www.github.com/tauri-apps/tauri/pull/8749)) Fix regression on the JavaScript code that processes the IPC message.\n\n## \\[2.0.0-beta.0]\n\n### New Features\n\n- [`74a2a603`](https://www.github.com/tauri-apps/tauri/commit/74a2a6036a5e57462f161d728cbd8a6f121028ca)([#8661](https://www.github.com/tauri-apps/tauri/pull/8661)) Implement access control list for IPC usage.\n- [`a2fc3a63`](https://www.github.com/tauri-apps/tauri/commit/a2fc3a63579ca739646d696870cbecbb3a169d33)([#8657](https://www.github.com/tauri-apps/tauri/pull/8657)) Add `visibleOnAllWorkspaces` option when creating the window in JS and `Window.setVisibleOnAllWorkspaces` method.\n- [`af610232`](https://www.github.com/tauri-apps/tauri/commit/af6102327376884364b2075b468bdf08ee0d02aa)([#8710](https://www.github.com/tauri-apps/tauri/pull/8710)) Added `Window::destroy` to force close a window.\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Add multiwebview support behind the `unstable` feature flag. See `WindowBuilder` and `WebviewBuilder` for more information.\n- [`9eaeb5a8`](https://www.github.com/tauri-apps/tauri/commit/9eaeb5a8cd95ae24b5e66205bdc2763cb7f965ce)([#8622](https://www.github.com/tauri-apps/tauri/pull/8622)) Add `WindowBuilder::parent` which is a convenient wrapper around parent functionality for Windows, Linux and macOS. Also added `WindowBuilder::owner` on Windows only. Also added `WindowBuilder::transient_for` and `WindowBuilder::transient_for_raw` on Linux only.\n\n### Enhancements\n\n- [`e8d3793c`](https://www.github.com/tauri-apps/tauri/commit/e8d3793c3c34715569312a91633fde4d58d7621c)([#8732](https://www.github.com/tauri-apps/tauri/pull/8732)) Add `common-controls-v6` cargo feature flag (enabled by default).\n- [`58fe2e81`](https://www.github.com/tauri-apps/tauri/commit/58fe2e812a85b9f4eba105286a63f271ea637836)([#8670](https://www.github.com/tauri-apps/tauri/pull/8670)) Allow IPC calls when window origin is a defined custom protocol.\n\n### Bug Fixes\n\n- [`95da1a27`](https://www.github.com/tauri-apps/tauri/commit/95da1a27476e01e06f6ce0335df8535b662dd9c4)([#8713](https://www.github.com/tauri-apps/tauri/pull/8713)) Fix calling `set_activation_policy` when the event loop is running.\n- [`e1eb911f`](https://www.github.com/tauri-apps/tauri/commit/e1eb911f5ebe84285aae710e0ebdd945ad389431)([#8582](https://www.github.com/tauri-apps/tauri/pull/8582)) Ensure initalize logic and dropping of menu item is done on the main thread, this fixes the crash when a menu item is dropped on another thread.\n- [`a093682d`](https://www.github.com/tauri-apps/tauri/commit/a093682d2df7169b024bb4f736c7f1fd2ea8b327)([#8621](https://www.github.com/tauri-apps/tauri/pull/8621)) Fix can not prevent closing a window from another webview.\n- [`7f033f6d`](https://www.github.com/tauri-apps/tauri/commit/7f033f6dcd54c69a4193765a5c1584755ba92c61)([#8537](https://www.github.com/tauri-apps/tauri/pull/8537)) Fix undecorated window resizing on Windows and Linux.\n\n### What's Changed\n\n- [`9f8037c2`](https://www.github.com/tauri-apps/tauri/commit/9f8037c2882abac19582025001675370f0d7b669)([#8633](https://www.github.com/tauri-apps/tauri/pull/8633)) On Windows, fix decorated window not transparent initially until resized.\n- [`7f033f6d`](https://www.github.com/tauri-apps/tauri/commit/7f033f6dcd54c69a4193765a5c1584755ba92c61)([#8537](https://www.github.com/tauri-apps/tauri/pull/8537)) Add `Window::start_resize_dragging` and `ResizeDirection` enum.\n- [`6639a579`](https://www.github.com/tauri-apps/tauri/commit/6639a579c76d45210f33a72d37e21d4c5a9d334b)([#8441](https://www.github.com/tauri-apps/tauri/pull/8441)) Added the `WindowConfig::proxy_url` `WebviewBuilder::proxy_url() / WebviewWindowBuilder::proxy_url()` options when creating a webview.\n\n### Dependencies\n\n- Upgraded to `tauri-build@22.0.0-beta.0`\n- Upgraded to `tauri-utils@2.0.0-beta.0`\n- Upgraded to `tauri-macros@2.0.0-beta.0`\n- Upgraded to `tauri-runtime@2.0.0-beta.0`\n- Upgraded to `tauri-runtime-wry@2.0.0-beta.0`\n\n### Breaking Changes\n\n- [`8de308d1`](https://www.github.com/tauri-apps/tauri/commit/8de308d1bf6a855d7a26af58bd0e744938ba47d8)([#8723](https://www.github.com/tauri-apps/tauri/pull/8723)) Restructured Tauri config per [RFC#5](https://github.com/tauri-apps/rfcs/blob/f3e82a6b0c5390401e855850d47dc7b7d9afd684/texts/0005-tauri-config-restructure.md):\n\n  - Moved `package.productName`, `package.version` and `tauri.bundle.identifier` fields to the top-level.\n  - Removed `package` object.\n  - Renamed `tauri` object to `app`.\n  - Moved `tauri.bundle` object to the top-level.\n  - Renamed `build.distDir` field to `frontendDist`.\n  - Renamed `build.devPath` field to `devUrl` and will no longer accepts paths, it will only accept URLs.\n  - Moved `tauri.pattern` to `app.security.pattern`.\n  - Removed `tauri.bundle.updater` object, and its fields have been moved to the updater plugin under `plugins.updater` object.\n  - Moved `build.withGlobalTauri` to `app.withGlobalTauri`.\n  - Moved `tauri.bundle.dmg` object to `bundle.macOS.dmg`.\n  - Moved `tauri.bundle.deb` object to `bundle.linux.deb`.\n  - Moved `tauri.bundle.appimage` object to `bundle.linux.appimage`.\n  - Removed all license fields from each bundle configuration object and instead added `bundle.license` and `bundle.licenseFile`.\n  - Renamed `AppUrl` to `FrontendDist` and refactored its variants to be more explicit.\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) The `invoke_system`, `on_page_load` hooks now gives you a `Webview` argument instead of a `Window`.\n- [`e1eb911f`](https://www.github.com/tauri-apps/tauri/commit/e1eb911f5ebe84285aae710e0ebdd945ad389431)([#8582](https://www.github.com/tauri-apps/tauri/pull/8582)) All menu item constructors `accelerator` argument have been changed to `Option<impl AsRef<str>>` so when providing `None` you need to specify the generic argument like `None::<&str>`.\n- [`e1eb911f`](https://www.github.com/tauri-apps/tauri/commit/e1eb911f5ebe84285aae710e0ebdd945ad389431)([#8582](https://www.github.com/tauri-apps/tauri/pull/8582)) All menu item constructors have been changed to return a `Result<Self>`\n- [`aa758a85`](https://www.github.com/tauri-apps/tauri/commit/aa758a850f7a3c8e57520ee4ea63a17689469cb0)([#8716](https://www.github.com/tauri-apps/tauri/pull/8716)) Moved the `command` module items to the `ipc` module so its import name does not clash with the `command` macro.\n- [`00e15675`](https://www.github.com/tauri-apps/tauri/commit/00e1567584721644797b587205187f9cbe4e5cd1)([#8708](https://www.github.com/tauri-apps/tauri/pull/8708)) `AppHandle::exit` and `AppHandle::restart` now go triggers `RunEvent::ExitRequested` and `RunEvent::Exit` and cannot be executed on the event loop handler.\n- [`ec9818ac`](https://www.github.com/tauri-apps/tauri/commit/ec9818accba567816cb5647c0fac2019f11c027a)([#8696](https://www.github.com/tauri-apps/tauri/pull/8696)) Added a callback to the `App::run_iteration` and removed its return value.\n- [`a093682d`](https://www.github.com/tauri-apps/tauri/commit/a093682d2df7169b024bb4f736c7f1fd2ea8b327)([#8621](https://www.github.com/tauri-apps/tauri/pull/8621)) Refactored the event system to better accommodate the new window types:\n\n  - Added `EventTarget` enum.\n  - Added `App/AppHandle::listen`, `App/AppHandle::once` and `App/AppHandle::unlisten` to listen to events targeting `App/AppHandle`\n  - `App/AppHandle/Window/Webview/WebviewWindow::emit` will now emit to all event listeners.\n  - `App/AppHandle/Window/Webview/WebviewWindow::emit_to` will emit to event targets that match the given label, see `EventTarget` enum.\n  - `App/AppHandle/Window/Webview/WebviewWindow::emit_filter` will emit to event targets based on a filter callback which now takes `&EventTarget` instead of `&Window`.\n  - Renamed `Manager::listen_global` and `Manager::once_global` to `listen_any` and `once_any` respectively to be consistent with `EventTarget::Any`, it will now also listen to any event to any target (aka event sniffer).\n- [`9eaeb5a8`](https://www.github.com/tauri-apps/tauri/commit/9eaeb5a8cd95ae24b5e66205bdc2763cb7f965ce)([#8622](https://www.github.com/tauri-apps/tauri/pull/8622)) Renamed `WindowBuilder::owner_window` to `WindowBuilder::owner_raw` and `WindowBuilder::parent_window` to `WindowBuilder::parent_raw`.\n- [`9eaeb5a8`](https://www.github.com/tauri-apps/tauri/commit/9eaeb5a8cd95ae24b5e66205bdc2763cb7f965ce)([#8622](https://www.github.com/tauri-apps/tauri/pull/8622)) Changed `WindowBuilder::from_config` to return a `Result<Self>`.\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Renamed `Window` to `WebviewWindow`, `WindowBuilder` to `WebviewWindowBuilder`, `Manager::windows` to `Manager::webview_windows` and `Manager::get_window` to `Manager::get_webview_window`.\n- [`af610232`](https://www.github.com/tauri-apps/tauri/commit/af6102327376884364b2075b468bdf08ee0d02aa)([#8710](https://www.github.com/tauri-apps/tauri/pull/8710)) `Window::close` now triggers a close requested event instead of forcing the window to be closed.\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Renamed the `window-data-url` feature flag to `webview-data-url`.\n\n## \\[2.0.0-alpha.21]\n\n### New Features\n\n- [`29ced5ce`](https://www.github.com/tauri-apps/tauri/commit/29ced5ceec40b2934094ade2db9a8855f294e1d1)([#8159](https://www.github.com/tauri-apps/tauri/pull/8159)) Added `WindowBuilder::on_download` to handle download request events.\n\n### Enhancements\n\n- [`d621d343`](https://www.github.com/tauri-apps/tauri/commit/d621d3437ce3947175eecf345b2c6d1c4c7ce020)([#8607](https://www.github.com/tauri-apps/tauri/pull/8607)) Added tracing for window startup, plugins, `Window::eval`, events, IPC, updater and custom protocol request handlers behind the `tracing` feature flag.\n\n### What's Changed\n\n- [`cb640c8e`](https://www.github.com/tauri-apps/tauri/commit/cb640c8e949a3d78d78162e2e61b51bf8afae983)([#8393](https://www.github.com/tauri-apps/tauri/pull/8393)) Fix `RunEvent::WindowEvent(event: WindowEvent::FileDrop(FileDropEvent))` never triggered and always prevent default OS behavior when `disable_file_drop_handler` is not used.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@1.0.0-alpha.9`\n- Upgraded to `tauri-utils@2.0.0-alpha.13`\n- Upgraded to `tauri-runtime@1.0.0-alpha.8`\n- Upgraded to `tauri-macros@2.0.0-alpha.13`\n- Upgraded to `tauri-build@2.0.0-alpha.14`\n\n### Breaking Changes\n\n- [`2032228c`](https://www.github.com/tauri-apps/tauri/commit/2032228cad0de6500616ca765af5c9ff1f231f0f)([#8430](https://www.github.com/tauri-apps/tauri/pull/8430)) Removed `GlobalWindowEvent` struct, and unpacked its field to be passed directly to `tauri::Builder::on_window_event`.\n\n## \\[2.0.0-alpha.20]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.12`\n- Upgraded to `tauri-runtime@1.0.0-alpha.7`\n- Upgraded to `tauri-runtime-wry@1.0.0-alpha.8`\n- Upgraded to `tauri-macros@2.0.0-alpha.12`\n- Upgraded to `tauri-build@2.0.0-alpha.13`\n\n## \\[2.0.0-alpha.19]\n\n### New Features\n\n- [`b59f2f54`](https://www.github.com/tauri-apps/tauri/commit/b59f2f54e7c1e01e33baf710d50c046401edb9d8)([#8432](https://www.github.com/tauri-apps/tauri/pull/8432)) Expose `scope::fs::Scope::new`.\n- [`bf095df5`](https://www.github.com/tauri-apps/tauri/commit/bf095df55aa27fb22c9240ddf8d673cfe0a4a2db)([#8276](https://www.github.com/tauri-apps/tauri/pull/8276)) Exposed `Manager::resources_table` to access the resources table used by tauri, which could be used by plugins or app authors to store their resources and retrieve it later using an id and can be used to create Rust-backed resources in JS.\n\n### Enhancements\n\n- [`5848b4e8`](https://www.github.com/tauri-apps/tauri/commit/5848b4e8e9fde1e0d895238cafdb57ed20be4c07)([#8386](https://www.github.com/tauri-apps/tauri/pull/8386)) Fixed the deserialisation of a `Channel` in iOS.\n- [`11a1529d`](https://www.github.com/tauri-apps/tauri/commit/11a1529d6ace47e731f5d69bf421e7f59f0d7567)([#8419](https://www.github.com/tauri-apps/tauri/pull/8419)) Include CORS header on custom protocol response errors to ensure frontend can read the error message.\n- [`db127777`](https://www.github.com/tauri-apps/tauri/commit/db127777423e467758781d58c1121cbe94844161)([#8380](https://www.github.com/tauri-apps/tauri/pull/8380)) Added `test::get_ipc_response`.\n\n### Bug Fixes\n\n- [`effe5871`](https://www.github.com/tauri-apps/tauri/commit/effe5871aff1267a73ecbba1693304941a691932)([#8420](https://www.github.com/tauri-apps/tauri/pull/8420)) Fixes file scope checks on Android.\n- [`f98ce5aa`](https://www.github.com/tauri-apps/tauri/commit/f98ce5aa475d1b3f8606e773579e77f41309feb0)([#8328](https://www.github.com/tauri-apps/tauri/pull/8328)) Fix incorrect menu item for `PredefinedMenuItem::close_window`\n\n## \\[2.0.0-alpha.18]\n\n### Bug Fixes\n\n- [`b5f40ae5`](https://www.github.com/tauri-apps/tauri/commit/b5f40ae58dded34b9ddef8e301d0e6e777d135d6)([#8147](https://www.github.com/tauri-apps/tauri/pull/8147)) Fixes global events not reaching to window listeners.\n\n### Dependencies\n\n- Upgraded to `tauri-macros@2.0.0-alpha.11`\n- Upgraded to `tauri-build@2.0.0-alpha.12`\n- Upgraded to `tauri-utils@2.0.0-alpha.11`\n- Upgraded to `tauri-runtime@1.0.0-alpha.5`\n- Upgraded to `tauri-runtime-wry@1.0.0-alpha.6`\n\n## \\[2.0.0-alpha.17]\n\n### Enhancements\n\n- [`b89de9fa`](https://www.github.com/tauri-apps/tauri/commit/b89de9fa43b793c74a42230c7a82c11c3734278e)([#8092](https://www.github.com/tauri-apps/tauri/pull/8092)) Add support for onResume and onPause events in android plugins.\n- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release.\n- [`198abe3c`](https://www.github.com/tauri-apps/tauri/commit/198abe3c2cae06dacab860b3a93f715dcf529a95)([#8076](https://www.github.com/tauri-apps/tauri/pull/8076)) Mobile plugins can now resolve using an arbitrary object instead of using the `JSObject` class via `Invoke.resolve` on iOS and `Invoke.resolveObject` on Android.\n\n### Bug Fixes\n\n- [`22f26882`](https://www.github.com/tauri-apps/tauri/commit/22f26882cfe0adbfe4c51586a1c9fdcf8e9cfb68)([#8049](https://www.github.com/tauri-apps/tauri/pull/8049)) Prevent crash on iOS when the Swift plugin data is not a valid JSON string.\n\n### Dependencies\n\n- Upgraded to `tauri-build@2.0.0-alpha.11`\n- Upgraded to `tauri-macros@2.0.0-alpha.10`\n- Upgraded to `tauri-utils@2.0.0-alpha.10`\n- Upgraded to `tauri-runtime@1.0.0-alpha.4`\n- Upgraded to `tauri-runtime-wry@1.0.0-alpha.5`\n- [`9580df1d`](https://www.github.com/tauri-apps/tauri/commit/9580df1d7b027befb9e5f025ea2cbaf2dcc82c8e)([#8084](https://www.github.com/tauri-apps/tauri/pull/8084)) Upgrade `gtk` to 0.18.\n- [`c7c2507d`](https://www.github.com/tauri-apps/tauri/commit/c7c2507da16a9beb71bf06745fe7ac1325ab7c2a)([#8035](https://www.github.com/tauri-apps/tauri/pull/8035)) Update `windows` to version `0.51` and `webview2-com` to version `0.27`\n- [`9580df1d`](https://www.github.com/tauri-apps/tauri/commit/9580df1d7b027befb9e5f025ea2cbaf2dcc82c8e)([#8084](https://www.github.com/tauri-apps/tauri/pull/8084)) Updated to wry@0.34, removing the `dox` feature flag.\n\n### Breaking Changes\n\n- [`198abe3c`](https://www.github.com/tauri-apps/tauri/commit/198abe3c2cae06dacab860b3a93f715dcf529a95)([#8076](https://www.github.com/tauri-apps/tauri/pull/8076)) The Android `PluginManager.loadConfig` now takes a third parameter to define the class type of the config object.\n- [`198abe3c`](https://www.github.com/tauri-apps/tauri/commit/198abe3c2cae06dacab860b3a93f715dcf529a95)([#8076](https://www.github.com/tauri-apps/tauri/pull/8076)) Mobile plugins now have access to a parser for the invoke arguments instead of relying on the `Invoke#get${TYPE}` methods.\n- [`74d2464d`](https://www.github.com/tauri-apps/tauri/commit/74d2464d0e490fae341ad73bdf2964cf215fe6c5)([#8116](https://www.github.com/tauri-apps/tauri/pull/8116)) Added `WindowBuilder::on_page_load` and refactored the `Builder::on_page_load` handler to take references.\n  The page load hook is now triggered for load started and finished events, to determine what triggered it see `PageLoadPayload::event`.\n- [`93c8a77b`](https://www.github.com/tauri-apps/tauri/commit/93c8a77b347b9934ec0732784d4b78b3260abc08)([#7996](https://www.github.com/tauri-apps/tauri/pull/7996)) The event system APIS on Rust is recieving a few changes for consistency and quality of life improvements:\n\n  - Renamed `Manager::emit_all` to just `Manager::emit` and will now both trigger the events on JS side as well as Rust.\n  - Removed `Manager::trigger_global`, use `Manager::emit`\n  - Added `Manager::emit_filter`.\n  - Removed `Window::emit`, and moved the implementation to `Manager::emit`.\n  - Removed `Window::emit_and_trigger` and `Window::trigger`, use `Window::emit` instead.\n  - Changed `Window::emit_to` to only trigger the target window listeners so it won't be catched by `Manager::listen_global`\n\n## \\[2.0.0-alpha.16]\n\n### New Features\n\n- [`c085adda`](https://www.github.com/tauri-apps/tauri/commit/c085addab58ba851398373c6fd13f9cb026d71e8)([#8009](https://www.github.com/tauri-apps/tauri/pull/8009)) Added `set_progress_bar` to `Window`.\n- [`c1ec0f15`](https://www.github.com/tauri-apps/tauri/commit/c1ec0f155118527361dd5645d920becbc8afd569)([#7933](https://www.github.com/tauri-apps/tauri/pull/7933)) Added `Window::set_always_on_bottom` and the `always_on_bottom` option when creating a window.\n- [`880266a7`](https://www.github.com/tauri-apps/tauri/commit/880266a7f697e1fe58d685de3bb6836ce5251e92)([#8031](https://www.github.com/tauri-apps/tauri/pull/8031)) Bump the MSRV to 1.70.\n- [`ed32257d`](https://www.github.com/tauri-apps/tauri/commit/ed32257d044f90b5eb15053efd1667125def2d2b)([#7794](https://www.github.com/tauri-apps/tauri/pull/7794)) On Windows, add `Effect::Tabbed`,`Effect::TabbedDark` and `Effect::TabbedLight` effects.\n\n### Enhancements\n\n- [`46dcb941`](https://www.github.com/tauri-apps/tauri/commit/46dcb94110ac16d0d4328fa149bb86975b658f59)([#8006](https://www.github.com/tauri-apps/tauri/pull/8006)) Include mobile on docs.rs targets.\n\n### What's Changed\n\n- [`fb10b879`](https://www.github.com/tauri-apps/tauri/commit/fb10b87970a43320ef4d14564f45e7579b774eaf)([#8039](https://www.github.com/tauri-apps/tauri/pull/8039)) Added the `app` plugin back into core.\n- [`c9a9246c`](https://www.github.com/tauri-apps/tauri/commit/c9a9246c37bdf190661355c8ee406dac6c427344)([#8007](https://www.github.com/tauri-apps/tauri/pull/8007)) Added the `window` plugin back into core.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@1.0.0-alpha.3`\n- Upgraded to `tauri-runtime-wry@1.0.0-alpha.4`\n- Upgraded to `tauri-utils@2.0.0-alpha.9`\n- Upgraded to `tauri-build@2.0.0-alpha.10`\n- Upgraded to `tauri-macros@2.0.0-alpha.9`\n\n### Breaking Changes\n\n- [`a63e71f9`](https://www.github.com/tauri-apps/tauri/commit/a63e71f9799e9bbc82521d2f17b5238fbf690e89)([#7942](https://www.github.com/tauri-apps/tauri/pull/7942)) The initialization script for `Builder::invoke_system` now must initialize the `window.__TAURI_INTERNALS__.postMessage` function instead of `window.__TAURI_POST_MESSAGE__`.\n- [`12b8d18b`](https://www.github.com/tauri-apps/tauri/commit/12b8d18bf7ff833047cb87f356fd007d47d082c9)([#7875](https://www.github.com/tauri-apps/tauri/pull/7875)) -   Removed `tauri::path::Error` and added its variants to `tauri::Error`\n  - Removed `tauri::path::Result` and `tauri::plugin::Result` aliases, you should use `tauri::Result` or your own `Result` type.\n- [`8b166e9b`](https://www.github.com/tauri-apps/tauri/commit/8b166e9bf82e69ddb3200a3a825614980bd8d433)([#7949](https://www.github.com/tauri-apps/tauri/pull/7949)) Changed `TAURI_AUTOMATION` to `TAURI_WEBVIEW_AUTOMATION`\n- [`2558fab8`](https://www.github.com/tauri-apps/tauri/commit/2558fab861006936296e8511e43ccd69a38f61b0)([#7939](https://www.github.com/tauri-apps/tauri/pull/7939)) This release contains a number of breaking changes to improve the consistency of tauri internals and the public facing APIs\n  and simplifying the types where applicable:\n\n  - Removed `EventHandler` type.\n  - Added `EventId` type\n  - Changed `Manager::listen_global` and `Window::listen` to return the new `EventId` type instead of `EventHandler`.\n  - Removed the return type of `Manager::once_global` and `Window::once`\n  - Changed `Manager::unlisten` and `Window::unlisten` to take he new `EventId` type.\n  - Added `tauri::scope::ScopeEventId`\n  - Changed `FsScope::listen` to return the new `ScopeEventId` instead of `Uuid`.\n  - Added `FsScope::unlisten`\n\n## \\[2.0.0-alpha.15]\n\n### Enhancements\n\n- [`b597aa5f`](https://www.github.com/tauri-apps/tauri/commit/b597aa5f3974f5ca5ca5159d441abc9ed3e80721)([#7871](https://www.github.com/tauri-apps/tauri/pull/7871)) Set `main` as the default `id` for the tray icon registered from the configuration file, so if the `id` is not specified, it can be retrieved using `app.tray_by_id(\"main\")`.\n\n### Bug Fixes\n\n- [`a2021c30`](https://www.github.com/tauri-apps/tauri/commit/a2021c30ba1139fcfe4db2522b96125f3fa4d9d6)([#7866](https://www.github.com/tauri-apps/tauri/pull/7866)) Changed `IconMenuItem::set_native_icon` signature to take `&self` instead of `&mut self` to fix compilation error on macos.\n- [`a68ccaf5`](https://www.github.com/tauri-apps/tauri/commit/a68ccaf59a6731dc030bdb4642a35e3bc64d5769)([#7822](https://www.github.com/tauri-apps/tauri/pull/7822)) Fix `asset` protocol failing to fetch files.\n- [`6fbd6dba`](https://www.github.com/tauri-apps/tauri/commit/6fbd6dba5290dc017ab0ba5a44cf4358b022836f)([#17](https://www.github.com/tauri-apps/tauri/pull/17)) Fix the validation of `std::env::current_exe` warn the user if AppImage is not mounted instead of panicking\n\n### Dependencies\n\n- Upgraded to `tauri-macros@2.0.0-alpha.8`\n- Upgraded to `tauri-utils@2.0.0-alpha.8`\n- Upgraded to `tauri-build@2.0.0-alpha.9`\n- Upgraded to `tauri-runtime@1.0.0-alpha.2`\n- Upgraded to `tauri-runtime-wry@1.0.0-alpha.3`\n\n### Breaking Changes\n\n- [`092a561c`](https://www.github.com/tauri-apps/tauri/commit/092a561ca69a631d2a03777e29debeba37b197a7)([#7874](https://www.github.com/tauri-apps/tauri/pull/7874)) Removed `tauri::api` module as most apis have been moved to either a plugin or we recommend using other crates.\n- [`deea9436`](https://www.github.com/tauri-apps/tauri/commit/deea9436261f651188e0bc86104779bf30029c32)([#7876](https://www.github.com/tauri-apps/tauri/pull/7876)) Changed `Env.args` to `Env.args_os` and now uses `OsString` instead of `String`\n- [`b7fd88e1`](https://www.github.com/tauri-apps/tauri/commit/b7fd88e18d24e4450129a5a5007f2e740c69afe5)([#7944](https://www.github.com/tauri-apps/tauri/pull/7944)) `tauri::scope` module is recieving a couple of consistency changes:\n\n  - Added `tauri::scope::fs` module.\n  - Removed `scope::IpcScope` re-export, use `scope::ipc::Scope`.\n  - Removed `FsScope`, `GlobPattern` and `FsScopeEvent`, use `scope::fs::Scope`, `scope::fs::Pattern` and `scope::fs::Event` respectively.\n- [`c0d03af4`](https://www.github.com/tauri-apps/tauri/commit/c0d03af4704c828698e06e9662dab1087c99c42e)([#7943](https://www.github.com/tauri-apps/tauri/pull/7943)) Changed `TrayIconBuilder/TrayIcon::on_tray_event` to `TrayIconBuilder/TrayIcon::on_tray_icon_event` for consistency of naming.\n\n## \\[2.0.0-alpha.14]\n\n### Bug Fixes\n\n- [`d5074af5`](https://www.github.com/tauri-apps/tauri/commit/d5074af562b2b5cb6c5711442097c4058af32db6)([#7801](https://www.github.com/tauri-apps/tauri/pull/7801)) Fixes custom protocol not working on Windows.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@1.0.0-alpha.2`\n\n### Breaking Changes\n\n- [`d5074af5`](https://www.github.com/tauri-apps/tauri/commit/d5074af562b2b5cb6c5711442097c4058af32db6)([#7801](https://www.github.com/tauri-apps/tauri/pull/7801)) The custom protocol on Android now uses the `http` scheme instead of `https`.\n\n## \\[2.0.0-alpha.13]\n\n### Breaking Changes\n\n- [`4cb51a2d`](https://www.github.com/tauri-apps/tauri/commit/4cb51a2d56cfcae0749062c79ede5236bd8c02c2)([#7779](https://www.github.com/tauri-apps/tauri/pull/7779)) The custom protocol on Windows now uses the `http` scheme instead of `https`.\n\n## \\[2.0.0-alpha.12]\n\n### Enhancements\n\n- [`8a676617`](https://www.github.com/tauri-apps/tauri/commit/8a6766173b3da4446a87642e7282c73a5b631afe)([#7618](https://www.github.com/tauri-apps/tauri/pull/7618)) Ensure Builder is Send by requiring the menu closure to be Send.\n- [`0d63732b`](https://www.github.com/tauri-apps/tauri/commit/0d63732b962e71b98430f8d7b34ea5b59a2e8bb4)([#7754](https://www.github.com/tauri-apps/tauri/pull/7754)) Added `Builder::register_asynchronous_uri_scheme_protocol` to allow resolving a custom URI scheme protocol request asynchronously to prevent blocking the main thread.\n\n### Bug Fixes\n\n- [`0d63732b`](https://www.github.com/tauri-apps/tauri/commit/0d63732b962e71b98430f8d7b34ea5b59a2e8bb4)([#7754](https://www.github.com/tauri-apps/tauri/pull/7754)) Fixes invalid header value type when requesting IPC body through a channel.\n- [`e98393e4`](https://www.github.com/tauri-apps/tauri/commit/e98393e499c03504851e97dd9f740b817c4534df)([#7673](https://www.github.com/tauri-apps/tauri/pull/7673)) No longer unpacking and flattening the `payload` over the IPC so that commands with arguments called `cmd`, `callback`, `error`, `options` or `payload` aren't breaking the IPC.\n- [`29818de6`](https://www.github.com/tauri-apps/tauri/commit/29818de682146a75cd9a886f7b215154cad1d42d)([#7662](https://www.github.com/tauri-apps/tauri/pull/7662)) Fixes IPC failing to communicate for remote URLs on macOS and iOS.\n\n### What's Changed\n\n- [`6177150b`](https://www.github.com/tauri-apps/tauri/commit/6177150b6f83b52ca359d6e20f7e540f7554e4eb)([#7601](https://www.github.com/tauri-apps/tauri/pull/7601)) Changed `FileDropEvent` to include drop and hover position.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@1.0.0-alpha.1`\n- Upgraded to `tauri-runtime-wry@1.0.0-alpha.1`\n\n### Breaking Changes\n\n- [`0d63732b`](https://www.github.com/tauri-apps/tauri/commit/0d63732b962e71b98430f8d7b34ea5b59a2e8bb4)([#7754](https://www.github.com/tauri-apps/tauri/pull/7754)) Changed `Builder::register_uri_scheme_protocol` to return a `http::Response` instead of `Result<http::Response>`. To return an error response, manually create a response with status code >= 400.\n- [`0d63732b`](https://www.github.com/tauri-apps/tauri/commit/0d63732b962e71b98430f8d7b34ea5b59a2e8bb4)([#7754](https://www.github.com/tauri-apps/tauri/pull/7754)) `tauri-runtime` no longer implements its own HTTP types and relies on the `http` crate instead.\n- [`0d63732b`](https://www.github.com/tauri-apps/tauri/commit/0d63732b962e71b98430f8d7b34ea5b59a2e8bb4)([#7754](https://www.github.com/tauri-apps/tauri/pull/7754)) Changed `Builder::invoke_system` to take references instead of owned values.\n- [`5c95152c`](https://www.github.com/tauri-apps/tauri/commit/5c95152c76391607746f6da942ec57d23c89e89e)([#7621](https://www.github.com/tauri-apps/tauri/pull/7621)) Changed `MenuBuilder\\SubmenuBuilder::text`, `MenuBuilder\\SubmenuBuilder::check`, `MenuBuilder\\SubmenuBuilder::icon` and `MenuBuilder\\SubmenuBuilder::native_icon` to take an `id` as the first argument.\n- [`0d63732b`](https://www.github.com/tauri-apps/tauri/commit/0d63732b962e71b98430f8d7b34ea5b59a2e8bb4)([#7754](https://www.github.com/tauri-apps/tauri/pull/7754)) Changed `Window::on_message` signature to take a responder closure instead of returning the response object in order to asynchronously process the request.\n\n## \\[2.0.0-alpha.11]\n\n### New Features\n\n- [`4db363a0`](https://www.github.com/tauri-apps/tauri/commit/4db363a03c182349f8491f46ced258d84723b11f)([#6589](https://www.github.com/tauri-apps/tauri/pull/6589)) Added `visible_on_all_workspaces` configuration option to `WindowBuilder`, `Window`, and `WindowConfig`.\n- [`84c41597`](https://www.github.com/tauri-apps/tauri/commit/84c4159754b2e59244211ed9e1fc702d851a0562)([#6394](https://www.github.com/tauri-apps/tauri/pull/6394)) Add `App::primary_monitor`, `App::available_monitors`, `AppHandle::primary_monitor`, and `AppHandle::available_monitors`\n- [`2a000e15`](https://www.github.com/tauri-apps/tauri/commit/2a000e150d02dff28c8b20ad097b29e209160045)([#7235](https://www.github.com/tauri-apps/tauri/pull/7235)) Added `Window::navigate`.\n- [`3b98141a`](https://www.github.com/tauri-apps/tauri/commit/3b98141aa26f74c641a4090874247b97079bd58a)([#3736](https://www.github.com/tauri-apps/tauri/pull/3736)) Added support to file associations.\n- [`3a2c3e74`](https://www.github.com/tauri-apps/tauri/commit/3a2c3e74710bef9a14932dce74c351cca6215429)([#7306](https://www.github.com/tauri-apps/tauri/pull/7306)) Added `PluginBuilder::on_navigation`.\n  Added `Plugin::on_navigation`.\n- [`753900dd`](https://www.github.com/tauri-apps/tauri/commit/753900dd6e549aaf56f419144382669e3b246404)([#7440](https://www.github.com/tauri-apps/tauri/pull/7440)) Expose `RunEvent::Opened` on macOS and iOS for deep link support.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) Add `App::cleanup_before_exit` and `AppHandle::cleanup_before_exit` to manually call the cleanup logic. **You should always exit the tauri app immediately after this function returns and not use any tauri-related APIs.**\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) On Linux, add `Window::default_vbox` to get a reference to the `gtk::Box` that contains the menu bar and the webview.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) Add `linux-libxdo` feature flag (disabled by default) to enable linking to `libxdo` which is used to make `Cut`, `Copy`, `Paste` and `SelectAll` native menu items work on Linux.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) On macOS, add `Window::ns_view` to get a pointer to the NSWindow content view.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) Expose `run_on_main_thread` method on `App` that is similar to `AppHandle::run_on_main_thread`.\n\n### Enhancements\n\n- [`a5752db9`](https://www.github.com/tauri-apps/tauri/commit/a5752db9852bb852e61f19dfb48a9435c1fdc79c)([#7436](https://www.github.com/tauri-apps/tauri/pull/7436)) Listen to `onNewIntent` and forward it to registered plugins.\n- [`fbeb5b91`](https://www.github.com/tauri-apps/tauri/commit/fbeb5b9185baeda19e865228179e3e44c165f1d9)([#7170](https://www.github.com/tauri-apps/tauri/pull/7170)) Added `Channel::new` allowing communication from a mobile plugin with Rust.\n- [`fbeb5b91`](https://www.github.com/tauri-apps/tauri/commit/fbeb5b9185baeda19e865228179e3e44c165f1d9)([#7170](https://www.github.com/tauri-apps/tauri/pull/7170)) Use custom protocols on the IPC implementation to enhance performance.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@1.0.0-alpha.0`\n- Upgraded to `tauri-utils@2.0.0-alpha.7`\n- Upgraded to `tauri-macros@2.0.0-alpha.7`\n- Upgraded to `tauri-runtime-wry@1.0.0-alpha.0`\n- Upgraded to `tauri-build@2.0.0-alpha.7`\n- [`d1a6e2f3`](https://www.github.com/tauri-apps/tauri/commit/d1a6e2f33326161a78a9a72bd9320dcb1b1f9710)([#7252](https://www.github.com/tauri-apps/tauri/pull/7252)) Update `state` to v0.6.\n\n### Breaking Changes\n\n- [`fd5dc788`](https://www.github.com/tauri-apps/tauri/commit/fd5dc788d10b2a048e0804b5415b84ae8f9152ea)([#7352](https://www.github.com/tauri-apps/tauri/pull/7352)) -   Removed `tauri::api::file` and `tauri::api::dir` modules, use `std::fs` instead.\n  - Removed `tauri::api::version` module, use `semver` crate instead.\n- [`fbeb5b91`](https://www.github.com/tauri-apps/tauri/commit/fbeb5b9185baeda19e865228179e3e44c165f1d9)([#7170](https://www.github.com/tauri-apps/tauri/pull/7170)) Moved `tauri::api::ipc` to `tauri::ipc` and refactored all types.\n- [`fbeb5b91`](https://www.github.com/tauri-apps/tauri/commit/fbeb5b9185baeda19e865228179e3e44c165f1d9)([#7170](https://www.github.com/tauri-apps/tauri/pull/7170)) Removed the `linux-protocol-headers` feature (now always enabled) and added `linux-ipc-protocol`.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) Changed `App::handle` and `Manager::app_handle` to return a reference to an `AppHandle` instead of an owned value.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) The tray icon and menu have received a huge refactor with a lot of breaking changes in order to add new functionalities and improve the DX around using them and here is an overview of the changes:\n\n  - All menu and tray types are now exported from `tauri::menu` and `tauri::tray` modules with new names so make sure to check the new types.\n  - Removed `tauri::Builder::system_tray`, instead you should use `tauri::tray::TrayIconBuilder` inside `tauri::Builder::setup` hook to create your tray icons.\n  - Changed `tauri::Builder::menu` to be a function to accommodate for new menu changes, you can passe `tauri::menu::Menu::default` to it to create a default menu.\n  - Renamed `tauri::Context` methods `system_tray_icon`, `tauri::Context::system_tray_icon_mut` and `tauri::Context::set_system_tray_icon` to `tauri::Context::tray_icon`, `tauri::Context::tray_icon_mut` and `tauri::Context::set_tray_icon` to be consistent with new type names.\n  - Added `RunEvent::MenuEvent` and `RunEvent::TrayIconEvent`.\n  - Added `App/AppHandle::set_menu`, `App/AppHandle::remove_menu`, `App/AppHandle::show_menu`, `App/AppHandle::hide_menu` and `App/AppHandle::menu` to access, remove, hide or show the app-wide menu that is used as the global menu on macOS and on all windows that don't have a specific menu set for it on Windows and Linux.\n  - Added `Window::set_menu`, `Window::remove_menu`, `Window::show_menu`, `Window::hide_menu`, `Window::is_menu_visible` and `Window::menu` to access, remove, hide or show the menu on this window.\n  - Added `Window::popup_menu` and `Window::popup_menu_at` to show a context menu on the window at the cursor position or at a specific position. You can also popup a context menu using `popup` and `popup_at` methods from `ContextMenu` trait which is implemented for `Menu` and `Submenu` types.\n  - Added `App/AppHandle::tray`, `App/AppHandle::tray_by_id`, `App/AppHandle::remove_tray` and `App/AppHandle::remove_tray_by_id` to access or remove a registered tray.\n  - Added `WindowBuilder/App/AppHandle::on_menu_event` to register a new menu event handler.\n  - Added `App/AppHandle::on_tray_icon_event` to register a new tray event handler.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) Renamed `system-tray` feature flag to `tray-icon`.\n- [`3a2c3e74`](https://www.github.com/tauri-apps/tauri/commit/3a2c3e74710bef9a14932dce74c351cca6215429)([#7306](https://www.github.com/tauri-apps/tauri/pull/7306)) The `Window#on_navigation` closure now receives a `&Url` argument instead of `Url`.\n\n## \\[2.0.0-alpha.10]\n\n### New Features\n\n- [`f2d68cf7`](https://www.github.com/tauri-apps/tauri/commit/f2d68cf7d4e53443b2d53d2ae841e56c16a92514)([#6767](https://www.github.com/tauri-apps/tauri/pull/6767)) Add `incognito` option to the window configuration object.\n- [`f2d68cf7`](https://www.github.com/tauri-apps/tauri/commit/f2d68cf7d4e53443b2d53d2ae841e56c16a92514)([#6767](https://www.github.com/tauri-apps/tauri/pull/6767)) Add `WindowBuilder::incognito`\n- [`e0f0dce2`](https://www.github.com/tauri-apps/tauri/commit/e0f0dce220730e2822fc202463aedf0166145de7)([#6442](https://www.github.com/tauri-apps/tauri/pull/6442)) Added the `window_effects` option when creating a window and `Window::set_effects` to change it at runtime.\n\n### Enhancements\n\n- [`2d2fd6ab`](https://www.github.com/tauri-apps/tauri/commit/2d2fd6abe291ddf645fa2fdecc08111d2c0e258e)([#7191](https://www.github.com/tauri-apps/tauri/pull/7191)) Use correct HTTP method when making requests to the proxied server on mobile.\n- [`b66e7d60`](https://www.github.com/tauri-apps/tauri/commit/b66e7d60f27d9a7973eae48d54cb72e30a710cca)([#7174](https://www.github.com/tauri-apps/tauri/pull/7174)) Implement `Clone` for `Channel`\n- [`8124145d`](https://www.github.com/tauri-apps/tauri/commit/8124145d6c6a629809c138d2c34082e1feb4fdbf)([#7171](https://www.github.com/tauri-apps/tauri/pull/7171)) Fixes path commands not being added.\n- [`4652c446`](https://www.github.com/tauri-apps/tauri/commit/4652c446b361a801252bcf45e9da39813bf85482)([#7144](https://www.github.com/tauri-apps/tauri/pull/7144)) Add `temp_dir` method to `PathResolver`\n\n### Bug Fixes\n\n- [`8e855765`](https://www.github.com/tauri-apps/tauri/commit/8e85576506f5dea066d7e9317dbcab3681baff73)([#6809](https://www.github.com/tauri-apps/tauri/pull/6809)) Fix default log path for linux and windows\n\n## \\[2.0.0-alpha.9]\n\n- [`256c30c7`](https://www.github.com/tauri-apps/tauri/commit/256c30c72b737e49ced0d6a6483910dc779fc185)([#6863](https://www.github.com/tauri-apps/tauri/pull/6863)) Enhance parsing of annotated Android plugin methods to support private functions.\n- [`73c803a5`](https://www.github.com/tauri-apps/tauri/commit/73c803a561181137f20366f5d52511392a619f2b)([#6837](https://www.github.com/tauri-apps/tauri/pull/6837)) Added static function `loadConfig` on the Android `PluginManager` class.\n- [`edb16d13`](https://www.github.com/tauri-apps/tauri/commit/edb16d13a503da4b264ce459319fec25374c5c4f)([#6831](https://www.github.com/tauri-apps/tauri/pull/6831)) Adjust Android plugin exception error.\n- [`0ab5f40d`](https://www.github.com/tauri-apps/tauri/commit/0ab5f40d3a4207f20e4440587b41c4e78f91d233)([#6813](https://www.github.com/tauri-apps/tauri/pull/6813)) Add channel API for sending data across the IPC.\n- [`31444ac1`](https://www.github.com/tauri-apps/tauri/commit/31444ac196add770f2ad18012d7c18bce7538f22)([#6725](https://www.github.com/tauri-apps/tauri/pull/6725)) On Android, update proguard rules.\n- [`8ce32e74`](https://www.github.com/tauri-apps/tauri/commit/8ce32e74b5573931c3bc81e8e893a6d3b9686b0e)([#6986](https://www.github.com/tauri-apps/tauri/pull/6986)) Add `default_window_icon` getter on `App` and `AppHandle`.\n- [`2a5175a8`](https://www.github.com/tauri-apps/tauri/commit/2a5175a8f8f318aac9a6434271f2cc065e5989ae)([#6779](https://www.github.com/tauri-apps/tauri/pull/6779)) Enhance Android's `JSObject` return types.\n- [`bb2a8ccf`](https://www.github.com/tauri-apps/tauri/commit/bb2a8ccf1356e59b98947d827d61e4e99533f2bc)([#6830](https://www.github.com/tauri-apps/tauri/pull/6830)) Use actual iOS plugin instance to run command with `throws`.\n- [`94224906`](https://www.github.com/tauri-apps/tauri/commit/942249060ed12a5d21a2b21c30e0638c1d2b9df0)([#6783](https://www.github.com/tauri-apps/tauri/pull/6783)) Generate `TauriActivity` Kotlin class on the build script.\n- [`7a4b1fb9`](https://www.github.com/tauri-apps/tauri/commit/7a4b1fb96da475053c61960f362bbecf18cd00d4)([#6839](https://www.github.com/tauri-apps/tauri/pull/6839)) Added support to attibutes for each command path in the `generate_handler` macro.\n- [`9a79dc08`](https://www.github.com/tauri-apps/tauri/commit/9a79dc085870e0c1a5df13481ff271b8c6cc3b78)([#6947](https://www.github.com/tauri-apps/tauri/pull/6947)) Remove `enable_tauri_api` from the IPC scope.\n- [`dfa407ff`](https://www.github.com/tauri-apps/tauri/commit/dfa407ffcbc8a853d61139b68b55747ae49fb231)([#6763](https://www.github.com/tauri-apps/tauri/pull/6763)) Expose plugin configuration on the Android and iOS plugin classes.\n- [`3245d14b`](https://www.github.com/tauri-apps/tauri/commit/3245d14b9eb256a5c5675c7030bac7082855df47)([#6895](https://www.github.com/tauri-apps/tauri/pull/6895)) Moved the `app` feature to its own plugin in the plugins-workspace repository.\n- [`09376af5`](https://www.github.com/tauri-apps/tauri/commit/09376af59424cc27803fa2820d2ac0d4cdc90a6d)([#6704](https://www.github.com/tauri-apps/tauri/pull/6704)) Moved the `cli` feature to its own plugin in the plugins-workspace repository.\n- [`2d5378bf`](https://www.github.com/tauri-apps/tauri/commit/2d5378bfc1ba817ee2f331b41738a90e5997e5e8)([#6717](https://www.github.com/tauri-apps/tauri/pull/6717)) Moved the dialog APIs to its own plugin in the plugins-workspace repository.\n- [`39f1b04f`](https://www.github.com/tauri-apps/tauri/commit/39f1b04f7be4966488484829cd54c8ce72a04200)([#6943](https://www.github.com/tauri-apps/tauri/pull/6943)) Moved the `event` JS APIs to a plugin.\n- [`fc4d687e`](https://www.github.com/tauri-apps/tauri/commit/fc4d687ef0ef2ea069ed73c40916da733b5dcb8f)([#6716](https://www.github.com/tauri-apps/tauri/pull/6716)) Moved the file system APIs to its own plugin in the plugins-workspace repository.\n- [`f78a3783`](https://www.github.com/tauri-apps/tauri/commit/f78a378344bbec48533641661d865920a8f46f8f)([#6742](https://www.github.com/tauri-apps/tauri/pull/6742)) Moved the `http` feature to its own plugin in the plugins-workspace repository.\n- [`29ce9ce2`](https://www.github.com/tauri-apps/tauri/commit/29ce9ce2ce7dfb260d556d5cffd075e8fe06660c)([#6902](https://www.github.com/tauri-apps/tauri/pull/6902)) Moved the `os` feature to its own plugin in the plugins-workspace repository.\n- [`60cf9ed2`](https://www.github.com/tauri-apps/tauri/commit/60cf9ed2fcd7be4df41e86cf18735efe9b6cb254)([#6905](https://www.github.com/tauri-apps/tauri/pull/6905)) Moved the `process` feature to its own plugin in the plugins-workspace repository.\n- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Moved the `protocol` scope configuration to the `asset_protocol` field in `SecurityConfig`.\n- [`96639ca2`](https://www.github.com/tauri-apps/tauri/commit/96639ca239c9e4f75142fc07868ac46822111cff)([#6749](https://www.github.com/tauri-apps/tauri/pull/6749)) Moved the `shell` functionality to its own plugin in the plugins-workspace repository.\n- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Moved the updater configuration to the `BundleConfig`.\n- [`b072daa3`](https://www.github.com/tauri-apps/tauri/commit/b072daa3bd3e38b808466666619ddb885052c5b2)([#6919](https://www.github.com/tauri-apps/tauri/pull/6919)) Moved the `updater` feature to its own plugin in the plugins-workspace repository.\n- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65.\n- [`d693e526`](https://www.github.com/tauri-apps/tauri/commit/d693e526e8607129d7f7b62a10db715f3b87d2b9)([#6780](https://www.github.com/tauri-apps/tauri/pull/6780)) Added the `onNewIntent` Plugin hook on Android.\n- [`34b8f339`](https://www.github.com/tauri-apps/tauri/commit/34b8f339a4276ebff20b9d52caa103e8e3a7af66)([#6705](https://www.github.com/tauri-apps/tauri/pull/6705)) Add `app` method for the `PluginApi` struct.\n- [`96639ca2`](https://www.github.com/tauri-apps/tauri/commit/96639ca239c9e4f75142fc07868ac46822111cff)([#6749](https://www.github.com/tauri-apps/tauri/pull/6749)) Moved the `tauri::api::process` module to `tauri::process`.\n- [`cdad6e08`](https://www.github.com/tauri-apps/tauri/commit/cdad6e083728ea61bd6fc734ef93f6306056ea2e)([#6774](https://www.github.com/tauri-apps/tauri/pull/6774)) Changed how the `tauri-android` dependency is injected. This requires the `gen/android` project to be recreated.\n- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Removed the allowlist configuration.\n- [`cebd7526`](https://www.github.com/tauri-apps/tauri/commit/cebd75261ac71b98976314a450cb292eeeec1515)([#6728](https://www.github.com/tauri-apps/tauri/pull/6728)) Moved the `clipboard` feature to its own plugin in the plugins-workspace repository.\n- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Removed extract and move APIs from `tauri::api::file`.\n- [`3f17ee82`](https://www.github.com/tauri-apps/tauri/commit/3f17ee82f6ff21108806edb7b00500b8512b8dc7)([#6737](https://www.github.com/tauri-apps/tauri/pull/6737)) Moved the `global-shortcut` feature to its own plugin in the plugins-workspace repository.\n- [`ae102980`](https://www.github.com/tauri-apps/tauri/commit/ae102980fcdde3f55effdc0623ea425b48d07dd1)([#6719](https://www.github.com/tauri-apps/tauri/pull/6719)) Refactor the `Context` conditional fields and only parse the tray icon on desktop.\n- [`2d5378bf`](https://www.github.com/tauri-apps/tauri/commit/2d5378bfc1ba817ee2f331b41738a90e5997e5e8)([#6717](https://www.github.com/tauri-apps/tauri/pull/6717)) Remove the updater's dialog option.\n- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Removed `UpdaterEvent`. See `tauri-plugin-updater` for new usage.\n- [`9a79dc08`](https://www.github.com/tauri-apps/tauri/commit/9a79dc085870e0c1a5df13481ff271b8c6cc3b78)([#6947](https://www.github.com/tauri-apps/tauri/pull/6947)) Moved the `window` JS APIs to its own plugin in the plugins-workspace repository.\n- [`22a76338`](https://www.github.com/tauri-apps/tauri/commit/22a763381622407d58ae72aa24c0afff00b40e04)([#6713](https://www.github.com/tauri-apps/tauri/pull/6713)) Expose `SafePathBuf` type in `tauri::path`.\n- [`c4171152`](https://www.github.com/tauri-apps/tauri/commit/c4171152c1846f425a937e82f8af1759bcc8c9ac)([#6909](https://www.github.com/tauri-apps/tauri/pull/6909)) Enable shadows by default.\n- [`dfa407ff`](https://www.github.com/tauri-apps/tauri/commit/dfa407ffcbc8a853d61139b68b55747ae49fb231)([#6763](https://www.github.com/tauri-apps/tauri/pull/6763)) Change iOS plugin init function signature to `func init_plugin() -> Plugin`.\n\n## \\[2.0.0-alpha.8]\n\n- Fixes boolean plugin parameters freezing the application.\n  - [9de89791](https://www.github.com/tauri-apps/tauri/commit/9de897919aa7236913ba6ca7c34a68099f4ff600) fix(core): iOS plugin freezing when receiving a bool parameter ([#6700](https://www.github.com/tauri-apps/tauri/pull/6700)) on 2023-04-13\n\n## \\[2.0.0-alpha.7]\n\n- Change minimum Android SDK version to 21 for the plugin library.\n  - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07\n- Improve the `run_mobile_plugin` function error handling.\n  - [f0570d9f](https://www.github.com/tauri-apps/tauri/commit/f0570d9feee05792cc720d26ef32da5eaed7f797) feat(core): improve `run_mobile_plugin` error handling ([#6655](https://www.github.com/tauri-apps/tauri/pull/6655)) on 2023-04-07\n- Implement `Clone` for `plugin::PluginHandle`.\n  - [052c5822](https://www.github.com/tauri-apps/tauri/commit/052c5822b53d55e118674d13914f58113a0d1121) feat(core): implement Clone for PluginHandle ([#6644](https://www.github.com/tauri-apps/tauri/pull/6644)) on 2023-04-05\n\n## \\[2.0.0-alpha.6]\n\n- Fix compilation issues without the shell API features.\n  - [a8137927](https://www.github.com/tauri-apps/tauri/commit/a813792786b55c51173e557834f515d4b2f7ce00) fix(core): compilation issues without execute or sidecar features ([#6621](https://www.github.com/tauri-apps/tauri/pull/6621)) on 2023-04-03\n\n## \\[2.0.0-alpha.5]\n\n- Fixes ProGuard rules.\n  - [adf4627b](https://www.github.com/tauri-apps/tauri/commit/adf4627b73bd7098772b7f3020b4aca7228bf239) fix(core): adjust ProGuard rules ([#6588](https://www.github.com/tauri-apps/tauri/pull/6588)) on 2023-03-31\n- Added `raw` encoding option to read stdout and stderr raw bytes.\n  - [f992e7f5](https://www.github.com/tauri-apps/tauri/commit/f992e7f58bf975c654a3daf36780b31a32bac064) chore(changes): readd change file on 2023-04-03\n- Renamed the `default-tls` feature to `native-tls` and added `rustls-tls` feature.\n  - [cfdee00f](https://www.github.com/tauri-apps/tauri/commit/cfdee00f2b1455a9719bc44823fdaeabbe4c1cb2) refactor(core): fix tls features, use rustls on mobile ([#6591](https://www.github.com/tauri-apps/tauri/pull/6591)) on 2023-03-30\n\n## \\[2.0.0-alpha.4]\n\n- Allow a wry plugin to be registered at runtime.\n  - [ae296f3d](https://www.github.com/tauri-apps/tauri/commit/ae296f3de16fb6a8badbad5555075a5861681fe5) refactor(tauri-runtime-wry): register runtime plugin after run() ([#6478](https://www.github.com/tauri-apps/tauri/pull/6478)) on 2023-03-17\n- Inject `proguard-tauri.pro` file in the Android project.\n  - [bef4ef51](https://www.github.com/tauri-apps/tauri/commit/bef4ef51bc2c633b88db121c2087a38dddb7d6bf) feat(android): enable minify on release, add proguard rules ([#6257](https://www.github.com/tauri-apps/tauri/pull/6257)) on 2023-02-13\n- Return `bool` in the invoke handler.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n- Use correct lib name in xcode project.\n  - [d1752fb1](https://www.github.com/tauri-apps/tauri/commit/d1752fb1f6223fa47d224cb6c62df9b74944a507) fix(cli): use correct lib name in xcode project ([#6387](https://www.github.com/tauri-apps/tauri/pull/6387)) on 2023-03-08\n- Run Android and iOS native plugins on the invoke handler if a Rust plugin command is not found.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n- Added `initialize_android_plugin` and `initialize_ios_plugin` APIs on `AppHandle`.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n- Changed the plugin setup hook to take a second argument of type `PluginApi`.\n  - [6aaba834](https://www.github.com/tauri-apps/tauri/commit/6aaba83476339fa413fe34d28877a932cb485117) refactor(plugin): add PluginApi and PluginHandle, expose on setup hook ([#6291](https://www.github.com/tauri-apps/tauri/pull/6291)) on 2023-02-16\n- Refactored the implementation of the `mobile_entry_point` macro.\n  - [9feab904](https://www.github.com/tauri-apps/tauri/commit/9feab904bf08b5c168d4779c21d0419409a68d30) feat(core): add API to call Android plugin ([#6239](https://www.github.com/tauri-apps/tauri/pull/6239)) on 2023-02-10\n- Removed the attohttpc client. The `reqwest-*` Cargo features were also removed.\n  - [dddaa943](https://www.github.com/tauri-apps/tauri/commit/dddaa943e7e0bf13935d567ef2f3f73e1c913300) refactor(core): remove attohttpc client, closes [#6415](https://www.github.com/tauri-apps/tauri/pull/6415) ([#6468](https://www.github.com/tauri-apps/tauri/pull/6468)) on 2023-03-17\n- Added `App::run_mobile_plugin` and `AppHandle::run_mobile_plugin`.\n  - [bfb2ab24](https://www.github.com/tauri-apps/tauri/commit/bfb2ab24e0b1d0860ea6e37688b5209541f0eda1) feat: add API to call iOS plugin ([#6242](https://www.github.com/tauri-apps/tauri/pull/6242)) on 2023-02-11\n- Added the `shadow` option when creating a window and `Window::set_shadow`.\n  - [a81750d7](https://www.github.com/tauri-apps/tauri/commit/a81750d779bc72f0fdb7de90b7fbddfd8049b328) feat(core): add shadow APIs ([#6206](https://www.github.com/tauri-apps/tauri/pull/6206)) on 2023-02-08\n- Implemented `with_webview` on Android and iOS.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n\n## \\[2.0.0-alpha.3]\n\n- Update gtk to 0.16.\n  - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30\n- Show all application logs on iOS.\n  - [dee9460f](https://www.github.com/tauri-apps/tauri/commit/dee9460f9c9bc92e9c638e7691e616849ac2085b) feat: keep CLI alive when iOS app exits, show logs, closes [#5855](https://www.github.com/tauri-apps/tauri/pull/5855) ([#5902](https://www.github.com/tauri-apps/tauri/pull/5902)) on 2022-12-27\n- Bump the MSRV to 1.64.\n  - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30\n- Only proxy the dev server on mobile to simplify desktop usage.\n  - [78eaadae](https://www.github.com/tauri-apps/tauri/commit/78eaadae2e75ab165d1970e592bb1455bb8636e3) refactor(core): only proxy on mobile ([#6126](https://www.github.com/tauri-apps/tauri/pull/6126)) on 2023-01-23\n- Removed mobile logging initialization, which will be handled by `tauri-plugin-log`.\n  - [](https://www.github.com/tauri-apps/tauri/commit/undefined)  on undefined\n- Update rfd to 0.11.\n  - [f0a1d9cd](https://www.github.com/tauri-apps/tauri/commit/f0a1d9cdbcfb645ce1c5f1cdd597f764991772cd) chore: update rfd and wry versions ([#6174](https://www.github.com/tauri-apps/tauri/pull/6174)) on 2023-02-03\n\n## \\[2.0.0-alpha.2]\n\n- Fix the filesystem scope allowing sub-directories of the directory picked by the dialog when `recursive` option was `false`.\n  - [9ad0a9a0](https://www.github.com/tauri-apps/tauri/commit/9ad0a9a0aa88a67c3d81ef84df4aad23556affde) Merge pull request from GHSA-6mv3-wm7j-h4w5 on 2022-12-22\n\n## \\[2.0.0-alpha.1]\n\n- Implement response cache on the dev server proxy, used when the server responds with status 304.\n  - [3ad5e72f](https://www.github.com/tauri-apps/tauri/commit/3ad5e72ff147b76267c010c778a3b94bba209bb0) feat(core): cache dev server proxy responses for 304 status code ([#5818](https://www.github.com/tauri-apps/tauri/pull/5818)) on 2022-12-12\n- Properly proxy dev server requests with query strings and fragments.\n  - [a9b4cf20](https://www.github.com/tauri-apps/tauri/commit/a9b4cf20a3e9a5cc984727a56111591504e084c0) fix(core): use entire request URL on dev server proxy ([#5819](https://www.github.com/tauri-apps/tauri/pull/5819)) on 2022-12-12\n\n## \\[2.0.0-alpha.0]\n\n- Added the `default-tls` and `reqwest-default-tls` Cargo features for enabling TLS suppport to connect over HTTPS.\n  - [f6f9192a](https://www.github.com/tauri-apps/tauri/commit/f6f9192aa51bd842df8aa1d1aa538b12aa6c2d29) fix(core): Android compilation on Windows ([#5658](https://www.github.com/tauri-apps/tauri/pull/5658)) on 2022-11-20\n- **Breaking change:** Use the custom protocol as a proxy to the development server on all platforms except Linux.\n  - [6f061504](https://www.github.com/tauri-apps/tauri/commit/6f0615044d09ec58393a7ebca5e45bb175e20db3) feat(cli): add `android dev` and `ios dev` commands ([#4982](https://www.github.com/tauri-apps/tauri/pull/4982)) on 2022-08-20\n- Support `with_webview` for Android platform alowing execution of JNI code in context.\n  - [8ea87e9c](https://www.github.com/tauri-apps/tauri/commit/8ea87e9c9ca8ba4c7017c8281f78aacd08f45785) feat(android): with_webview access for jni execution ([#5148](https://www.github.com/tauri-apps/tauri/pull/5148)) on 2022-09-08\n- First mobile alpha release!\n  - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08\n- **Breaking change:** The window creation and setup hook are now called when the event loop is ready.\n  - [b4622ea4](https://www.github.com/tauri-apps/tauri/commit/b4622ea4d32720bc3bb2a8c740bb70cfe32fed93) refactor(app): run setup and window creation when event loop is ready ([#4914](https://www.github.com/tauri-apps/tauri/pull/4914)) on 2022-08-11\n- Export types required by the `mobile_entry_point` macro.\n  - [98904863](https://www.github.com/tauri-apps/tauri/commit/9890486321c9c79ccfb7c547fafee85b5c3ffa71) feat(core): add `mobile_entry_point` macro ([#4983](https://www.github.com/tauri-apps/tauri/pull/4983)) on 2022-08-21\n\n## \\[1.6.0]\n\n### New Features\n\n- [`6e488378`](https://www.github.com/tauri-apps/tauri/commit/6e48837860203582d2ef8e59d4524f98511a14c0)([#8474](https://www.github.com/tauri-apps/tauri/pull/8474)) Re-export `Url` type.\n\n### Enhancements\n\n- [`8ce51cec`](https://www.github.com/tauri-apps/tauri/commit/8ce51cec3baf4ed88d80c59bf3bbe96fd369c7a0)([#7718](https://www.github.com/tauri-apps/tauri/pull/7718)) On Windows, retain command line args when relaunching the app after an update. Supports NSIS and WiX (without elevated update task).\n\n### Bug Fixes\n\n- [`cc3d8e77`](https://www.github.com/tauri-apps/tauri/commit/cc3d8e77313672f25520e278bbe8fae1b275a735)([#8539](https://www.github.com/tauri-apps/tauri/pull/8539)) Fixes a deadlock when reading a stdout or stderr line returns an error.\n- [`b546b42d`](https://www.github.com/tauri-apps/tauri/commit/b546b42db7e75a59232367dd6212fe3b75bb4c6d)([#8577](https://www.github.com/tauri-apps/tauri/pull/8577)) Preserve the order of JS object/map keys in IPC calls. This also fixes issues with the JS `http` module when calling to servers that required a specific order of `FormBody` contents.\n- [`8f8729d9`](https://www.github.com/tauri-apps/tauri/commit/8f8729d91843acd2bd2a24731db865d690dd9ab1)([#8312](https://www.github.com/tauri-apps/tauri/pull/8312)) On macOS, allow cancelling maximization when doubleclick happens on `data-tauri-drag-region` by simply keeping the left moust button pressed and then moving the mouse away of the starting position of the click, which is consistent with the native behavior of macOS.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@0.14.4`\n\n## \\[1.5.4]\n\n### Enhancements\n\n- [`3c371aa8`](https://www.github.com/tauri-apps/tauri/commit/3c371aa8ee4032998f859b570702e81e26e77c6c)([#8228](https://www.github.com/tauri-apps/tauri/pull/8228)) Added `test::get_ipc_response`.\n\n### Bug Fixes\n\n- [`50a3d170`](https://www.github.com/tauri-apps/tauri/commit/50a3d170f242178d41fe7e8a3adf964541f6fe9c)([#8408](https://www.github.com/tauri-apps/tauri/pull/8408)) On Windows, fix `open` dialog `defaultPath`, when invoked from JS, not working if the path uses forward slash (`/`)\n- [`645e1dcc`](https://www.github.com/tauri-apps/tauri/commit/645e1dcc6e113564e2ddaacf9cb8338aed1a0bd0)([#8404](https://www.github.com/tauri-apps/tauri/pull/8404)) Fix NSIS updater failing to launch when using `basicUi` mode.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@0.14.3`\n- Upgraded to `tauri-utils@1.5.2`\n- Upgraded to `tauri-runtime@0.14.2`\n- Upgraded to `tauri-macros@1.4.3`\n\n## \\[1.5.3]\n\n### Enhancements\n\n- [`b3e53e72`](https://www.github.com/tauri-apps/tauri/commit/b3e53e7243311a2659b7569dddc20c56ac9f9d8e)([#8288](https://www.github.com/tauri-apps/tauri/pull/8288)) Added `AssetResolver::iter` to iterate on all embedded assets.\n- [`5e05236b`](https://www.github.com/tauri-apps/tauri/commit/5e05236b4987346697c7caae0567d3c50714c198)([#8289](https://www.github.com/tauri-apps/tauri/pull/8289)) Added tracing for window startup, plugins, `Window::eval`, events, IPC, updater and custom protocol request handlers behind the `tracing` feature flag.\n\n### Bug Fixes\n\n- [`2ba88563`](https://www.github.com/tauri-apps/tauri/commit/2ba8856343e284ed022f28cff6d16db15ad4645f)([#8095](https://www.github.com/tauri-apps/tauri/pull/8095)) Fix docs.rs build for `x86_64-apple-darwin`.\n- [`4b6a602a`](https://www.github.com/tauri-apps/tauri/commit/4b6a602a89b36f24d34d6ccd8e3c9b7ce202c9eb)([#8234](https://www.github.com/tauri-apps/tauri/pull/8234)) Escape path of the updater msi to avoid crashing on installers with spaces.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime-wry@0.14.2`\n- Upgraded to `tauri-macros@1.4.2`\n\n## \\[1.5.2]\n\n### Bug Fixes\n\n- [`21cdbb41`](https://www.github.com/tauri-apps/tauri/commit/21cdbb41a38f465148bbeb82feb3e7886c320182)([#7982](https://www.github.com/tauri-apps/tauri/pull/7982)) Set the correct `truncate` option on `OpenOptions` so that `write_file` can completely overwrite existing files.\n\n## \\[1.5.1]\n\n### Bug Fixes\n\n- [`3671edbc`](https://www.github.com/tauri-apps/tauri/commit/3671edbcff37447c95382ab4c9fd1c36a460a037)([#7937](https://www.github.com/tauri-apps/tauri/pull/7937)) Fix devtools not toggling on `ctrl+shift+i` or `cmd+alt+i` shortcuts.\n\n## \\[1.5.0]\n\n### New Features\n\n- [`eeb6be54`](https://www.github.com/tauri-apps/tauri/commit/eeb6be54228f3e5463a28c68956abb06a694c010)([#7512](https://www.github.com/tauri-apps/tauri/pull/7512)) Add `tauri::Manager::emit_filter` and only serialize once when emitting to multiple windows.\n- [`6c408b73`](https://www.github.com/tauri-apps/tauri/commit/6c408b736c7aa2a0a91f0a40d45a2b7a7dedfe78)([#7269](https://www.github.com/tauri-apps/tauri/pull/7269)) Add option to specify notification sound.\n- [`fdaee9a5`](https://www.github.com/tauri-apps/tauri/commit/fdaee9a5ce988c448dd035c2050c339d275e8d15)([#7350](https://www.github.com/tauri-apps/tauri/pull/7350)) Add `tauri::plugin::Builder::register_uri_scheme_protocol`\n- [`10e362d0`](https://www.github.com/tauri-apps/tauri/commit/10e362d098c9bed48f832bad471fb2fab83ab0bb)([#7432](https://www.github.com/tauri-apps/tauri/pull/7432)) Added `UpdateBuilder::endpoints` to add request endpoints at runtime.\n- [`10e362d0`](https://www.github.com/tauri-apps/tauri/commit/10e362d098c9bed48f832bad471fb2fab83ab0bb)([#7432](https://www.github.com/tauri-apps/tauri/pull/7432)) Added `UpdateResponse::header` and `UpdateResponse::remove_header` to modify the update download request headers.\n\n### Enhancements\n\n- [`757e959e`](https://www.github.com/tauri-apps/tauri/commit/757e959eb276ed535cfddb0dea8897c56441c644)([#7344](https://www.github.com/tauri-apps/tauri/pull/7344)) Open links externally when `<base target=\"_blank\" />` exists\n- [`c9827338`](https://www.github.com/tauri-apps/tauri/commit/c98273387c0ffbb8d0de78ce17006411a1f503ee)([#7416](https://www.github.com/tauri-apps/tauri/pull/7416)) Enhance `readDir` API error with path information.\n- [`58d6b899`](https://www.github.com/tauri-apps/tauri/commit/58d6b899e21d37bb42810890d289deb57f2273bd)([#7636](https://www.github.com/tauri-apps/tauri/pull/7636)) Add `append` option to `FsOptions` in the `fs` JS module, used in `writeTextFile` and `writeBinaryFile`, to be able to append to existing files instead of overwriting it.\n- [`9aa34ada`](https://www.github.com/tauri-apps/tauri/commit/9aa34ada5769dbefa7dfe5f7a6288b3d20b294e4)([#7645](https://www.github.com/tauri-apps/tauri/pull/7645)) Add setting to switch to `http://<scheme>.localhost/` for custom protocols on Windows.\n\n### Bug Fixes\n\n- [`4bf1e85e`](https://www.github.com/tauri-apps/tauri/commit/4bf1e85e6bf85a7ec92d50c8465bc0588a6399d8)([#7722](https://www.github.com/tauri-apps/tauri/pull/7722)) Properly respect the `focused` option when creating the webview.\n- [`0797a002`](https://www.github.com/tauri-apps/tauri/commit/0797a002caad29cd8bedccf01f64bf3b45a5e528)([#7746](https://www.github.com/tauri-apps/tauri/pull/7746)) On macOS, fixed tapping on custom title bar doesn't maximize the window.\n- [`1a3dcdb8`](https://www.github.com/tauri-apps/tauri/commit/1a3dcdb8302fad511f2c1cd418fbc4cff0bd62ac)([#7185](https://www.github.com/tauri-apps/tauri/pull/7185)) On Windows, fix NSIS installers requiring administrator rights failing to be launched by updater.\n- [`fa7f9b77`](https://www.github.com/tauri-apps/tauri/commit/fa7f9b77ab8f0c890e9d7b120901610e0d3e4c46)([#7341](https://www.github.com/tauri-apps/tauri/pull/7341)) Fix updater not following endpoint redirects.\n- [`6fbd6dba`](https://www.github.com/tauri-apps/tauri/commit/6fbd6dba5290dc017ab0ba5a44cf4358b022836f)([#17](https://www.github.com/tauri-apps/tauri/pull/17)) Fix the validation of `std::env::current_exe` warn the user if AppImage is not mounted instead of panicking\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.0`\n- Upgraded to `tauri-runtime-wry@0.14.1`\n- Upgraded to `tauri-runtime@0.14.1`\n- Upgraded to `tauri-macros@1.4.1`\n\n## \\[1.4.1]\n\n### Bug Fixes\n\n- [`6afd3472`](https://www.github.com/tauri-apps/tauri/commit/6afd34727f153b32dbc568d169dbb17fb8dc3539)([#6680](https://www.github.com/tauri-apps/tauri/pull/6680)) Revert [#6680](https://github.com/tauri-apps/tauri/pull/6680) which added a default sound for notifications on Windows. This introduced inconsistency with other platforms that has silent notifications by default. In the upcoming releases, we will add support for modifying the notification sound across all platforms.\n\n### Security fixes\n\n- [`066c09a6`](https://www.github.com/tauri-apps/tauri/commit/066c09a6ea06f42f550d090715e06beb65cd5564)([#7227](https://www.github.com/tauri-apps/tauri/pull/7227)) Fix regression in `1.4` where the default behavior of the file system scope was changed to allow reading hidden files and directories by default.\n\n## \\[1.4.0]\n\n### New Features\n\n- [`7c237209`](https://www.github.com/tauri-apps/tauri/commit/7c237209207cd2938df660b6fd87d3b7d728bd03)([#6546](https://www.github.com/tauri-apps/tauri/pull/6546)) Added `tauri::VERSION` const to get Tauri's version from Rust.\n- [`4c39e46a`](https://www.github.com/tauri-apps/tauri/commit/4c39e46a3b438d007f139166ab2a0ba34291a10a)([#7026](https://www.github.com/tauri-apps/tauri/pull/7026)) Added `tauri::webview_version` , to get webview version.\n- [`359058ce`](https://www.github.com/tauri-apps/tauri/commit/359058cecca44a9c30b65140c44a8bb3a6dd3be8)([#5939](https://www.github.com/tauri-apps/tauri/pull/5939)) Add `tauri::api::os::locale` function to get the system locale.\n- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `maximizable`, `minimizable` and `closable` options to the window builder.\n- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `set_maximizable`, `set_minimizable`, `set_closable`, `is_maximizable`, `is_minimizable` and `is_closable` methods on `Window`.\n- [`1d99f8a3`](https://www.github.com/tauri-apps/tauri/commit/1d99f8a3c2f989d1a5ba4d805e3a40b07a3ca8a5)([#4752](https://www.github.com/tauri-apps/tauri/pull/4752)) Expose the `test` module behind the `test` Cargo feature.\n- [`000104bc`](https://www.github.com/tauri-apps/tauri/commit/000104bc3bc0c9ff3d20558ab9cf2080f126e9e0)([#6472](https://www.github.com/tauri-apps/tauri/pull/6472)) Add `Window::is_focused` and `Manager::get_focused_window` getters.\n- [`441f9646`](https://www.github.com/tauri-apps/tauri/commit/441f96465488a4f8a5731cc51b8ac97b685898c7)([#5491](https://www.github.com/tauri-apps/tauri/pull/5491)) Add `MenuHandle::try_get_item` and `SystemTrayHandle::try_get_item` which returns a `Option` instead of panicking.\n\n### Enhancements\n\n- [`45330e38`](https://www.github.com/tauri-apps/tauri/commit/45330e38193d0b2a01aa926aec433acc6b8f6597)([#6375](https://www.github.com/tauri-apps/tauri/pull/6375)) Enhance the `asset` protocol to support streaming of large files.\n- [`df89ccc1`](https://www.github.com/tauri-apps/tauri/commit/df89ccc1912db6b81d43d56c9e6d66980ece2e8d)([#6955](https://www.github.com/tauri-apps/tauri/pull/6955)) Support `passive` mode for NSIS updater.\n- [`cd3846c8`](https://www.github.com/tauri-apps/tauri/commit/cd3846c8ce61ab2879b3911e831525e6242aaab2)([#6955](https://www.github.com/tauri-apps/tauri/pull/6955)) Restart the app after the NSIS updater is finished.\n- [`db7c5fbf`](https://www.github.com/tauri-apps/tauri/commit/db7c5fbf2e86f3694720f65834eb2c258b7c1291)([#7143](https://www.github.com/tauri-apps/tauri/pull/7143)) Remove `attohttpc` in favor of `reqwest`.\n- [`d2710e9d`](https://www.github.com/tauri-apps/tauri/commit/d2710e9d2e8fd93975ef6494512370faa8cb3b7e)([#6944](https://www.github.com/tauri-apps/tauri/pull/6944)) Unpin `time`, `ignore`, and `winnow` crate versions. Developers now have to pin crates if needed themselves. A list of crates that need pinning to adhere to Tauri's MSRV will be visible in Tauri's GitHub workflow: https://github.com/tauri-apps/tauri/blob/dev/.github/workflows/test-core.yml#L85.\n- [`5d85d099`](https://www.github.com/tauri-apps/tauri/commit/5d85d0990cd13a1446953a58633edc24eda55afe)([#7128](https://www.github.com/tauri-apps/tauri/pull/7128)) Send updater status events even if default dialog is enabled.\n\n### Bug Fixes\n\n- [`82169e69`](https://www.github.com/tauri-apps/tauri/commit/82169e69fc904d2c7980534c4479bb6f38259fb4)([#5208](https://www.github.com/tauri-apps/tauri/pull/5208)) Fix parsing `allowlist > http > scope` urls that added a trailing slash which broke matching the incoming requests url.\n- [`b41b57eb`](https://www.github.com/tauri-apps/tauri/commit/b41b57ebb27befd366db5befaafb6043c18fdfef)([#7105](https://www.github.com/tauri-apps/tauri/pull/7105)) Fix panics when registering an invalid global shortcuts or checking it is registered and return proper errors instead.\n- [`aecf1469`](https://www.github.com/tauri-apps/tauri/commit/aecf14690947d109745b4ad823a3e8f4338de47a)([#6889](https://www.github.com/tauri-apps/tauri/pull/6889)) Fix IPC failing after a failed navigation to an external URL.\n- [`076e1a81`](https://www.github.com/tauri-apps/tauri/commit/076e1a81a50468e3dfb34ae9ca7e77c5e1758daa)([#7119](https://www.github.com/tauri-apps/tauri/pull/7119)) Fix unlistening to window events failing sometimes.\n- [`3f35b452`](https://www.github.com/tauri-apps/tauri/commit/3f35b452637ef1c794a423f1eda62a15d2ddaf42)([#4080](https://www.github.com/tauri-apps/tauri/pull/4080)) Fix `WindowBuilder::on_navigation` handler not registered properly.\n- [`0503eb69`](https://www.github.com/tauri-apps/tauri/commit/0503eb69ce7df6b4ed8f5249fdb519b86cd57d8d)([#7078](https://www.github.com/tauri-apps/tauri/pull/7078)) On macOS and Linux, fix app crashing when creating a window with `data:` uri.\n- [`3700793a`](https://www.github.com/tauri-apps/tauri/commit/3700793a2f1ea3686b1889c345d73007bb622a29)([#6934](https://www.github.com/tauri-apps/tauri/pull/6934)) Emit `UPTODATE` update status to javascript when the updater server returns status code `204`\n- [`ff5e4dbb`](https://www.github.com/tauri-apps/tauri/commit/ff5e4dbbb01bf3fc9c5143df732c75eef6fd98cb)([#6794](https://www.github.com/tauri-apps/tauri/pull/6794)) Fix some configurations not applied when creating the window through Javascript.\n- [`65fd674f`](https://www.github.com/tauri-apps/tauri/commit/65fd674f50ba0395d5cea23daf9d2f49f95647fe)([#6652](https://www.github.com/tauri-apps/tauri/pull/6652)) Play a sound when showing a notification on Windows.\n- [`696d77c3`](https://www.github.com/tauri-apps/tauri/commit/696d77c3ce480f4a3b4c1c57dae64ff2bb7842ce)([#4493](https://www.github.com/tauri-apps/tauri/pull/4493)) Fixes global events not being received on window-specific event listeners.\n\n## \\[1.3.0]\n\n- Added the `additional_browser_args` option when creating a window.\n  - [3dc38b15](https://www.github.com/tauri-apps/tauri/commit/3dc38b150ea8c59c8ba67fd586f921016928f47c) feat(core): expose additional_browser_args to window config (fix: [#5757](https://www.github.com/tauri-apps/tauri/pull/5757)) ([#5799](https://www.github.com/tauri-apps/tauri/pull/5799)) on 2022-12-14\n- Fix passing `--profile` to cargo in `tauri build` causing conflict with `--release` passed by the CLI.\n  - [bfa69691](https://www.github.com/tauri-apps/tauri/commit/bfa69691a5171af97cc6a6d880cb3090338ed9e9) fix(cli): detect `--profile`. closes [#6255](https://www.github.com/tauri-apps/tauri/pull/6255) ([#6268](https://www.github.com/tauri-apps/tauri/pull/6268)) on 2023-02-18\n- Added the `content_protected` option when creating a window and `Window::set_content_protected` to change it at runtime.\n  - [4ab5545b](https://www.github.com/tauri-apps/tauri/commit/4ab5545b7a831c549f3c65e74de487ede3ab7ce5) feat: add content protection api, closes [#5132](https://www.github.com/tauri-apps/tauri/pull/5132) ([#5513](https://www.github.com/tauri-apps/tauri/pull/5513)) on 2022-12-13\n- Fix serialization of js `Map` when used in `invoke`.\n  - [d4d6a98d](https://www.github.com/tauri-apps/tauri/commit/d4d6a98d98fd09ba4e9ee4857ef3604d9e454337) fix(core): convert js `Map` to object before serialization, closes [#6078](https://www.github.com/tauri-apps/tauri/pull/6078) ([#6099](https://www.github.com/tauri-apps/tauri/pull/6099)) on 2023-01-19\n- Added `Window::on_navigation`.\n  - [3f35b452](https://www.github.com/tauri-apps/tauri/commit/3f35b452637ef1c794a423f1eda62a15d2ddaf42) Expose wry navigation_handler via WindowBuilder closes [#4080](https://www.github.com/tauri-apps/tauri/pull/4080) ([#5686](https://www.github.com/tauri-apps/tauri/pull/5686)) on 2022-12-27\n- Sync `__TAURI_METADATA__.__windows` across all windows.\n  - [146a794c](https://www.github.com/tauri-apps/tauri/commit/146a794cb696816854648d33e2124e82154c7b2f) fix(core): sync windows metadata across all windows, closes [#5571](https://www.github.com/tauri-apps/tauri/pull/5571) ([#5615](https://www.github.com/tauri-apps/tauri/pull/5615)) on 2022-12-27\n- Fix `UpdaterBuilder::check` returning a parsing error when `204` is sent from server where it should instead return a `UpToDate` error.\n  - [eb1ec041](https://www.github.com/tauri-apps/tauri/commit/eb1ec0416c52c75830dee77e4d714d882d1145b3) fix(core/updater): read and parse response after checking status code, closes [#6192](https://www.github.com/tauri-apps/tauri/pull/6192) ([#6575](https://www.github.com/tauri-apps/tauri/pull/6575)) on 2023-03-31\n- Added `OkWithLabel` and `OkCancelWithLabels` variants to the `api::dialog::MessageDialogButtons` enum to set the text of the dialog buttons.\n  - [00e1efaa](https://www.github.com/tauri-apps/tauri/commit/00e1efaa9b33876d41dd360624b69971e70d3856) feat: customize button texts of message dialog ([#4383](https://www.github.com/tauri-apps/tauri/pull/4383)) on 2022-12-28\n- Added `Builder::device_event_filter` and `App::set_device_event_filter` methods.\n  - [73fd60ee](https://www.github.com/tauri-apps/tauri/commit/73fd60eef2b60f5dc84525ef9c315f4d80c4414f) expose set_device_event_filter in tauri ([#5562](https://www.github.com/tauri-apps/tauri/pull/5562)) on 2022-12-13\n- Fix resize glitch when double clicking a custom titlebar in the top resize area.\n  - [4892637f](https://www.github.com/tauri-apps/tauri/commit/4892637f83b0f64822a5d304e8786a4c92a8957d) fix: Resizing glitch on custom titlebar click (closes [#2549](https://www.github.com/tauri-apps/tauri/pull/2549)) ([#5966](https://www.github.com/tauri-apps/tauri/pull/5966)) on 2023-01-04\n- Fixes tray events not being delivered.\n  - [138cb8d7](https://www.github.com/tauri-apps/tauri/commit/138cb8d739b15bccdb388e555c20f17ffe16318c) fix(tauri-runtime-wry): tray event listener not registered ([#6270](https://www.github.com/tauri-apps/tauri/pull/6270)) on 2023-02-14\n- Fix the filesystem scope allowing sub-directories of the directory picked by the dialog when `recursive` option was `false`.\n  - [72389b00](https://www.github.com/tauri-apps/tauri/commit/72389b00d7b495ffd7750eb1e75a3b8537d07cf3) Merge pull request from GHSA-6mv3-wm7j-h4w5 on 2022-12-22\n- Add `is_minimized()` window method.\n  - [62144ef3](https://www.github.com/tauri-apps/tauri/commit/62144ef3be63b237869e511826edfb938e2c7174) feat: add is_minimized (fix [#3878](https://www.github.com/tauri-apps/tauri/pull/3878)) ([#5618](https://www.github.com/tauri-apps/tauri/pull/5618)) on 2022-12-13\n- Bump minimum supported Rust version to 1.60.\n  - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31\n- Update the `open` crate to v3.2 to fix an URL encoding bug on Windows.\n  - [708efbd9](https://www.github.com/tauri-apps/tauri/commit/708efbd9b72508a5a5aa55092a48d3218e008ce3) fix(core/tauri): upgrade `open` to 3.2 to fix a bug on Windows ([#6441](https://www.github.com/tauri-apps/tauri/pull/6441)) on 2023-04-06\n- Added support to `mailto:` and `tel:` links on the shell API.\n  - [d0d873e3](https://www.github.com/tauri-apps/tauri/commit/d0d873e39a3cd5e51e9cf0145a024ffdb0c2a941) feat(core): add support to mailto: and tel: links, closes [#5521](https://www.github.com/tauri-apps/tauri/pull/5521) ([#5544](https://www.github.com/tauri-apps/tauri/pull/5544)) on 2022-12-12\n- Pin `os_info` to `=3.5`.\n  - [a8d640b3](https://www.github.com/tauri-apps/tauri/commit/a8d640b3c659c7cfea23fe60cc5d9ef377841c5e) fix(core): pin unarray and os_info ([#6212](https://www.github.com/tauri-apps/tauri/pull/6212)) on 2023-02-07\n- Pin raw-window-handle to 0.5.0 to keep MSRV.\n  - [c46c09f3](https://www.github.com/tauri-apps/tauri/commit/c46c09f31d9f5169ca8a7e62406a9ea170e3a5c5) fix(deps): pin raw-window-handle to 0.5.0 ([#6480](https://www.github.com/tauri-apps/tauri/pull/6480)) on 2023-03-17\n- Pin `time` to `0.3.15`.\n  - [3d16461b](https://www.github.com/tauri-apps/tauri/commit/3d16461b68583ba7db037fbc217786e79b46ddf2) fix(core): pin time to 0.3.15 ([#6312](https://www.github.com/tauri-apps/tauri/pull/6312)) on 2023-02-19\n- Added configuration to specify remote URLs allowed to access the IPC.\n  - [ee71c31f](https://www.github.com/tauri-apps/tauri/commit/ee71c31fd09cc5427da6d29d37c003a914547696) feat(core): allow configuring remote domains with IPC access, closes [#5088](https://www.github.com/tauri-apps/tauri/pull/5088) ([#5918](https://www.github.com/tauri-apps/tauri/pull/5918)) on 2023-04-11\n- Add `title` getter on window.\n  - [233e43b0](https://www.github.com/tauri-apps/tauri/commit/233e43b0c34fada1ca025378533a0b76931a6540) feat: add `title` getter on window, closes [#5023](https://www.github.com/tauri-apps/tauri/pull/5023) ([#5515](https://www.github.com/tauri-apps/tauri/pull/5515)) on 2022-12-13\n- Implement `SystemTray::with_tooltip` and `SystemTrayHandle::set_tooltip` for Windows and macOS.\n  - [2265e097](https://www.github.com/tauri-apps/tauri/commit/2265e09718f6ebfeb1d200f11e1e1e069075af6e) feat(windows): implement `with_tooltip` ([#5938](https://www.github.com/tauri-apps/tauri/pull/5938)) on 2023-01-01\n- Added window's `url()` getter.\n  - [d17027e1](https://www.github.com/tauri-apps/tauri/commit/d17027e1a0db3e8c5ae81fc4f472c5918fbce611) feat: expose url method ([#5914](https://www.github.com/tauri-apps/tauri/pull/5914)) on 2022-12-26\n- On Windows, change webview theme based on Window theme for more accurate `prefers-color-scheme` support.\n  - [7a8d570d](https://www.github.com/tauri-apps/tauri/commit/7a8d570db72667367eb24b75ddc5dd07a968f7c0) fix: sync webview theme with window theme on Windows, closes [#5802](https://www.github.com/tauri-apps/tauri/pull/5802) ([#5874](https://www.github.com/tauri-apps/tauri/pull/5874)) on 2022-12-27\n- Add a method to the `WindowBuilder` struct to recreate windows from tauri.conf.json configurations.\n  - [49dff27e](https://www.github.com/tauri-apps/tauri/commit/49dff27ef1b36c48dbfd49f44b9b3ac07b372bdf) feat(core): create WindowBuilder from WindowConfig ([#6073](https://www.github.com/tauri-apps/tauri/pull/6073)) on 2023-01-17\n- On Windows, Fix missing `WindowEvent::Focused` in `App::run` callback.\n  - [ff4ea1ea](https://www.github.com/tauri-apps/tauri/commit/ff4ea1eabbf2874b113c6b4698002929bbac737a) fix: dispatch focus event to app.run on Windows, closes [#6460](https://www.github.com/tauri-apps/tauri/pull/6460) ([#6504](https://www.github.com/tauri-apps/tauri/pull/6504)) on 2023-03-31\n- Pin `winnow` crate to 0.4.1 to keep the 1.60 MSRV.\n\n## \\[1.2.5]\n\n- Block remote URLs from accessing the IPC.\n  - [9c0593c33](https://www.github.com/tauri-apps/tauri/commit/9c0593c33af52cd9e00ec784d15f63efebdf039c) feat(core): block remote URLs from accessing the IPC on 2023-04-12\n\n## \\[1.2.4]\n\n- Pin `ignore` to `=0.4.18`.\n  - [adcb082b](https://www.github.com/tauri-apps/tauri/commit/adcb082b1651ecb2a6208b093e12f4185aa3fc98) chore(deps): pin `ignore` to =0.4.18 on 2023-01-17\n\n## \\[1.2.3]\n\n- Fix the filesystem scope allowing sub-directories of the directory picked by the dialog when `recursive` option was `false`.\n  - [f1b0ad6e](https://www.github.com/tauri-apps/tauri/commit/f1b0ad6e8b721cf1420a9a4b9be5b05c39941d16) Merge pull request from GHSA-6mv3-wm7j-h4w5 on 2022-12-22\n\n## \\[1.2.2]\n\n- Invoke event listener in windows safely to avoid causing uncaught errors in windows that have loaded external urls\n  - [c14b1df3](https://www.github.com/tauri-apps/tauri/commit/c14b1df37284020b3edb32400eb4b9e32c945472) fix(core): Invoke event listener in windows safely to avoid causing uncaught errors in windows that have loaded external urls ([#5563](https://www.github.com/tauri-apps/tauri/pull/5563)) on 2022-12-08\n- Cleanup sidecar and tray icons when calling `app.exit()` from JS.\n  - [0f269608](https://www.github.com/tauri-apps/tauri/commit/0f26960891228c5909e06d9f850c44ffaebf536c) fix(core/api): cleanup before exit ([#5765](https://www.github.com/tauri-apps/tauri/pull/5765)) on 2022-12-07\n- Fix compatibility with older Linux distributions.\n  - [b490308c](https://www.github.com/tauri-apps/tauri/commit/b490308c8897b893292951754607c2253abbc6e1) fix(core): compilation error on older Linux versions, fixes [#5684](https://www.github.com/tauri-apps/tauri/pull/5684) ([#5697](https://www.github.com/tauri-apps/tauri/pull/5697)) on 2022-11-28\n- Add `tauri::Builder::enable_macos_default_menu` to enable or disable the default menu creation on macOS.\n  - [8866ecac](https://www.github.com/tauri-apps/tauri/commit/8866ecac3cd1af8bf02e29569d605be5a1afe22c) feat(core): add `tauri::Builder::enable_macos_default_menu` ([#5756](https://www.github.com/tauri-apps/tauri/pull/5756)) on 2022-12-07\n  - [b293da35](https://www.github.com/tauri-apps/tauri/commit/b293da35dd5ae8c1569a3f3c994b4c1a4c227f4a) fix(changes): change `enable_macos_default_menu` bump to patch on 2022-12-08\n\n## \\[1.2.1]\n\n- Fixes a double serialization on the IPC.\n  - [677838cc](https://www.github.com/tauri-apps/tauri/commit/677838ccfadfdf37039be53bfad666bbe1dab8c3) fix double serialize on invoke ([#5639](https://www.github.com/tauri-apps/tauri/pull/5639)) on 2022-11-20\n- Moved the custom protocol headers support on Linux behind the `linux-protocol-headers` Cargo feature to enhance compatibility with older Linux distributions.\n  - [d7109460](https://www.github.com/tauri-apps/tauri/commit/d710946064c47fa488eca01a62403e70b2b5ff87) refactor: move Linux custom protocol headers support behind feature flag ([#5683](https://www.github.com/tauri-apps/tauri/pull/5683)) on 2022-11-24\n- Fixes definition of `impl HasRawDisplayHandle` for `AppHandle` and `App`.\n  - [ed43ff32](https://www.github.com/tauri-apps/tauri/commit/ed43ff324330d1bd9c042a53a6636dfc7d97b410) fix(tauri): add missing generics on AppHandle and App ([#5642](https://www.github.com/tauri-apps/tauri/pull/5642)) on 2022-11-17\n\n## \\[1.2.0]\n\n- Add `accept_first_mouse` option for macOS windows.\n  - [95f467ad](https://www.github.com/tauri-apps/tauri/commit/95f467add51448319983c54e2f382c7c09fb72d6) feat(core): add window `accept_first_mouse` option, closes [#5347](https://www.github.com/tauri-apps/tauri/pull/5347) ([#5374](https://www.github.com/tauri-apps/tauri/pull/5374)) on 2022-10-17\n- Add new app-specific `BaseDirectory` enum variants `AppConfig`, `AppData`, `AppLocalData`, `AppCache` and `AppLog` along with equivalent functions in `path` module and deprecated ambiguous variants `Log` and `App` along with their equivalent functions in `path` module.\n  - [5d89905e](https://www.github.com/tauri-apps/tauri/commit/5d89905e39ce0e6eaaec50a693679335449edb32) feat(api): add app-specific directory APIs, closes [#5263](https://www.github.com/tauri-apps/tauri/pull/5263) ([#5272](https://www.github.com/tauri-apps/tauri/pull/5272)) on 2022-09-28\n- Set the correct mimetype when streaming files through `asset:` protocol\n  - [39443b43](https://www.github.com/tauri-apps/tauri/commit/39443b4350bd208c4d6eec5e1095f215199f8aa3) fix(core): set correct mimetype for asset protocol streams, closes [#5203](https://www.github.com/tauri-apps/tauri/pull/5203) ([#5210](https://www.github.com/tauri-apps/tauri/pull/5210)) on 2022-09-30\n  - [2d9c2b47](https://www.github.com/tauri-apps/tauri/commit/2d9c2b472416339829f9113f976f193bf8e0665f) Revert \"fix(core): set correct mimetype for asset protocol streams, closes [#5203](https://www.github.com/tauri-apps/tauri/pull/5203) ([#5210](https://www.github.com/tauri-apps/tauri/pull/5210))\" on 2022-09-30\n  - [9b1a6a1c](https://www.github.com/tauri-apps/tauri/commit/9b1a6a1c02b8d62dd47d9ce42aa05723d7c1b892) fix(core): set correct mimetype for asset protocol streams,  [#5203](https://www.github.com/tauri-apps/tauri/pull/5203) ([#5536](https://www.github.com/tauri-apps/tauri/pull/5536)) on 2022-11-04\n- Disable automatic window tabbing on macOS when the `tabbing_identifier` option is not defined, the window is transparent or does not have decorations.\n  - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19\n- The custom protocol now validates the request URI. This has implications when using the `asset` protocol without the `convertFileSrc` helper, the URL must now use the `asset://localhost/$filePath` format.\n  - [357480f4](https://www.github.com/tauri-apps/tauri/commit/357480f4ae43aa8da99f7ba61ae2ee51b4552c60) feat(core): custom protocol headers on Linux, closes [#4496](https://www.github.com/tauri-apps/tauri/pull/4496) ([#5421](https://www.github.com/tauri-apps/tauri/pull/5421)) on 2022-10-17\n- Escape glob special characters in files/directories when dropping files or using the open/save dialogs.\n  - [4cbdf0fb](https://www.github.com/tauri-apps/tauri/commit/4cbdf0fb1c0de5004eab51c36d5843a9816f18af) fix(core): escape glob characters in drop/dialogs , closes [#5234](https://www.github.com/tauri-apps/tauri/pull/5234) ([#5237](https://www.github.com/tauri-apps/tauri/pull/5237)) on 2022-10-05\n- Properly emit events with object payload.\n  - [79dd6e16](https://www.github.com/tauri-apps/tauri/commit/79dd6e16a7306351e2acf21166506b2876b58a7e) fix(core): properly emit events with object payload, closes [#5482](https://www.github.com/tauri-apps/tauri/pull/5482) ([#5492](https://www.github.com/tauri-apps/tauri/pull/5492)) on 2022-10-27\n- Fixes access to the `WebviewWindow.getByLabel` function in a `tauri://window-created` event listener.\n  - [e00b1e5f](https://www.github.com/tauri-apps/tauri/commit/e00b1e5f94b3f841bf107cc17ee74be9203ea080) fix(core): update metadata before window-created listeners, closes [#5191](https://www.github.com/tauri-apps/tauri/pull/5191) ([#5458](https://www.github.com/tauri-apps/tauri/pull/5458)) on 2022-10-22\n- Fixes resource reading being always rejected by the scope.\n  - [a06dc699](https://www.github.com/tauri-apps/tauri/commit/a06dc6993148f10ff7623c9dcc81f313dd960ad0) fix(core): canonicalize resource dir to fix scope check, closes [#5196](https://www.github.com/tauri-apps/tauri/pull/5196) ([#5218](https://www.github.com/tauri-apps/tauri/pull/5218)) on 2022-09-29\n- Fixes `__TAURI_PATTERN__` object freeze.\n  - [49f06ca4](https://www.github.com/tauri-apps/tauri/commit/49f06ca4b9f1d02933e46bbc50330b84ac81be87) fix: deepfreeze check by prop ([#5407](https://www.github.com/tauri-apps/tauri/pull/5407)) on 2022-10-17\n- Readd the option to create an unfocused window via the `focused` method. The `focus` function has been deprecated.\n  - [4036e15f](https://www.github.com/tauri-apps/tauri/commit/4036e15f5af933bdc0d0913508b5103958afc143) feat(core): reimplement window initial focus flag, closes [#5120](https://www.github.com/tauri-apps/tauri/pull/5120) ([#5338](https://www.github.com/tauri-apps/tauri/pull/5338)) on 2022-10-08\n- Add `hidden_title` option for macOS windows.\n  - [321f3fed](https://www.github.com/tauri-apps/tauri/commit/321f3fed19df40c1223099bce953332b7f00f7a9) feat(macos): `title_bar_style` and `hidden_title` window options, closes [#2663](https://www.github.com/tauri-apps/tauri/pull/2663) ([#3965](https://www.github.com/tauri-apps/tauri/pull/3965)) on 2022-09-30\n- Custom protocol headers are now implemented on Linux when running on webkit2gtk 2.36 or above.\n  - [357480f4](https://www.github.com/tauri-apps/tauri/commit/357480f4ae43aa8da99f7ba61ae2ee51b4552c60) feat(core): custom protocol headers on Linux, closes [#4496](https://www.github.com/tauri-apps/tauri/pull/4496) ([#5421](https://www.github.com/tauri-apps/tauri/pull/5421)) on 2022-10-17\n- Add `App::show()`, `AppHandle::show()`, `App::hide()` and `AppHandle::hide()` for hiding/showing the entire application on macOS.\n  - [39bf895b](https://www.github.com/tauri-apps/tauri/commit/39bf895b73ec6b53f5758815396ba85dda6b9c67) feat(macOS): Add application `show` and `hide` methods ([#3689](https://www.github.com/tauri-apps/tauri/pull/3689)) on 2022-10-03\n- Fix a deadlock when modifying the menu in the `on_menu_event` closure.\n  - [ae65951b](https://www.github.com/tauri-apps/tauri/commit/ae65951bc477126b71816d77424f8167814bbe8d) fix(core): fix deadlock in `on_menu_event`, closes [#5254](https://www.github.com/tauri-apps/tauri/pull/5254) ([#5257](https://www.github.com/tauri-apps/tauri/pull/5257)) on 2022-09-28\n- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28\n- Resolve base system directory in shell scope.\n  - [99fe1c56](https://www.github.com/tauri-apps/tauri/commit/99fe1c562ffcea4089f785c73f4e6706d4ebc16b) fix(core): resolve base dir in shell scope, closes [#5480](https://www.github.com/tauri-apps/tauri/pull/5480) ([#5508](https://www.github.com/tauri-apps/tauri/pull/5508)) on 2022-11-04\n- Added `tabbing_identifier` to the window builder on macOS.\n  - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19\n- Add `title_bar_style` option for macOS windows.\n  - [321f3fed](https://www.github.com/tauri-apps/tauri/commit/321f3fed19df40c1223099bce953332b7f00f7a9) feat(macos): `title_bar_style` and `hidden_title` window options, closes [#2663](https://www.github.com/tauri-apps/tauri/pull/2663) ([#3965](https://www.github.com/tauri-apps/tauri/pull/3965)) on 2022-09-30\n- Added methods to set the system tray title on macOS.\n  - [8f1ace77](https://www.github.com/tauri-apps/tauri/commit/8f1ace77956ac3477826ceb059a191e55b3fff93) feat: expose `set_title` for MacOS tray ([#5182](https://www.github.com/tauri-apps/tauri/pull/5182)) on 2022-09-30\n- Added the `user_agent` option when creating a window.\n  - [a6c94119](https://www.github.com/tauri-apps/tauri/commit/a6c94119d8545d509723b147c273ca5edfe3729f) feat(core): expose user_agent to window config ([#5317](https://www.github.com/tauri-apps/tauri/pull/5317)) on 2022-10-02\n\n## \\[1.1.4]\n\n- Block remote URLs from accessing the IPC.\n  - [58ea0b452](https://www.github.com/tauri-apps/tauri/commit/58ea0b45268dbd46cbac0ebb0887353d057ca767) feat(core): block remote URLs from accessing the IPC on 2023-04-12\n\n## \\[1.1.3]\n\n- Fix the filesystem scope allowing sub-directories of the directory picked by the dialog when `recursive` option was `false`.\n  - [2654c0f4](https://www.github.com/tauri-apps/tauri/commit/2654c0f49da23434d36447d0908fa24e61ff5e4e) Merge pull request from GHSA-6mv3-wm7j-h4w5 on 2022-12-22\n\n## \\[1.1.2]\n\n- Escape glob special characters in files/directories when dropping files or using the open/save dialogs.\n  - [e4dc5bed](https://www.github.com/tauri-apps/tauri/commit/e4dc5bedbb54fbe6e06ab833d7fb7e0cacebad10) fix(core): escape glob characters in drop/dialogs , closes [#5234](https://www.github.com/tauri-apps/tauri/pull/5234) ([#5237](https://www.github.com/tauri-apps/tauri/pull/5237)) on 2022-10-05\n\n## \\[1.1.1]\n\n- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs.\n  - Bumped due to a bump in tauri-utils.\n  - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16\n\n## \\[1.1.0]\n\n- Implement `api::http::ClientBuilder::max_redirections` for the default attohttpc client.\n  - [ba5560b2](https://www.github.com/tauri-apps/tauri/commit/ba5560b2a1a61666d8e0bda31424333815714b2f) feat(core): implement max_redirections for attohttpc, ref [#4795](https://www.github.com/tauri-apps/tauri/pull/4795) ([#4811](https://www.github.com/tauri-apps/tauri/pull/4811)) on 2022-07-31\n- Implement `From<api::process::Command> for std::process::Command`.\n  - [9f1d34c2](https://www.github.com/tauri-apps/tauri/commit/9f1d34c288cbe64f0453cf210bc9488bb42ed19a) feat: implement From<Command> for std::process::Command, closes [#4673](https://www.github.com/tauri-apps/tauri/pull/4673) ([#4836](https://www.github.com/tauri-apps/tauri/pull/4836)) on 2022-08-02\n- Added support to configuration files in TOML format (Tauri.toml file).\n  - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02\n- Enhance `SystemTray::with_icon` to accept `tauri::Icon`.\n  - [964926ff](https://www.github.com/tauri-apps/tauri/commit/964926ff850b82e104d29fec4c8a1d9a16798c06) feat(core): enhance `SystemTray::with_icon` ([#4849](https://www.github.com/tauri-apps/tauri/pull/4849)) on 2022-08-03\n- Fixes CLI parser ignoring inner subcommands.\n  - [dcd50667](https://www.github.com/tauri-apps/tauri/commit/dcd506676c0a15ac4af7705b62574cc2eea3bb43) fix(core): parse inner CLI subcommands, closes [#4688](https://www.github.com/tauri-apps/tauri/pull/4688) ([#4841](https://www.github.com/tauri-apps/tauri/pull/4841)) on 2022-08-02\n- Fix `fs.readDir` recursive option reading symlinked directories that are not allowed by the scope.\n  - [f4121c12](https://www.github.com/tauri-apps/tauri/commit/f4121c128e69b06c3eb5eea14dd2af4720afed49) fix(endpoints/fs/readDir): don't read symlinks that are not allowed b… ([#5123](https://www.github.com/tauri-apps/tauri/pull/5123)) on 2022-09-08\n- Fix typo in invalid state access panic message.\n  - [c7fec3e1](https://www.github.com/tauri-apps/tauri/commit/c7fec3e1ff73f7d857548cab78777a3aaf084c68) fix typo in state.rs ([#4699](https://www.github.com/tauri-apps/tauri/pull/4699)) on 2022-07-25\n- Fixes updater breaking the app icon in Finder.\n  - [58fc1f21](https://www.github.com/tauri-apps/tauri/commit/58fc1f2150b6ddd1b322deb03ca9083222fc3522) fix(updater): blank icon after update on macOS, closes [#4613](https://www.github.com/tauri-apps/tauri/pull/4613) ([#4861](https://www.github.com/tauri-apps/tauri/pull/4861)) on 2022-08-04\n- Implement theme APIs for Linux.\n  - [f21cbecd](https://www.github.com/tauri-apps/tauri/commit/f21cbecdeb3571ac4ad971b9a865ff62a131a176) feat(core): implement theme APIs for Linux ([#4808](https://www.github.com/tauri-apps/tauri/pull/4808)) on 2022-08-02\n- Implement `raw_window_handle::HasRawDisplayHandle` for `App` and `AppHandle`\n  - [0ad9531d](https://www.github.com/tauri-apps/tauri/commit/0ad9531d799d81e1f807000a9d74dfd7998206fe) chore(deps): update tao to 0.13, wry to 0.20, rfd to 0.10, raw-window-handle to 0.5 ([#4804](https://www.github.com/tauri-apps/tauri/pull/4804)) on 2022-07-31\n- Retain command line arguments in `api::process::restart`.\n  - [6218c31e](https://www.github.com/tauri-apps/tauri/commit/6218c31e175d43b59b87bd5b36ee773467566f67) fix(core): retain command line arguments on restart, closes [#4760](https://www.github.com/tauri-apps/tauri/pull/4760) ([#4763](https://www.github.com/tauri-apps/tauri/pull/4763)) on 2022-07-25\n- Added APIs to create a system tray at runtime.\n  - [4d063ae9](https://www.github.com/tauri-apps/tauri/commit/4d063ae9ee9538cd6fa5e01b80070c6edf8eaeb9) feat(core): create system tray at runtime, closes [#2278](https://www.github.com/tauri-apps/tauri/pull/2278) ([#4862](https://www.github.com/tauri-apps/tauri/pull/4862)) on 2022-08-09\n- Add `api::Command::encoding` method to set the stdout/stderr encoding.\n  - [d8cf9f9f](https://www.github.com/tauri-apps/tauri/commit/d8cf9f9fcd617ac24fa418952fd4a32c08804f5c) Command support for specified character encoding, closes [#4644](https://www.github.com/tauri-apps/tauri/pull/4644) ([#4772](https://www.github.com/tauri-apps/tauri/pull/4772)) on 2022-07-28\n- Do not follow redirects when `api::http::ClientBuilder::max_redirections` is `0`.\n  - [d576e8ae](https://www.github.com/tauri-apps/tauri/commit/d576e8ae72b025ca41f96ddf7a885b84f950a4b1) feat(core): do not follow redirects if `max_redirects` is 0 closes [#4795](https://www.github.com/tauri-apps/tauri/pull/4795) ([#4812](https://www.github.com/tauri-apps/tauri/pull/4812)) on 2022-07-31\n- Added the `SystemTrayHandle::destroy` method.\n  - [4d063ae9](https://www.github.com/tauri-apps/tauri/commit/4d063ae9ee9538cd6fa5e01b80070c6edf8eaeb9) feat(core): create system tray at runtime, closes [#2278](https://www.github.com/tauri-apps/tauri/pull/2278) ([#4862](https://www.github.com/tauri-apps/tauri/pull/4862)) on 2022-08-09\n- Added `native-tls-vendored` and `reqwest-native-tls-vendored` Cargo features to compile and statically link to a vendored copy of OpenSSL on Linux.\n  - [331f3460](https://www.github.com/tauri-apps/tauri/commit/331f3460027614738ddbbbbcd04bfc59a349f3de) feat(core): add option to use vendored openssl, closes [#4470](https://www.github.com/tauri-apps/tauri/pull/4470) ([#4809](https://www.github.com/tauri-apps/tauri/pull/4809)) on 2022-08-02\n- Update windows to 0.39.0 and webview2-com to 0.19.1.\n  - [e6d9b670](https://www.github.com/tauri-apps/tauri/commit/e6d9b670b0b314ed667b0e164f2c8d27048e678f) refactor: remove unneeded focus code ([#5065](https://www.github.com/tauri-apps/tauri/pull/5065)) on 2022-09-03\n- Add `exists` function to the fs module.\n  - [3c62dbc9](https://www.github.com/tauri-apps/tauri/commit/3c62dbc902c904d35a7472ce72a969084c95fbbe) feat(api): Add `exists` function to the fs module. ([#5060](https://www.github.com/tauri-apps/tauri/pull/5060)) on 2022-09-15\n\n## \\[1.0.9]\n\n- Block remote URLs from accessing the IPC.\n  - [fa90214b0](https://www.github.com/tauri-apps/tauri/commit/fa90214b052b1a5d38d54fbf1ca422b4c37cfd1f) feat(core): block remote URLs from accessing the IPC on 2023-04-12\n\n## \\[1.0.8]\n\n- Fix the filesystem scope allowing sub-directories of the directory picked by the dialog when `recursive` option was `false`.\n  - [f0602e7c](https://www.github.com/tauri-apps/tauri/commit/f0602e7c294245ab6ef6fbf2a976ef398340ef58) Merge pull request from GHSA-6mv3-wm7j-h4w5 on 2022-12-22\n\n## \\[1.0.7]\n\n- Escape glob special characters in files/directories when dropping files or using the open/save dialogs.\n  - [bcd9dc7f](https://www.github.com/tauri-apps/tauri/commit/bcd9dc7f859fa7e65fea5de835fa938ca1368eaf) fix(core): escape glob characters in drop/dialogs , closes [#5234](https://www.github.com/tauri-apps/tauri/pull/5234) ([#5237](https://www.github.com/tauri-apps/tauri/pull/5237)) on 2022-11-08\n\n## \\[1.0.6]\n\n- Fix `fs.readDir` recursive option reading symlinked directories that are not allowed by the scope.\n  - [bb178829](https://www.github.com/tauri-apps/tauri/commit/bb178829086e80916f9be190f02d83bc25802799) fix(endpoints/fs/readDir): don't read symlinks that are not allowed b… ([#5123](https://www.github.com/tauri-apps/tauri/pull/5123)) on 2022-09-08\n\n## \\[1.0.5]\n\n- Escape the MSI file path when running msiexec via powershell.\n  - [9af43134](https://www.github.com/tauri-apps/tauri/commit/9af43134e1e58369907281024d31bdb4d16ee6f6) fix(updater): escape MSI path ([#4737](https://www.github.com/tauri-apps/tauri/pull/4737)) on 2022-07-22\n\n## \\[1.0.4]\n\n- Reduce the amount of allocations when converting cases.\n  - [bc370e32](https://www.github.com/tauri-apps/tauri/commit/bc370e326810446e15b1f50fb962b980114ba16b) feat: reduce the amount of `heck`-related allocations ([#4634](https://www.github.com/tauri-apps/tauri/pull/4634)) on 2022-07-11\n\n## \\[1.0.3]\n\n- `tauri::Builder` will now include a default menu for macOS without explicitly using `Menu::os_default`, you can still override it through `tauri::Builder::menu` or remove it using `tauri::Builder::enable_macos_default_menu(false)`.\n  - [91055883](https://www.github.com/tauri-apps/tauri/commit/9105588373cc8401bd9ad79bdef26f509b2d76b7) feat: add implicit default menu for macOS only, closes [#4551](https://www.github.com/tauri-apps/tauri/pull/4551) ([#4570](https://www.github.com/tauri-apps/tauri/pull/4570)) on 2022-07-04\n- Use `toString()` on message/confirm/ask dialogs title and message values.\n  - [b8cd2a79](https://www.github.com/tauri-apps/tauri/commit/b8cd2a7993cd2aa5b71b30c545b3307245d254bf) feat(api): call `toString()` on dialog title and message, closes [#4583](https://www.github.com/tauri-apps/tauri/pull/4583) ([#4588](https://www.github.com/tauri-apps/tauri/pull/4588)) on 2022-07-04\n- Fix stack overflow on Windows on commands by changing the implementation of the `async_runtime::spawn` method.\n  - [7e3ac847](https://www.github.com/tauri-apps/tauri/commit/7e3ac8475cfa146f80e13cd4e3cdf82502018d9a) fix(core): command stack overflow on Windows, closes [#4548](https://www.github.com/tauri-apps/tauri/pull/4548) ([#4562](https://www.github.com/tauri-apps/tauri/pull/4562)) on 2022-07-03\n- Emits RunEvent::Exit prior to killing child processes managed by Tauri, allowing graceful shutdown of sidecar binaries.\n  - [34879f73](https://www.github.com/tauri-apps/tauri/commit/34879f73446e218fd08d0d079c086fa556a08b4e) fix: allow graceful shutdown of sidecar commands on exit ([#4561](https://www.github.com/tauri-apps/tauri/pull/4561)) on 2022-07-03\n- Added option to disable tray menu on left click on macOS.\n  - [f8a3becb](https://www.github.com/tauri-apps/tauri/commit/f8a3becb287942db7f7b551b5db6aeb5a2e939ee) feat(core): add option to disable tray menu on left click, closes [#4584](https://www.github.com/tauri-apps/tauri/pull/4584) ([#4587](https://www.github.com/tauri-apps/tauri/pull/4587)) on 2022-07-05\n- Only run the updater default dialog mode in supported platforms or development mode.\n  - [e29fff25](https://www.github.com/tauri-apps/tauri/commit/e29fff2566bf130e9f9cae432edba2f842607516) fix(updater): do not run in dialog mode on .deb, closes [#4573](https://www.github.com/tauri-apps/tauri/pull/4573) ([#4577](https://www.github.com/tauri-apps/tauri/pull/4577)) on 2022-07-05\n- Configure the updater to relaunch after installing the update on Windows.\n  - [0fa74534](https://www.github.com/tauri-apps/tauri/commit/0fa745344e0667434ebf3011a8b9fd776dfc5f59) feat(updater): relaunch on Windows, closes [#4220](https://www.github.com/tauri-apps/tauri/pull/4220) ([#4568](https://www.github.com/tauri-apps/tauri/pull/4568)) on 2022-07-03\n\n## \\[1.0.2]\n\n- Fixes check for local URL when an external URL is provided to the window and it is based on the configured devPath.\n  - [2e74d20e](https://www.github.com/tauri-apps/tauri/commit/2e74d20e834c0cb8c19fed25f8e7968f758ca8b1) fix(core): check whether external url is local, ref [#4449](https://www.github.com/tauri-apps/tauri/pull/4449) ([#4536](https://www.github.com/tauri-apps/tauri/pull/4536)) on 2022-06-30\n\n## \\[1.0.1]\n\n- Added `fn new` constructors for `PhysicalSize`, `LogicalSize`, `PhysicalPosition` and `LogicalPosition` and missing conversion methods.\n  - [c7d13a1c](https://www.github.com/tauri-apps/tauri/commit/c7d13a1c60cdbe0c42834ea059321d7a3a7f01a0) feat(core): add missing methods to the dpi module ([#4393](https://www.github.com/tauri-apps/tauri/pull/4393)) on 2022-06-19\n- Set the bundle name and app metadata in the Info.plist file in development mode.\n  - [38f5db6e](https://www.github.com/tauri-apps/tauri/commit/38f5db6e6a8b496b50d486db6fd8204266de3a69) feat(codegen): fill app metadata in development Info.plist on 2022-06-21\n- Set the application icon in development mode on macOS.\n  - [307c2ebf](https://www.github.com/tauri-apps/tauri/commit/307c2ebfb68238dacab6088f9c6ba310c727c68c) feat(core): set macOS app icon in development ([#4385](https://www.github.com/tauri-apps/tauri/pull/4385)) on 2022-06-19\n- Fixes the error message when using the `window.unminimize` API without enabling it in the allowlist.\n  - [cbceb7d6](https://www.github.com/tauri-apps/tauri/commit/cbceb7d6cf7c9ee8c093e81d8569285ef3ca5fe3) fix: some typos ([#4403](https://www.github.com/tauri-apps/tauri/pull/4403)) on 2022-06-19\n- Initialize Tauri script when `devPath` is an external URL with path.\n  - [079b1cc0](https://www.github.com/tauri-apps/tauri/commit/079b1cc06e1e5437686bac9049d5ac569c3f42df) fix(core): properly get external URL origin, closes [#4414](https://www.github.com/tauri-apps/tauri/pull/4414) ([#4417](https://www.github.com/tauri-apps/tauri/pull/4417)) on 2022-06-21\n- Fixes deadlock when a plugin window ready event needs to block the thread waiting on the event loop.\n  - [9d33d093](https://www.github.com/tauri-apps/tauri/commit/9d33d09341fd995740149f92387b88120e33dcad) fix(core): deadlock on plugin webview ready hook ([#4462](https://www.github.com/tauri-apps/tauri/pull/4462)) on 2022-06-24\n- Adjust the updater to fallback to `$HOME/.cache` or the current working directory as temp directory if the system default is in a different mount point.\n  - [fd125f76](https://www.github.com/tauri-apps/tauri/commit/fd125f76d768099dc3d4b2d4114349ffc31ffac9) fix(updater): fallback if tmp is on different mount point, closes [#4500](https://www.github.com/tauri-apps/tauri/pull/4500) ([#4504](https://www.github.com/tauri-apps/tauri/pull/4504)) on 2022-06-28\n- Properly fill the origin window when using `emit_to` and `emit_all` from `Window`.\n  - [643ae846](https://www.github.com/tauri-apps/tauri/commit/643ae846d86108b00cec748cd02a307b5badba18) fix: fire window-specific event on Window emit_to/emit_all, closes [#4493](https://www.github.com/tauri-apps/tauri/pull/4493) ([#4498](https://www.github.com/tauri-apps/tauri/pull/4498)) on 2022-06-28\n- Implement `raw_window_handle::HasRawWindowHandle` on Linux.\n  - [3efbc67f](https://www.github.com/tauri-apps/tauri/commit/3efbc67f7469ce65a2d9ea4ff2b60b51d2a36aa5) feat: implement `raw_window_handle` on Linux ([#4469](https://www.github.com/tauri-apps/tauri/pull/4469)) on 2022-06-26\n- Added `on_drop` hook to the `plugin::Builder`.\n  - [be4bb391](https://www.github.com/tauri-apps/tauri/commit/be4bb391a9bcd76dd949c001b1ace11684a8c6dc) feat: add `AppHandle::remove_plugin` and plugin `on_drop`, closes [#4361](https://www.github.com/tauri-apps/tauri/pull/4361) ([#4443](https://www.github.com/tauri-apps/tauri/pull/4443)) on 2022-06-23\n- Refactored the `tauri-runtime-wry` plugin interface.\n  - [e39e2999](https://www.github.com/tauri-apps/tauri/commit/e39e2999e0ab1843a8195ba83aea3d6de705c3d8) refactor(tauri-runtime-wry): enhance plugin interface ([#4476](https://www.github.com/tauri-apps/tauri/pull/4476)) on 2022-06-27\n- Added `AppHandle::remove_plugin`.\n  - [be4bb391](https://www.github.com/tauri-apps/tauri/commit/be4bb391a9bcd76dd949c001b1ace11684a8c6dc) feat: add `AppHandle::remove_plugin` and plugin `on_drop`, closes [#4361](https://www.github.com/tauri-apps/tauri/pull/4361) ([#4443](https://www.github.com/tauri-apps/tauri/pull/4443)) on 2022-06-23\n- The theme API is now implemented on macOS 10.14+.\n  - [6d94ce42](https://www.github.com/tauri-apps/tauri/commit/6d94ce42353204a02fe9c82ed397d349439f75ef) feat(core): theme is now implemented on macOS ([#4380](https://www.github.com/tauri-apps/tauri/pull/4380)) on 2022-06-17\n- Suppress unused variable warning in release builds.\n  - [45981851](https://www.github.com/tauri-apps/tauri/commit/45981851e35119266c1a079e1ff27a39f1fdfaed) chore(lint): unused variable warnings for release builds ([#4411](https://www.github.com/tauri-apps/tauri/pull/4411)) on 2022-06-22\n- Update tao to 0.12 and wry to 0.19.\n  - [f6edc6df](https://www.github.com/tauri-apps/tauri/commit/f6edc6df29b1c45b483fa87c481a3b95730b131b) chore(deps): update tao to 0.12, wry to 0.19, closes [#3220](https://www.github.com/tauri-apps/tauri/pull/3220) ([#4502](https://www.github.com/tauri-apps/tauri/pull/4502)) on 2022-06-28\n- Added `Notification::notify` API behind the `windows7-compat` Cargo feature, which includes Windows 7 support.\n  - [57039fb2](https://www.github.com/tauri-apps/tauri/commit/57039fb2166571de85271b014a8711a29f06be1a) fix(core): add windows 7 notification support ([#4491](https://www.github.com/tauri-apps/tauri/pull/4491)) on 2022-06-28\n\n## \\[1.0.0]\n\n- Allow choosing multiple folders in `dialog.open`.\n  - [4e51dce6](https://www.github.com/tauri-apps/tauri/commit/4e51dce6ca21c7664de779bc78a04be1051371f7) fix: dialog open supports multiple dirs, fixes [#4091](https://www.github.com/tauri-apps/tauri/pull/4091) ([#4354](https://www.github.com/tauri-apps/tauri/pull/4354)) on 2022-06-15\n- Upgrade to `stable`!\n  - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15\n\n## \\[1.0.0-rc.17]\n\n- Add `Menu::os_default` which will create a menu filled with default menu items and submenus.\n  - [4c4acc30](https://www.github.com/tauri-apps/tauri/commit/4c4acc3094218dd9cee0f1ad61810c979e0b41fa) feat: implement `Default` for `Menu`, closes [#2398](https://www.github.com/tauri-apps/tauri/pull/2398) ([#4291](https://www.github.com/tauri-apps/tauri/pull/4291)) on 2022-06-15\n\n## \\[1.0.0-rc.16]\n\n- **Breaking change:** The `TrayIcon` enum has been removed and now `Icon` is used instead.\n  This allows you to use more image formats and use embedded icons on Linux.\n  - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14\n\n## \\[1.0.0-rc.15]\n\n- Fixes filesystem scope check when using the HTTP API to upload files.\n  - [8ce5b762](https://www.github.com/tauri-apps/tauri/commit/8ce5b7626478db5c4767e894d005fafb79aa5a7f) fix: scope check when using the HTTP API to upload files closes [#4312](https://www.github.com/tauri-apps/tauri/pull/4312) on 2022-06-10\n- Fixes a memory leak in the command system.\n  - [f72cace3](https://www.github.com/tauri-apps/tauri/commit/f72cace36821dc675a6d25268ae85a21bdbd6296) fix: never remove ipc callback & mem never be released ([#4274](https://www.github.com/tauri-apps/tauri/pull/4274)) on 2022-06-05\n- Fixes the `Content-Type` header value when sending multipart requests using the `reqwest-client` feature.\n  - [f6205afc](https://www.github.com/tauri-apps/tauri/commit/f6205afc0d0e419ecb56b9b6b04bd0a0cc9f62ca) fix(core): wrong Content-Type when using reqwest's multipart, ref [#4312](https://www.github.com/tauri-apps/tauri/pull/4312) on 2022-06-10\n- Kill sidecar processes on app exit even when only the `shell-sidecar` feature is enabled.\n  - [6ba91272](https://www.github.com/tauri-apps/tauri/commit/6ba9127298632531b64d2831bccec6d22ef6c874) Fix: sidecar cleanup when only `shell-sidecar` is enabled ([#4254](https://www.github.com/tauri-apps/tauri/pull/4254)) on 2022-06-04\n- Fixes a crash when a request is made to `https://tauri.$URL` on Windows where `$URL` is not `localhost/**` e.g. `https://tauri.app`.\n  - [74457222](https://www.github.com/tauri-apps/tauri/commit/74457222b47221f08388f528a7d52133b6734af6) fix(core): handle requests to `https://tauri.*` on Windows ([#4270](https://www.github.com/tauri-apps/tauri/pull/4270)) on 2022-06-05\n- Set notification icon to app icon on Linux.\n  - [235e448d](https://www.github.com/tauri-apps/tauri/commit/235e448defd8271739804d9b005ffee9c149dd8e) fix: add a default icon to notifications on linux ([#4283](https://www.github.com/tauri-apps/tauri/pull/4283)) on 2022-06-09\n- **Breaking change:** Revert the window creation to be blocking in the main thread. This ensures the window is created before using other methods, but has an issue on Windows where the program deadlocks when creating a window in a Tauri command if it is not `async`. The documentation now states that commands must be `async` in other to prevent it until the issue is fixed in Webview2.\n  - [69ae6f14](https://www.github.com/tauri-apps/tauri/commit/69ae6f14943f94285f4279c3a92f9003f74aacff) refactor(window): block main thread when creating a new window ([#4298](https://www.github.com/tauri-apps/tauri/pull/4298)) on 2022-06-08\n- No longer ask for permission to send notifications and always allow it.\n  - [f482b094](https://www.github.com/tauri-apps/tauri/commit/f482b0942276e9402ab3725957535039bacb4fef) fix: remove notification permission prompt ([#4302](https://www.github.com/tauri-apps/tauri/pull/4302)) on 2022-06-09\n- **Breaking change:** Removed the `settings` module.\n  - [f482b094](https://www.github.com/tauri-apps/tauri/commit/f482b0942276e9402ab3725957535039bacb4fef) fix: remove notification permission prompt ([#4302](https://www.github.com/tauri-apps/tauri/pull/4302)) on 2022-06-09\n- **Breaking change**: Removed the `gtk-tray` and `ayatana-tray` Cargo features.\n  - [6216eb49](https://www.github.com/tauri-apps/tauri/commit/6216eb49e72863bfb6d4c9edb8827b21406ac393) refactor(core): drop `ayatana-tray` and `gtk-tray` Cargo features ([#4247](https://www.github.com/tauri-apps/tauri/pull/4247)) on 2022-06-02\n- Call `preventDefault()` in the mousedown event handler for `[data-tauri-drag-region]` elements.\n  - [a0e20621](https://www.github.com/tauri-apps/tauri/commit/a0e20621f5369682700daa234ec1f4df0c3c4d28) fix: preventDefault mousedown on data-tauri-drag-region, closes [#4059](https://www.github.com/tauri-apps/tauri/pull/4059) on 2022-06-13\n- Set permission to `0o700` for the tmp folder used to move the current AppImage on the updater process.\n  - [b77877fd](https://www.github.com/tauri-apps/tauri/commit/b77877fd2c643c810659fd059cb1bca5b2c68238) fix(updater): set tmp folder permissions ([#4311](https://www.github.com/tauri-apps/tauri/pull/4311)) on 2022-06-12\n\n## \\[1.0.0-rc.14]\n\n- **Breaking change:** `PackageInfo::version` is now a `semver::Version` instead of a `String`.\n  - [2badbd2d](https://www.github.com/tauri-apps/tauri/commit/2badbd2d7ed51bf33c1b547b4c837b600574bd4a) refactor: force semver versions, change updater `should_install` sig ([#4215](https://www.github.com/tauri-apps/tauri/pull/4215)) on 2022-05-25\n  - [a7388e23](https://www.github.com/tauri-apps/tauri/commit/a7388e23c3b9019d48b078cae00a75c74d74d11b) fix(ci): adjust change file to include tauri-utils and tauri-codegen on 2022-05-27\n- **Breaking change**: `UpdateBuilder::should_update` now takes the current version as a `semver::Version` and a `RemoteRelease` struct, allowing you to check other release fields.\n  - [2badbd2d](https://www.github.com/tauri-apps/tauri/commit/2badbd2d7ed51bf33c1b547b4c837b600574bd4a) refactor: force semver versions, change updater `should_install` sig ([#4215](https://www.github.com/tauri-apps/tauri/pull/4215)) on 2022-05-25\n- **Breaking change:** The `tauri::UpdaterEvent::UpdateEvent` date field is now an `Option<time::OffsetDateTime>`.\n  - [ac7656ab](https://www.github.com/tauri-apps/tauri/commit/ac7656ab19ebeda1955698ff7a16199d7055d640) refactor(updater): strong type for the `pub_date` field, ref [#4162](https://www.github.com/tauri-apps/tauri/pull/4162) ([#4218](https://www.github.com/tauri-apps/tauri/pull/4218)) on 2022-05-25\n- **Breaking change:** The updater response `pub_date` now must be a valid RFC 3339 string.\n  - [ac7656ab](https://www.github.com/tauri-apps/tauri/commit/ac7656ab19ebeda1955698ff7a16199d7055d640) refactor(updater): strong type for the `pub_date` field, ref [#4162](https://www.github.com/tauri-apps/tauri/pull/4162) ([#4218](https://www.github.com/tauri-apps/tauri/pull/4218)) on 2022-05-25\n\n## \\[1.0.0-rc.13]\n\n- Fix `` cannot find macro `message_dialog`  `` error.\n  - [80458a03](https://www.github.com/tauri-apps/tauri/commit/80458a031b15c83019f11a6c222a2993e16d5dc3) Fix `message_dialog` flag ([#4204](https://www.github.com/tauri-apps/tauri/pull/4204)) on 2022-05-25\n\n## \\[1.0.0-rc.12]\n\n- Expose option to set the dialog type.\n  - [f46175d5](https://www.github.com/tauri-apps/tauri/commit/f46175d5d46fa3eae66ad2415a0eb1efb7d31da2) feat(core): expose option to set dialog type, closes [#4183](https://www.github.com/tauri-apps/tauri/pull/4183) ([#4187](https://www.github.com/tauri-apps/tauri/pull/4187)) on 2022-05-21\n- Expose `title` option in the message dialog API.\n  - [ae99f991](https://www.github.com/tauri-apps/tauri/commit/ae99f991674d77c322a2240d10ed4b78ed2f4d4b) feat(core): expose message dialog's title option, ref [#4183](https://www.github.com/tauri-apps/tauri/pull/4183) ([#4186](https://www.github.com/tauri-apps/tauri/pull/4186)) on 2022-05-21\n- Immediately create windows when using `tauri::App` as manager.\n  - [52d17754](https://www.github.com/tauri-apps/tauri/commit/52d177543ab5d0b316628ef30c2a32f303bc451d) fix(core): immediately create window when using tauri::App, closes [#4170](https://www.github.com/tauri-apps/tauri/pull/4170) ([#4172](https://www.github.com/tauri-apps/tauri/pull/4172)) on 2022-05-21\n- Account the monitor position when centering a window.\n  - [a7a9fde1](https://www.github.com/tauri-apps/tauri/commit/a7a9fde16fb7c35d48d4f97e83ff95b8baf9e090) fix(core): account for monitor position when centering window ([#4166](https://www.github.com/tauri-apps/tauri/pull/4166)) on 2022-05-21\n- Allow the canonical, absolute form of a path for the filesystem scope on Windows if `std::fs::canonicalize` returns a path, fallback to `\\\\?\\$PATH`.\n  - [78f2565e](https://www.github.com/tauri-apps/tauri/commit/78f2565e14a5a8292045200967a36d6a40899721) fix: allow return value of fs::canonicalize on fs scope, closes [#4130](https://www.github.com/tauri-apps/tauri/pull/4130) ([#4188](https://www.github.com/tauri-apps/tauri/pull/4188)) on 2022-05-21\n- Fixes updater documentation not showing on docs.rs.\n  - [55892c35](https://www.github.com/tauri-apps/tauri/commit/55892c35f5c60836a8b7244e1660a83a01a4bc37) fix(core): updater documentation not showing on docs.rs ([#4190](https://www.github.com/tauri-apps/tauri/pull/4190)) on 2022-05-22\n- Fixes HTTP timeout not working on Windows when using the `attohttpc` client.\n  - [d99c5d58](https://www.github.com/tauri-apps/tauri/commit/d99c5d583bdb1429966283cc5cd4a977cf0770b2) fix(core): HTTP timeout not working on Windows, closes [#4050](https://www.github.com/tauri-apps/tauri/pull/4050) ([#4185](https://www.github.com/tauri-apps/tauri/pull/4185)) on 2022-05-21\n- Update `windows-rs` to `0.37.0`, which requires Rust 1.61.0+.\n  - [2326be39](https://www.github.com/tauri-apps/tauri/commit/2326be39821890cdd4de76e7029a531424dcb26f) feat(core): update windows-rs to 0.37.0 ([#4199](https://www.github.com/tauri-apps/tauri/pull/4199)) on 2022-05-24\n- **Breaking change:** The `WindowBuilder` struct now has a lifetime annotation `WindowBuilder<R: Runtime, 'a>`.\n  - [52d17754](https://www.github.com/tauri-apps/tauri/commit/52d177543ab5d0b316628ef30c2a32f303bc451d) fix(core): immediately create window when using tauri::App, closes [#4170](https://www.github.com/tauri-apps/tauri/pull/4170) ([#4172](https://www.github.com/tauri-apps/tauri/pull/4172)) on 2022-05-21\n\n## \\[1.0.0-rc.11]\n\n- Added the `App::get_cli_matches` helper function.\n  - [617f1144](https://www.github.com/tauri-apps/tauri/commit/617f1144f3f5c4a88e229ff410a511aa72795eef) feat(core): add `App::get_cli_matches` helper ref [#4145](https://www.github.com/tauri-apps/tauri/pull/4145) on 2022-05-17\n- Fixes `fileDropEnabled` option not working.\n  - [706fcbd3](https://www.github.com/tauri-apps/tauri/commit/706fcbd39c9c08f58da12f874199bd0c06016fb2) fix(core): fileDropEnabled option is not working when creating a new WebviewWindow ([#4146](https://www.github.com/tauri-apps/tauri/pull/4146)) on 2022-05-18\n- Prepare window icon and menu even when loading remote URLs. Previously it was behind a `is local URL?` condition.\n  - [25aa4347](https://www.github.com/tauri-apps/tauri/commit/25aa4347b3a85c66d445e09f864b7fe6c140a0f9) fix(core): prepare window icon and menu on remote URLs, closes [#4131](https://www.github.com/tauri-apps/tauri/pull/4131) ([#4140](https://www.github.com/tauri-apps/tauri/pull/4140)) on 2022-05-16\n- Fix `.mjs` not being recognised as a file extension for JavaScript files (`text/javascript`).\n  - [45c45253](https://www.github.com/tauri-apps/tauri/commit/45c45253866ce0de317a6a547af3ea0434d4bcac) fix: add mjs mime type (fix: [#4098](https://www.github.com/tauri-apps/tauri/pull/4098)) ([#4108](https://www.github.com/tauri-apps/tauri/pull/4108)) on 2022-05-13\n- Added `PathResolver::resolve_resource` API.\n  - [e35aaebc](https://www.github.com/tauri-apps/tauri/commit/e35aaebc309f5796cf6f380d1ac7d5a8094b9033) feat(core): add `PathResolver::resolve_resource` API ([#4116](https://www.github.com/tauri-apps/tauri/pull/4116)) on 2022-05-13\n- Allow configuring the display options for the MSI execution allowing quieter updates.\n  - [9f2c3413](https://www.github.com/tauri-apps/tauri/commit/9f2c34131952ea83c3f8e383bc3cec7e1450429f) feat(core): configure msiexec display options, closes [#3951](https://www.github.com/tauri-apps/tauri/pull/3951) ([#4061](https://www.github.com/tauri-apps/tauri/pull/4061)) on 2022-05-15\n\n## \\[1.0.0-rc.10]\n\n- Update wry to 0.16.2 and webkit2gtk to 0.18.0.\n  - [71a553b7](https://www.github.com/tauri-apps/tauri/commit/71a553b715312e2bcceb963c83e42cffca7a63bc) chore(deps): update wry to 0.16.2, webkit2gtk to 0.18.0 ([#4099](https://www.github.com/tauri-apps/tauri/pull/4099)) on 2022-05-10\n- Adds the `App#wry_plugin` API to inject a plugin for the wry integration.\n  - [c8e0e5b9](https://www.github.com/tauri-apps/tauri/commit/c8e0e5b97d542e549b37be08b545515c862af0e5) feat(tauri-runtime-wry): add plugin API ([#4094](https://www.github.com/tauri-apps/tauri/pull/4094)) on 2022-05-10\n\n## \\[1.0.0-rc.9]\n\n- The `dangerous_allow_asset_csp_modification` configuration value has been changed to allow a list of CSP directives to disable.\n  - [164078c0](https://www.github.com/tauri-apps/tauri/commit/164078c0b719ccbc12e956fecf8a7d4a3c5044e1) feat: allow limiting dangerousDisableAssetCspModification, closes [#3831](https://www.github.com/tauri-apps/tauri/pull/3831) ([#4021](https://www.github.com/tauri-apps/tauri/pull/4021)) on 2022-05-02\n- The file drop event payloads are now percent-decoded.\n  - [a0ecd81a](https://www.github.com/tauri-apps/tauri/commit/a0ecd81a934e1aa8935151a74cad686786054204) fix(core): percent decode file drop payloads, closes [#4034](https://www.github.com/tauri-apps/tauri/pull/4034) ([#4035](https://www.github.com/tauri-apps/tauri/pull/4035)) on 2022-05-03\n- Fix dialog crash on macOS when the `default_path` value is just the file name.\n  - [d31167c5](https://www.github.com/tauri-apps/tauri/commit/d31167c520e4e5ea5c75518c180574f5fffc1a40) fix(core): dialog crashing on macOS when the parent is empty ([#4028](https://www.github.com/tauri-apps/tauri/pull/4028)) on 2022-05-02\n- Fixes the `title` option being ignored in the dialog API endpoints.\n  - [220e7460](https://www.github.com/tauri-apps/tauri/commit/220e7460148df476171579878c3cfffcdb1423d8) fix(core): set dialog title via API, closes [#4029](https://www.github.com/tauri-apps/tauri/pull/4029) ([#4030](https://www.github.com/tauri-apps/tauri/pull/4030)) on 2022-05-02\n- Fixes nested isolation iframe injection.\n  - [022eed46](https://www.github.com/tauri-apps/tauri/commit/022eed46675976e8dfe5f352a875754b4bd78131) fix(core): nested isolation iframes, closes [#4015](https://www.github.com/tauri-apps/tauri/pull/4015) ([#4020](https://www.github.com/tauri-apps/tauri/pull/4020)) on 2022-05-01\n- Deserialize numeric values (seconds) in the http API `ClientBuilder.connect_timeout` and `HttpRequestBuilder.timeout` fields.\n  - [f3c5ca89](https://www.github.com/tauri-apps/tauri/commit/f3c5ca89e79d429183c4e15a9e7cebada2b493a0) fix(core): http api `connect_timeout` deserialization, closes [#4004](https://www.github.com/tauri-apps/tauri/pull/4004) ([#4006](https://www.github.com/tauri-apps/tauri/pull/4006)) on 2022-04-29\n- Fix updater dialog removing single and double quotes from the release notes\n  - [0180dcc8](https://www.github.com/tauri-apps/tauri/commit/0180dcc812bacb78822bc0f97a3202633821dbce) fix(updater): remove single\\&double quotes escaping in updater dialog … ([#4047](https://www.github.com/tauri-apps/tauri/pull/4047)) on 2022-05-04\n- Expose methods to access the underlying native handles of the webview.\n  - [c82b4761](https://www.github.com/tauri-apps/tauri/commit/c82b4761e1660592472dc55308ad69d9efc5855b) feat(core): expose `with_webview` API to access the platform webview ([#4058](https://www.github.com/tauri-apps/tauri/pull/4058)) on 2022-05-04\n\n## \\[1.0.0-rc.8]\n\n- **Breaking change:** Removed the `ayatana-tray` from the default features. You must select one of `ayatana-tray` and `gtk-tray` to use system tray on Linux.\n  - [62cdb2b3](https://www.github.com/tauri-apps/tauri/commit/62cdb2b307534cf1cb69fdb8688f7c0f3176f41b) refactor(tauri): remove ayatana-tray from the default features ([#3976](https://www.github.com/tauri-apps/tauri/pull/3976)) on 2022-04-26\n- Re-export the `GlobalShortcutManager` when the `global-shortcut` feature is enabled.\n  - [62cdb2b3](https://www.github.com/tauri-apps/tauri/commit/62cdb2b307534cf1cb69fdb8688f7c0f3176f41b) refactor(tauri): remove ayatana-tray from the default features ([#3976](https://www.github.com/tauri-apps/tauri/pull/3976)) on 2022-04-26\n- Fixes `docs.rs` documentation build.\n  - [dd94917b](https://www.github.com/tauri-apps/tauri/commit/dd94917b619f22849a7487805315c5306e4847b8) fix(tauri): docs.rs build error ([#3974](https://www.github.com/tauri-apps/tauri/pull/3974)) on 2022-04-26\n\n## \\[1.0.0-rc.7]\n\n- **Breaking change:** Removed `tauri::api::file::ArchiveFormat::Plain`.\n  - [f7d3d93b](https://www.github.com/tauri-apps/tauri/commit/f7d3d93b625ffc8bccf8068793f44af2f9a4f997) refactor(core): improve performance of the `extract` API ([#3963](https://www.github.com/tauri-apps/tauri/pull/3963)) on 2022-04-25\n- Fallback to `{path}.html` when `{path}` is not found in the Tauri custom protocol handler.\n  - [7864d41d](https://www.github.com/tauri-apps/tauri/commit/7864d41de8650f20fa73cefbddd78381b9d8aa11) feat(core): fallback to `{path}.html` in Tauri protocol loader ref [#3887](https://www.github.com/tauri-apps/tauri/pull/3887) ([#3939](https://www.github.com/tauri-apps/tauri/pull/3939)) on 2022-04-21\n- **Breaking change:** Use ayatana-appindicator for Linux system tray by default. Use the `gtk-tray` Cargo feature to use `libappindicator` instead.\n  - [f2a30d8b](https://www.github.com/tauri-apps/tauri/commit/f2a30d8bc54fc3ba49e16f69a413eca5f61a9b1f) refactor(core): use ayatana appindicator by default, keep option to use gtk ([#3916](https://www.github.com/tauri-apps/tauri/pull/3916)) on 2022-04-19\n- Reduce the amount of generated code for the API endpoints.\n  - [c23f139b](https://www.github.com/tauri-apps/tauri/commit/c23f139ba86628fe0217a966bc8676afe7202a05) perf(core): improve binary size with api enum serde refactor ([#3952](https://www.github.com/tauri-apps/tauri/pull/3952)) on 2022-04-24\n- \\**Breaking change::* Added the `clipboard` Cargo feature.\n  - [24e4ff20](https://www.github.com/tauri-apps/tauri/commit/24e4ff208ee0fe1a4cc5b10667ea0922ac63dfb5) refactor(core): add clipboard Cargo feature, enhancing binary size ([#3957](https://www.github.com/tauri-apps/tauri/pull/3957)) on 2022-04-24\n- **Breaking change:** The process Command API stdio lines now includes the trailing `\\r`.\n  - [b5622882](https://www.github.com/tauri-apps/tauri/commit/b5622882cf3748e1e5a90915f415c0cd922aaaf8) fix(cli): exit on non-compilation Cargo errors, closes [#3930](https://www.github.com/tauri-apps/tauri/pull/3930) ([#3942](https://www.github.com/tauri-apps/tauri/pull/3942)) on 2022-04-22\n- Expose Window cursor APIs `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position`.\n  - [c54ddfe9](https://www.github.com/tauri-apps/tauri/commit/c54ddfe9338e7eb90b4d5b02dfde687d432d5bc1) feat: expose window cursor APIs, closes [#3888](https://www.github.com/tauri-apps/tauri/pull/3888) [#3890](https://www.github.com/tauri-apps/tauri/pull/3890) ([#3935](https://www.github.com/tauri-apps/tauri/pull/3935)) on 2022-04-21\n- **Breaking change:** The `tauri::api::file::Extract#extract_file` function has been moved to `tauri::api::file::Entry#extract`.\n  - [f7d3d93b](https://www.github.com/tauri-apps/tauri/commit/f7d3d93b625ffc8bccf8068793f44af2f9a4f997) refactor(core): improve performance of the `extract` API ([#3963](https://www.github.com/tauri-apps/tauri/pull/3963)) on 2022-04-25\n- **Breaking change:** The `tauri::api::file::Extract#files` function has been renamed to `with_files` for performance reasons.\n  - [f7d3d93b](https://www.github.com/tauri-apps/tauri/commit/f7d3d93b625ffc8bccf8068793f44af2f9a4f997) refactor(core): improve performance of the `extract` API ([#3963](https://www.github.com/tauri-apps/tauri/pull/3963)) on 2022-04-25\n- Improved the performance of the `tauri::api::fs::Extract` API.\n  - [f7d3d93b](https://www.github.com/tauri-apps/tauri/commit/f7d3d93b625ffc8bccf8068793f44af2f9a4f997) refactor(core): improve performance of the `extract` API ([#3963](https://www.github.com/tauri-apps/tauri/pull/3963)) on 2022-04-25\n- Fixes a panic when using the `create_tao_window` API.\n  - [320329a9](https://www.github.com/tauri-apps/tauri/commit/320329a9a7d8a249c0fc9dee6db5669057ca8b39) fix(core): insert to webview_id_map on tao window creation, closes [#3883](https://www.github.com/tauri-apps/tauri/pull/3883) ([#3932](https://www.github.com/tauri-apps/tauri/pull/3932)) on 2022-04-21\n- Fixes the HTTP API form text fields.\n  - [cc356084](https://www.github.com/tauri-apps/tauri/commit/cc35608430b47101255b93ae2f58dfd15e1297d3) fix(core): do not serialize strings in http api form, closes [#3910](https://www.github.com/tauri-apps/tauri/pull/3910) ([#3928](https://www.github.com/tauri-apps/tauri/pull/3928)) on 2022-04-20\n- Set the application bundle identifier for the notifications on macOS.\n  - [f67ae6bd](https://www.github.com/tauri-apps/tauri/commit/f67ae6bd6052d5bc30305ecaa3b83d071da3cfca) fix(core): set bundle id for notifications on macOS ([#3839](https://www.github.com/tauri-apps/tauri/pull/3839)) on 2022-04-01\n- Fixes a panic when a menu event is triggered when all windows are minimized on macOS.\n  - [70ff55c1](https://www.github.com/tauri-apps/tauri/commit/70ff55c1aa69ed59cd2a78d865e1cb398ef2a4ba) fix(core): panic on menu event with minimized windows, closes [#3902](https://www.github.com/tauri-apps/tauri/pull/3902) ([#3918](https://www.github.com/tauri-apps/tauri/pull/3918)) on 2022-04-20\n- Fixes a rendering issue when resizing the window with the devtools open.\n  - [80b714af](https://www.github.com/tauri-apps/tauri/commit/80b714af6b31365b9026bc92f8631b1721950447) fix: rendering issue when resizing with devtools open closes [#3914](https://www.github.com/tauri-apps/tauri/pull/3914) [#3814](https://www.github.com/tauri-apps/tauri/pull/3814) ([#3915](https://www.github.com/tauri-apps/tauri/pull/3915)) on 2022-04-19\n- Fixes the `WindowBuilder` export.\n  - [985d2508](https://www.github.com/tauri-apps/tauri/commit/985d250898f07ebc975b579ea25555337947ab76) fix(tauri): export `WindowBuilder` struct instead of trait, closes [#3827](https://www.github.com/tauri-apps/tauri/pull/3827) ([#3833](https://www.github.com/tauri-apps/tauri/pull/3833)) on 2022-03-31\n- The HTTP API now supports `multipart/form-data` requests. You need to set the `Content-Type` header and enable the `http-multipart` Cargo feature.\n  - [1397d912](https://www.github.com/tauri-apps/tauri/commit/1397d9121aae8188bceceacae5565fbbeb67ddd9) feat(core): add support to multipart/form-data requests, closes [#2118](https://www.github.com/tauri-apps/tauri/pull/2118) ([#3929](https://www.github.com/tauri-apps/tauri/pull/3929)) on 2022-04-22\n- \\**Breaking change::* Added the `global-shortcut` Cargo feature.\n  - [e11878bc](https://www.github.com/tauri-apps/tauri/commit/e11878bcf7174b261a1fa146fc7d564d12e6312a) refactor(core): add global-shortcut Cargo feature, enhancing binary size ([#3956](https://www.github.com/tauri-apps/tauri/pull/3956)) on 2022-04-24\n- Added `tauri::api::http::HttpRequestBuilder#header` method.\n  - [81705bb3](https://www.github.com/tauri-apps/tauri/commit/81705bb332623a53433c28d941ff00da1a6d50fa) feat(updater): add method to set request headers closes [#3896](https://www.github.com/tauri-apps/tauri/pull/3896) ([#3931](https://www.github.com/tauri-apps/tauri/pull/3931)) on 2022-04-22\n- **Breaking change:** The `tauri::api::http::HttpRequestBuilder#headers` method now takes `header::HeaderMap` instead of a `HashMap`.\n  - [81705bb3](https://www.github.com/tauri-apps/tauri/commit/81705bb332623a53433c28d941ff00da1a6d50fa) feat(updater): add method to set request headers closes [#3896](https://www.github.com/tauri-apps/tauri/pull/3896) ([#3931](https://www.github.com/tauri-apps/tauri/pull/3931)) on 2022-04-22\n- **Breaking change:** The `tauri::api::http::Response#headers` method now returns `&header::HeaderMap` instead of `&HashMap`.\n  - [81705bb3](https://www.github.com/tauri-apps/tauri/commit/81705bb332623a53433c28d941ff00da1a6d50fa) feat(updater): add method to set request headers closes [#3896](https://www.github.com/tauri-apps/tauri/pull/3896) ([#3931](https://www.github.com/tauri-apps/tauri/pull/3931)) on 2022-04-22\n- **Breaking change:** The `api::http` timeouts are now represented as `std::time::Duration` instead of a `u64`.\n  - [0ecfad59](https://www.github.com/tauri-apps/tauri/commit/0ecfad5924d7e19719812fd2e460e5242d8f352f) refactor(updater): unset request timeout, add builder setter ([#3847](https://www.github.com/tauri-apps/tauri/pull/3847)) on 2022-04-02\n- **Breaking change:** The `tauri::api::http::FormPart::Bytes` enum variant has been renamed to `File` with a value object `{ file, mime, file_name }`.\n  - [1397d912](https://www.github.com/tauri-apps/tauri/commit/1397d9121aae8188bceceacae5565fbbeb67ddd9) feat(core): add support to multipart/form-data requests, closes [#2118](https://www.github.com/tauri-apps/tauri/pull/2118) ([#3929](https://www.github.com/tauri-apps/tauri/pull/3929)) on 2022-04-22\n- **Breaking change:** Removed `App::create_window`, `AppHandle::create_window`, `Builder::create_window` and `Window::create_window`.\n  - [7c7d854a](https://www.github.com/tauri-apps/tauri/commit/7c7d854ab9183d3f78bafd9027f0304d84abe159) refactor(core): remove deprecated APIs ([#3834](https://www.github.com/tauri-apps/tauri/pull/3834)) on 2022-04-01\n- **Breaking change:** Removed `tauri::api::http::FormPart::File`.\n  - [cc356084](https://www.github.com/tauri-apps/tauri/commit/cc35608430b47101255b93ae2f58dfd15e1297d3) fix(core): do not serialize strings in http api form, closes [#3910](https://www.github.com/tauri-apps/tauri/pull/3910) ([#3928](https://www.github.com/tauri-apps/tauri/pull/3928)) on 2022-04-20\n- Added `WindowEvent::ThemeChanged(theme)`.\n  - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21\n- Added `theme` getter on `Window`.\n  - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21\n- Added `UpdateResponse::body` and `UpdateResponse::date`.\n  - [c7696f34](https://www.github.com/tauri-apps/tauri/commit/c7696f34ecb7baa0da583736e727f74a01f5996a) feat(updater): add `body` and `date` getters ([#3802](https://www.github.com/tauri-apps/tauri/pull/3802)) on 2022-03-29\n- **Breaking change**: Removed the `tauri::updater::Error::UnsupportedPlatform` variant and added `UnsupportedLinuxPackage`, `UnsupportedOs` and `UnsupportedArch` for better error information.\n  - [ed716793](https://www.github.com/tauri-apps/tauri/commit/ed71679368845f603680465fdffcc90fe842bb8c) refactor(updater): improve unsupported error variants, closes [#3817](https://www.github.com/tauri-apps/tauri/pull/3817) ([#3849](https://www.github.com/tauri-apps/tauri/pull/3849)) on 2022-04-05\n- Add updater `Downloaded` status event.\n  - [9712ed1a](https://www.github.com/tauri-apps/tauri/commit/9712ed1a6893438270e99cfecbc7968b68716cba) feat(updater): add `Downloaded` status event ([#3804](https://www.github.com/tauri-apps/tauri/pull/3804)) on 2022-04-01\n- Allow setting app updater request headers via `AppHandle::updater().header()`.\n  - [81705bb3](https://www.github.com/tauri-apps/tauri/commit/81705bb332623a53433c28d941ff00da1a6d50fa) feat(updater): add method to set request headers closes [#3896](https://www.github.com/tauri-apps/tauri/pull/3896) ([#3931](https://www.github.com/tauri-apps/tauri/pull/3931)) on 2022-04-22\n- The updater default timeout is now unset, and the `UpdateBuilder` has a `timeout` setter.\n  - [0ecfad59](https://www.github.com/tauri-apps/tauri/commit/0ecfad5924d7e19719812fd2e460e5242d8f352f) refactor(updater): unset request timeout, add builder setter ([#3847](https://www.github.com/tauri-apps/tauri/pull/3847)) on 2022-04-02\n- Added `theme` setter to the WindowBuilder.\n  - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21\n\n## \\[1.0.0-rc.6]\n\n- Fixes the `WindowEvent` type used on `Window::on_window_event`.\n  - [06aa87b6](https://www.github.com/tauri-apps/tauri/commit/06aa87b65f4608ded0dc312d526e83065618a1c8) fix(core): `WindowEvent` type used on `Window::on_window_event` ([#3796](https://www.github.com/tauri-apps/tauri/pull/3796)) on 2022-03-28\n- Fixes `WindowEvent::Destroyed` not firing.\n  - [169b5035](https://www.github.com/tauri-apps/tauri/commit/169b5035a93e3f33a420d4b2b0f8943e6404e07f) fix(core): actually fire `WindowEvent::Destroyed` ([#3797](https://www.github.com/tauri-apps/tauri/pull/3797)) on 2022-03-28\n\n## \\[1.0.0-rc.5]\n\n- Added `updater_target` method to the `Builder` struct.\n  - [579312f8](https://www.github.com/tauri-apps/tauri/commit/579312f834f08dca15e51e4f43c1d0bb65b54a51) feat(updater): separate intel and apple silicon targets, closes [#3359](https://www.github.com/tauri-apps/tauri/pull/3359) ([#3739](https://www.github.com/tauri-apps/tauri/pull/3739)) on 2022-03-23\n\n- Added an option to disable the CSP injection of distributable assets nonces and hashes.\n  - [f6e32ee1](https://www.github.com/tauri-apps/tauri/commit/f6e32ee1880eb364ed76beb937c9d12e14d54910) feat(core): add dangerous option to disable compile time CSP injection ([#3775](https://www.github.com/tauri-apps/tauri/pull/3775)) on 2022-03-28\n\n- Toggle devtools when `Ctrl + Shift + I` or `Command + Option + I` is pressed.\n  - [e05d718a](https://www.github.com/tauri-apps/tauri/commit/e05d718a7b46476d1fe4817c169008080e84f959) feat(core): add hotkey to toggle devtools, closes [#3776](https://www.github.com/tauri-apps/tauri/pull/3776) ([#3791](https://www.github.com/tauri-apps/tauri/pull/3791)) on 2022-03-28\n\n- Use asynchronous file dialog on macOS and Windows to properly set the parent window.\n  - [bf89a05f](https://www.github.com/tauri-apps/tauri/commit/bf89a05fcfef976886a833b24346e010fd1bd06c) fix(core): dialog parent window on macOS, closes [#3312](https://www.github.com/tauri-apps/tauri/pull/3312) ([#3753](https://www.github.com/tauri-apps/tauri/pull/3753)) on 2022-03-23\n\n- The `Error` enum is now `Send + Sync`.\n  - [da1e8793](https://www.github.com/tauri-apps/tauri/commit/da1e879358895f7b190b1c1b20d23da23666a74b) feat(core): improve and cleanup the `Error` enum ([#3748](https://www.github.com/tauri-apps/tauri/pull/3748)) on 2022-03-22\n\n- Do not allow path traversal on the asset protocol.\n  - [34a402f9](https://www.github.com/tauri-apps/tauri/commit/34a402f9b559af377b276d73b800e5e8b7dacbb1) fix(core): do not allow path traversal on the asset protocol ([#3774](https://www.github.com/tauri-apps/tauri/pull/3774)) on 2022-03-27\n\n- Properly apply the CSP when loading a route that fallbacks to index.html.\n  - [bcd43168](https://www.github.com/tauri-apps/tauri/commit/bcd43168a528dc4c54e28788430a93654c8fb452) fix(core): properly add CSP header to fallback routes ([#3641](https://www.github.com/tauri-apps/tauri/pull/3641)) on 2022-03-08\n\n- Fix CSP usage on Linux when changing it via the `on_web_resource_request` handler.\n  - [f5efc248](https://www.github.com/tauri-apps/tauri/commit/f5efc248da511e0924c9673b947d5de7ef69ac45) fix(core): runtime CSP changes on Linux on 2022-03-07\n\n- Improved the updater response validation and error messages.\n  - [dbc2873e](https://www.github.com/tauri-apps/tauri/commit/dbc2873e82dd56e13025f73281769fff323d32aa) feat(updater): improve validation and error messages, closes [#3761](https://www.github.com/tauri-apps/tauri/pull/3761) ([#3780](https://www.github.com/tauri-apps/tauri/pull/3780)) on 2022-03-27\n\n- **Breaking change:** The `MenuItem::About` variant is now associated with a tuple value `(String, AboutMetadata)`.\n  - [5fb74332](https://www.github.com/tauri-apps/tauri/commit/5fb74332ab9210ac062d96b0e9afd1c942ee2911) chore(deps): update wry to 0.14, tao to 0.7 ([#3790](https://www.github.com/tauri-apps/tauri/pull/3790)) on 2022-03-28\n\n- Replace multiple dependencies who's C code compiled concurrently and caused\n  the other ones to bloat compile time significantly.\n\n- `zstd` -> `brotli`\n\n- `blake3` -> a vendored version of the blake3 reference\n\n- `ring` -> `getrandom`\n\nSee https://github.com/tauri-apps/tauri/pull/3773 for more information about\nthese specific choices.\n\n- [8661e3e2](https://www.github.com/tauri-apps/tauri/commit/8661e3e24d96c399bfbcdee5d8e9d6beba2265a7) replace dependencies with long build times when used together (closes [#3571](https://www.github.com/tauri-apps/tauri/pull/3571)) ([#3773](https://www.github.com/tauri-apps/tauri/pull/3773)) on 2022-03-27\n- **Breaking change:** The `Window::hwnd` method now returns *HWND* from `windows-rs` crate instead of *c_void* on Windows.\n  - [4e807a53](https://www.github.com/tauri-apps/tauri/commit/4e807a53e2d6d3f3cd5293d90013d5cdded5454e) Support window parenting on macOS, closes [#3751](https://www.github.com/tauri-apps/tauri/pull/3751) ([#3754](https://www.github.com/tauri-apps/tauri/pull/3754)) on 2022-03-23\n- Support window parenting on macOS\n  - [4e807a53](https://www.github.com/tauri-apps/tauri/commit/4e807a53e2d6d3f3cd5293d90013d5cdded5454e) Support window parenting on macOS, closes [#3751](https://www.github.com/tauri-apps/tauri/pull/3751) ([#3754](https://www.github.com/tauri-apps/tauri/pull/3754)) on 2022-03-23\n- **Breaking change:** The updater default targets have been renamed to include better support for different architectures.\n  - [579312f8](https://www.github.com/tauri-apps/tauri/commit/579312f834f08dca15e51e4f43c1d0bb65b54a51) feat(updater): separate intel and apple silicon targets, closes [#3359](https://www.github.com/tauri-apps/tauri/pull/3359) ([#3739](https://www.github.com/tauri-apps/tauri/pull/3739)) on 2022-03-23\n- **Breaking change:** Removed `RunEvent::CloseRequested` and `RunEvent::WindowClosed` and added `RunEvent::WindowEvent`.\n  - [edad9f4f](https://www.github.com/tauri-apps/tauri/commit/edad9f4f55dcc69a06cd9d6d5a5068c94ecb77dd) refactor(core): add `RunEvent::WindowEvent` ([#3793](https://www.github.com/tauri-apps/tauri/pull/3793)) on 2022-03-28\n- **Breaking change:** Removed `window_label` from `RunEvent::ExitRequested`.\n  - [9ddf8d84](https://www.github.com/tauri-apps/tauri/commit/9ddf8d84a22cd6ccdce04bcc98b2b0f5fc54381a) fix(core): properly fire `WindowEvent::Destroyed`, closes [#3688](https://www.github.com/tauri-apps/tauri/pull/3688) ([#3778](https://www.github.com/tauri-apps/tauri/pull/3778)) on 2022-03-28\n- **Breaking change:** The `tauri://` events are no longer emitted to listeners using `Window::listen`. Use the `App::run` closure, `Window::on_window_event` and `Window::on_menu_event` instead.\n  - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15\n- The `App::setup` closure can now return a boxed error directly.\n  - [da1e8793](https://www.github.com/tauri-apps/tauri/commit/da1e879358895f7b190b1c1b20d23da23666a74b) feat(core): improve and cleanup the `Error` enum ([#3748](https://www.github.com/tauri-apps/tauri/pull/3748)) on 2022-03-22\n- Implement `Debug` for `tauri::State`.\n  - [0b49dd56](https://www.github.com/tauri-apps/tauri/commit/0b49dd566dae21c4dcb1cf110115aab982a7dab6) impl Debug for State closes [#3676](https://www.github.com/tauri-apps/tauri/pull/3676) ([#3677](https://www.github.com/tauri-apps/tauri/pull/3677)) on 2022-03-12\n- **Breaking change:** The `Manager::manage` function now returns a bool indicating whether the type is already managed or not.\n  - [263b45e1](https://www.github.com/tauri-apps/tauri/commit/263b45e1b4e72d6c99fc27e41d0c5e1d134f363b) refactor(core): return boolean on `Manager::manage` ([#3682](https://www.github.com/tauri-apps/tauri/pull/3682)) on 2022-03-13\n- Set the `Access-Control-Allow-Origin` header on the `tauri` protocol response with the initial webview URL as value.\n  - [1730b1a5](https://www.github.com/tauri-apps/tauri/commit/1730b1a51d1220e6c6a2eec405a3830cc3878224) feat(core): enable CORS on the tauri protocol ([#3750](https://www.github.com/tauri-apps/tauri/pull/3750)) on 2022-03-22\n- **Breaking change:** The `tauri_runtime` crate is no longer exported since its API is not stable.\n  - [1099a969](https://www.github.com/tauri-apps/tauri/commit/1099a9696e6639b46d736f8f3b446d2dfc4ef2f0) refactor(core): do not export `tauri_runtime` on `tauri` ([#3749](https://www.github.com/tauri-apps/tauri/pull/3749)) on 2022-03-22\n- Added `Temp` to the `BaseDirectory` enum.\n  - [266156a0](https://www.github.com/tauri-apps/tauri/commit/266156a0b08150b21140dd552c8bc252fe413cdd) feat(core): add `BaseDirectory::Temp` and `$TEMP` variable ([#3763](https://www.github.com/tauri-apps/tauri/pull/3763)) on 2022-03-24\n- Added `$TEMP` to the allowed variables to the filesystem and asset protocol scopes.\n  - [266156a0](https://www.github.com/tauri-apps/tauri/commit/266156a0b08150b21140dd552c8bc252fe413cdd) feat(core): add `BaseDirectory::Temp` and `$TEMP` variable ([#3763](https://www.github.com/tauri-apps/tauri/pull/3763)) on 2022-03-24\n- Update `wry` to `0.14` and `tao` to `0.7`.\n  - [f2d24ef2](https://www.github.com/tauri-apps/tauri/commit/f2d24ef2fbd95ec7d3433ba651964f4aa3b7f48c) chore(deps): update wry ([#1482](https://www.github.com/tauri-apps/tauri/pull/1482)) on 2021-04-14\n  - [e267ebf1](https://www.github.com/tauri-apps/tauri/commit/e267ebf1f1009b99829e0a7d71519925f5792f9f) Apply Version Updates From Current Changes ([#1486](https://www.github.com/tauri-apps/tauri/pull/1486)) on 2021-04-14\n  - [5fb74332](https://www.github.com/tauri-apps/tauri/commit/5fb74332ab9210ac062d96b0e9afd1c942ee2911) chore(deps): update wry to 0.14, tao to 0.7 ([#3790](https://www.github.com/tauri-apps/tauri/pull/3790)) on 2022-03-28\n- Added `updater` method to `App` and `AppHandle`, a builder to check for app updates.\n  - [4094494a](https://www.github.com/tauri-apps/tauri/commit/4094494a1b3125bf01676dabaa69e56cc8741d59) feat(core): add API to manually trigger updater check ([#3712](https://www.github.com/tauri-apps/tauri/pull/3712)) on 2022-03-17\n  - [c64268f9](https://www.github.com/tauri-apps/tauri/commit/c64268f9274bdb7352da1a53184e487b03437dc2) feat(updater): expose builder, allow setting a custom version checker ([#3792](https://www.github.com/tauri-apps/tauri/pull/3792)) on 2022-03-28\n- Allow using a custom updater version checker via `App::updater().should_install()`.\n  - [c64268f9](https://www.github.com/tauri-apps/tauri/commit/c64268f9274bdb7352da1a53184e487b03437dc2) feat(updater): expose builder, allow setting a custom version checker ([#3792](https://www.github.com/tauri-apps/tauri/pull/3792)) on 2022-03-28\n- Added download progress events to the updater.\n  - [f0db3f9b](https://www.github.com/tauri-apps/tauri/commit/f0db3f9b8357dc304a8254426034c4d1733fbd45) feat(updater): add download progress events ([#3734](https://www.github.com/tauri-apps/tauri/pull/3734)) on 2022-03-18\n- Send updater events to the `App::run` closure.\n  - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15\n- Run the updater on startup even if no window was created.\n  - [c4ca80f9](https://www.github.com/tauri-apps/tauri/commit/c4ca80f919551cbcae53d931f860115c2d591d14) feat(core): use AppHandle instead of Window on the updater logic ([#3702](https://www.github.com/tauri-apps/tauri/pull/3702)) on 2022-03-15\n- Properly fire the window destroyed event.\n  - [9ddf8d84](https://www.github.com/tauri-apps/tauri/commit/9ddf8d84a22cd6ccdce04bcc98b2b0f5fc54381a) fix(core): properly fire `WindowEvent::Destroyed`, closes [#3688](https://www.github.com/tauri-apps/tauri/pull/3688) ([#3778](https://www.github.com/tauri-apps/tauri/pull/3778)) on 2022-03-28\n- Added `close_devtools` and `is_devtools_open` APIs to the `Window` struct.\n  - [e05d718a](https://www.github.com/tauri-apps/tauri/commit/e05d718a7b46476d1fe4817c169008080e84f959) feat(core): add hotkey to toggle devtools, closes [#3776](https://www.github.com/tauri-apps/tauri/pull/3776) ([#3791](https://www.github.com/tauri-apps/tauri/pull/3791)) on 2022-03-28\n- Added the `WindowEvent::FileDrop` variant.\n  - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13\n- Added a configuration flag for disallowing install downgrades on Windows.\n  **Breaking change:** The default behavior on Windows is now to allow downgrades.\n  - [8b807e09](https://www.github.com/tauri-apps/tauri/commit/8b807e09d6868f6bff8357f16d27b15bd1fccadd) refactor(bundler): allow downgrades, add option to disallow on Windows ([#3777](https://www.github.com/tauri-apps/tauri/pull/3777)) on 2022-03-27\n\n## \\[1.0.0-rc.4]\n\n- Run `AppHandle` cleanup code before restarting the application on the `process > relaunch` API.\n  - [9c65abce](https://www.github.com/tauri-apps/tauri/commit/9c65abce1bd3895b7f8d9d4d8a449ac4802fce24) feat(core): run cleanup code on the relaunch API ([#3629](https://www.github.com/tauri-apps/tauri/pull/3629)) on 2022-03-07\n- **Breaking change:** The `Builder#create_window` API now returns a Result validating the window label.\n  - [64e00542](https://www.github.com/tauri-apps/tauri/commit/64e0054299c95f10ef5a1a9d3f914bbaeff3d73f) refactor(core): do not panic on invalid window labels,[#3544](https://www.github.com/tauri-apps/tauri/pull/3544) ([#3596](https://www.github.com/tauri-apps/tauri/pull/3596)) on 2022-03-03\n- Added `tsp` config option under `tauri > bundle > windows`, which enables Time-Stamp Protocol (RFC 3161) for the timestamping\n  server under code signing on Windows if set to `true`.\n  - [bdd5f7c2](https://www.github.com/tauri-apps/tauri/commit/bdd5f7c2f03af4af8b60a9527e55bb18525d989b) fix: add support for Time-Stamping Protocol for Windows codesigning (fix [#3563](https://www.github.com/tauri-apps/tauri/pull/3563)) ([#3570](https://www.github.com/tauri-apps/tauri/pull/3570)) on 2022-03-07\n- Revert the `clap` usage back to the version 3.0 API.\n  - [2b554c38](https://www.github.com/tauri-apps/tauri/commit/2b554c38a5181b948d7674b6ef33e4049ca7d327) fix(core): revert to clap 3.0 API, allow deprecations, closes [#3549](https://www.github.com/tauri-apps/tauri/pull/3549) ([#3552](https://www.github.com/tauri-apps/tauri/pull/3552)) on 2022-02-24\n- The `tauri::api::process::Command` API now properly reads stdout and stderr messages that ends with a carriage return (`\\r`) instead of just a newline (`\\n`).\n  - [0a0de8ab](https://www.github.com/tauri-apps/tauri/commit/0a0de8ab6ed80b7722012e83636dff41a813b770) fix: read Command output ending with a carriage return, closes [#3508](https://www.github.com/tauri-apps/tauri/pull/3508) ([#3523](https://www.github.com/tauri-apps/tauri/pull/3523)) on 2022-02-24\n- Fixes filesystem and asset scope stripping the first component of the allowed path.\n  - [4d0e2ecc](https://www.github.com/tauri-apps/tauri/commit/4d0e2eccd9e0e1db16e6cc20613ffdc3dbe0474e) fix(core): scope should not strip the first path component, closes [#3592](https://www.github.com/tauri-apps/tauri/pull/3592) ([#3602](https://www.github.com/tauri-apps/tauri/pull/3602)) on 2022-03-03\n- Ignore trailing slashes on path scope validation.\n  - [929a83dd](https://www.github.com/tauri-apps/tauri/commit/929a83dd4d2614a119ab05122663669648e8c701) fix(core): ignore trailing slashes on scope validation, closes [#3580](https://www.github.com/tauri-apps/tauri/pull/3580) ([#3601](https://www.github.com/tauri-apps/tauri/pull/3601)) on 2022-03-03\n- Fixes `Command::output` and `Command::status` deadlock when running on async commands.\n  - [0163489e](https://www.github.com/tauri-apps/tauri/commit/0163489ed60ec0fe9486b5556ec0234499852a16) fix(core): `safe_block_on` usage on async contexts, closes [#3505](https://www.github.com/tauri-apps/tauri/pull/3505) ([#3513](https://www.github.com/tauri-apps/tauri/pull/3513)) on 2022-02-24\n- Update tray menu id map when `SystemTrayHandle::set_menu` is called.\n  - [da882431](https://www.github.com/tauri-apps/tauri/commit/da8824318affb50475f28b9c78c56da3fb048708) fix(core): update tray menu ids on `set_menu`, closes [#3608](https://www.github.com/tauri-apps/tauri/pull/3608) ([#3611](https://www.github.com/tauri-apps/tauri/pull/3611)) on 2022-03-04\n- Allow absolute paths on the filesystem APIs as long as it does not include parent directory components.\n  - [b744cd27](https://www.github.com/tauri-apps/tauri/commit/b744cd2758bbc5da39d6105fd82002dc6536dd16) feat: extend scopes with user selected paths, closes [#3591](https://www.github.com/tauri-apps/tauri/pull/3591) ([#3595](https://www.github.com/tauri-apps/tauri/pull/3595)) on 2022-03-03\n- **Breaking change:** The `tauri::api::file::Extract` API is now available when the `fs-extract-api` feature is enabled.\n  - [0f155898](https://www.github.com/tauri-apps/tauri/commit/0f1558980a0fb1d6c042988e173047f0590b6574) fix(core): docs.rs on Windows and macOS ([#3566](https://www.github.com/tauri-apps/tauri/pull/3566)) on 2022-03-02\n- Allow listening to events on the filesystem and asset scopes.\n  - [58070c1e](https://www.github.com/tauri-apps/tauri/commit/58070c1eb443c29b01638b7fc3f69aacdcc464a6) feat(core): filesystem and asset protocol scope events ([#3609](https://www.github.com/tauri-apps/tauri/pull/3609)) on 2022-03-04\n- Allow configuring forbidden paths on the asset and filesystem scopes.\n  - [983ccb81](https://www.github.com/tauri-apps/tauri/commit/983ccb815b1cb094ac681c2b36c69ec16f123b4e) feat(core): allow denying paths on the fs and asset scopes ([#3607](https://www.github.com/tauri-apps/tauri/pull/3607)) on 2022-03-03\n- Extend the allowed patterns for the filesystem and asset protocol when the user selects a path (dialog open and save commands and file drop on the window).\n  - [b744cd27](https://www.github.com/tauri-apps/tauri/commit/b744cd2758bbc5da39d6105fd82002dc6536dd16) feat: extend scopes with user selected paths, closes [#3591](https://www.github.com/tauri-apps/tauri/pull/3591) ([#3595](https://www.github.com/tauri-apps/tauri/pull/3595)) on 2022-03-03\n- The HTTP scope now matches the entire URL using a glob pattern instead of only its path.\n  - [944b124c](https://www.github.com/tauri-apps/tauri/commit/944b124ce04a15a415fcbb0afdfa78b0e900c97a) feat(core): enhance HTTP scope glob validation, closes [#3507](https://www.github.com/tauri-apps/tauri/pull/3507) ([#3515](https://www.github.com/tauri-apps/tauri/pull/3515)) on 2022-02-24\n- Parse window icons at compile time.\n  - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05\n- **Breaking change:** Move `ico` and `png` parsing behind `icon-ico` and `icon-png` Cargo features.\n  - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05\n- Return an error when creating a window with an invalid label instead of panicking.\n  - [64e00542](https://www.github.com/tauri-apps/tauri/commit/64e0054299c95f10ef5a1a9d3f914bbaeff3d73f) refactor(core): do not panic on invalid window labels,[#3544](https://www.github.com/tauri-apps/tauri/pull/3544) ([#3596](https://www.github.com/tauri-apps/tauri/pull/3596)) on 2022-03-03\n- Allows the configuration CSP to be an object mapping a directive name to its source list.\n  - [3fe0260f](https://www.github.com/tauri-apps/tauri/commit/3fe0260f4c7f47dba28b0d049c741d885bc6d356) feat(core): allow CSP configuration to be an object, ref [#3533](https://www.github.com/tauri-apps/tauri/pull/3533) ([#3603](https://www.github.com/tauri-apps/tauri/pull/3603)) on 2022-03-04\n- Allow range in the form of `bytes=0-*` on the asset protocol.\n  - [d06efc77](https://www.github.com/tauri-apps/tauri/commit/d06efc7704092a549c886be701122ad420db5543) fix(core): parse range `bytes=0-*`, closes [#3143](https://www.github.com/tauri-apps/tauri/pull/3143) ([#3516](https://www.github.com/tauri-apps/tauri/pull/3516)) on 2022-02-24\n- Reimplement endpoint to read file as string for performance.\n  - [834ccc51](https://www.github.com/tauri-apps/tauri/commit/834ccc51539401d36a7dfa1c0982623c9c446a4c) feat(core): reimplement `readTextFile` for performance ([#3631](https://www.github.com/tauri-apps/tauri/pull/3631)) on 2022-03-07\n- **Breaking change:** Renamed the `command` Cargo feature to `process-command-api`.\n  - [4e1af005](https://www.github.com/tauri-apps/tauri/commit/4e1af005a1fc80e911299b5fd6d6b0f3eb44f8e7) refactor(core): rename `command` feature to `process-command-api` ([#3594](https://www.github.com/tauri-apps/tauri/pull/3594)) on 2022-03-03\n- Disabled the default features for the `zip` crate.\n  - [5293445f](https://www.github.com/tauri-apps/tauri/commit/5293445f08f1484639ad9f56c45aad53039ba579) refactor(core): disable default features for the zip crate ([#3624](https://www.github.com/tauri-apps/tauri/pull/3624)) on 2022-03-06\n- The `cmd` field is no longer required on the shell scope for sidecars.\n  - [9b3b163b](https://www.github.com/tauri-apps/tauri/commit/9b3b163baa9b8eb2ef0bbc53f6303cc7a59a01af) feat(core): simplify scope definition for sidecars ([#3574](https://www.github.com/tauri-apps/tauri/pull/3574)) on 2022-03-02\n- Fixes a regression on the `unlisten` command.\n  - [76c791bd](https://www.github.com/tauri-apps/tauri/commit/76c791bd2b836d2055410e37e71716172a3f81ef) fix(core): regression on the unlisten function ([#3623](https://www.github.com/tauri-apps/tauri/pull/3623)) on 2022-03-06\n- Run `AppHandle` cleanup code before restarting the application when a new update is installed.\n  - [fce7d3bb](https://www.github.com/tauri-apps/tauri/commit/fce7d3bbae8d9e7e928a31b04a0a87dcaf4cd65f) feat(core): run app cleanup code before updater restart, closes [#3605](https://www.github.com/tauri-apps/tauri/pull/3605) ([#3616](https://www.github.com/tauri-apps/tauri/pull/3616)) on 2022-03-04\n- Added a `WindowBuilder` type.\n  - [141133a4](https://www.github.com/tauri-apps/tauri/commit/141133a414154631d42a0873c6abe2d76391b87d) feat(core): add WindowBuilder type ([#3598](https://www.github.com/tauri-apps/tauri/pull/3598)) on 2022-03-04\n- Added `WindowBuilder::on_web_resource_request`, which allows customizing the tauri custom protocol response.\n  - [3b13fda5](https://www.github.com/tauri-apps/tauri/commit/3b13fda56f515c708014c0396ed5ca295faaef84) feat(core): add `WindowBuilder::on_request`, closes [#3533](https://www.github.com/tauri-apps/tauri/pull/3533) ([#3618](https://www.github.com/tauri-apps/tauri/pull/3618)) on 2022-03-06\n\n## \\[1.0.0-rc.3]\n\n- `tauri::plugin::Builder` closures are no longer required to implement `Sync`.\n  - [fb7ee2c9](https://www.github.com/tauri-apps/tauri/commit/fb7ee2c987d9fca23f08bd470789069d9477c66e) drop Sync req from `setup` and `setup_with_config` ([#3471](https://www.github.com/tauri-apps/tauri/pull/3471)) on 2022-02-16\n  - [b8e4d651](https://www.github.com/tauri-apps/tauri/commit/b8e4d651f9866b34bd3afcf2392812a18e1cee53) fix(core): drop all plugin builder Sync requirements ([#3490](https://www.github.com/tauri-apps/tauri/pull/3490)) on 2022-02-17\n- Added context to the file system endpoint errors.\n  - [06053833](https://www.github.com/tauri-apps/tauri/commit/060538331c138473159cf8fee0fcb7904ca33d3b) feat(core): add context to the filesystem APIs errors, closes [#3457](https://www.github.com/tauri-apps/tauri/pull/3457) ([#3480](https://www.github.com/tauri-apps/tauri/pull/3480)) on 2022-02-16\n- Changed the default value for `tauri > bundle > macOS > minimumSystemVersion` to `10.13`.\n  - [fce344b9](https://www.github.com/tauri-apps/tauri/commit/fce344b90b7227f8f5514853c2f885fb24d3648e) feat(core): set default value for `minimum_system_version` to 10.13 ([#3497](https://www.github.com/tauri-apps/tauri/pull/3497)) on 2022-02-17\n\n## \\[1.0.0-rc.2]\n\n- Ease the requirements for plugin hooks. `setup` and `setup_with_config` can now be `FnOnce` and `on_webview_ready`, `on_event` and `on_page_load` can be `FnMut`.\n  - [fd557e98](https://www.github.com/tauri-apps/tauri/commit/fd557e984db17335a7c2ff10e6fe9c07065a95ed) Ease plugin hook restrictions ([#3404](https://www.github.com/tauri-apps/tauri/pull/3404)) on 2022-02-13\n- Fixes an issue with the updater when replacing the `{{target}}` and `{{current_version}}` variables due to percent-encoding on the `Url` that is parsed from the configuration.\n  - [20f0477f](https://www.github.com/tauri-apps/tauri/commit/20f0477f95b836cdf03ca66dadd9c0d3c95b198a) fix(core): updater not replacing variables, closes [#3428](https://www.github.com/tauri-apps/tauri/pull/3428) ([#3432](https://www.github.com/tauri-apps/tauri/pull/3432)) on 2022-02-13\n\n## \\[1.0.0-rc.1]\n\n- Change default value for the `freezePrototype` configuration to `false`.\n  - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12\n\n## \\[1.0.0-rc.0]\n\n- The dialog allowlist now includes flags for the `message`, `ask` and `confirm` APIs.\n  - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \\[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09\n- - **Breaking change:** Renamed `tauri::Event`  to `tauri::RunEvent`\n- Exported `tauri::Event` and `tauri::EventHandler` so you can define a function and pass it to `Window::listen`\n- [15358b18](https://www.github.com/tauri-apps/tauri/commit/15358b1895487cb9c258a8ca4d2336b4215e2a8f) Expose event interface. fixes [#2733](https://www.github.com/tauri-apps/tauri/pull/2733) ([#3321](https://www.github.com/tauri-apps/tauri/pull/3321)) on 2022-02-04\n- The `tauri::api` modules `http`, `notification`, `dialog`, and `process::Command` APIs are now hidden behind a feature flag, `http-api`, `notification`, `dialog` and `command`, respectively.\n  - [6feb5a0c](https://www.github.com/tauri-apps/tauri/commit/6feb5a0c50852a4792a9cc80967eab0fcf18609e) refactor(core): api feature flags, documentation ([#26](https://www.github.com/tauri-apps/tauri/pull/26)) on 2022-01-09\n- Add `title` option to file open/save dialogs.\n  - [e1d6a6e6](https://www.github.com/tauri-apps/tauri/commit/e1d6a6e6445637723e2331ca799a662e720e15a8) Create api-file-dialog-title.md ([#3235](https://www.github.com/tauri-apps/tauri/pull/3235)) on 2022-01-16\n  - [62c7a8ad](https://www.github.com/tauri-apps/tauri/commit/62c7a8ad30fd3031b8679960590e5ef3eef8e4da) chore(covector): prepare for `rc` release ([#3376](https://www.github.com/tauri-apps/tauri/pull/3376)) on 2022-02-10\n- Added `any_thread()` to the `tauri::Builder` to run applications on any thread (only exposed on Linux and Windows).\n  - [af44bf81](https://www.github.com/tauri-apps/tauri/commit/af44bf8168310cf77fbe102a53e7c433f11641a3) feat(core): allow app run on any thread on Linux & Windows, closes [#3172](https://www.github.com/tauri-apps/tauri/pull/3172) ([#3353](https://www.github.com/tauri-apps/tauri/pull/3353)) on 2022-02-07\n- Enable CORS on the `asset` protocol.\n  - [d28ac8aa](https://www.github.com/tauri-apps/tauri/commit/d28ac8aac0d19a70bb658f12e56330ec8ac4dda5) fix(core): enable CORS on the `asset` protocol, closes [#2965](https://www.github.com/tauri-apps/tauri/pull/2965) ([#2974](https://www.github.com/tauri-apps/tauri/pull/2974)) on 2021-12-09\n- The `asset://` custom protocol is only defined when either the `api-all`, `protocol-all` or `protocol-asset` feature flags are enabled. These feature flags are accessible with the `tauri.conf.json` allowlist.\n  - [7920ff14](https://www.github.com/tauri-apps/tauri/commit/7920ff14e6424079c48ea5645d9aa13e7a272b87) feat: scope the `fs` API and the `asset` protocol \\[TRI-026] \\[TRI-010] \\[TRI-011] ([#10](https://www.github.com/tauri-apps/tauri/pull/10)) on 2022-01-09\n- Expose the `asset_resolver` API on the `App` and `AppHandle` structs.\n  - [7c6c7adc](https://www.github.com/tauri-apps/tauri/commit/7c6c7adcc4c1ddd7406dd2d3b31541d5b962b385) feat(core): add `asset_resolver` API ([#2879](https://www.github.com/tauri-apps/tauri/pull/2879)) on 2021-11-12\n- **Breaking change:** Refactored the types returned from the `async_runtime` module.\n  - [a3537078](https://www.github.com/tauri-apps/tauri/commit/a3537078ddeaac7d26250f111f071072cf328b0b) feat(core): allow running along another tokio runtime, closes [#2838](https://www.github.com/tauri-apps/tauri/pull/2838) ([#2973](https://www.github.com/tauri-apps/tauri/pull/2973)) on 2021-12-08\n- Added `tauri::async_runtime::set` method, allowing to share your tokio runtime with Tauri.\n  - [a3537078](https://www.github.com/tauri-apps/tauri/commit/a3537078ddeaac7d26250f111f071072cf328b0b) feat(core): allow running along another tokio runtime, closes [#2838](https://www.github.com/tauri-apps/tauri/pull/2838) ([#2973](https://www.github.com/tauri-apps/tauri/pull/2973)) on 2021-12-08\n- Added `tauri::async_runtime::spawn_blocking` API.\n  - [a3537078](https://www.github.com/tauri-apps/tauri/commit/a3537078ddeaac7d26250f111f071072cf328b0b) feat(core): allow running along another tokio runtime, closes [#2838](https://www.github.com/tauri-apps/tauri/pull/2838) ([#2973](https://www.github.com/tauri-apps/tauri/pull/2973)) on 2021-12-08\n- The `callback` and `error` invoke fields, along with other `transformCallback` usages, are now validated to be numeric.\n  - [a48b8b18](https://www.github.com/tauri-apps/tauri/commit/a48b8b18d428bcc404d489daa690bbefe1f57311) feat(core): validate callbacks and event names \\[TRI-038] \\[TRI-020] ([#21](https://www.github.com/tauri-apps/tauri/pull/21)) on 2022-01-09\n- Change `Error::ParseCliArguments(clap::Error)` to `Error::ParseCliArguments(String)` because `clap::Error` is not `Send`.\n  - [1f988535](https://www.github.com/tauri-apps/tauri/commit/1f98853573a837dd0cfc2161b206a5033ec2da5e) chore(deps) Update Tauri Core ([#2480](https://www.github.com/tauri-apps/tauri/pull/2480)) on 2021-08-24\n- The `api::process::Command` APIs are now hidden behind the `command` feature flag.\n  - [eed01728](https://www.github.com/tauri-apps/tauri/commit/eed017287fed2ade689af4268e8b63b9c9f2e585) feat(core): add `shell > sidecar` allowlist and `process` feature flag \\[TRI-037] ([#18](https://www.github.com/tauri-apps/tauri/pull/18)) on 2021-10-24\n- Add `tauri::api::path::log_dir` function to access the suggested log directory path.\n  - [acbb3ae7](https://www.github.com/tauri-apps/tauri/commit/acbb3ae7bb0165846b9456aea103269f027fc548) feat: add Log directory ([#2736](https://www.github.com/tauri-apps/tauri/pull/2736)) on 2021-10-16\n  - [62c7a8ad](https://www.github.com/tauri-apps/tauri/commit/62c7a8ad30fd3031b8679960590e5ef3eef8e4da) chore(covector): prepare for `rc` release ([#3376](https://www.github.com/tauri-apps/tauri/pull/3376)) on 2022-02-10\n- The `process`, `path` and `updater` APIs now takes a `tauri::Env` argument, used to force environment variables load on startup to prevent env var update attacks.\n  - [7209fdf7](https://www.github.com/tauri-apps/tauri/commit/7209fdf732ffe1893aaa9cd970ab6491a883d997) refactor(core): load APPIMAGE and APPDIR env vars on startup \\[TRI-007] \\[TRI-041] on 2022-01-09\n- Now `resolve()`, `join()` and `normalize()` from the `path` module, won't throw errors if the path doesn't exist, which matches NodeJS behavior.\n  - [fe381a0b](https://www.github.com/tauri-apps/tauri/commit/fe381a0bde86ebf4014007f6e21af4c1a9e58cef) fix: `join` no longer cares if path doesn't exist, closes [#2499](https://www.github.com/tauri-apps/tauri/pull/2499) ([#2548](https://www.github.com/tauri-apps/tauri/pull/2548)) on 2021-09-21\n- **Breaking change:** Return `Window` on `App` and `AppHandle`'s `create_window` function.\n  - [e15a8af8](https://www.github.com/tauri-apps/tauri/commit/e15a8af8434805da9716f3f840daa4e87b18b0a2) refactor(core): return `Window` on `create_window` API ([#3211](https://www.github.com/tauri-apps/tauri/pull/3211)) on 2022-01-13\n  - [62c7a8ad](https://www.github.com/tauri-apps/tauri/commit/62c7a8ad30fd3031b8679960590e5ef3eef8e4da) chore(covector): prepare for `rc` release ([#3376](https://www.github.com/tauri-apps/tauri/pull/3376)) on 2022-02-10\n- Apply `nonce` to `script` and `style` tags and set them on the `CSP` (`script-src` and `style-src` fetch directives).\n  - [cf54dcf9](https://www.github.com/tauri-apps/tauri/commit/cf54dcf9c81730e42c9171daa9c8aa474c95b522) feat: improve `CSP` security with nonces and hashes, add `devCsp` \\[TRI-004] ([#8](https://www.github.com/tauri-apps/tauri/pull/8)) on 2022-01-09\n- The path returned from `tauri::api::process::current_binary` is now cached when loading the binary.\n  - [7c3db7a3](https://www.github.com/tauri-apps/tauri/commit/7c3db7a3811fd4de3e71c78cfd00894fa51ab786) cache current binary path much sooner ([#45](https://www.github.com/tauri-apps/tauri/pull/45)) on 2022-02-01\n- Added an API to use a custom invoke system to receive and respond to commands (`Builder#invoke_system`).\n  - [15164d93](https://www.github.com/tauri-apps/tauri/commit/15164d930a64807054057d83a2b6dae8540bcd9a) feat(core): allow defining a custom invoke system ([#2899](https://www.github.com/tauri-apps/tauri/pull/2899)) on 2021-11-16\n- Inject configured `CSP` on `data:` URLs.\n  - [8259cd64](https://www.github.com/tauri-apps/tauri/commit/8259cd64c2087b23b694a78aa98c43866762faec) feat(core): inject `CSP` on data URLs \\[TRI-049] ([#16](https://www.github.com/tauri-apps/tauri/pull/16)) on 2022-01-09\n- Emit `tauri://*` events to Rust listeners.\n  - [4c4ab1eb](https://www.github.com/tauri-apps/tauri/commit/4c4ab1eb8b0b8b98ded402d0afb9dbca7ffe08e8) fix(core): trigger `tauri://*` events to Rust listeners, closes [#2901](https://www.github.com/tauri-apps/tauri/pull/2901) ([#2902](https://www.github.com/tauri-apps/tauri/pull/2902)) on 2021-11-16\n- Emit `tauri://window-created` event for windows created on the backend.\n  - [1dbd887a](https://www.github.com/tauri-apps/tauri/commit/1dbd887a6e0fc77b56e1b4d1b96dae8ddea1d5ea) fix(core): emit tauri://window-created event for windows created on Rust ([#3299](https://www.github.com/tauri-apps/tauri/pull/3299)) on 2022-02-04\n- Enable non-session cookie persistence on Linux.\n  - [d7c02a30](https://www.github.com/tauri-apps/tauri/commit/d7c02a30a56de79100804969138b379e703f0e07) feat(core): persist non-session cookies on Linux ([#3052](https://www.github.com/tauri-apps/tauri/pull/3052)) on 2021-12-09\n- Expose `tauri::api::ipc::{serialize_js_with, serialize_js}` functions.\n  - [5a94200f](https://www.github.com/tauri-apps/tauri/commit/5a94200f65feb4fddc79f0c68f8bc0a4067ad51a) feat(core): expose functions to serialize `serde::Serialize` values to JS ([#3354](https://www.github.com/tauri-apps/tauri/pull/3354)) on 2022-02-07\n- Resolve `asset` protocol HTTP request instead of panicking if the file does not exist or cannot be read.\n  - [03fc92c8](https://www.github.com/tauri-apps/tauri/commit/03fc92c8304efe897a09de6e82da2fea7f69ecab) fix(core): resolve request instead of panicking on asset protocol ([#3347](https://www.github.com/tauri-apps/tauri/pull/3347)) on 2022-02-06\n- Avoid `async_runtime::block_on` panics when used along another tokio runtime.\n  - [a3537078](https://www.github.com/tauri-apps/tauri/commit/a3537078ddeaac7d26250f111f071072cf328b0b) feat(core): allow running along another tokio runtime, closes [#2838](https://www.github.com/tauri-apps/tauri/pull/2838) ([#2973](https://www.github.com/tauri-apps/tauri/pull/2973)) on 2021-12-08\n- Prevent window closing if `tauri://close-requested` is listened on the JS layer. Users must call `appWindow.close()` manually when listening to that event.\n  - [74dff536](https://www.github.com/tauri-apps/tauri/commit/74dff536d4757820e68ab05301be83c0870c22ed) fix(core): emit `tauri://close-requested` to JS, closes [#2996](https://www.github.com/tauri-apps/tauri/pull/2996) ([#3041](https://www.github.com/tauri-apps/tauri/pull/3041)) on 2021-12-09\n- Fixes a deadlock when creating a window from a menu event handler.\n  - [9c82006b](https://www.github.com/tauri-apps/tauri/commit/9c82006b2fe166d20510183e36cee099bf96e8d9) fix(core): deadlock when creating window from menu handler, closes [#3110](https://www.github.com/tauri-apps/tauri/pull/3110) ([#3126](https://www.github.com/tauri-apps/tauri/pull/3126)) on 2021-12-28\n- Fixes the dialog `defaultPath` usage on Linux.\n  - [2212bd5d](https://www.github.com/tauri-apps/tauri/commit/2212bd5d75146f5a2df27cc2157a057642f626da) fix: dialog default path on Linux, closes [#3091](https://www.github.com/tauri-apps/tauri/pull/3091) ([#3123](https://www.github.com/tauri-apps/tauri/pull/3123)) on 2021-12-27\n- Fixes `WindowEvent::Focus` and `WindowEvent::Blur` events not firing.\n  - [3b33d67a](https://www.github.com/tauri-apps/tauri/commit/3b33d67aa4f48dcf4e32b3b8a5f45e83808efc2d) fix: re-adding focus/blur events for linux and macos (fix [#2485](https://www.github.com/tauri-apps/tauri/pull/2485)) ([#2489](https://www.github.com/tauri-apps/tauri/pull/2489)) on 2021-08-24\n- Fixes `tauri://focus` and `tauri://blur` events not firing.\n  - [3b33d67a](https://www.github.com/tauri-apps/tauri/commit/3b33d67aa4f48dcf4e32b3b8a5f45e83808efc2d) fix: re-adding focus/blur events for linux and macos (fix [#2485](https://www.github.com/tauri-apps/tauri/pull/2485)) ([#2489](https://www.github.com/tauri-apps/tauri/pull/2489)) on 2021-08-24\n- Use webview's inner_size instead of window's value to get the correct size on macOS.\n  - [4c0c780e](https://www.github.com/tauri-apps/tauri/commit/4c0c780e00d8851be38cb1c22f636d9e4ed34a23) fix(core): window's inner_size usage, closes [#2187](https://www.github.com/tauri-apps/tauri/pull/2187) ([#2690](https://www.github.com/tauri-apps/tauri/pull/2690)) on 2021-09-29\n- Fixes resource directory resolution on Linux.\n  - [1a28904b](https://www.github.com/tauri-apps/tauri/commit/1a28904b8ebea92e143d5dc21ebd209e9edec531) fix(core): resource path resolution on Linux, closes [#2493](https://www.github.com/tauri-apps/tauri/pull/2493) on 2021-08-22\n- Fixes the menu id mapping not reflecting the current window.\n  - [ac37b56e](https://www.github.com/tauri-apps/tauri/commit/ac37b56ef43c9e97039967a5fd99f0d2dccb5b5a) fix(core): menu id map not reflecting the current window menu ([#2726](https://www.github.com/tauri-apps/tauri/pull/2726)) on 2021-10-08\n- `Manager::once_global` and `Window::once` allow `FnOnce` callbacks.\n  - [d5400a3d](https://www.github.com/tauri-apps/tauri/commit/d5400a3d62ff2a37ccad20987dfea309725975a6) `once_global` and `once` accept FnOnce callbacks ([#3383](https://www.github.com/tauri-apps/tauri/pull/3383)) on 2022-02-10\n- Properly check if document is loaded before invoking commands.\n  - [000d126e](https://www.github.com/tauri-apps/tauri/commit/000d126e0e7231fb666b9ef53d6a1479dca774f7) fix(core): properly check if document is loaded, closes [#2716](https://www.github.com/tauri-apps/tauri/pull/2716) ([#2900](https://www.github.com/tauri-apps/tauri/pull/2900)) on 2021-11-16\n- Initialize system tray before windows so `tray_handle` can be accessed on command handlers.\n  - [dbe0d21b](https://www.github.com/tauri-apps/tauri/commit/dbe0d21b6fdc0f7d4c7b5e099cef805b60ef203e) fix(core): initialize system tray before app windows on 2021-08-31\n- Reimplement `remove_system_tray` on Windows to drop the `SystemTray` to run its cleanup code.\n  - [a03b8554](https://www.github.com/tauri-apps/tauri/commit/a03b85545a4b0b61a598a43eabe96e03565dcaf0) fix(core): tray not closing on Windows ([#3351](https://www.github.com/tauri-apps/tauri/pull/3351)) on 2022-02-07\n- Immediately listen to `tauri://window-created` event to catch it before the application triggers it.\n  - [878b8b9a](https://www.github.com/tauri-apps/tauri/commit/878b8b9a1fc825b1ea34f955e053311409a1468d) fix(core): immediately listen to window-created, closes [#3297](https://www.github.com/tauri-apps/tauri/pull/3297) ([#3298](https://www.github.com/tauri-apps/tauri/pull/3298)) on 2022-02-04\n- The `tauri::Window#emit` function now correctly sends the event to all windows that has a registered listener.\n  **Breaking change:** `Window#emit_and_trigger` and `Window#emit` now requires the payload to be cloneable.\n  - [9b340552](https://www.github.com/tauri-apps/tauri/commit/9b340552643a1d8b9311219101329ce23c9271ea) fix(core): window-specific event delivery, closes [#3302](https://www.github.com/tauri-apps/tauri/pull/3302) ([#3344](https://www.github.com/tauri-apps/tauri/pull/3344)) on 2022-02-06\n- Allow using a fixed version for the Webview2 runtime via the `tauri > bundle > windows > webviewFixedRuntimePath` config option.\n  - [85df94f2](https://www.github.com/tauri-apps/tauri/commit/85df94f2b0d40255812b42c5e32a70c4b45392df) feat(core): config for fixed webview2 runtime version path ([#27](https://www.github.com/tauri-apps/tauri/pull/27)) on 2021-11-02\n- The updater `pubkey` is now a required field for security reasons. Sign your updates with the `tauri signer` command.\n  - [d95cc831](https://www.github.com/tauri-apps/tauri/commit/d95cc83105dda52df7514e30e54f3676cdb374ee) feat: enforce updater public key \\[TRI-015] ([#42](https://www.github.com/tauri-apps/tauri/pull/42)) on 2022-01-09\n- `tauri::api::HttpRequestBuilder::new` now returns a `Result` to validate the url.\n  - [0ad1c651](https://www.github.com/tauri-apps/tauri/commit/0ad1c6515f696fadefddbf133a9561836b3d5934) feat(core): add `http` allowlist scope \\[TRI-008] ([#24](https://www.github.com/tauri-apps/tauri/pull/24)) on 2021-10-29\n- Added the `isolation` pattern.\n  - [d5d6d2ab](https://www.github.com/tauri-apps/tauri/commit/d5d6d2abc17cd89c3a079d2ce01581193469dbc0) Isolation Pattern ([#43](https://www.github.com/tauri-apps/tauri/pull/43)) Co-authored-by: Ngo Iok Ui (Wu Yu Wei) <wusyong9104@gmail.com> Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app> on 2022-01-17\n- Added `abort` method to `tauri::async_runtime::JoinHandle`.\n  - [ad169759](https://www.github.com/tauri-apps/tauri/commit/ad16975938afc9e87747de5fdcb0f07fc2d24811) feat: Add JoinHandle::abort() ([#2877](https://www.github.com/tauri-apps/tauri/pull/2877)) on 2021-11-13\n- Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension.\n\nHere is the logic flow that determines if JSON or JSON5 will be used to parse the config:\n\n1. Check if `tauri.conf.json` exists\n   a. Parse it with `serde_json`\n   b. Parse it with `json5` if `serde_json` fails\n   c. Return original `serde_json` error if all above steps failed\n2. Check if `tauri.conf.json5` exists\n   a. Parse it with `json5`\n   b. Return error if all above steps failed\n3. Return error if all above steps failed\n\n- [995de57a](https://www.github.com/tauri-apps/tauri/commit/995de57a76cf51215277673e526d7ec32b86b564) Add seamless support for using JSON5 in the config file ([#47](https://www.github.com/tauri-apps/tauri/pull/47)) on 2022-02-03\n- Allow preventing opening the default browser on a click on an `<a target=\"_blank\">` element via `stopImmediatePropagation()`.\n  - [10e3190f](https://www.github.com/tauri-apps/tauri/commit/10e3190fdd38893a720a51b7bfd06744e0306626) fix(core): do not use capture on \\_blank link event handler, closes [#2791](https://www.github.com/tauri-apps/tauri/pull/2791) ([#3349](https://www.github.com/tauri-apps/tauri/pull/3349)) on 2022-02-07\n- The `run_return` API is now available on Linux.\n  - [8483fde9](https://www.github.com/tauri-apps/tauri/commit/8483fde975aac8833d2ce426e42fb40aeaeecba9) feat(core): expose `run_return` on Linux ([#3352](https://www.github.com/tauri-apps/tauri/pull/3352)) on 2022-02-07\n- Allow window, global shortcut and clipboard APIs to be called on the main thread.\n  - [2812c446](https://www.github.com/tauri-apps/tauri/commit/2812c4464b93a365ab955935d05b5cea8cb03aab) feat(core): window, shortcut and clipboard API calls on main thread ([#2659](https://www.github.com/tauri-apps/tauri/pull/2659)) on 2021-09-26\n  - [d24fd8d1](https://www.github.com/tauri-apps/tauri/commit/d24fd8d10242da3da143a971d976b42ec4de6079) feat(tauri-runtime-wry): allow window creation and closing on the main thread ([#2668](https://www.github.com/tauri-apps/tauri/pull/2668)) on 2021-09-27\n- Add `Menu::with_items` constructor, taking an iterator of `MenuEntry`.\n  - [7cc95e10](https://www.github.com/tauri-apps/tauri/commit/7cc95e10ec66d8b155e9bb7f89cf73df56d1f107) feat(core): add `Menu::with_items`, closes [#2807](https://www.github.com/tauri-apps/tauri/pull/2807) ([#2966](https://www.github.com/tauri-apps/tauri/pull/2966)) on 2021-12-27\n- The updater now expects signatures created with the latest CLI release.\n  - [c2a6e8d7](https://www.github.com/tauri-apps/tauri/commit/c2a6e8d7e64284080129f09b3a0ae6cf93a87df0) chore(deps) Update Tauri Core ([#2746](https://www.github.com/tauri-apps/tauri/pull/2746)) on 2021-10-13\n- Change event loop callbacks definition to allow callers to move in mutable values.\n  - [bdbf905e](https://www.github.com/tauri-apps/tauri/commit/bdbf905e5d802b58693d2bd27582ce4269faf79c) Transformed event-loop callback to FnMut to allow mutable values ([#2667](https://www.github.com/tauri-apps/tauri/pull/2667)) on 2021-09-27\n- Fixes `Notification.requestPermission()` deadlock.\n  - [48f3768c](https://www.github.com/tauri-apps/tauri/commit/48f3768c41a2c68e2e097fcc1ef50e549c1dfb78) fix(core): `Notification.requestPermission()` deadlock regression on 2021-08-24\n- Added `Window#open_devtools` API.\n  - [55aa22de](https://www.github.com/tauri-apps/tauri/commit/55aa22de80c3de873e29bcffcb5b2fe236a637a6) feat(core): add `Window#open_devtools` API, closes [#1213](https://www.github.com/tauri-apps/tauri/pull/1213) ([#3350](https://www.github.com/tauri-apps/tauri/pull/3350)) on 2022-02-07\n- Add a `plugin::Builder` struct to make plugin creation more convenient.\n  - [9aed2996](https://www.github.com/tauri-apps/tauri/commit/9aed299621763a6e0a35d102cdf012da522dac35) feat: `plugin::Builder` closes [#2959](https://www.github.com/tauri-apps/tauri/pull/2959) ([#3005](https://www.github.com/tauri-apps/tauri/pull/3005)) on 2022-02-07\n  - [62c7a8ad](https://www.github.com/tauri-apps/tauri/commit/62c7a8ad30fd3031b8679960590e5ef3eef8e4da) chore(covector): prepare for `rc` release ([#3376](https://www.github.com/tauri-apps/tauri/pull/3376)) on 2022-02-10\n- Added `on_event` on the `Plugin` trait, which allows a plugin to react to the event loop.\n  - [cc2f39a2](https://www.github.com/tauri-apps/tauri/commit/cc2f39a29fc8852724aa3954ff0d42a36484929b) feat(core): add `on_event` hook on the `Plugin` trait ([#2656](https://www.github.com/tauri-apps/tauri/pull/2656)) on 2021-09-26\n- Prevent path traversal on the file system APIs.\n  - [4d89f60d](https://www.github.com/tauri-apps/tauri/commit/4d89f60d77a2abe7f3358cec00e15ecacf5e1148) refactor(core): prevent path traversal \\[TRI-012] ([#35](https://www.github.com/tauri-apps/tauri/pull/35)) on 2021-12-06\n- **Breaking change:** Add `macos-private-api` feature flag, enabled via `tauri.conf.json > tauri > macOSPrivateApi`.\n  - [6ac21b3c](https://www.github.com/tauri-apps/tauri/commit/6ac21b3cef7f14358df38cc69ea3d277011accaf) feat: add private api feature flag ([#7](https://www.github.com/tauri-apps/tauri/pull/7)) on 2022-01-09\n- Add `raw_headers` to `tauri::api::http::ResponseData`.\n  - [b7a2345b](https://www.github.com/tauri-apps/tauri/commit/b7a2345b06ca0306988b4ba3d3deadd449e65af9) feat(core): add raw headers to HTTP API, closes [#2695](https://www.github.com/tauri-apps/tauri/pull/2695) ([#3053](https://www.github.com/tauri-apps/tauri/pull/3053)) on 2022-01-07\n- Implement `raw_window_handle::RawWindowHandle` for `tauri::Window` on `Windows` and `macOS`. The `tauri::api::dialog::window_parent` function was removed since now you can use the window directly.\n  - [e98c1af4](https://www.github.com/tauri-apps/tauri/commit/e98c1af44279a5ff6c8a6f0a506ecc219c9f77af) feat(core): expose message dialog APIs, fix window.confirm, implement HasRawWindowHandle for Window, closes [#2535](https://www.github.com/tauri-apps/tauri/pull/2535) ([#2700](https://www.github.com/tauri-apps/tauri/pull/2700)) on 2021-10-02\n- Refactor `create_tao_window` API to return `Weak<Window>` instead of `Arc<Window>`.\n  - [c1494b35](https://www.github.com/tauri-apps/tauri/commit/c1494b353233c6a9552d7ace962fdf8d5b1f199a) refactor: return Weak<Window> on create_tao_window on 2021-08-31\n- Added the `tauri::api::dialog::blocking` module.\n  - [4818531a](https://www.github.com/tauri-apps/tauri/commit/4818531aba47e126af91253d5d0eae3972b27d4c) refactor(core): add blocking dialog APIs, improve docs, closes [#3255](https://www.github.com/tauri-apps/tauri/pull/3255) ([#3270](https://www.github.com/tauri-apps/tauri/pull/3270)) on 2022-02-05\n- The notification endpoint now checks for the permission flag and requests if the value is not set.\n  - [239bba56](https://www.github.com/tauri-apps/tauri/commit/239bba56666c96acce24f30851f2d0719c95fc2c) refactor(core): check notification permission on the Rust endpoint \\[TRI-017] ([#23](https://www.github.com/tauri-apps/tauri/pull/23)) on 2022-01-09\n- **Breaking change:** The `WindowEvent::CloseRequested` variant now includes `label` and `signal_tx` fields to allow preventing closing the window.\n  - [74dff536](https://www.github.com/tauri-apps/tauri/commit/74dff536d4757820e68ab05301be83c0870c22ed) fix(core): emit `tauri://close-requested` to JS, closes [#2996](https://www.github.com/tauri-apps/tauri/pull/2996) ([#3041](https://www.github.com/tauri-apps/tauri/pull/3041)) on 2021-12-09\n- **Breaking change:** Move `__currentWindow` and `__windows` values from `window.__TAURI__` to `window.__TAURI_METADATA__`.\n  - [f5109e0c](https://www.github.com/tauri-apps/tauri/commit/f5109e0c962e3d25404995194968bade1be33b16) fix(api): window label null instead of actual value, closes [#3295](https://www.github.com/tauri-apps/tauri/pull/3295) ([#3332](https://www.github.com/tauri-apps/tauri/pull/3332)) on 2022-02-04\n- Remove the `BaseDirectory::Current` enum variant for security reasons.\n  - [696dca58](https://www.github.com/tauri-apps/tauri/commit/696dca58a9f8ee127a1cf857eb848e09f5845d18) refactor(core): remove `BaseDirectory::Current` variant on 2022-01-26\n- **Breaking change:** Remove default webview window when `tauri.conf.json > tauri > windows` is not set.\n  - [c119060e](https://www.github.com/tauri-apps/tauri/commit/c119060e3d9a5a824639fb6b3c45a87e7a62e4e2) refactor(core): empty default value for config > tauri > windows ([#3380](https://www.github.com/tauri-apps/tauri/pull/3380)) on 2022-02-10\n- **Breaking change:** Renamed the `rpc` module to `ipc`.\n  - [3420aa50](https://www.github.com/tauri-apps/tauri/commit/3420aa5031b3274a95c6c5fa0f8683ca13213396) refactor: IPC handler \\[TRI-019] ([#9](https://www.github.com/tauri-apps/tauri/pull/9)) on 2022-01-09\n- Expose `run_on_main_thread` APIs on `Window` and `AppHandle`.\n  - [53fdfe52](https://www.github.com/tauri-apps/tauri/commit/53fdfe52bb30d52653c72ca9f42506c3863dcf4a) feat(core): expose `run_on_main_thread` API ([#2711](https://www.github.com/tauri-apps/tauri/pull/2711)) on 2021-10-04\n- The minimum Rust version is now `1.56`.\n  - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22\n- The minimum Rust version is now 1.57.\n  - [d5d6d2ab](https://www.github.com/tauri-apps/tauri/commit/d5d6d2abc17cd89c3a079d2ce01581193469dbc0) Isolation Pattern ([#43](https://www.github.com/tauri-apps/tauri/pull/43)) Co-authored-by: Ngo Iok Ui (Wu Yu Wei) <wusyong9104@gmail.com> Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app> on 2022-01-17\n- Scopes the `filesystem` APIs from the webview access using `tauri.conf.json > tauri > allowlist > fs > scope`.\n  Scopes the `asset` protocol access using `tauri.conf.json > tauri > allowlist > protocol > assetScope`.\n  Scopes the `http` APIs from the webview access using `tauri.conf.json > tauri > allowlist > http > scope`.\n  Scopes the `shell` execute API from the webview access using `tauri.conf.json > tauri > allowlist > shell > scope`. Additionally, check the `tauri.conf.json > tauri > bundle > externalBin` to prevent access to unknown sidecars.\n  - [7920ff14](https://www.github.com/tauri-apps/tauri/commit/7920ff14e6424079c48ea5645d9aa13e7a272b87) feat: scope the `fs` API and the `asset` protocol \\[TRI-026] \\[TRI-010] \\[TRI-011] ([#10](https://www.github.com/tauri-apps/tauri/pull/10)) on 2022-01-09\n  - [0ad1c651](https://www.github.com/tauri-apps/tauri/commit/0ad1c6515f696fadefddbf133a9561836b3d5934) feat(core): add `http` allowlist scope \\[TRI-008] ([#24](https://www.github.com/tauri-apps/tauri/pull/24)) on 2021-10-29\n  - [d4db95e7](https://www.github.com/tauri-apps/tauri/commit/d4db95e7161e064d9463bc84d871a5391c3ce2d7) feat(core): shell execute API scope \\[TRI-002] ([#36](https://www.github.com/tauri-apps/tauri/pull/36)) on 2021-12-01\n- `Builder#setup` closure type changed from `Fn` to `FnOnce`.\n  - [3f3599b9](https://www.github.com/tauri-apps/tauri/commit/3f3599b9cc238da06b36aff4c120a013a97fc3f0) refactor(core): change `setup` closure type to `FnOnce`, closes [#3061](https://www.github.com/tauri-apps/tauri/pull/3061) ([#3065](https://www.github.com/tauri-apps/tauri/pull/3065)) on 2021-12-27\n- The `tauri::api::shell::open`'s `with` argument is now an enum value instead of any string.\n  - [63921fad](https://www.github.com/tauri-apps/tauri/commit/63921fada436f010c33bb5e647bd67c6e549571c) refactor: change `tauri::api::open` `with` argument to an enum \\[TRI-022] ([#19](https://www.github.com/tauri-apps/tauri/pull/19)) on 2022-01-09\n- The `shell` allowlist now includes a `sidecar` flag, which enables the use of the `shell` API to execute sidecars.\n  - [eed01728](https://www.github.com/tauri-apps/tauri/commit/eed017287fed2ade689af4268e8b63b9c9f2e585) feat(core): add `shell > sidecar` allowlist and `process` feature flag \\[TRI-037] ([#18](https://www.github.com/tauri-apps/tauri/pull/18)) on 2021-10-24\n- **Breaking change:** The sidecar's target triple suffix is now removed at build time.\n  - [3035e458](https://www.github.com/tauri-apps/tauri/commit/3035e4581c161ec7f0bd6d9b42e9015cf1dd1d77) Remove target triple from sidecar bin paths, closes [#3355](https://www.github.com/tauri-apps/tauri/pull/3355) ([#3356](https://www.github.com/tauri-apps/tauri/pull/3356)) on 2022-02-07\n- Fix streaming of small files using the `asset` protocol.\n  - [151e629e](https://www.github.com/tauri-apps/tauri/commit/151e629ebf15ec5c068eb623e3dbc0ecdef1f816) fix(core): streaming of small files using `asset://`, closes [#2854](https://www.github.com/tauri-apps/tauri/pull/2854) ([#3039](https://www.github.com/tauri-apps/tauri/pull/3039)) on 2021-12-09\n- Add `set_menu` API on `tauri::SystemTrayHandle`.\n  - [0e4d12b5](https://www.github.com/tauri-apps/tauri/commit/0e4d12b541652d98c5a73de65034db8214e0363c) fix: [#2502](https://www.github.com/tauri-apps/tauri/pull/2502) Expose `set_menu` from tao through the TrayHandle struct ([#2532](https://www.github.com/tauri-apps/tauri/pull/2532)) on 2021-10-02\n- Adds `unlisten` function to the `Window` struct.\n  - [3a59f5f7](https://www.github.com/tauri-apps/tauri/commit/3a59f5f79e1b3ea08a224e6f2e0a56668d96cda3) Unlisten to an event on this window ([#2664](https://www.github.com/tauri-apps/tauri/pull/2664)) on 2021-09-28\n- Force updater endpoint URL to use `https` on release builds.\n  - [c077f449](https://www.github.com/tauri-apps/tauri/commit/c077f449270cffbf7956b1af81e1fb237ebf564a) feat: force endpoint URL to use https on release \\[TRI-015] ([#41](https://www.github.com/tauri-apps/tauri/pull/41)) on 2022-01-09\n- Validate the `std::env::current_exe` return value if `APPDIR` or `APPIMAGE` environment variables are set.\n  - [6fbd6dba](https://www.github.com/tauri-apps/tauri/commit/6fbd6dba5290dc017ab0ba5a44cf4358b022836f) feat(core): validate `AppImage` execution when env vars are set \\[TRI-041] ([#17](https://www.github.com/tauri-apps/tauri/pull/17)) on 2021-10-24\n- The event name is now validated. On a IPC message, it returns an error if it fails validation; on the Rust side, it panics.\n  It must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n  - [a48b8b18](https://www.github.com/tauri-apps/tauri/commit/a48b8b18d428bcc404d489daa690bbefe1f57311) feat(core): validate callbacks and event names \\[TRI-038] \\[TRI-020] ([#21](https://www.github.com/tauri-apps/tauri/pull/21)) on 2022-01-09\n- The window label is now validated and must be alphanumeric, resulting in a panic if it isn't.\n  - [680554de](https://www.github.com/tauri-apps/tauri/commit/680554de3ef6b7fccf87c441ad355cfef7aab6fe) feat: validate window label \\[TRI-021] ([#13](https://www.github.com/tauri-apps/tauri/pull/13)) on 2021-10-23\n- Allow `tauri.conf.json > package > version` to specify a path to a `package.json` file and pull the version from it.\n  - [46f2eae8](https://www.github.com/tauri-apps/tauri/commit/46f2eae8aad7c6a228eaf48480d5603dae6454b4) feat: allow config's version to be a path to package.json, closes [#2967](https://www.github.com/tauri-apps/tauri/pull/2967) ([#2971](https://www.github.com/tauri-apps/tauri/pull/2971)) on 2022-01-07\n- Added `clipboard` field on the `WebviewAttributes` struct, which must be set to `true` to enable clipboard access on the webview.\n  - [d42ccfb3](https://www.github.com/tauri-apps/tauri/commit/d42ccfb34f71851dfeb22fe74c83a8bdbddb5550) feat: add `clipboard` flag to `WebviewAttributes` \\[TRI-032] ([#12](https://www.github.com/tauri-apps/tauri/pull/12)) on 2021-10-23\n- Replace all of the `winapi` crate references with the `windows` crate, and replace `webview2` and `webview2-sys` with `webview2-com` and `webview2-com-sys` built with the `windows` crate. This goes along with updates to the TAO and WRY `next` branches.\n  - [bb00d5bd](https://www.github.com/tauri-apps/tauri/commit/bb00d5bd6c9dfcb6bdd0d308dadb70e6c6aafe5c) Replace winapi with windows crate and use webview2-com instead of webview2 ([#2615](https://www.github.com/tauri-apps/tauri/pull/2615)) on 2021-09-24\n- Show `Ok/Cancel` buttons instead of `Yes/No` when executing `window.confirm`.\n  - [e98c1af4](https://www.github.com/tauri-apps/tauri/commit/e98c1af44279a5ff6c8a6f0a506ecc219c9f77af) feat(core): expose message dialog APIs, fix window.confirm, implement HasRawWindowHandle for Window, closes [#2535](https://www.github.com/tauri-apps/tauri/pull/2535) ([#2700](https://www.github.com/tauri-apps/tauri/pull/2700)) on 2021-10-02\n- Update the `windows` crate to 0.25.0, which comes with pre-built libraries. WRY and Tao can both reference the same types directly from the `windows` crate instead of sharing bindings in `webview2-com-sys`.\n  - [34be6cf3](https://www.github.com/tauri-apps/tauri/commit/34be6cf37a98ee7cbd66623ebddae08e5a6520fd) Update webview2-com and windows crates ([#2875](https://www.github.com/tauri-apps/tauri/pull/2875)) on 2021-11-11\n\n## \\[1.0.0-beta.8]\n\n- Fix missing asset protocol path.Now the protocol is `https://asset.localhost/path/to/file` on Windows. Lunix and macOS\n  is still `asset://path/to/file`.\n  - [994b5325](https://www.github.com/tauri-apps/tauri/commit/994b5325dd385f564b37fe1530c5d798dc925fff) fix: missing asset protocol path ([#2484](https://www.github.com/tauri-apps/tauri/pull/2484)) on 2021-08-23\n- **Breaking change:** Removed `register_uri_scheme_protocol` from the `WebviewAttributes` struct and renamed `register_global_uri_scheme_protocol` to `register_uri_scheme_protocol` on the `Builder` struct, which now takes a `Fn(&AppHandle, &http::Request) -> http::Response` closure.\n  - [539e4489](https://www.github.com/tauri-apps/tauri/commit/539e4489e0bac7029d86917e9982ea49e02fe489) refactor: custom protocol ([#2503](https://www.github.com/tauri-apps/tauri/pull/2503)) on 2021-08-23\n- Migrate to latest custom protocol allowing `Partial content` streaming and Header parsing.\n  - [539e4489](https://www.github.com/tauri-apps/tauri/commit/539e4489e0bac7029d86917e9982ea49e02fe489) refactor: custom protocol ([#2503](https://www.github.com/tauri-apps/tauri/pull/2503)) on 2021-08-23\n\n## \\[1.0.0-beta.7]\n\n- Cleanup application on `AppHandle#exit`.\n  - [a54bba6c](https://www.github.com/tauri-apps/tauri/commit/a54bba6c868508844d68a9f4ea9f5519a2b94d09) fix(core): cleanup app before exit, closes [#2464](https://www.github.com/tauri-apps/tauri/pull/2464) ([#2466](https://www.github.com/tauri-apps/tauri/pull/2466)) on 2021-08-17\n- Fix `raw-window-handle` dependency declaration.\n  - [aecdfaf7](https://www.github.com/tauri-apps/tauri/commit/aecdfaf76fcf0d2820d6ce6eb7590ebe399bfa04) fix(core): `raw-window-handle` dependency, closes [#2460](https://www.github.com/tauri-apps/tauri/pull/2460) ([#2465](https://www.github.com/tauri-apps/tauri/pull/2465)) on 2021-08-17\n\n## \\[1.0.0-beta.6]\n\n- **Breaking change:** The `tauri::async_runtime::spawn` function now returns `tauri::async_runtime::JoinHandle<T>`.\n  - [9aeb04fa](https://www.github.com/tauri-apps/tauri/commit/9aeb04faf40989e8fd9d3dcac1d430a9e8bd23a9) feat(core): `async_runtime` `handle` API, `spawn` returns `JoinHandle` ([#2399](https://www.github.com/tauri-apps/tauri/pull/2399)) on 2021-08-11\n\n- **Breaking change:** Added `window_parent: Option<&Window>` as first argument to the `ask` and `message` APIs on the `tauri::api::dialog` module.\n  - [c76f4b7d](https://www.github.com/tauri-apps/tauri/commit/c76f4b7d39a620c7710c2046bb13b140a4793881) feat(core): set parent window on ask and message dialog APIs ([#2454](https://www.github.com/tauri-apps/tauri/pull/2454)) on 2021-08-16\n\n- Allow the `tauri::api::dialog` APIs to be executed on any secondary thread.\n  **Breaking change:** All dialog APIs now takes a closure instead of returning the response on the function call.\n  - [2088cd0f](https://www.github.com/tauri-apps/tauri/commit/2088cd0f24cd56ba427241136138c74bebee28f2) refactor(core): handle dialog threading internally, closes [#2223](https://www.github.com/tauri-apps/tauri/pull/2223) ([#2429](https://www.github.com/tauri-apps/tauri/pull/2429)) on 2021-08-14\n  - [60b1e260](https://www.github.com/tauri-apps/tauri/commit/60b1e260f511f50bbebceb6367f412c11f8dcf11) chore: adjust change file on 2021-08-16\n\n- **Breaking change:** The `Plugin` trait `initialize` method now takes an `AppHandle` reference instead of `App`.\n  - [c17532f7](https://www.github.com/tauri-apps/tauri/commit/c17532f7412bdcc57ae850c1251052ad1421fd67) refactor(core): change Plugin `initialize` signature, move register t… ([#2347](https://www.github.com/tauri-apps/tauri/pull/2347)) on 2021-08-03\n\n- **Breaking change:** Remove menu feature flag since there's no package dependency need to be installed on any platform anymore.\n  - [f81ebddf](https://www.github.com/tauri-apps/tauri/commit/f81ebddfcc1aea0d4989706aef43538e8ea98bea) feat: remove menu feature flag ([#2415](https://www.github.com/tauri-apps/tauri/pull/2415)) on 2021-08-13\n\n- Adds `set_activation_policy` API to the `tauri::App` struct (macOS only).\n  - [4a031add](https://www.github.com/tauri-apps/tauri/commit/4a031add69014a1f3823f4ea19b172a2557f6794) feat(core): expose `set_activation_policy`, closes [#2258](https://www.github.com/tauri-apps/tauri/pull/2258) ([#2420](https://www.github.com/tauri-apps/tauri/pull/2420)) on 2021-08-13\n\n- Add `handle` API to `tauri::async_runtime`.\n  - [9aeb04fa](https://www.github.com/tauri-apps/tauri/commit/9aeb04faf40989e8fd9d3dcac1d430a9e8bd23a9) feat(core): `async_runtime` `handle` API, `spawn` returns `JoinHandle` ([#2399](https://www.github.com/tauri-apps/tauri/pull/2399)) on 2021-08-11\n\n- Assets will now fallback to `<uri>/index.html` before `/index.html`, allowing anchor links to work as expected.\n  - [d22da650](https://www.github.com/tauri-apps/tauri/commit/d22da650ef5b51ab7dd0e45dd9527a9c5a01f84d) fix(core): fallback to `{asset}/index.html` before `index.html`, closes [#2328](https://www.github.com/tauri-apps/tauri/pull/2328) ([#2329](https://www.github.com/tauri-apps/tauri/pull/2329)) on 2021-08-02\n\n- Fix `data-tauri-drag-region` double-click, will now respect `resizable: false` and won't maximize.\n  - [1a510066](https://www.github.com/tauri-apps/tauri/commit/1a510066732d5f61c88c0ceed1c5f5cc559faf7d) fix(core): `data-tauri-drag-region` didn't respect resizable, closes [#2314](https://www.github.com/tauri-apps/tauri/pull/2314) ([#2316](https://www.github.com/tauri-apps/tauri/pull/2316)) on 2021-08-02\n\n- Fix `Notification.requestPermission()` throwing `Unhandled Promise Rejection: TypeError: undefined is not a function (near '...window.__TAURI__.invoke...')`\n  - [cf9f6aa1](https://www.github.com/tauri-apps/tauri/commit/cf9f6aa148db31c6fd4e3571b301db18654a1249) fix(core): fix typo in notifications, closes [#2330](https://www.github.com/tauri-apps/tauri/pull/2330) ([#2331](https://www.github.com/tauri-apps/tauri/pull/2331)) on 2021-08-02\n\n- Fix blur/focus events being incorrect on Windows.\n  - [d832d575](https://www.github.com/tauri-apps/tauri/commit/d832d575d9b03a0ff78accabe4631cc638c08c3b) fix(windows): use webview events on windows ([#2277](https://www.github.com/tauri-apps/tauri/pull/2277)) on 2021-07-23\n\n- Move items which `tauri::api` re-exports from `tauri-utils` to individual module `utils`. Because these items has their\n  own Error/Result types which are not related to api module at all.\n  - [12642a1a](https://www.github.com/tauri-apps/tauri/commit/12642a1ad42b23d8d4e31ae67cb8de287ba526c0) doc: update doc in tauri-utils and tauri ([#2435](https://www.github.com/tauri-apps/tauri/pull/2435)) on 2021-08-15\n  - [cd55d671](https://www.github.com/tauri-apps/tauri/commit/cd55d671498d48cf83ae603c4241031b9827119b) doc: update tauri documentations ([#2446](https://www.github.com/tauri-apps/tauri/pull/2446)) on 2021-08-16\n\n- Allow registering a plugin through an `AppHandle` instance using the `plugin` method.\n  - [5b7be813](https://www.github.com/tauri-apps/tauri/commit/5b7be8133a6a861128ad53d1202d633f7ea8c2d2) feat(core): add plugin register API on the `Manager` trait ([#2340](https://www.github.com/tauri-apps/tauri/pull/2340)) on 2021-08-02\n  - [c17532f7](https://www.github.com/tauri-apps/tauri/commit/c17532f7412bdcc57ae850c1251052ad1421fd67) refactor(core): change Plugin `initialize` signature, move register t… ([#2347](https://www.github.com/tauri-apps/tauri/pull/2347)) on 2021-08-03\n\n- Embed Info.plist file contents on binary on dev.\n  - [537ab1b6](https://www.github.com/tauri-apps/tauri/commit/537ab1b6d5a792c550a535619965c9e4126292e6) feat(core): inject src-tauri/Info.plist file on dev and merge on bundle, closes [#1570](https://www.github.com/tauri-apps/tauri/pull/1570) [#2338](https://www.github.com/tauri-apps/tauri/pull/2338) ([#2444](https://www.github.com/tauri-apps/tauri/pull/2444)) on 2021-08-15\n\n- Add `ExitRequested` event that allows preventing the app from exiting when all windows are closed, and an `AppHandle.exit()` function to exit the app manually.\n  - [892c63a0](https://www.github.com/tauri-apps/tauri/commit/892c63a0538f8d62680dce5848657128ad6b7af3) feat([#2287](https://www.github.com/tauri-apps/tauri/pull/2287)): Add `ExitRequested` event to let users prevent app from exiting ([#2293](https://www.github.com/tauri-apps/tauri/pull/2293)) on 2021-08-09\n\n- Change `App.create_window()` and `AppHandle.create_window()` to accept an `Into<String>` type instead of `String`.\n  - [8216cba1](https://www.github.com/tauri-apps/tauri/commit/8216cba13da10b272aae8e5058b1ec2cc937e196) `App.create_window()` to accept any `Into<String>` type (fix [#2290](https://www.github.com/tauri-apps/tauri/pull/2290)) ([#2291](https://www.github.com/tauri-apps/tauri/pull/2291)) on 2021-07-26\n\n- Fixes `defaultPath` option on dialog API not setting the file name if it doesn't exist on Linux.\n  - [8b2cc261](https://www.github.com/tauri-apps/tauri/commit/8b2cc2615d784100980807a520d16681819d83c7) fix(core): dialog's `defaultPath` behavior on Linux, closes [#2232](https://www.github.com/tauri-apps/tauri/pull/2232) ([#2382](https://www.github.com/tauri-apps/tauri/pull/2382)) on 2021-08-10\n\n- Fix ES Module detection for default imports with relative paths or scoped packages and exporting of async functions.\n  - [b2b36cfe](https://www.github.com/tauri-apps/tauri/commit/b2b36cfe8dfcccb341638a4cb6dc23a514c54148) fix(core): fixes ES Module detection for default imports with relative paths or scoped packages ([#2380](https://www.github.com/tauri-apps/tauri/pull/2380)) on 2021-08-10\n  - [fbf8caf5](https://www.github.com/tauri-apps/tauri/commit/fbf8caf5c419cb4fc3d123be910e094a8e8c4bef) fix(core): ESM detection when using `export async function` ([#2425](https://www.github.com/tauri-apps/tauri/pull/2425)) on 2021-08-14\n\n- Fix `listen` calls receiving past events.\n  - [1ecb8651](https://www.github.com/tauri-apps/tauri/commit/1ecb8651a795aa53eded31e3fb357c857dcf2ab1) fix(core): `listen` receiving past events, closes [#2323](https://www.github.com/tauri-apps/tauri/pull/2323) ([#2371](https://www.github.com/tauri-apps/tauri/pull/2371)) on 2021-08-09\n\n- Fixes file drop events being swapped (`file-drop-hover` on drop and `file-drop` on hover).\n  - [c2b0fe1c](https://www.github.com/tauri-apps/tauri/commit/c2b0fe1ce58e54dbcfdb63162ad17d7e6d8774d9) fix(core): fix wrong file drop events ([#2300](https://www.github.com/tauri-apps/tauri/pull/2300)) on 2021-07-31\n\n- Fixes `app.listen_global` not receiving events emitted in javascript.\n  - [a8c1de55](https://www.github.com/tauri-apps/tauri/commit/a8c1de5547cd3968aa238a06c410cb936391db5d) fix listen_global not listening to events with a window label ([#2272](https://www.github.com/tauri-apps/tauri/pull/2272)) on 2021-07-23\n\n- Fixes minimum window height being used as maximum height.\n  - [e3f99165](https://www.github.com/tauri-apps/tauri/commit/e3f9916526b226866137cb663e5cafab2b6a0e01) fix(core) minHeight being used as maxHeight ([#2247](https://www.github.com/tauri-apps/tauri/pull/2247)) on 2021-07-19\n\n- Fixes `unlisten` calls from JavaScript removing every registered event listener.\n  - [aa498e72](https://www.github.com/tauri-apps/tauri/commit/aa498e72614f59afcdd1f637b4e3bdf6fe00b137) fix: unlisten removes all listeners, closes [#2264](https://www.github.com/tauri-apps/tauri/pull/2264) ([#2302](https://www.github.com/tauri-apps/tauri/pull/2302)) on 2021-07-29\n\n- Use [`Url.join()`](https://docs.rs/url/2.2.2/url/struct.Url.html#method.join) when building webview URLs in\n  `WindowManager`, to handle edge cases and leading/trailing slashes in paths and urls.\n  - [31685c9f](https://www.github.com/tauri-apps/tauri/commit/31685c9f9d9c2f12d3debc3189ed67e8d669f3d8) fix([#2281](https://www.github.com/tauri-apps/tauri/pull/2281)): Prevent double slashes when joining URLs ([#2282](https://www.github.com/tauri-apps/tauri/pull/2282)) on 2021-07-23\n\n- Fixes `fs-all` feature not requiring the `base64` crate.\n  - [9b32b939](https://www.github.com/tauri-apps/tauri/commit/9b32b93996201190d83fcad4767709774e5a0780) fix(core): `fs-all` feature not including `base64` crate, closes [#2336](https://www.github.com/tauri-apps/tauri/pull/2336) ([#2368](https://www.github.com/tauri-apps/tauri/pull/2368)) on 2021-08-08\n\n- Update gtk and its related libraries to v0.14. This also remove requirements of `clang` as build dependency.\n  - [63ad3039](https://www.github.com/tauri-apps/tauri/commit/63ad303903bbee7c9a7382413b342e2a05d3ea75) chore(linux): bump gtk to v0.14 ([#2361](https://www.github.com/tauri-apps/tauri/pull/2361)) on 2021-08-07\n\n- Use `HeaderValue::from_bytes` instead of `HeaderValue::from_str` and `HeaderValue#to_bytes` instead of `HeaderValue#to_str` to improve compatibility.\n  - [1635798a](https://www.github.com/tauri-apps/tauri/commit/1635798a669ced825577941dc66f1334c5904b43) feat(core): improve HeaderValue compatibility, closes [#2162](https://www.github.com/tauri-apps/tauri/pull/2162) ([#2438](https://www.github.com/tauri-apps/tauri/pull/2438)) on 2021-08-15\n\n- Implement `Debug` on public API structs and enums.\n  - [fa9341ba](https://www.github.com/tauri-apps/tauri/commit/fa9341ba18ba227735341530900714dba0f27291) feat(core): implement `Debug` on public API structs/enums, closes [#2292](https://www.github.com/tauri-apps/tauri/pull/2292) ([#2387](https://www.github.com/tauri-apps/tauri/pull/2387)) on 2021-08-11\n\n- Adds `Resumed` and `MainEventsCleared` variants to the `Event` enum.\n  - [6be3f433](https://www.github.com/tauri-apps/tauri/commit/6be3f4339168651fe4e003b09f7d181fd12cd5a8) feat(core): add `Resumed` and `MainEventsCleared` events, closes [#2127](https://www.github.com/tauri-apps/tauri/pull/2127) ([#2439](https://www.github.com/tauri-apps/tauri/pull/2439)) on 2021-08-15\n\n- Panic when a dispatcher getter method (`Window`, `GlobalShortcutHandle`, `ClipboardManager` and `MenuHandle` APIs) is called on the main thread.\n  - [50ffdc06](https://www.github.com/tauri-apps/tauri/commit/50ffdc06fbde56aba32b4291fd130104935d1408) feat(core): panic when a dispatcher getter is used on the main thread ([#2455](https://www.github.com/tauri-apps/tauri/pull/2455)) on 2021-08-16\n\n- Use `percent_encoding::percent_decode` on the `asset` custom protocol URL before reading the file.\n  - [9acd8301](https://www.github.com/tauri-apps/tauri/commit/9acd83017f25b5b04e97e6e98af80f3076f8dbe3) fix(core): percent decode asset protocol URL ([#2427](https://www.github.com/tauri-apps/tauri/pull/2427)) on 2021-08-14\n\n- Keep original value on `config > package > productName` on Linux (previously converted to kebab-case).\n  - [3f039cb8](https://www.github.com/tauri-apps/tauri/commit/3f039cb8a308b0f18deaa37d7cfb1cc50d308d0e) fix: keep original `productName` for .desktop `Name` field, closes [#2295](https://www.github.com/tauri-apps/tauri/pull/2295) ([#2384](https://www.github.com/tauri-apps/tauri/pull/2384)) on 2021-08-10\n\n- Inject the invoke key on regular `<script></script>` tags.\n  - [d0142e87](https://www.github.com/tauri-apps/tauri/commit/d0142e87ddf5231fd46e2cbe4769bb16f3fe01e9) fix(core): invoke key injection on regular JS scripts, closes [#2342](https://www.github.com/tauri-apps/tauri/pull/2342) ([#2344](https://www.github.com/tauri-apps/tauri/pull/2344)) on 2021-08-03\n\n- Remove salt-related APIs (no longer needed after the `__TAURI_INVOKE_KEY__` implementation).\n  - [e2a0704c](https://www.github.com/tauri-apps/tauri/commit/e2a0704c6c7a447b628a95f8920f9bbe9feef229) refactor(core): remove salt APIs ([#2426](https://www.github.com/tauri-apps/tauri/pull/2426)) on 2021-08-14\n\n- Update minimum Rust version to 1.54.0.\n  - [a5394716](https://www.github.com/tauri-apps/tauri/commit/a53947160985a4f5b0ad1fbb4aa6865d6f852c66) chore: update rust to 1.54.0 ([#2434](https://www.github.com/tauri-apps/tauri/pull/2434)) on 2021-08-15\n\n- Run the setup callback after preparing the system tray.\n  - [1792c455](https://www.github.com/tauri-apps/tauri/commit/1792c45592cd4999af063fa89017f52a985553c1) fix(core): run setup after preparing system tray ([#2312](https://www.github.com/tauri-apps/tauri/pull/2312)) on 2021-07-28\n\n- Fixes a consistency issue on the order of `tauri::process::Command` emitted events.\n  - [737da872](https://www.github.com/tauri-apps/tauri/commit/737da87244cbdeb1158c93944bcb5e10bb383b31) fix(core): random shell command output order, closes [#2184](https://www.github.com/tauri-apps/tauri/pull/2184) ([#2376](https://www.github.com/tauri-apps/tauri/pull/2376)) on 2021-08-09\n\n- Force data directory even on non-local window.\n  - [70a19414](https://www.github.com/tauri-apps/tauri/commit/70a1941468f55f0dc09ac2e13802945891d766f4) fix(core): Force data_directory on Windows ([#2288](https://www.github.com/tauri-apps/tauri/pull/2288)) on 2021-07-23\n\n- Allow creation of empty Window with `create_tao_window()` and management with `send_tao_window_event()` on the AppHandler.\n  - [88080855](https://www.github.com/tauri-apps/tauri/commit/8808085541a629b8e22b612a06cef01cf9b3722e) feat(window): Allow creation of Window without `wry` ([#2321](https://www.github.com/tauri-apps/tauri/pull/2321)) on 2021-07-29\n  - [15566cfd](https://www.github.com/tauri-apps/tauri/commit/15566cfd64f5072fa4980a6ce5b33259958e9021) feat(core): add API to send wry window message to the event loop ([#2339](https://www.github.com/tauri-apps/tauri/pull/2339)) on 2021-08-02\n\n- Make `ClipboardManager` and `GlobalShortcutManager` public as they are exposed in the `AppHandle`.\n  - [6e0dbf63](https://www.github.com/tauri-apps/tauri/commit/6e0dbf639ac2c79e00fee9270a2ca8e613dc1f98) fix(core): Expose `ClipboardManager` and `GlobalShortcutManager` ([#2263](https://www.github.com/tauri-apps/tauri/pull/2263)) on 2021-08-03\n\n- - Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color.\n\n- Images you mark as template images should consist of only black and clear colors. You can use the alpha channel in the image to adjust the opacity of black content, however.\n\n- [426a6b49](https://www.github.com/tauri-apps/tauri/commit/426a6b49962de8faf061db2e820ac10fcbb300d6) feat(macOS): Implement tray icon template ([#2322](https://www.github.com/tauri-apps/tauri/pull/2322)) on 2021-07-29\n\n- Add `Event::Ready` on the `run()` callback. Triggered once when the event loop is ready.\n  - [28c6b7ad](https://www.github.com/tauri-apps/tauri/commit/28c6b7adfe98e701b158e936eafb7541ddc700e0) feat: add `Event::Ready` ([#2433](https://www.github.com/tauri-apps/tauri/pull/2433)) on 2021-08-15\n\n- - Do not run the updater with UAC task if server don't tell us. (Allow toggling server-side)\n\n- The updater expect a field named `with_elevated_task` with a `boolean` and will not run if the task is not installed first. (windows only)\n\n- [c5761190](https://www.github.com/tauri-apps/tauri/commit/c576119013297f3731d76924a887c5c2a62c13ba) fix(updater): Run elevated task only if server tell us ([#2357](https://www.github.com/tauri-apps/tauri/pull/2357)) on 2021-08-08\n\n- Add `try_state` API to the `Manager` trait.\n  - [84a0e04c](https://www.github.com/tauri-apps/tauri/commit/84a0e04cbe242b2b7abb388da2d878fce10bc27d) feat(core): `try_state` API on the `Manager` trait ([#2341](https://www.github.com/tauri-apps/tauri/pull/2341)) on 2021-08-02\n\n## \\[1.0.0-beta.5]\n\n- Allow preventing window close when the user requests it.\n  - [8157a68a](https://www.github.com/tauri-apps/tauri/commit/8157a68af1d94de1b90a14aa44139bb123b3436b) feat(core): allow listening to event loop events & prevent window close ([#2131](https://www.github.com/tauri-apps/tauri/pull/2131)) on 2021-07-06\n- Add `App#run` method with callback argument (event loop event handler).\n  - [8157a68a](https://www.github.com/tauri-apps/tauri/commit/8157a68af1d94de1b90a14aa44139bb123b3436b) feat(core): allow listening to event loop events & prevent window close ([#2131](https://www.github.com/tauri-apps/tauri/pull/2131)) on 2021-07-06\n- Fixes `data-tauri-drag-region` not firing its events.\n  - [578610a2](https://www.github.com/tauri-apps/tauri/commit/578610a29d5cefb8df070606b7587318b14c397a) fix(core): fix drag-region not sending its events correctly ([#2196](https://www.github.com/tauri-apps/tauri/pull/2196)) on 2021-07-12\n- Fix macOS `EXC_BAD_ACCESS` panic when app is code-signed.\n  - [456a94f6](https://www.github.com/tauri-apps/tauri/commit/456a94f6637746800b9b85fc3922e82871603402) fix(macOS): updater `EXC_BAD_ACCESS` ([#2181](https://www.github.com/tauri-apps/tauri/pull/2181)) on 2021-07-12\n- Fixes SVG loading on custom protocol.\n  - [e663bdd5](https://www.github.com/tauri-apps/tauri/commit/e663bdd5938830ab4eba961e69c3985191b499dd) fix(core): svg mime type ([#2129](https://www.github.com/tauri-apps/tauri/pull/2129)) on 2021-06-30\n- Expose `gtk_window` getter.\n  - [e0a8e09c](https://www.github.com/tauri-apps/tauri/commit/e0a8e09cab6799eeb9ec524b5f7780d1e5a84299) feat(core): expose `gtk_window`, closes [#2083](https://www.github.com/tauri-apps/tauri/pull/2083) ([#2141](https://www.github.com/tauri-apps/tauri/pull/2141)) on 2021-07-02\n- Inject invoke key on `script` tags with `type=\"module\"`.\n  - [f03eea9c](https://www.github.com/tauri-apps/tauri/commit/f03eea9c9b964709532afbc4d1dd343b3fd96081) feat(core): inject invoke key on `<script type=\"module\">` ([#2120](https://www.github.com/tauri-apps/tauri/pull/2120)) on 2021-06-29\n- Fix macOS high CPU usage.\n  - [a280ee90](https://www.github.com/tauri-apps/tauri/commit/a280ee90af0749ce18d6d0b00939b06473717bc9) Fix high cpu usage on mac, fix [#2074](https://www.github.com/tauri-apps/tauri/pull/2074) ([#2125](https://www.github.com/tauri-apps/tauri/pull/2125)) on 2021-06-30\n- Export `MenuHandle` and `MenuEvent` types on `tauri::window`.\n  - [acb88929](https://www.github.com/tauri-apps/tauri/commit/acb88929c4dd1bd2ac94f7896a1e54afb9417768) fix(core): export `MenuHandle` and `MenuEvent` ([#2148](https://www.github.com/tauri-apps/tauri/pull/2148)) on 2021-07-03\n- Use glib context for linux updater to prevent GTK panic.\n  - [3389bd81](https://www.github.com/tauri-apps/tauri/commit/3389bd8180ed79d0f6778f7f41a88ad6d4598932) fix(linux): use glib main context for the updater on linux ([#2222](https://www.github.com/tauri-apps/tauri/pull/2222)) on 2021-07-16\n- Bump `wry` 0.11 and fix focus integration to make it compatible with tao 0.4.\n  - [f0a8db62](https://www.github.com/tauri-apps/tauri/commit/f0a8db62e445dbbc5770e7addf0390ce3844c1ea) core(deps): bump `wry` to `0.11` ([#2210](https://www.github.com/tauri-apps/tauri/pull/2210)) on 2021-07-15\n- `Params` has been removed, along with all the associated types on it. Functions that previously accepted those\n  associated types now accept strings instead. Type that used a generic parameter `Params` now use `Runtime` instead. If\n  you use the `wry` feature, then types with a `Runtime` generic parameter should default to `Wry`, letting you omit the\n  explicit type and let the compiler infer it instead.\n\n`tauri`:\n\n- See `Params` note\n- If you were using `Params` inside a function parameter or definition, all references to it have been replaced with a\n  simple runtime that defaults to `Wry`. If you are not using a custom runtime, just remove `Params` from the definition\n  of functions/items that previously took it. If you are using a custom runtime, you *may* need to pass the runtime type\n  to these functions.\n- If you were using custom types for `Params` (uncommon and if you don't understand you probably were not using it), all\n  methods that were previously taking the custom type now takes an `Into<String>` or a `&str`. The types were already\n  required to be string-able, so just make sure to convert it into a string before passing it in if this breaking change\n  affects you.\n\n`tauri-macros`:\n\n- (internal) Added private `default_runtime` proc macro to allow us to give item definitions a custom runtime only when\n  the specified feature is enabled.\n\n`tauri-runtime`:\n\n- See `Params` note\n- Removed `Params`, `MenuId`, `Tag`, `TagRef`.\n- Added `menu::{MenuHash, MenuId, MenuIdRef}` as type aliases for the internal type that menu types now use.\n  - All previous menu items that had a `MenuId` generic now use the underlying `MenuId` type without a generic.\n- `Runtime`, `RuntimeHandle`, and `Dispatch` have no more generic parameter on `create_window(...)` and instead use the\n  `Runtime` type directly\n- `Runtime::system_tray` has no more `MenuId` generic and uses the string based `SystemTray` type directly.\n- (internal) `CustomMenuItem::id_value()` is now hashed on creation and exposed as the `id` field with type `MenuHash`.\n\n`tauri-runtime-wry`:\n\n- See `Params` note\n- update menu and runtime related types to the ones changed in `tauri-runtime`.\n\n`tauri-utils`:\n\n- `Assets::get` signature has changed to take a `&AssetKey` instead of `impl Into<AssetKey>` to become trait object\n  safe.\n- [fd8fab50](https://www.github.com/tauri-apps/tauri/commit/fd8fab507c8fa1b113b841af14c6693eb3955f6b) refactor(core): remove `Params` and replace with strings ([#2191](https://www.github.com/tauri-apps/tauri/pull/2191)) on 2021-07-15\n\n## \\[1.0.0-beta.4]\n\n- Double clicking a `data-tauri-drag-region` element will toggle the window maximized state.\n  - [8b7ac1ad](https://www.github.com/tauri-apps/tauri/commit/8b7ac1ad1432db1fb1b85b3f72d336b303414554) feat: double-click tauri-drag-region to maximize, closes [#1839](https://www.github.com/tauri-apps/tauri/pull/1839) ([#2106](https://www.github.com/tauri-apps/tauri/pull/2106)) on 2021-06-29\n- Fixes `asset` protocol crashing application.\n  - [99d96084](https://www.github.com/tauri-apps/tauri/commit/99d960841c4411c3805219d07640185b1d04c37a) fix(core): custom protocol regression ([#2115](https://www.github.com/tauri-apps/tauri/pull/2115)) on 2021-06-28\n\n## \\[1.0.0-beta.3]\n\n- Fixes `api::process::Command` events not firing consistently.\n  - [8c13344f](https://www.github.com/tauri-apps/tauri/commit/8c13344f8f97bc67b8fcde68ce14da438f7c66ba) fix(core): command events not firing consistently ([#2082](https://www.github.com/tauri-apps/tauri/pull/2082)) on 2021-06-27\n- Detect ESM scripts and inject the invoke key directly instead of using an IIFE.\n  - [7765c7fa](https://www.github.com/tauri-apps/tauri/commit/7765c7fa281853ddfb26b6b17534df95eaede804) fix(core): invoke key injection on ES module, improve performance ([#2094](https://www.github.com/tauri-apps/tauri/pull/2094)) on 2021-06-27\n- Improve invoke key code injection performance time rewriting code at compile time.\n  - [7765c7fa](https://www.github.com/tauri-apps/tauri/commit/7765c7fa281853ddfb26b6b17534df95eaede804) fix(core): invoke key injection on ES module, improve performance ([#2094](https://www.github.com/tauri-apps/tauri/pull/2094)) on 2021-06-27\n- Enforce uniqueness of window label.\n  - [d18b5367](https://www.github.com/tauri-apps/tauri/commit/d18b5367a91fd53d408510b456897630c70abcca) feat(core): enforce label uniqueness, closes [#2067](https://www.github.com/tauri-apps/tauri/pull/2067) ([#2097](https://www.github.com/tauri-apps/tauri/pull/2097)) on 2021-06-27\n- `Window` is now `Send + Sync` on Windows.\n  - [fe32afcc](https://www.github.com/tauri-apps/tauri/commit/fe32afcc933920d6282ae1d63b041b182278a031) fix(core): `Window` must be `Send + Sync` on Windows, closes [#2078](https://www.github.com/tauri-apps/tauri/pull/2078) ([#2093](https://www.github.com/tauri-apps/tauri/pull/2093)) on 2021-06-27\n\n## \\[1.0.0-beta.2]\n\n- Remove anonymous lifetimes on examples.\n  - [c1f8e113](https://www.github.com/tauri-apps/tauri/commit/c1f8e11342941b846479d73bf50cb0a328ac3574) chore: remove unnecessary anonymous lifetimes ([#1829](https://www.github.com/tauri-apps/tauri/pull/1829)) on 2021-05-14\n\n- Moves `shell`, `dialog::FileDialogBuilder` and `process::Command` APIs behind their allowlist feature flags.\n  - [aab3e1f1](https://www.github.com/tauri-apps/tauri/commit/aab3e1f18b74efd63fdc227e84382ee6e9e63817) refactor(core): move api modules behind allowlist feature flags ([#1864](https://www.github.com/tauri-apps/tauri/pull/1864)) on 2021-05-19\n\n- Adds `create_window` API to the `AppHandle` struct.\n  - [95d518af](https://www.github.com/tauri-apps/tauri/commit/95d518afa14fe613302d7091622b4b115543e1f2) feat(core): expose `AppHandle`, add `create_window` API ([#1855](https://www.github.com/tauri-apps/tauri/pull/1855)) on 2021-05-18\n\n- Adds a `handle` function to the `App` struct, which returns a `Send` handle to the app instance.\n  - [95d518af](https://www.github.com/tauri-apps/tauri/commit/95d518afa14fe613302d7091622b4b115543e1f2) feat(core): expose `AppHandle`, add `create_window` API ([#1855](https://www.github.com/tauri-apps/tauri/pull/1855)) on 2021-05-18\n\n- Use `attohttpc` on the HTTP API by default for bundle size optimization. `reqwest` is implemented behind the `reqwest-client` feature flag.\n  - [17c7c439](https://www.github.com/tauri-apps/tauri/commit/17c7c4396ff2d5e13fc8726c2965b4e810fad6b9) refactor(core): use `attohttpc` by default ([#1861](https://www.github.com/tauri-apps/tauri/pull/1861)) on 2021-05-19\n\n- Kill child processes spawned with `tauri::api::process::Command` on `tauri::App` drop. Can be skipped with `tauri::Builder#skip_cleanup_on_drop`.\n  - [4bdc4066](https://www.github.com/tauri-apps/tauri/commit/4bdc406679363f460e39079cb26319c39ab8cac8) feat(core): kill sidecar child processes on App drop, closes [#1896](https://www.github.com/tauri-apps/tauri/pull/1896) ([#1932](https://www.github.com/tauri-apps/tauri/pull/1932)) on 2021-06-01\n\n- Adds `clipboard` APIs (write and read text).\n  - [285bf64b](https://www.github.com/tauri-apps/tauri/commit/285bf64bf9569efb2df904c69c6df405ff0d62e2) feat(core): add clipboard writeText and readText APIs ([#2035](https://www.github.com/tauri-apps/tauri/pull/2035)) on 2021-06-21\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n\n- Allow accessing an `AppHandle` instance on a command through dependency injection.\n  - [59784c7e](https://www.github.com/tauri-apps/tauri/commit/59784c7e5587ff10d7f0c37ac414499da42d0707) feat(core): implement `CommandArg` for `AppHandle` ([#2037](https://www.github.com/tauri-apps/tauri/pull/2037)) on 2021-06-21\n\n- Fixes child processes messages not arriving until the subprocess is terminated.\n  - [df21ffc6](https://www.github.com/tauri-apps/tauri/commit/df21ffc61f42ffb4dfde50c74a01c07a59a76e3e) fix(core): command mpsc usage, closes [#1935](https://www.github.com/tauri-apps/tauri/pull/1935) ([#1936](https://www.github.com/tauri-apps/tauri/pull/1936)) on 2021-06-01\n\n- Adds `config` and `package_info` getters to the `App` and `AppHandle` structs.\n  - [70fc87a7](https://www.github.com/tauri-apps/tauri/commit/70fc87a7ff5a149c0cb22c13afdf90f25ddafde8) feat(core): add `config` and `package_info` getters on App and AppHandle ([#2016](https://www.github.com/tauri-apps/tauri/pull/2016)) on 2021-06-20\n\n- Expose mutable getters for the rest of the public `Context` getters.\n\n- `pub fn assets_mut(&mut self) -> &mut Arc<A>`\n\n- `pub fn default_window_icon_mut(&mut self) -> &mut Option<Vec<u8>>`\n\n- `pub fn system_tray_icon_mut(&mut self) -> &mut Option<Icon>`\n\n- `pub fn package_info_mut(&mut self) -> &mut tauri::api::PackageInfo`\n\n- [754c2e76](https://www.github.com/tauri-apps/tauri/commit/754c2e766abe25da053236ce311318aa1410d106) feat(core): finish mutable getters for `Context` ([#1814](https://www.github.com/tauri-apps/tauri/pull/1814)) on 2021-05-13\n\n- Adds `request_user_attention` API to the `Window` struct.\n  - [7dcca6e9](https://www.github.com/tauri-apps/tauri/commit/7dcca6e9281182b11ad3d4a79871f09b30b9b419) feat(core): add `request_user_attention` API, closes [#2023](https://www.github.com/tauri-apps/tauri/pull/2023) ([#2026](https://www.github.com/tauri-apps/tauri/pull/2026)) on 2021-06-20\n\n- Adds `show`, `hide`, `is_visible` and `toggle` APIs to the `MenuHandle`.\n  - [954460c5](https://www.github.com/tauri-apps/tauri/commit/954460c5205d57444ef4b1412051fbedf3e38676) feat(core): MenuHandle `show`, `hide`, `is_visible` and `toggle` APIs ([#1958](https://www.github.com/tauri-apps/tauri/pull/1958)) on 2021-06-15\n\n- Allow `dev_path` and `dist_dir` to be an array of root files and directories to embed.\n  - [6ec54c53](https://www.github.com/tauri-apps/tauri/commit/6ec54c53b504eec3873d326b1a45e450227d46ed) feat(core): allow `dev_path`, `dist_dir` as array of paths, fixes [#1897](https://www.github.com/tauri-apps/tauri/pull/1897) ([#1926](https://www.github.com/tauri-apps/tauri/pull/1926)) on 2021-05-31\n\n- Validate `tauri.conf.json > build > devPath` and `tauri.conf.json > build > distDir` values.\n  - [e97846aa](https://www.github.com/tauri-apps/tauri/commit/e97846aae933cad5cba284a2a133ae7aaee1107c) feat(core): validate `devPath` and `distDir` values ([#1848](https://www.github.com/tauri-apps/tauri/pull/1848)) on 2021-05-17\n\n- Set the Tauri window as parent for dialogs.\n  - [abf78c58](https://www.github.com/tauri-apps/tauri/commit/abf78c5860cdc52fbfd2bc5dbca29a864e2da8f9) fix(core): set parent window handle on dialogs, closes [#1876](https://www.github.com/tauri-apps/tauri/pull/1876) ([#1889](https://www.github.com/tauri-apps/tauri/pull/1889)) on 2021-05-21\n\n- Fallback to `index.html` on asset loading so router with history mode works.\n  - [8a7921e5](https://www.github.com/tauri-apps/tauri/commit/8a7921e5cbcbecea16d4ad4a29ef05d984ad3110) fix(core): fallback to index.html on asset loading, closes [#2020](https://www.github.com/tauri-apps/tauri/pull/2020) [#2021](https://www.github.com/tauri-apps/tauri/pull/2021) ([#2022](https://www.github.com/tauri-apps/tauri/pull/2022)) on 2021-06-20\n\n- Fixes custom protocol asset loader not decoding the percent-encoded path.\n  - [c021968e](https://www.github.com/tauri-apps/tauri/commit/c021968eb81f541599a3ce08eebb44e92604e39a) fix(core): asset loading not decoding percent-encoded path, closes [#1879](https://www.github.com/tauri-apps/tauri/pull/1879) ([#1938](https://www.github.com/tauri-apps/tauri/pull/1938)) on 2021-06-01\n\n- As some frameworks automatically add \"true\" as the value of the attribute, we need to check if it exists instead.\n  - [23707764](https://www.github.com/tauri-apps/tauri/commit/23707764dc54037e9aaba3023f2509459e3a9a8d) Drag region attribute check ([#1907](https://www.github.com/tauri-apps/tauri/pull/1907)) on 2021-05-30\n\n- Fixes build without the dialog Cargo features.\n  - [49fb3b72](https://www.github.com/tauri-apps/tauri/commit/49fb3b72255993ea6f92e8d475021adac0c914a7) fix(core): build without dialog Cargo features ([#1973](https://www.github.com/tauri-apps/tauri/pull/1973)) on 2021-06-09\n\n- Allow disabling the webview file drop handler (required to use drag and drop on the frontend on Windows) using the `tauri.conf.json > tauri > windows > fileDropEnabled` flag or the `WebviewAttributes#disable_file_drop_handler` method.\n  - [9cd10df4](https://www.github.com/tauri-apps/tauri/commit/9cd10df4d520de12f3b13fe88cc1c1a1b4bd48bf) feat(core): allow disabling file drop handler, closes [#2014](https://www.github.com/tauri-apps/tauri/pull/2014) ([#2030](https://www.github.com/tauri-apps/tauri/pull/2030)) on 2021-06-21\n\n- Fixes the HTTP API binary response serialization.\n  - [47f75584](https://www.github.com/tauri-apps/tauri/commit/47f7558417cc654bdb1d018127e8900bc4eac622) fix(core): resolve HTTP API on non-ok status code, fix binary response, closes [#2046](https://www.github.com/tauri-apps/tauri/pull/2046) ([#2053](https://www.github.com/tauri-apps/tauri/pull/2053)) on 2021-06-23\n\n- The `http` APIs now resolve the returned promise when the API call finishes with an error status code.\n  - [47f75584](https://www.github.com/tauri-apps/tauri/commit/47f7558417cc654bdb1d018127e8900bc4eac622) fix(core): resolve HTTP API on non-ok status code, fix binary response, closes [#2046](https://www.github.com/tauri-apps/tauri/pull/2046) ([#2053](https://www.github.com/tauri-apps/tauri/pull/2053)) on 2021-06-23\n\n- Run the `notification.show()` method on a dedicated async task to prevent a panic on Windows.\n  - [86d0aaa0](https://www.github.com/tauri-apps/tauri/commit/86d0aaa021f146529a65228de9e07d5f4df5b099) fix(core): notification panic on Windows, closes [#917](https://www.github.com/tauri-apps/tauri/pull/917) ([#2011](https://www.github.com/tauri-apps/tauri/pull/2011)) on 2021-06-19\n\n- Fixes HTTP API headers being overwritten when using the `reqwest` client.\n  - [1006c1cf](https://www.github.com/tauri-apps/tauri/commit/1006c1cf3be9be054d0650f0d9787b5aee045f5a) fix(core): HTTP headers being overwritten by reqwest, closes [#2032](https://www.github.com/tauri-apps/tauri/pull/2032) ([#2036](https://www.github.com/tauri-apps/tauri/pull/2036)) on 2021-06-21\n\n- Remove closed window from the `window.__TAURI__.__windows` array, used by the `window.getAll` API from `@tauri-apps/api`.\n  - [ebaa33cb](https://www.github.com/tauri-apps/tauri/commit/ebaa33cb47e045af75140d818565d211f45946b4) fix(core): remove closed window from `window.__TAURI__.__windows` ([#2057](https://www.github.com/tauri-apps/tauri/pull/2057)) on 2021-06-23\n\n- Panic on window getters usage on the main thread when the event loop is not running and document it.\n  - [ab3eb44b](https://www.github.com/tauri-apps/tauri/commit/ab3eb44bac7a3bf73a4985df38ccc2b87a913be7) fix(core): deadlock on window getters, fixes [#1893](https://www.github.com/tauri-apps/tauri/pull/1893) ([#1998](https://www.github.com/tauri-apps/tauri/pull/1998)) on 2021-06-16\n\n- Adds `focus` API to the WindowBuilder.\n  - [5f351622](https://www.github.com/tauri-apps/tauri/commit/5f351622c7812ad1bb56ddb37364ccaa4124c24b) feat(core): add focus API to the WindowBuilder and WindowOptions, [#1737](https://www.github.com/tauri-apps/tauri/pull/1737) on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n\n- **Breaking change:** The global shortcut API is now managed by `tao` so it cannot be accessed globally, the manager is now exposed on the `App` and `AppHandle` structs.\n  - [3280c4aa](https://www.github.com/tauri-apps/tauri/commit/3280c4aa91e50a8ccdd561a8b48a12a4a13ea8d5) refactor(core): global shortcut is now provided by `tao` ([#2031](https://www.github.com/tauri-apps/tauri/pull/2031)) on 2021-06-21\n\n- Hide `phf` crate export (not public API).\n  - [cd1a299a](https://www.github.com/tauri-apps/tauri/commit/cd1a299a7d5a9bd164063a32c87a27762b71e9a8) chore(core): hide phf, closes [#1961](https://www.github.com/tauri-apps/tauri/pull/1961) ([#1964](https://www.github.com/tauri-apps/tauri/pull/1964)) on 2021-06-09\n\n- (internal): allow `wry` dependency to be optional again while keeping default args.\n  code that wishes to expose a struct with a default arg should use the `crate::manager::default_args!` macro to declare\n  the struct, so that it can automatically feature-gate `DefaultArgs` behind using `wry`.\n  - [3d8dcbbf](https://www.github.com/tauri-apps/tauri/commit/3d8dcbbf8188b9e96d6b03dc984ca022eebf53e4) fix(core): allow wry to be an optional dep again (fix [#1841](https://www.github.com/tauri-apps/tauri/pull/1841)) ([#1854](https://www.github.com/tauri-apps/tauri/pull/1854)) on 2021-05-17\n\n- Adds `is_decorated` getter on Window.\n  - [f58a2114](https://www.github.com/tauri-apps/tauri/commit/f58a2114fbfd5307c349f05c88f2e08fd8baa8aa) feat(core): add `is_decorated` Window getter on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n\n- Adds `is_resizable` getter on Window.\n  - [1e8af280](https://www.github.com/tauri-apps/tauri/commit/1e8af280c27f381828d6209722b10e889082fa00) feat(core): add `is_resizable` Window getter on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n\n- Adds `is_visible` getter on Window.\n  - [36506c96](https://www.github.com/tauri-apps/tauri/commit/36506c967de82bc7ff453d11e6104ecf66d7a588) feat(core): add `is_visible` API on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n\n- Read `tauri.conf.json > tauri > bundle > icons` and use the first `.png` icon as window icon on Linux. Defaults to `icon/icon.png` if a PNG icon is not configured.\n  - [40b717ed](https://www.github.com/tauri-apps/tauri/commit/40b717edc57288a1393fad0529390e101ab903c1) feat(core): set window icon on Linux, closes [#1922](https://www.github.com/tauri-apps/tauri/pull/1922) ([#1937](https://www.github.com/tauri-apps/tauri/pull/1937)) on 2021-06-01\n\n- Adds `accelerator` method to the `CustomMenuItem` struct to define a keyboard shortcut for the menu item.\n  - [034c2601](https://www.github.com/tauri-apps/tauri/commit/034c26013bce0c7bbe6db067ea7fd24a53a5c998) feat(core): add `accelerator` method to `CustomMenuItem` ([#2043](https://www.github.com/tauri-apps/tauri/pull/2043)) on 2021-06-22\n\n- **Breaking change:** The `menu` API was not designed to have all the new features: submenus, item updates, disabled state... so we broke it before going to stable.\n  - [f7e9fe8f](https://www.github.com/tauri-apps/tauri/commit/f7e9fe8f3f7c83532713be6cc4ef84e8b127c208) refactor(core): new system tray and window menu APIs, closes [#1898](https://www.github.com/tauri-apps/tauri/pull/1898) ([#1944](https://www.github.com/tauri-apps/tauri/pull/1944)) on 2021-06-04\n\n- Adds a `PathResolver` struct to simplify the usage of the `tauri::api::path::{app_dir, resource_dir}` APIs, accessible through the `App` and `AppHandle` `path_resolver` methods.\n  - [5ca462f6](https://www.github.com/tauri-apps/tauri/commit/5ca462f6ccc6c970a6f2c8c6c1bc0e3343a52bfb) feat(core): add path resolver API to the App and AppHandle structs ([#2015](https://www.github.com/tauri-apps/tauri/pull/2015)) on 2021-06-19\n\n- Removes `image` dependency. For now only `.ico` icons on Windows are supported, and we'll implement other types on demand to optimize bundle size.\n  - [1be37a3f](https://www.github.com/tauri-apps/tauri/commit/1be37a3f30ff789d9396ec9009f9c0dd0bb928a7) refactor(core): remove `image` dependency ([#1859](https://www.github.com/tauri-apps/tauri/pull/1859)) on 2021-05-18\n\n- Remove window object from the `Manager` internal `HashMap` on close. This fixes the behavior of using `[App|AppHandle|Window]#get_window` after the window is closed (now correctly returns `None`).\n  - [08c161c5](https://www.github.com/tauri-apps/tauri/commit/08c161c5e85d9f6392be55723b573030fa70637b) fix(core): remove window from HashMap on close ([#2024](https://www.github.com/tauri-apps/tauri/pull/2024)) on 2021-06-20\n\n- Improve RPC security by requiring a numeric code to invoke commands. The codes are generated by the Rust side and injected into the app's code using a closure, so external scripts can't access the backend. This change doesn't protect `withGlobalTauri` (`window.__TAURI__`) usage.\n  - [160fb052](https://www.github.com/tauri-apps/tauri/commit/160fb0529fd31d755574ae30fbdf01fa221a2acb) feat(core): improve RPC security, closes [#814](https://www.github.com/tauri-apps/tauri/pull/814) ([#2047](https://www.github.com/tauri-apps/tauri/pull/2047)) on 2021-06-22\n\n- Adds `run_iteration` API to the `App` and return the app instance on the `build` method of the `Builder`. The `run_iteration` method runs the window event loop step by step, allowing Tauri to be run along other applications.\n  - [8c0d0739](https://www.github.com/tauri-apps/tauri/commit/8c0d0739eebf7286b64a5380e922746411eb52c6) feat(core): add `run_iteration`, `parent_window` and `owner_window` APIs, closes [#1872](https://www.github.com/tauri-apps/tauri/pull/1872) ([#1874](https://www.github.com/tauri-apps/tauri/pull/1874)) on 2021-05-21\n\n- The `run_on_main_thread` API now uses WRY's UserEvent, so it wakes the event loop.\n  - [9bf82f0d](https://www.github.com/tauri-apps/tauri/commit/9bf82f0d9261808f58bdb5b5dbd6a255e5dcd333) fix(core): `run_on_main_thread` now wakes the event loop ([#1949](https://www.github.com/tauri-apps/tauri/pull/1949)) on 2021-06-04\n\n- Adds `set_focus` API on Window.\n  - [bb6992f8](https://www.github.com/tauri-apps/tauri/commit/bb6992f888196ca7c87bb2fe74ad2bd8bf393e05) feat(core): add `set_focus` window API, fixes [#1737](https://www.github.com/tauri-apps/tauri/pull/1737) on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n\n- Adds `set_skip_taskbar` API on Window.\n  - [e06aa277](https://www.github.com/tauri-apps/tauri/commit/e06aa277384450cfef617c0e57b0d5d403bb1e7f) feat(core): add `set_skip_taskbar` API on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n\n- Adds `skip_taskbar` API to the WindowBuilder.\n  - [5525b03a](https://www.github.com/tauri-apps/tauri/commit/5525b03a78a2232c650043fbd9894ce1553cad41) feat(core): add `skip_taskbar` API to the WindowBuilder/WindowOptions on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n\n- **Breaking change:** The `system_tray` and `on_system_tray_event` APIs were not designed to have all the new features: submenus, item updates, click events, positioning... so we broke it before going to stable.\n  - [f7e9fe8f](https://www.github.com/tauri-apps/tauri/commit/f7e9fe8f3f7c83532713be6cc4ef84e8b127c208) refactor(core): new system tray and window menu APIs, closes [#1898](https://www.github.com/tauri-apps/tauri/pull/1898) ([#1944](https://www.github.com/tauri-apps/tauri/pull/1944)) on 2021-06-04\n\n- Fix loading url containing URI fragment\n  - [07fd9a92](https://www.github.com/tauri-apps/tauri/commit/07fd9a92dea695fc8178d34bb71e0745f21cd634) fix(core): add hash symbol in uri parse, closes [#1943](https://www.github.com/tauri-apps/tauri/pull/1943) ([#1947](https://www.github.com/tauri-apps/tauri/pull/1947)) on 2021-06-05\n\n- Adds `Window#center` and `WindowBuilder#center` APIs.\n  - [5cba6eb4](https://www.github.com/tauri-apps/tauri/commit/5cba6eb4d28d53f06855d60d4d0eae6b95233ccf) feat(core): add window `center` API, closes [#1822](https://www.github.com/tauri-apps/tauri/pull/1822) ([#1954](https://www.github.com/tauri-apps/tauri/pull/1954)) on 2021-06-05\n\n- Adds window native handle getter (HWND on Windows).\n  - [abf78c58](https://www.github.com/tauri-apps/tauri/commit/abf78c5860cdc52fbfd2bc5dbca29a864e2da8f9) fix(core): set parent window handle on dialogs, closes [#1876](https://www.github.com/tauri-apps/tauri/pull/1876) ([#1889](https://www.github.com/tauri-apps/tauri/pull/1889)) on 2021-05-21\n\n## \\[1.0.0-beta.1]\n\n- Adds a mutable `config` getter on the `Context` struct.\n  - [144d6b9](https://www.github.com/tauri-apps/tauri/commit/144d6b9d4d327debae13392715103a3208ce8a45) feat(core): add mutable `config` getter on the `Context` struct ([#1803](https://www.github.com/tauri-apps/tauri/pull/1803)) on 2021-05-12\n\n## \\[1.0.0-beta.0]\n\n- **Breaking:** `api::path::resolve_path()` and `api::path::app_dir()` now takes the config as first argument and the `PackageInfo` as second argument.\n  **Breaking:** `api::path::app_dir()` now resolves to `${configDir}/${config.tauri.bundle.identifier}`.\n  - [428d50a](https://www.github.com/tauri-apps/tauri/commit/428d50add4da937325d189434dbaf3a02d745187) refactor(core): use bundle identifier on app dir, closes [#1693](https://www.github.com/tauri-apps/tauri/pull/1693) ([#1703](https://www.github.com/tauri-apps/tauri/pull/1703)) on 2021-05-04\n  - [7bb7dda](https://www.github.com/tauri-apps/tauri/commit/7bb7dda7523bc1a81e890e0aeafffd35e3ed767f) refactor(core): resolve resource_dir using the package info ([#1762](https://www.github.com/tauri-apps/tauri/pull/1762)) on 2021-05-10\n\n- Adds `manage` API to the `Builder` struct, which manages app state.\n  - [8b6f3de](https://www.github.com/tauri-apps/tauri/commit/8b6f3de0ad47684e72a2ae5f884d8675acfaeeac) feat(core): add state management, closes [#1655](https://www.github.com/tauri-apps/tauri/pull/1655) ([#1665](https://www.github.com/tauri-apps/tauri/pull/1665)) on 2021-05-02\n\n- **Breaking:** The `assets` field on the `tauri::Context` struct is now a `Arc<impl Assets>`.\n  - [5110c70](https://www.github.com/tauri-apps/tauri/commit/5110c704be67e51d49fb83f3710afb593973dcf9) feat(core): allow users to access the Assets instance ([#1691](https://www.github.com/tauri-apps/tauri/pull/1691)) on 2021-05-03\n\n- Only commands with a `async fn` are executed on a separate task. `#[command] fn command_name` runs on the main thread.\n  - [bb8dafb](https://www.github.com/tauri-apps/tauri/commit/bb8dafbe1ea6edde7385631d41ac05e96a3309ef) feat(core): #\\[command] return with autoref specialization workaround fix [#1672](https://www.github.com/tauri-apps/tauri/pull/1672) ([#1734](https://www.github.com/tauri-apps/tauri/pull/1734)) on 2021-05-09\n\n- Renamed the `command` API module to `process`.\n  - [b0bb796](https://www.github.com/tauri-apps/tauri/commit/b0bb796a42e2560233aea47ce6ced54ac238eb53) refactor: rename `command` mod to `process`, move restart_application ([#1667](https://www.github.com/tauri-apps/tauri/pull/1667)) on 2021-04-30\n\n- Adds `options` argument to the shell command API (`env` and `cwd` configuration).\n  - [721e98f](https://www.github.com/tauri-apps/tauri/commit/721e98f175567b360c86f30565ab1b9d08e7cf85) feat(core): add env, cwd to the command API, closes [#1634](https://www.github.com/tauri-apps/tauri/pull/1634) ([#1635](https://www.github.com/tauri-apps/tauri/pull/1635)) on 2021-04-28\n\n- Improves support for commands returning `Result`.\n  - [bb8dafb](https://www.github.com/tauri-apps/tauri/commit/bb8dafbe1ea6edde7385631d41ac05e96a3309ef) feat(core): #\\[command] return with autoref specialization workaround fix [#1672](https://www.github.com/tauri-apps/tauri/pull/1672) ([#1734](https://www.github.com/tauri-apps/tauri/pull/1734)) on 2021-05-09\n\n- Adds `status` and `output` APIs to the `tauri::api::process::Command` struct.\n  - [d92fde7](https://www.github.com/tauri-apps/tauri/commit/d92fde75053d1f3fbac4f926c40a1e8cf29bf2a4) feat(core): add `output` and `status` APIs to the `Command` struct ([#1668](https://www.github.com/tauri-apps/tauri/pull/1668)) on 2021-05-02\n\n- The `create_window` API callback now takes two arguments: the `WindowBuilder` and the `WebviewAttributes` and must return a tuple containing both values.\n  - [c31f097](https://www.github.com/tauri-apps/tauri/commit/c31f0978c535f794fffb75a121e69a323e70b06e) refactor: update to wry 0.9 ([#1630](https://www.github.com/tauri-apps/tauri/pull/1630)) on 2021-04-28\n\n- Reintroduce `csp` injection, configured on `tauri.conf.json > tauri > security > csp`.\n  - [6132f3f](https://www.github.com/tauri-apps/tauri/commit/6132f3f4feb64488ef618f690a4f06adce864d91) feat(core): reintroduce CSP injection ([#1704](https://www.github.com/tauri-apps/tauri/pull/1704)) on 2021-05-04\n\n- Adds the default types used with `Builder::default()` to items that expose `Params` in their type. This allows you to\n  skip specifying a generic parameter to types like `Window<P>` if you use the default type.\n  - [27a7810](https://www.github.com/tauri-apps/tauri/commit/27a78107673b63b6dad42fcf2bc8b7acd90b6ec4) feat(core): add default Args to all types exposing Params ([#1777](https://www.github.com/tauri-apps/tauri/pull/1777)) on 2021-05-11\n\n- Change draggable region element detection from `drag-region` class to `data-tauri-drag-region` attribute.\n  - [4f1e87f](https://www.github.com/tauri-apps/tauri/commit/4f1e87f87bbd4e094116b83edff448847da178ea) refactor(core): change drag element detection to data attr, fixes [#1656](https://www.github.com/tauri-apps/tauri/pull/1656) ([#1659](https://www.github.com/tauri-apps/tauri/pull/1659)) on 2021-04-29\n\n- Emit `tauri://resize`, `tauri://move`, `tauri://close-requested`, `tauri://destroyed`, `tauri://focus`, `tauri://blur` and `tauri://scale-change` events to the window.\n  - [9c10ccf](https://www.github.com/tauri-apps/tauri/commit/9c10ccf8d30af92cb90044a732e904c53507771a) feat(core) window events, closes [#1523](https://www.github.com/tauri-apps/tauri/pull/1523) ([#1726](https://www.github.com/tauri-apps/tauri/pull/1726)) on 2021-05-06\n\n- The event `emit` function payload type is now `impl Serialize` instead of `Option<impl Serialize>`.\n  - [4687538](https://www.github.com/tauri-apps/tauri/commit/4687538987af7638e8625342f2e3d065771c12c7) refactor(core): drop `Option` payload type on `event::emit` ([#1760](https://www.github.com/tauri-apps/tauri/pull/1760)) on 2021-05-09\n\n- Update `tauri-hotkey` to v0.1.2, fixing a compilation issue on 32-bit platforms.\n  - [92a01a7](https://www.github.com/tauri-apps/tauri/commit/92a01a7cab6d55f368b60a0d6bcc96d2847b2a81) chore(deps): bump tauri-hotkey to 0.1.2 ([#1701](https://www.github.com/tauri-apps/tauri/pull/1701)) on 2021-05-04\n\n- Implemented window menus APIs.\n  - [41d5d6a](https://www.github.com/tauri-apps/tauri/commit/41d5d6aff29750beca7263a9c186bf209388b8ee) feat(core): window menus ([#1745](https://www.github.com/tauri-apps/tauri/pull/1745)) on 2021-05-08\n\n- Added the \\`#\\[non_exhaustive] attribute where appropriate.\n  - [e087f0f](https://www.github.com/tauri-apps/tauri/commit/e087f0f9374355ac4b4a48f94727ef8b26b1c4cf) feat: add `#[non_exhaustive]` attribute ([#1725](https://www.github.com/tauri-apps/tauri/pull/1725)) on 2021-05-05\n\n- `Notification.requestPermission()` now returns `\"denied\"` when not allowlisted.\n  `IsNotificationPermissionGranted` returns `false` when not allowlisted.\n  - [8941790](https://www.github.com/tauri-apps/tauri/commit/8941790f98206adce441d7bdc42374af39edc786) fix(core): notification permission check when !allowlisted, closes [#1666](https://www.github.com/tauri-apps/tauri/pull/1666) ([#1677](https://www.github.com/tauri-apps/tauri/pull/1677)) on 2021-05-02\n\n- Refactored the `Plugin` trait `initialize` and `extend_api` signatures.\n  `initialize` now takes the `App` as first argument, and `extend_api` takes an `Invoke` instead of `InvokeMessage`.\n  This adds support to managed state on plugins.\n  - [8b6f3de](https://www.github.com/tauri-apps/tauri/commit/8b6f3de0ad47684e72a2ae5f884d8675acfaeeac) feat(core): add state management, closes [#1655](https://www.github.com/tauri-apps/tauri/pull/1655) ([#1665](https://www.github.com/tauri-apps/tauri/pull/1665)) on 2021-05-02\n  - [1d6f418](https://www.github.com/tauri-apps/tauri/commit/1d6f41812925142eb40f1908d2498880ab4a6266) refactor(core): merge invoke items into single struct, allow ? ([#1683](https://www.github.com/tauri-apps/tauri/pull/1683)) on 2021-05-02\n\n- `window.print()` now works on all platforms.\n  - [56e74cc](https://www.github.com/tauri-apps/tauri/commit/56e74ccf748ef24075b1095170d764dcfda4ddeb) feat(core): ensure `window.print()`works on macOS ([#1738](https://www.github.com/tauri-apps/tauri/pull/1738)) on 2021-05-07\n\n- **Breaking:** `Context` fields are now private, and is expected to be created through `Context::new(...)`.\n  All fields previously available through `Context` are now public methods.\n  - [5542359](https://www.github.com/tauri-apps/tauri/commit/55423590ddbf560684dab6a0214acf95aadfa8d2) refactor(core): Context fields now private, Icon used on all platforms ([#1774](https://www.github.com/tauri-apps/tauri/pull/1774)) on 2021-05-11\n\n- `Settings` is now serialized using `bincode`.\n  - [455c550](https://www.github.com/tauri-apps/tauri/commit/455c550f347fe09581b4440bb868cacbbbbe2ad2) refactor(core): `Settings` serialization using `bincode` ([#1758](https://www.github.com/tauri-apps/tauri/pull/1758)) on 2021-05-09\n\n- The window management API was refactored: removed `setX`, `setY`, `setWidth`, `setHeight` APIs, renamed `resize` to `setSize` and the size and position APIs now allow defining both logical and physical values.\n  - [6bfac86](https://www.github.com/tauri-apps/tauri/commit/6bfac866a703f1499a64237fb29b2625703f4e22) refactor(core): add window getters, physical & logical sizes/positions ([#1723](https://www.github.com/tauri-apps/tauri/pull/1723)) on 2021-05-05\n\n- Removed the `tcp` module from `tauri::api`.\n  - [e087f0f](https://www.github.com/tauri-apps/tauri/commit/e087f0f9374355ac4b4a48f94727ef8b26b1c4cf) feat: add `#[non_exhaustive]` attribute ([#1725](https://www.github.com/tauri-apps/tauri/pull/1725)) on 2021-05-05\n\n- Removes the `with_window` attribute on the `command` macro. Tauri now infers it using the `CommandArg` trait.\n  - [8b6f3de](https://www.github.com/tauri-apps/tauri/commit/8b6f3de0ad47684e72a2ae5f884d8675acfaeeac) feat(core): add state management, closes [#1655](https://www.github.com/tauri-apps/tauri/pull/1655) ([#1665](https://www.github.com/tauri-apps/tauri/pull/1665)) on 2021-05-02\n  - [1d6f418](https://www.github.com/tauri-apps/tauri/commit/1d6f41812925142eb40f1908d2498880ab4a6266) refactor(core): merge invoke items into single struct, allow ? ([#1683](https://www.github.com/tauri-apps/tauri/pull/1683)) on 2021-05-02\n\n- Move `restart_application` API from `app` module to `process` module.\n  - [b0bb796](https://www.github.com/tauri-apps/tauri/commit/b0bb796a42e2560233aea47ce6ced54ac238eb53) refactor: rename `command` mod to `process`, move restart_application ([#1667](https://www.github.com/tauri-apps/tauri/pull/1667)) on 2021-04-30\n\n- `tauri-runtime` crate initial release.\n  - [665ec1d](https://www.github.com/tauri-apps/tauri/commit/665ec1d4a199ad06e369221da187dc838da71cbf) refactor: move runtime to `tauri-runtime` crate ([#1751](https://www.github.com/tauri-apps/tauri/pull/1751)) on 2021-05-09\n  - [45a7a11](https://www.github.com/tauri-apps/tauri/commit/45a7a111e0cf9d9956d713cc9a99fa7a5313eec7) feat(core): add `tauri-wry` crate ([#1756](https://www.github.com/tauri-apps/tauri/pull/1756)) on 2021-05-09\n\n- The `setup` Error type must be `Send`.\n  - [e087f0f](https://www.github.com/tauri-apps/tauri/commit/e087f0f9374355ac4b4a48f94727ef8b26b1c4cf) feat: add `#[non_exhaustive]` attribute ([#1725](https://www.github.com/tauri-apps/tauri/pull/1725)) on 2021-05-05\n\n- Simplify usage of app event and window label types. The following functions now\n  accept references the `Tag` can be borrowed as. This means an `&str` can now be\n  accepted for functions like `Window::emit`. This is a breaking change for the\n  following items, which now need to take a reference. Additionally, type inference\n  for `&\"event\".into()` will no longer work, but `&\"event\".to_string()` will. The\n  solution for this is to now just pass `\"event\"` because `Borrow<str>` is implemented\n  for the default event type `String`.\n\n- **Breaking:** `Window::emit` now accepts `Borrow` for the event.\n\n- **Breaking:** `Window::emit_others` now accepts `Borrow` for the event\n\n- **Breaking:** `Window::trigger` now accepts `Borrow` for the event.\n\n- **Breaking:** `Manager::emit_all` now accepts `Borrow` for the event.\n\n- **Breaking:** `Manager::emit_to` now accepts `Borrow` for both the event and window label.\n\n- **Breaking:** `Manager::trigger_global` now accepts `Borrow` for the event.\n\n- **Breaking:** `Manager::get_window` now accepts `Borrow` for the window label.\n\n- *(internal):* `trait tauri::runtime::tag::TagRef` helper for accepting tag references.\n  Any time you want to accept a tag reference, that trait will handle requiring the reference\n  to have all the necessary bounds, and generate errors when the exposed function doesn't\n  set a bound like `P::Event: Borrow<E>`.\n\n- [181e132](https://www.github.com/tauri-apps/tauri/commit/181e132aee895da23c1b63deb41a52e9910910cc) refactor(core): simplify usage of app event and window label types ([#1623](https://www.github.com/tauri-apps/tauri/pull/1623)) on 2021-04-27\n\n- [a755d23](https://www.github.com/tauri-apps/tauri/commit/a755d23e1bd0a3d6a2b6a85ff94feaf5a1a3a60d) refactor(core): more bounds for better errors from [#1623](https://www.github.com/tauri-apps/tauri/pull/1623) ([#1632](https://www.github.com/tauri-apps/tauri/pull/1632)) on 2021-04-27\n\n- `tauri-runtime-wry` initial release.\n  - [45a7a11](https://www.github.com/tauri-apps/tauri/commit/45a7a111e0cf9d9956d713cc9a99fa7a5313eec7) feat(core): add `tauri-wry` crate ([#1756](https://www.github.com/tauri-apps/tauri/pull/1756)) on 2021-05-09\n\n- Adds system tray support.\n  - [c090927](https://www.github.com/tauri-apps/tauri/commit/c0909270216983bed47453ddf5902abf5082fe42) feat(core): system tray, closes [#157](https://www.github.com/tauri-apps/tauri/pull/157) ([#1749](https://www.github.com/tauri-apps/tauri/pull/1749)) on 2021-05-09\n\n- Rename `Attributes` to `WindowBuilder`.\n  - [c31f097](https://www.github.com/tauri-apps/tauri/commit/c31f0978c535f794fffb75a121e69a323e70b06e) refactor: update to wry 0.9 ([#1630](https://www.github.com/tauri-apps/tauri/pull/1630)) on 2021-04-28\n\n- The `Window#create_window` API now has the same signature as `App#create_window`.\n  - [dbd9b07](https://www.github.com/tauri-apps/tauri/commit/dbd9b078aaa53663f61318153ba3d50c7e554ad8) refactor(core): `create_window` API signature on the Window struct ([#1746](https://www.github.com/tauri-apps/tauri/pull/1746)) on 2021-05-08\n\n- Adds `on_window_event` API to the `Window` struct.\n  - [9c10ccf](https://www.github.com/tauri-apps/tauri/commit/9c10ccf8d30af92cb90044a732e904c53507771a) feat(core) window events, closes [#1523](https://www.github.com/tauri-apps/tauri/pull/1523) ([#1726](https://www.github.com/tauri-apps/tauri/pull/1726)) on 2021-05-06\n\n- Adds window getters.\n  - [6bfac86](https://www.github.com/tauri-apps/tauri/commit/6bfac866a703f1499a64237fb29b2625703f4e22) refactor(core): add window getters, physical & logical sizes/positions ([#1723](https://www.github.com/tauri-apps/tauri/pull/1723)) on 2021-05-05\n\n- Update `wry` to v0.9.\n  - [c31f097](https://www.github.com/tauri-apps/tauri/commit/c31f0978c535f794fffb75a121e69a323e70b06e) refactor: update to wry 0.9 ([#1630](https://www.github.com/tauri-apps/tauri/pull/1630)) on 2021-04-28\n\n## \\[1.0.0-beta-rc.4]\n\n- Update `tauri-macros` and `tauri-utils` to `1.0.0-beta-rc.1`.\n\n## \\[1.0.0-beta-rc.3]\n\n- `tauri::error::CreateWebview` now has the error string message attached.\n  - [7471e34](https://www.github.com/tauri-apps/tauri/commit/7471e347d3b23b7604c19040b0d989da8f48cb91) feat(core): add error message on `error::CreateWebview` ([#1602](https://www.github.com/tauri-apps/tauri/pull/1602)) on 2021-04-23\n- If the dialog `defaultPath` is a file, use it as starting file path.\n  - [aa7e273](https://www.github.com/tauri-apps/tauri/commit/aa7e2738ccafd8e4f5df866206f12888f6db8973) feat: use `rfd::FileDialog#set_file_name` if `default_path` is a file ([#1598](https://www.github.com/tauri-apps/tauri/pull/1598)) on 2021-04-23\n- Validate dialog option `defaultPath` - it must exists.\n  - [cfa74eb](https://www.github.com/tauri-apps/tauri/commit/cfa74ebf68de96cf46bc9471c61f9a84dd0be9ee) feat(core): validate dialog `default_path` (it must exist) ([#1599](https://www.github.com/tauri-apps/tauri/pull/1599)) on 2021-04-23\n- Expose `async_runtime` module.\n  - [d3fdeb4](https://www.github.com/tauri-apps/tauri/commit/d3fdeb45184d9aed8405ded53607a7cca979275e) feat(core): expose `async_runtime` module ([#1576](https://www.github.com/tauri-apps/tauri/pull/1576)) on 2021-04-21\n- Expose `PageLoadPayload` struct.\n  - [5e65b76](https://www.github.com/tauri-apps/tauri/commit/5e65b768e5930708695512260faf8c12d679c04e) fix(core): expose `PageLoadPayload` struct ([#1590](https://www.github.com/tauri-apps/tauri/pull/1590)) on 2021-04-22\n- Fixes the Message `command` name value on plugin invoke handler.\n  - [422dd5e](https://www.github.com/tauri-apps/tauri/commit/422dd5e2a0a03bb1556915c78f110bfab092c874) fix(core): command name on plugin invoke handler ([#1577](https://www.github.com/tauri-apps/tauri/pull/1577)) on 2021-04-21\n- Allow `window.__TAURI__.invoke` to be moved to another variable.\n  - [be65f04](https://www.github.com/tauri-apps/tauri/commit/be65f04db7d2fce23477156ebba368a897ceee3c) fix(core): make window.**TAURI**.invoke context free, fixes [#1547](https://www.github.com/tauri-apps/tauri/pull/1547) ([#1565](https://www.github.com/tauri-apps/tauri/pull/1565)) on 2021-04-21\n- Make sure custom protocol is treated as secure content on macOS.\n  - [5909c1e](https://www.github.com/tauri-apps/tauri/commit/5909c1e01437e10c45694c24f9037f4b176a03ec) Make sure custom protocol is handled as secure context on macOS ([#1551](https://www.github.com/tauri-apps/tauri/pull/1551)) on 2021-04-22\n- Fixes macOS shortcut modifiers keycodes.\n  - [ceadf2f](https://www.github.com/tauri-apps/tauri/commit/ceadf2f556f5f327b34f8fdd01e5e07969182b13) fix(core): macos shortcut modifiers, closes [#1542](https://www.github.com/tauri-apps/tauri/pull/1542) ([#1560](https://www.github.com/tauri-apps/tauri/pull/1560)) on 2021-04-21\n- Adds APIs to determine global and webview-specific URI scheme handlers.\n  - [938fb62](https://www.github.com/tauri-apps/tauri/commit/938fb624f5cc0f2a4499ea67cd30b014a18a6526) feat(core): expose custom protocol handler APIs ([#1553](https://www.github.com/tauri-apps/tauri/pull/1553)) on 2021-04-21\n  - [a868cb7](https://www.github.com/tauri-apps/tauri/commit/a868cb71762268aa7b78af26622c900bddf3344c) refactor(core): clear `uri_scheme_protocol` registration function names ([#1617](https://www.github.com/tauri-apps/tauri/pull/1617)) on 2021-04-25\n- The package info APIs now checks the `package` object on `tauri.conf.json`.\n  - [8fd1baf](https://www.github.com/tauri-apps/tauri/commit/8fd1baf69b14bb81d7be9d31605ed7f02058b392) fix(core): pull package info from tauri.conf.json if set ([#1581](https://www.github.com/tauri-apps/tauri/pull/1581)) on 2021-04-22\n- Change plugin trait `initialization` return type to `std::result::Result<(), Box<dyn std::error::Error>>`.\n  - [508eddc](https://www.github.com/tauri-apps/tauri/commit/508eddc78458cd7ff51259ed733fe8e6f206e293) refactor(core): plugin initialization return value ([#1575](https://www.github.com/tauri-apps/tauri/pull/1575)) on 2021-04-21\n- Fixes `sidecar` Command API.\n  - [99307d0](https://www.github.com/tauri-apps/tauri/commit/99307d02c3c28ce10ba418873ac02ce267af4f4f) fix(core): sidecar command path ([#1584](https://www.github.com/tauri-apps/tauri/pull/1584)) on 2021-04-22\n- Set LocalStorage and IndexedDB files path on Linux to `$HOME/.local/${bundleIdentifier}`.\n  - [5f033db](https://www.github.com/tauri-apps/tauri/commit/5f033db41cf6b043d9d2b4debe8b10bdc4633c58) feat(core): use bundle identifier on user data path ([#1580](https://www.github.com/tauri-apps/tauri/pull/1580)) on 2021-04-22\n- Use bundle identifier instead of `Tauri` for user data path on Windows.\n  - [5f033db](https://www.github.com/tauri-apps/tauri/commit/5f033db41cf6b043d9d2b4debe8b10bdc4633c58) feat(core): use bundle identifier on user data path ([#1580](https://www.github.com/tauri-apps/tauri/pull/1580)) on 2021-04-22\n\n## \\[1.0.0-beta-rc.2]\n\n- Prevent \"once\" events from being able to be called multiple times.\n\n- `Window::trigger(/*...*/)` is now properly `pub` instead of `pub(crate)`.\n\n- `Manager::once_global(/*...*/)` now returns an `EventHandler`.\n\n- `Window::once(/*...*/)` now returns an `EventHandler`.\n\n- (internal) `event::Listeners::trigger(/*...*/)` now handles removing \"once\" events.\n\n- [ece243d](https://www.github.com/tauri-apps/tauri/commit/ece243d17c9c3ef8d2ba8e3b25b872aa6ea0b6ab) don't remove once listener on new thread ([#1506](https://www.github.com/tauri-apps/tauri/pull/1506)) on 2021-04-14\n\n- Window and global events can now be nested inside event handlers. They will run as soon\n  as the event handler closure is finished in the order they were called. Previously, calling\n  events inside an event handler would produce a deadlock.\n\nNote: The order that event handlers are called when triggered is still non-deterministic.\n\n- [e447b8e](https://www.github.com/tauri-apps/tauri/commit/e447b8e0e6198c8972bae25625bb409850cb686b) allow event listeners to be nested ([#1513](https://www.github.com/tauri-apps/tauri/pull/1513)) on 2021-04-15\n\n## \\[1.0.0-beta-rc.1]\n\n- Missing the `files` property in the package.json which mean that the `dist` directory was not published and used.\n  - Bumped due to a bump in api.\n  - [b2569a7](https://www.github.com/tauri-apps/tauri/commit/b2569a729a3caa88bdba62abc31f0665e1323aaa) fix(js-api): dist ([#1498](https://www.github.com/tauri-apps/tauri/pull/1498)) on 2021-04-15\n\n## \\[1.0.0-beta-rc.0]\n\n- internal refactoring of `Params` to allow for easier usage without a private trait with only 1 implementor.\n  `ParamsPrivate` -> `ParamsBase`\n  `ManagerPrivate` -> `ManagerBase`\n  (new) `Args`, crate only. Now implements `Params`/`ParamsBase`.\n  `App` and `Window` use `WindowManager` directly\n- [ec27ca8](https://www.github.com/tauri-apps/tauri/commit/ec27ca81fe721e0b08ed574073547250b7a8153a) refactor(tauri): remove private params trait methods ([#1484](https://www.github.com/tauri-apps/tauri/pull/1484)) on 2021-04-14\n- Updated `wry`, fixing an issue with the draggable region.\n  - [f2d24ef](https://www.github.com/tauri-apps/tauri/commit/f2d24ef2fbd95ec7d3433ba651964f4aa3b7f48c) chore(deps): update wry ([#1482](https://www.github.com/tauri-apps/tauri/pull/1482)) on 2021-04-14\n- Now Tauri commands always returns Promise<T>.\n  - [ea73325](https://www.github.com/tauri-apps/tauri/commit/ea7332539d100bd63f93396101ffa01ff73c924b) refactor(core): all API are now promise based ([#1239](https://www.github.com/tauri-apps/tauri/pull/1239)) on 2021-02-16\n- Rename macOS bundle settings from `osx` to `macOS`.\n  - [080f639](https://www.github.com/tauri-apps/tauri/commit/080f6391bac4fd59e9e71b9785d7a2f835703805) refactor(bundler): specific settings on dedicated structs, update README ([#1380](https://www.github.com/tauri-apps/tauri/pull/1380)) on 2021-03-25\n- The shell process spawning API was rewritten and now includes stream access.\n  - [3713066](https://www.github.com/tauri-apps/tauri/commit/3713066e451bd30d0cc6f57bb437f08276f4c4ad) refactor(core): rewrite shell execute API, closes [#1229](https://www.github.com/tauri-apps/tauri/pull/1229) ([#1408](https://www.github.com/tauri-apps/tauri/pull/1408)) on 2021-03-31\n- The Tauri files are now read on the app space instead of the `tauri` create.\n  Also, the `AppBuilder` `build` function now returns a Result.\n  - [e02c941](https://www.github.com/tauri-apps/tauri/commit/e02c9419cb8c66f4e43ed598d2fc74d4b19384ec) refactor(tauri): support for building without environmental variables ([#850](https://www.github.com/tauri-apps/tauri/pull/850)) on 2021-02-09\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- Tauri now uses explicit Error variants with `thiserror` instead of relying on `anyhow`.\n  - [156a0ad](https://www.github.com/tauri-apps/tauri/commit/156a0ad5cb0a152eaa0dd038a6b3dba68f03eb21) refactor(tauri): use explicit error types instead of anyhow ([#1209](https://www.github.com/tauri-apps/tauri/pull/1209)) on 2021-02-10\n- Align HTTP API types with the [documentation](https://tauri.app/en/docs/api/js#http).\n  - [2fc39fc](https://www.github.com/tauri-apps/tauri/commit/2fc39fc341771431078c20a95fa6b2affe5155c9) fix(api/http): correct types ([#1360](https://www.github.com/tauri-apps/tauri/pull/1360)) on 2021-03-17\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- Replace `\\` with `\\\\` in css assets that are lazy loaded. Since these are injected in a template literal, backslashes must be escaped. Backslashes are sometimes used for octal sequences in CSS.\n  - [4491c70](https://www.github.com/tauri-apps/tauri/commit/4491c707907a6a931fd8c057c2baeb0b9e6db1d8) fix(tauri/asset): escape octal sequences in css ([#1166](https://www.github.com/tauri-apps/tauri/pull/1166)) on 2021-01-30\n- Replaces the embedded-server mode with Wry's custom protocol feature. This allows assets to be transferred to the webview directly, instead of through a localhost server.\n  - [0c691f4](https://www.github.com/tauri-apps/tauri/commit/0c691f40a338be184a4dd2c84d6e5d0b0ed6ee4b) feat(core): Use Wry custom protocol instead of embedded server ([#1296](https://www.github.com/tauri-apps/tauri/pull/1296)) on 2021-02-25\n- The `message` and `ask` dialogs now use `tinyfiledialogs-rs` instead of `tauri-dialog-rs`.\n  - [6eee355](https://www.github.com/tauri-apps/tauri/commit/6eee355a12ead3ac9cb4be0c98c1cfe5c0611291) refactor(core): use tinyfiledialogs-rs for message/confirmation dialogs ([#1255](https://www.github.com/tauri-apps/tauri/pull/1255)) on 2021-02-17\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- Refactor the event callback payload and return an unlisten function on the `listen` API.\n  - [b670ec5](https://www.github.com/tauri-apps/tauri/commit/b670ec55f2b7389b8a2f8c965d4fe1e0cb46e6dc) refactor(core): add `unlisten`, `once` APIs to the event system ([#1359](https://www.github.com/tauri-apps/tauri/pull/1359)) on 2021-03-16\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- Adds `unlisten` and `once` APIs on the Rust event system.\n  - [b670ec5](https://www.github.com/tauri-apps/tauri/commit/b670ec55f2b7389b8a2f8c965d4fe1e0cb46e6dc) refactor(core): add `unlisten`, `once` APIs to the event system ([#1359](https://www.github.com/tauri-apps/tauri/pull/1359)) on 2021-03-16\n- The `tauri::event` module has been moved to a Webview manager API.\n  - [07208df](https://www.github.com/tauri-apps/tauri/commit/07208dff6c1e8cff7c10780f4f7f8cee9de44a2e) feat(core): add mult-window support ([#1217](https://www.github.com/tauri-apps/tauri/pull/1217)) on 2021-02-11\n- The file dialog API now uses [rfd](https://github.com/PolyMeilex/rfd). The filter option is now an array of `{ name: string, extensions: string[] }`.\n  - [2326bcd](https://www.github.com/tauri-apps/tauri/commit/2326bcd399411f7f0eabdb7ade910be473adadae) refactor(core): use `nfd` for file dialogs, closes [#1251](https://www.github.com/tauri-apps/tauri/pull/1251) ([#1257](https://www.github.com/tauri-apps/tauri/pull/1257)) on 2021-02-18\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- Tauri now emits events on file drops on the webview window.\n  - [2db901e](https://www.github.com/tauri-apps/tauri/commit/2db901e744f51cd4296ddec4352d7a51c859b85b) feat(core): add file drop handler ([#1352](https://www.github.com/tauri-apps/tauri/pull/1352)) on 2021-03-12\n- Fixes `resource_dir` resolution on AppImage.\n  - [bd1df5d](https://www.github.com/tauri-apps/tauri/commit/bd1df5d80431f5de4cd905ffaf7f3f2628d6b8ab) fix: get correct resource dir in AppImge (fix [#1308](https://www.github.com/tauri-apps/tauri/pull/1308)) ([#1333](https://www.github.com/tauri-apps/tauri/pull/1333)) on 2021-03-12\n- Fixed missing 'App' variant & string promise instead of void promise.\n  - [44fc65c](https://www.github.com/tauri-apps/tauri/commit/44fc65c723f638f2a1b2ecafb79b32d509ed2f35) Fixing TS API typings ([#1451](https://www.github.com/tauri-apps/tauri/pull/1451)) on 2021-04-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- The HTTP API was improved with client caching and better payload and response types.\n  - [a7bc472](https://www.github.com/tauri-apps/tauri/commit/a7bc472e994730071f960d09a12ac85296a080ae) refactor(core): improve HTTP API, closes [#1098](https://www.github.com/tauri-apps/tauri/pull/1098) ([#1237](https://www.github.com/tauri-apps/tauri/pull/1237)) on 2021-02-15\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- Added new JavaScript API to extract `name`, `version`, `tauri version` from the running application. We exposed `relaunch` and `exit` as well to control your application state.\n  - [e511d39](https://www.github.com/tauri-apps/tauri/commit/e511d3991041a974273a2674a9bf60230b7519ee) feat(api): Expose application metadata and functions to JS api - fix [#1387](https://www.github.com/tauri-apps/tauri/pull/1387) ([#1445](https://www.github.com/tauri-apps/tauri/pull/1445)) on 2021-04-08\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- The event listener `once` kind was moved to a dedicated function.\n  - [372036c](https://www.github.com/tauri-apps/tauri/commit/372036ce20ac7f103dea05bae7e8686858d096a4) refactor(api): move event's `once` to its own function ([#1276](https://www.github.com/tauri-apps/tauri/pull/1276)) on 2021-02-23\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- Use ``JSON.parse(String.raw`{arg}`)`` for communicating serialized JSON objects and arrays < 1 GB to the Webview from Rust.\n\nhttps://github.com/GoogleChromeLabs/json-parse-benchmark\n\n- [eeb2030](https://www.github.com/tauri-apps/tauri/commit/eeb20308acdd83029abb6ce94fb5d0c896759060) Use JSON.parse instead of literal JS for callback formatting ([#1370](https://www.github.com/tauri-apps/tauri/pull/1370)) on 2021-04-07\n- [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- Update all code files to have our license header.\n  - [bf82136](https://www.github.com/tauri-apps/tauri/commit/bf8213646689175f8a158b956911f3a43e360690) feat(license): SPDX Headers ([#1449](https://www.github.com/tauri-apps/tauri/pull/1449)) on 2021-04-11\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Added support to multiple windows.\n  - [07208df](https://www.github.com/tauri-apps/tauri/commit/07208dff6c1e8cff7c10780f4f7f8cee9de44a2e) feat(core): add mult-window support ([#1217](https://www.github.com/tauri-apps/tauri/pull/1217)) on 2021-02-11\n- Adds `productName` and `version` configs on `tauri.conf.json > package`.\n  - [5b3d9b2](https://www.github.com/tauri-apps/tauri/commit/5b3d9b2c07da766f81981ba7c4961cd354d51340) feat(config): allow setting product name and version on tauri.conf.json ([#1358](https://www.github.com/tauri-apps/tauri/pull/1358)) on 2021-03-22\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Plugins are now configurable through a `tauri.conf.json > \"plugins\" > $pluginName` object.\n  - [2058cc3](https://www.github.com/tauri-apps/tauri/commit/2058cc39c9ac9d9d442802db2c2f3be34a85acc4) feat(tauri): add plugin `initialize` (with config) API, run in parallel ([#1194](https://www.github.com/tauri-apps/tauri/pull/1194)) on 2021-02-10\n- Renamed the `Plugin` trait `init_script` to `initialization_script`.\n  - [5c5d8f8](https://www.github.com/tauri-apps/tauri/commit/5c5d8f811fc094cca1b441ff966f15c7bf5d2e90) refactor(tauri): rename `init_script` to `initialization_script` ([#1200](https://www.github.com/tauri-apps/tauri/pull/1200)) on 2021-02-10\n- The plugin instance is now mutable and must be `Send`.\n  - [fb607ee](https://www.github.com/tauri-apps/tauri/commit/fb607ee97a912d1e23f6d7dd6dd3c28aac9b4527) refactor(tauri): plugin trait with mutable references ([#1197](https://www.github.com/tauri-apps/tauri/pull/1197)) on 2021-02-10\n  - [1318ffb](https://www.github.com/tauri-apps/tauri/commit/1318ffb47c5f2fb696d6323fbcee4f840396c6b3) refactor(core): remove async from app hooks, add InvokeMessage type ([#1392](https://www.github.com/tauri-apps/tauri/pull/1392)) on 2021-03-26\n- Fixes the event system usage on the plugin `ready` hook.\n  - [23132ac](https://www.github.com/tauri-apps/tauri/commit/23132acf765ab8b6a37b74151a4c175b68390657) fix(tauri): run plugin::ready without webview.dispatch ([#1164](https://www.github.com/tauri-apps/tauri/pull/1164)) on 2021-01-29\n- The `allowlist` configuration now has one object per module.\n  - [e0be59e](https://www.github.com/tauri-apps/tauri/commit/e0be59ea26df17fe2e31224759f21fb1d0cbdfd3) refactor(core): split allowlist configuration per module ([#1263](https://www.github.com/tauri-apps/tauri/pull/1263)) on 2021-02-20\n- The Tauri script is now injected with the webview `init` API, so it is available after page changes.\n  - [4412b7c](https://www.github.com/tauri-apps/tauri/commit/4412b7c438c2b10e519bf8b696e3ef827e9091f2) refactor(tauri): inject script with webview init API ([#1186](https://www.github.com/tauri-apps/tauri/pull/1186)) on 2021-02-05\n  - [8bdd894](https://www.github.com/tauri-apps/tauri/commit/8bdd8949254d63bfc57ad67ce2592d40a0b44bf8) refactor(core): move bundle script to /tauri crate ([#1377](https://www.github.com/tauri-apps/tauri/pull/1377)) on 2021-03-23\n- Removed the `no-server` mode, the `inliner`, the `dev` server proxy and the `loadAsset` API.\n  - [84d7cda](https://www.github.com/tauri-apps/tauri/commit/84d7cdae632eeb02a66f8d1d7577adfa65917a34) refactor(core): remove `no-server` and its APIs ([#1215](https://www.github.com/tauri-apps/tauri/pull/1215)) on 2021-02-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Adds a global shortcut API.\n  - [855effa](https://www.github.com/tauri-apps/tauri/commit/855effadd9ebfb6bc1a3555ac7fc733f6f766b7a) feat(core): globalShortcut API ([#1232](https://www.github.com/tauri-apps/tauri/pull/1232)) on 2021-02-14\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- Added `async` support to the Tauri Rust core on commit [#a169b67](https://github.com/tauri-apps/tauri/commit/a169b67ef0277b958bdac97e33c6e4c41b6844c3).\n  - [2bf55f8](https://www.github.com/tauri-apps/tauri/commit/2bf55f80564f5c31d89384bd4a82dd55307f8c75) chore: add changefile on 2021-02-03\n  - [e02c941](https://www.github.com/tauri-apps/tauri/commit/e02c9419cb8c66f4e43ed598d2fc74d4b19384ec) refactor(tauri): support for building without environmental variables ([#850](https://www.github.com/tauri-apps/tauri/pull/850)) on 2021-02-09\n- Alpha version of tauri-updater. Please refer to the `README` for more details.\n  - [6d70c8e](https://www.github.com/tauri-apps/tauri/commit/6d70c8e1e256fe839c4a947375bb529d7b4f7301) feat(updater): Alpha version ([#643](https://www.github.com/tauri-apps/tauri/pull/643)) on 2021-04-05\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- The Tauri integration with Webview was refactored to use traits, which allows custom implementations by developers and simplifies changes on the webview implementation.\n  - [b9ce7b9](https://www.github.com/tauri-apps/tauri/commit/b9ce7b94c4eb027bcbbd4ee600b75a7407f108ca) refactor(tauri): Webview traits ([#1183](https://www.github.com/tauri-apps/tauri/pull/1183)) on 2021-02-05\n- Added window management and window creation APIs.\n  - [a3d6dff](https://www.github.com/tauri-apps/tauri/commit/a3d6dff2163c7a45842253edd81dbc62248dc65d) feat(core): window API ([#1225](https://www.github.com/tauri-apps/tauri/pull/1225)) on 2021-02-13\n  - [641374b](https://www.github.com/tauri-apps/tauri/commit/641374b15343518cd835bd5ada811941c65dcf2e) feat(core): window creation at runtime ([#1249](https://www.github.com/tauri-apps/tauri/pull/1249)) on 2021-02-17\n- Use [WRY](https://github.com/tauri-apps/wry) as Webview interface, thanks to @wusyong.\n  - [99ecf7b](https://www.github.com/tauri-apps/tauri/commit/99ecf7bb3e8da6e611b57c6680a14b65179f8a35) feat(tauri): use WRY as webview engine ([#1190](https://www.github.com/tauri-apps/tauri/pull/1190)) on 2021-02-08\n\n## \\[0.11.1]\n\n- Update webview-official dependency which fix compatibility on macOS.\n  - [692312a](https://www.github.com/tauri-apps/tauri/commit/692312a0f51a05dd418d9cad553a695f3347b943) chore(deps) Update webview-official ([#1152](https://www.github.com/tauri-apps/tauri/pull/1152)) on 2021-01-24\n\n## \\[0.11.0]\n\n- Match writeBinaryFile command name between js and rust\n  - [486bd92](https://www.github.com/tauri-apps/tauri/commit/486bd920f899905bec0f690092aa1e3cac2c78f3) Fix: writeBinaryFile to call the correct command (fix [#1133](https://www.github.com/tauri-apps/tauri/pull/1133)) ([#1136](https://www.github.com/tauri-apps/tauri/pull/1136)) on 2021-01-06\n\n## \\[0.10.0]\n\n- Adds missing APIs features from `allowlist` to the tauri crate's manifest file.\n  - [2c0f09c](https://www.github.com/tauri-apps/tauri/commit/2c0f09c85c8a60c2fa304fb25174d5020663f0d7) fix(tauri) add missing API features, closes [#1023](https://www.github.com/tauri-apps/tauri/pull/1023) ([#1052](https://www.github.com/tauri-apps/tauri/pull/1052)) on 2020-10-17\n  - [72996be](https://www.github.com/tauri-apps/tauri/commit/72996be1bd3eb878c4cf30bfec79058071c26d7a) apply version updates ([#1024](https://www.github.com/tauri-apps/tauri/pull/1024)) on 2020-10-21\n- Adds a path resolution API (e.g. getting the download directory or resolving a path to the home directory).\n  - [771e401](https://www.github.com/tauri-apps/tauri/commit/771e4019b8cfd1973015ffa632c9d6c6b82c5657) feat: Port path api to js ([#1006](https://www.github.com/tauri-apps/tauri/pull/1006)) on 2020-09-24\n  - [72996be](https://www.github.com/tauri-apps/tauri/commit/72996be1bd3eb878c4cf30bfec79058071c26d7a) apply version updates ([#1024](https://www.github.com/tauri-apps/tauri/pull/1024)) on 2020-10-21\n- Update minimum Rust version to 1.42.0 due to a dependency update.\n  - [d13dcd9](https://www.github.com/tauri-apps/tauri/commit/d13dcd9fd8d30b1db147a78cecb878e924382274) chore(deps) Update Tauri Bundler ([#1045](https://www.github.com/tauri-apps/tauri/pull/1045)) on 2020-10-17\n  - [72996be](https://www.github.com/tauri-apps/tauri/commit/72996be1bd3eb878c4cf30bfec79058071c26d7a) apply version updates ([#1024](https://www.github.com/tauri-apps/tauri/pull/1024)) on 2020-10-21\n- Minimum Rust version updated to 1.47.0. Run `$ rustup update` to update to the latest version.\n  - [b4544b6](https://www.github.com/tauri-apps/tauri/commit/b4544b63f268dc6f3f47a4bfbad5d72cceee8698) chore(deps) Update Tauri API ([#1072](https://www.github.com/tauri-apps/tauri/pull/1072)) on 2020-11-07\n\n## \\[0.9.2]\n\n- Bump all deps as noted in #975, #976, #977, #978, and #979.\n  - [06dd75b](https://www.github.com/tauri-apps/tauri/commit/06dd75b68a15d388808c51ae2bf50554ae761d9d) chore: bump all js/rust deps ([#983](https://www.github.com/tauri-apps/tauri/pull/983)) on 2020-08-20\n\n## \\[0.9.1]\n\n- Adjust payload formatting to handle multibyte characters in front-end message\n  payloads.\n  \\- [df70ca5](https://www.github.com/tauri-apps/tauri/commit/df70ca51965665952a74161cc6eb1ff19eae45e2) Fix [#912](https://www.github.com/tauri-apps/tauri/pull/912) multibyte character breaks message ([#914](https://www.github.com/tauri-apps/tauri/pull/914)) on 2020-08-01\n\n## \\[0.9.0]\n\n- Make sure CSS content loaded with the `loadAsset` API is inside a template string and not injected raw.\n  - [e3e2e39](https://www.github.com/tauri-apps/tauri/commit/e3e2e3920833627400ee7a5b000dc6e51d8d332b) fix(tauri) ensure css content is loaded inside a string ([#884](https://www.github.com/tauri-apps/tauri/pull/884)) on 2020-07-22\n  - [b96b1fb](https://www.github.com/tauri-apps/tauri/commit/b96b1fb6b8a4f565fb946847bb9a29d9d939e2cb) inject css with template string to allow for line breaks ([#894](https://www.github.com/tauri-apps/tauri/pull/894)) on 2020-07-25\n- Pin the `tauri-api` dependency on the `tauri` crate so updates doesn't crash the build.\n  - [ad717c6](https://www.github.com/tauri-apps/tauri/commit/ad717c6f33b4d6e20fbb13cbe30e06946dbb74f6) chore(tauri) pin tauri-api dep version ([#872](https://www.github.com/tauri-apps/tauri/pull/872)) on 2020-07-21\n- Fixes the Webview initialization on Windows.\n  - [4abd12c](https://www.github.com/tauri-apps/tauri/commit/4abd12c2a42b5ace8527114ab64da38f4486754f) fix(tauri) webview initialization on windows, fixes [#879](https://www.github.com/tauri-apps/tauri/pull/879) ([#885](https://www.github.com/tauri-apps/tauri/pull/885)) on 2020-07-23\n\n## \\[0.8.0]\n\n- Use native dialog on `window.alert` and `window.confirm`.\n  Since every communication with the webview is asynchronous, the `window.confirm` returns a Promise resolving to a boolean value.\n  \\- [0245833](https://www.github.com/tauri-apps/tauri/commit/0245833bb56d5462a4e1249eb1e2f9f5e477592d) feat(tauri) make `window.alert` and `window.confirm` available, fix [#848](https://www.github.com/tauri-apps/tauri/pull/848) ([#854](https://www.github.com/tauri-apps/tauri/pull/854)) on 2020-07-18\n  \\- [dac0ae9](https://www.github.com/tauri-apps/tauri/commit/dac0ae976ea1b419ed5af078d00106b1476dee04) chore(changes) add tauri-api to JS dialogs changefile on 2020-07-19\n- The notification's `body` is now optional, closes #793.\n  - [dac1db3](https://www.github.com/tauri-apps/tauri/commit/dac1db39831ecbcf23c630351d5753af01ccd500) fix(tauri) notification body optional, requestPermission() regression, closes [#793](https://www.github.com/tauri-apps/tauri/pull/793) ([#844](https://www.github.com/tauri-apps/tauri/pull/844)) on 2020-07-16\n- Fixes a regression on the storage of requestPermission response.\n  ß\n  \\- [dac1db3](https://www.github.com/tauri-apps/tauri/commit/dac1db39831ecbcf23c630351d5753af01ccd500) fix(tauri) notification body optional, requestPermission() regression, closes [#793](https://www.github.com/tauri-apps/tauri/pull/793) ([#844](https://www.github.com/tauri-apps/tauri/pull/844)) on 2020-07-16\n- Plugin system added. You can hook into the webview lifecycle (`created`, `ready`) and extend the API adding logic to the `invoke_handler` by implementing the `tauri::plugin::Plugin` trait.\n  - [78afee9](https://www.github.com/tauri-apps/tauri/commit/78afee9725e0e372f9de7edeaac523011a1c02a3) feat(tauri) add plugin system for rust ([#494](https://www.github.com/tauri-apps/tauri/pull/494)) on 2020-07-12\n- Renaming `whitelist` to `allowlist` (see #645).\n  - [a6bb3b5](https://www.github.com/tauri-apps/tauri/commit/a6bb3b59059e08a844d7bb2b43f3d1192954d890) refactor(tauri) rename `whitelist` to `allowlist`, ref [#645](https://www.github.com/tauri-apps/tauri/pull/645) ([#858](https://www.github.com/tauri-apps/tauri/pull/858)) on 2020-07-19\n- Moving the webview implementation to [webview](https://github.com/webview/webview), with the [official Rust binding](https://github.com/webview/webview_rust).\n  This is a breaking change.\n  IE support has been dropped, so the `edge` object on `tauri.conf.json > tauri` no longer exists and you need to remove it.\n  `webview.handle()` has been replaced with `webview.as_mut()`.\n  \\- [cd5b401](https://www.github.com/tauri-apps/tauri/commit/cd5b401707d709bf8212b0a4c34623f902ae40f9) feature: import official webview rust binding ([#846](https://www.github.com/tauri-apps/tauri/pull/846)) on 2020-07-18\n\n## \\[0.7.5]\n\n- Fixes Edge blank screen on Windows when running tauri dev (Tauri crashing window due to Edge reloading app because of missing Content-Type header).\n  - Bumped due to a bump in tauri-api.\n  - [fedee83](https://www.github.com/tauri-apps/tauri/commit/fedee835e36daa4363b91aabd43143e8033c9a5c) fix(tauri.js) windows Edge blank screen on tauri dev ([#808](https://www.github.com/tauri-apps/tauri/pull/808)) on 2020-07-11\n\n## \\[0.7.4]\n\n- Ignoring non UTF-8 characters on the loopback command output.\n  - [f340b29](https://www.github.com/tauri-apps/tauri/commit/f340b2914dc9c3a94ca8606f4663964fa87b95ea) fix(tauri) addition to the previous commit on 2020-07-10\n\n## \\[0.7.3]\n\n- Properly run the loopback command on Windows.\n- Properly ignore the ${distDir}/index.html asset from the asset embedding. Previously every asset with name matching /(.+)index.html$/g were ignored.\n\n## \\[0.7.2]\n\nBumped due to dependency.\n\n## \\[0.7.1]\n\n- Fixes the assets embedding into the binary.\n\n## \\[0.7.0]\n\n- The execute_promise and execute_promise_sync helpers now accepts any tauri::Result<T> where T: impl Serialize.\n  This means that you do not need to serialize your response manually or deal with String quotes anymore.\n  As part of this refactor, the event::emit function also supports impl Serialize instead of String.\n\n## \\[0.6.2]\n\n- Fixes the Windows build with the latest Windows SDK.\n\n## \\[0.6.1] - (Not Published)\n\n## \\[0.6.0]\n\n- Adds a command line interface option to tauri apps, configurable under tauri.conf.json > tauri > cli.\n- Fixes no-server mode not running on another machine due to fs::read_to_string usage instead of the include_str macro.\n  Build no longer fails when compiling without environment variables, now the app will show an error.\n- Adds desktop notifications API.\n- Properly reflect tauri.conf.json changes on app when running tauri dev.\n"
  },
  {
    "path": "crates/tauri/Cargo.toml",
    "content": "[package]\nname = \"tauri\"\nversion = \"2.10.3\"\ndescription = \"Make tiny, secure apps for all desktop platforms with Tauri\"\nexclude = [\"/test\", \"/.scripts\", \"CHANGELOG.md\", \"/target\"]\nreadme = \"README.md\"\nlinks = \"Tauri\"\nauthors.workspace = true\nhomepage.workspace = true\nrepository.workspace = true\ncategories.workspace = true\nlicense.workspace = true\nedition.workspace = true\nrust-version.workspace = true\n\n[package.metadata.docs.rs]\nno-default-features = true\nfeatures = [\n  \"wry\",\n  \"unstable\",\n  \"custom-protocol\",\n  \"tray-icon\",\n  \"devtools\",\n  \"image-png\",\n  \"protocol-asset\",\n  \"test\",\n  \"specta\",\n  \"dynamic-acl\",\n]\ndefault-target = \"x86_64-unknown-linux-gnu\"\ntargets = [\n  \"x86_64-pc-windows-msvc\",\n  \"x86_64-unknown-linux-gnu\",\n  \"x86_64-apple-darwin\",\n  \"x86_64-linux-android\",\n  \"x86_64-apple-ios\",\n]\n\n[package.metadata.cargo-udeps.ignore]\nnormal = [\"reqwest\"]\nbuild = [\"tauri-build\"]\ndevelopment = [\"quickcheck_macros\"]\n\n[dependencies]\nserde_json = { version = \"1\", features = [\"raw_value\"] }\nserde = { version = \"1\", features = [\"derive\", \"rc\"] }\ntokio = { version = \"1\", features = [\n  \"rt\",\n  \"rt-multi-thread\",\n  \"sync\",\n  \"fs\",\n  \"io-util\",\n] }\nuuid = { version = \"1\", features = [\"v4\"], optional = true }\nurl = \"2\"\nanyhow = \"1\"\nthiserror = \"2\"\ntauri-runtime = { version = \"2.10.1\", path = \"../tauri-runtime\" }\ntauri-macros = { version = \"2.5.5\", path = \"../tauri-macros\" }\ntauri-utils = { version = \"2.8.3\", features = [\n  \"resources\",\n], path = \"../tauri-utils\" }\ntauri-runtime-wry = { version = \"2.10.1\", path = \"../tauri-runtime-wry\", default-features = false, optional = true }\ngetrandom = \"0.3\"\nserde_repr = \"0.1\"\nhttp = \"1\"\ndirs = \"6\"\npercent-encoding = \"2\"\nraw-window-handle = { version = \"0.6\", features = [\"std\"] }\nglob = \"0.3\"\nmime = \"0.3\"\ndata-url = { version = \"0.3\", optional = true }\nserialize-to-javascript = \"0.1.2\"\nimage = { version = \"0.25\", default-features = false, optional = true }\nhttp-range = { version = \"0.1\", optional = true }\ntracing = { version = \"0.1\", optional = true }\nheck = \"0.5\"\nlog = \"0.4.21\"\ndunce = \"1\"\nspecta = { version = \"^2.0.0-rc.16\", optional = true, default-features = false, features = [\n  \"function\",\n  \"derive\",\n] }\n# WARNING: cookie::Cookie is re-exported so bumping this is a breaking change, documented to be done as a minor bump\ncookie = \"0.18\"\n\n# desktop\n[target.'cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"windows\", target_os = \"macos\"))'.dependencies]\nmuda = { version = \"0.17\", default-features = false, features = [\n  \"serde\",\n  \"gtk\",\n] }\ntray-icon = { version = \"0.21\", default-features = false, features = [\n  \"serde\",\n], optional = true }\n\n# linux\n[target.'cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))'.dependencies]\ngtk = { version = \"0.18\", features = [\"v3_24\"] }\nwebkit2gtk = { version = \"2\", features = [\"v2_40\"], optional = true }\n\n# darwin\n[target.'cfg(target_vendor = \"apple\")'.dependencies]\nobjc2 = \"0.6\"\n\n# macOS\n[target.'cfg(target_os = \"macos\")'.dependencies]\nembed_plist = \"1.2\"\nplist = \"1\"\nobjc2-foundation = { version = \"0.3\", default-features = false, features = [\n  \"std\",\n  \"NSData\",\n  \"NSThread\",\n] }\nobjc2-app-kit = { version = \"0.3\", default-features = false, features = [\n  \"std\",\n  \"NSApplication\",\n  \"NSColor\",\n  \"NSResponder\",\n  \"NSView\",\n  \"NSWindow\",\n  \"NSImage\",\n] }\nobjc2-web-kit = { version = \"0.3\", default-features = false, features = [\n  \"objc2-app-kit\",\n  \"WKWebView\",\n  \"WKWebViewConfiguration\",\n  \"WKUserContentController\",\n] }\nwindow-vibrancy = \"0.6\"\n\n# windows\n[target.\"cfg(windows)\".dependencies]\nwebview2-com = { version = \"0.38\", optional = true }\nwindow-vibrancy = \"0.6\"\nwindows = { version = \"0.61\", features = [\n  \"Win32_Foundation\",\n  \"Win32_UI\",\n  \"Win32_UI_WindowsAndMessaging\",\n] }\n\n# mobile\n[target.'cfg(any(target_os = \"android\", all(target_vendor = \"apple\", not(target_os = \"macos\"))))'.dependencies]\nbytes = { version = \"1\", features = [\"serde\"] }\nreqwest = { version = \"0.13\", default-features = false, features = [\n  \"json\",\n  \"stream\",\n] }\nrustls = { version = \"0.23\", default-features = false, features = [\n  \"ring\",\n], optional = true }\n\n# android\n[target.'cfg(target_os = \"android\")'.dependencies]\njni = \"0.21\"\n\n# UIKit, i.e. iOS/tvOS/watchOS/visionOS\n[target.'cfg(all(target_vendor = \"apple\", not(target_os = \"macos\")))'.dependencies]\nlibc = \"0.2\"\nswift-rs = \"1\"\nobjc2-ui-kit = { version = \"0.3.0\", default-features = false, features = [\n  \"UIView\",\n] }\n\n[build-dependencies]\nglob = \"0.3\"\nheck = \"0.5\"\ntauri-build = { path = \"../tauri-build/\", default-features = false, version = \"2.5.6\" }\ntauri-utils = { path = \"../tauri-utils/\", version = \"2.8.3\", features = [\n  \"build\",\n] }\n\n[dev-dependencies]\nproptest = \"1.6.0\"\nquickcheck = \"1.0.3\"\nquickcheck_macros = \"1.0.0\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\ntauri = { path = \".\", default-features = false, features = [\"wry\"] }\ntokio = { version = \"1\", features = [\"full\"] }\ncargo_toml = \"0.22\"\nhttp-range = \"0.1.5\"\n\n[features]\ndefault = [\"wry\", \"compression\", \"common-controls-v6\", \"dynamic-acl\", \"x11\"]\nunstable = [\"tauri-runtime-wry?/unstable\"]\nx11 = [\"tauri-runtime-wry?/x11\"]\ncommon-controls-v6 = [\n  \"tray-icon?/common-controls-v6\",\n  \"muda/common-controls-v6\",\n  \"tauri-runtime-wry?/common-controls-v6\",\n]\ntray-icon = [\"dep:tray-icon\"]\ntracing = [\"dep:tracing\", \"tauri-macros/tracing\", \"tauri-runtime-wry?/tracing\"]\ntest = []\ncompression = [\"tauri-macros/compression\", \"tauri-utils/compression\"]\nwry = [\"webview2-com\", \"webkit2gtk\", \"tauri-runtime-wry\"]\n# TODO: Remove in v3 - wry does not have this feature anymore\nobjc-exception = []\nlinux-libxdo = [\"tray-icon/libxdo\", \"muda/libxdo\"]\nisolation = [\"tauri-utils/isolation\", \"tauri-macros/isolation\", \"uuid\"]\ncustom-protocol = [\"tauri-macros/custom-protocol\"]\n# TODO: Remove these flags in v3 and/or enable them by default behind a mobile flag https://github.com/tauri-apps/tauri/issues/12384\nnative-tls = [\"reqwest/native-tls\"]\nnative-tls-vendored = [\"reqwest/native-tls-vendored\"]\nrustls-tls = [\"reqwest/rustls-no-provider\", \"dep:rustls\"]\ndevtools = [\"tauri-runtime/devtools\", \"tauri-runtime-wry?/devtools\"]\nprocess-relaunch-dangerous-allow-symlink-macos = [\n  \"tauri-utils/process-relaunch-dangerous-allow-symlink-macos\",\n]\nmacos-private-api = [\n  \"tauri-runtime/macos-private-api\",\n  \"tauri-runtime-wry?/macos-private-api\",\n]\nwebview-data-url = [\"data-url\", \"tauri-utils/html-manipulation\"]\nprotocol-asset = [\"http-range\"]\nconfig-json5 = [\"tauri-macros/config-json5\"]\nconfig-toml = [\"tauri-macros/config-toml\"]\nimage-ico = [\"image/ico\"]\nimage-png = [\"image/png\"]\nmacos-proxy = [\"tauri-runtime-wry?/macos-proxy\"]\ndynamic-acl = []\nspecta = [\"dep:specta\"]\n\n[[example]]\nname = \"commands\"\npath = \"../../examples/commands/main.rs\"\n\n[[example]]\nname = \"helloworld\"\npath = \"../../examples/helloworld/main.rs\"\n\n[[example]]\nname = \"multiwebview\"\npath = \"../../examples/multiwebview/main.rs\"\nrequired-features = [\"unstable\"]\n\n[[example]]\nname = \"multiwindow\"\npath = \"../../examples/multiwindow/main.rs\"\n\n[[example]]\nname = \"run-return\"\npath = \"../../examples/run-return/main.rs\"\n\n[[example]]\nname = \"splashscreen\"\npath = \"../../examples/splashscreen/main.rs\"\n\n[[example]]\nname = \"state\"\npath = \"../../examples/state/main.rs\"\n\n[[example]]\nname = \"streaming\"\npath = \"../../examples/streaming/main.rs\"\n\n[[example]]\nname = \"isolation\"\npath = \"../../examples/isolation/main.rs\"\nrequired-features = [\"isolation\"]\n"
  },
  {
    "path": "crates/tauri/LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/tauri/LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2017 - Present Tauri Apps Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "crates/tauri/README.md",
    "content": "# Tauri\n\n <img align=\"right\" src=\"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\" height=\"128\" width=\"128\">\n\n[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev)\n[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri)\n[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield)\n[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S)\n[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app)\n[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)\n[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri)\n\n| Component | Version                                                                                        |\n| --------- | ---------------------------------------------------------------------------------------------- |\n| tauri     | [![](https://img.shields.io/crates/v/tauri?style=flat-square)](https://crates.io/crates/tauri) |\n\n## About Tauri\n\nTauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.\n\nTauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.\n\n## This module\n\nThis is the glue crate that holds everything together. It brings the runtimes, macros, utilities and API into one final product. It reads the `tauri.conf.json` file at compile time in order to bring in features and undertake actual configuration of the app (and even the `Cargo.toml` file in the project's folder). It handles script injection (for polyfills / prototype revision) at runtime, hosts the API for systems interaction, and even manages updating.\n\nTo learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.\n\n## Semver\n\n**tauri** is following [Semantic Versioning 2.0](https://semver.org/).\n\n## Licenses\n\nCode: (c) 2019 - 2021 - The Tauri Programme within The Commons Conservancy.\n\nMIT or MIT/Apache 2.0 where applicable.\n\nLogo: CC-BY-NC-ND\n\n- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)\n"
  },
  {
    "path": "crates/tauri/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse heck::AsShoutySnakeCase;\nuse tauri_utils::write_if_changed;\n\nuse std::{\n  collections::BTreeMap,\n  env, fs,\n  path::{Path, PathBuf},\n  sync::{Mutex, OnceLock},\n};\n\nstatic CHECKED_FEATURES: OnceLock<Mutex<Vec<String>>> = OnceLock::new();\nconst PLUGINS: &[(&str, &[(&str, bool)])] = &[\n  // (plugin_name, &[(command, enabled-by_default)])\n  // TODO: Enable this in v3\n  // (\"core:channel\", &[(\"fetch\", true)]),\n  (\n    \"core:path\",\n    &[\n      (\"resolve_directory\", true),\n      (\"resolve\", true),\n      (\"normalize\", true),\n      (\"join\", true),\n      (\"dirname\", true),\n      (\"extname\", true),\n      (\"basename\", true),\n      (\"is_absolute\", true),\n    ],\n  ),\n  (\n    \"core:event\",\n    &[\n      (\"listen\", true),\n      (\"unlisten\", true),\n      (\"emit\", true),\n      (\"emit_to\", true),\n    ],\n  ),\n  (\n    \"core:window\",\n    &[\n      (\"create\", false),\n      // getters\n      (\"get_all_windows\", true),\n      (\"scale_factor\", true),\n      (\"inner_position\", true),\n      (\"outer_position\", true),\n      (\"inner_size\", true),\n      (\"outer_size\", true),\n      (\"is_fullscreen\", true),\n      (\"is_minimized\", true),\n      (\"is_maximized\", true),\n      (\"is_focused\", true),\n      (\"is_decorated\", true),\n      (\"is_resizable\", true),\n      (\"is_maximizable\", true),\n      (\"is_minimizable\", true),\n      (\"is_closable\", true),\n      (\"is_visible\", true),\n      (\"is_enabled\", true),\n      (\"title\", true),\n      (\"current_monitor\", true),\n      (\"primary_monitor\", true),\n      (\"monitor_from_point\", true),\n      (\"available_monitors\", true),\n      (\"cursor_position\", true),\n      (\"theme\", true),\n      (\"is_always_on_top\", true),\n      // setters\n      (\"center\", false),\n      (\"request_user_attention\", false),\n      (\"set_enabled\", false),\n      (\"set_resizable\", false),\n      (\"set_maximizable\", false),\n      (\"set_minimizable\", false),\n      (\"set_closable\", false),\n      (\"set_title\", false),\n      (\"maximize\", false),\n      (\"unmaximize\", false),\n      (\"minimize\", false),\n      (\"unminimize\", false),\n      (\"show\", false),\n      (\"hide\", false),\n      (\"close\", false),\n      (\"destroy\", false),\n      (\"set_decorations\", false),\n      (\"set_shadow\", false),\n      (\"set_effects\", false),\n      (\"set_always_on_top\", false),\n      (\"set_always_on_bottom\", false),\n      (\"set_visible_on_all_workspaces\", false),\n      (\"set_content_protected\", false),\n      (\"set_size\", false),\n      (\"set_min_size\", false),\n      (\"set_size_constraints\", false),\n      (\"set_max_size\", false),\n      (\"set_position\", false),\n      (\"set_fullscreen\", false),\n      (\"set_simple_fullscreen\", false),\n      (\"set_focus\", false),\n      (\"set_focusable\", false),\n      (\"set_skip_taskbar\", false),\n      (\"set_cursor_grab\", false),\n      (\"set_cursor_visible\", false),\n      (\"set_cursor_icon\", false),\n      (\"set_cursor_position\", false),\n      (\"set_ignore_cursor_events\", false),\n      (\"start_dragging\", false),\n      (\"start_resize_dragging\", false),\n      (\"set_progress_bar\", false),\n      (\"set_badge_count\", false),\n      (\"set_overlay_icon\", false),\n      (\"set_badge_label\", false),\n      (\"set_icon\", false),\n      (\"set_title_bar_style\", false),\n      (\"set_theme\", false),\n      (\"toggle_maximize\", false),\n      (\"set_background_color\", false),\n      // internal\n      (\"internal_toggle_maximize\", true),\n    ],\n  ),\n  (\n    \"core:webview\",\n    &[\n      (\"create_webview\", false),\n      (\"create_webview_window\", false),\n      // getters\n      (\"get_all_webviews\", true),\n      (\"webview_position\", true),\n      (\"webview_size\", true),\n      // setters\n      (\"webview_close\", false),\n      (\"set_webview_size\", false),\n      (\"set_webview_position\", false),\n      (\"set_webview_focus\", false),\n      (\"set_webview_auto_resize\", false),\n      (\"set_webview_zoom\", false),\n      (\"webview_hide\", false),\n      (\"webview_show\", false),\n      (\"print\", false),\n      (\"reparent\", false),\n      (\"clear_all_browsing_data\", false),\n      (\"set_webview_background_color\", false),\n      // internal\n      (\"internal_toggle_devtools\", true),\n    ],\n  ),\n  (\n    \"core:app\",\n    &[\n      (\"version\", true),\n      (\"name\", true),\n      (\"tauri_version\", true),\n      (\"identifier\", true),\n      (\"app_show\", false),\n      (\"app_hide\", false),\n      (\"fetch_data_store_identifiers\", false),\n      (\"remove_data_store\", false),\n      (\"default_window_icon\", false),\n      (\"set_app_theme\", false),\n      (\"set_dock_visibility\", false),\n      (\"bundle_type\", true),\n      (\"register_listener\", true),\n      (\"remove_listener\", true),\n    ],\n  ),\n  (\n    \"core:image\",\n    &[\n      (\"new\", true),\n      (\"from_bytes\", true),\n      (\"from_path\", true),\n      (\"rgba\", true),\n      (\"size\", true),\n    ],\n  ),\n  (\"core:resources\", &[(\"close\", true)]),\n  (\n    \"core:menu\",\n    &[\n      (\"new\", true),\n      (\"append\", true),\n      (\"prepend\", true),\n      (\"insert\", true),\n      (\"remove\", true),\n      (\"remove_at\", true),\n      (\"items\", true),\n      (\"get\", true),\n      (\"popup\", true),\n      (\"create_default\", true),\n      (\"set_as_app_menu\", true),\n      (\"set_as_window_menu\", true),\n      (\"text\", true),\n      (\"set_text\", true),\n      (\"is_enabled\", true),\n      (\"set_enabled\", true),\n      (\"set_accelerator\", true),\n      (\"set_as_windows_menu_for_nsapp\", true),\n      (\"set_as_help_menu_for_nsapp\", true),\n      (\"is_checked\", true),\n      (\"set_checked\", true),\n      (\"set_icon\", true),\n    ],\n  ),\n  (\n    \"core:tray\",\n    &[\n      (\"new\", true),\n      (\"get_by_id\", true),\n      (\"remove_by_id\", true),\n      (\"set_icon\", true),\n      (\"set_menu\", true),\n      (\"set_tooltip\", true),\n      (\"set_title\", true),\n      (\"set_visible\", true),\n      (\"set_temp_dir_path\", true),\n      (\"set_icon_as_template\", true),\n      (\"set_show_menu_on_left_click\", true),\n    ],\n  ),\n];\n\n// checks if the given Cargo feature is enabled.\nfn has_feature(feature: &str) -> bool {\n  CHECKED_FEATURES\n    .get_or_init(Default::default)\n    .lock()\n    .unwrap()\n    .push(feature.to_string());\n\n  // when a feature is enabled, Cargo sets the `CARGO_FEATURE_<name>` env var to 1\n  // <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts>\n  std::env::var(format!(\"CARGO_FEATURE_{}\", AsShoutySnakeCase(feature)))\n    .map(|x| x == \"1\")\n    .unwrap_or(false)\n}\n\n// creates a cfg alias if `has_feature` is true.\n// `alias` must be a snake case string.\nfn alias(alias: &str, has_feature: bool) {\n  println!(\"cargo:rustc-check-cfg=cfg({alias})\");\n  if has_feature {\n    println!(\"cargo:rustc-cfg={alias}\");\n  }\n}\n\nfn main() {\n  let custom_protocol = has_feature(\"custom-protocol\");\n  let dev = !custom_protocol;\n  alias(\"custom_protocol\", custom_protocol);\n  alias(\"dev\", dev);\n\n  println!(\"cargo:dev={dev}\");\n\n  let target_os = std::env::var(\"CARGO_CFG_TARGET_OS\").unwrap();\n  let mobile = target_os == \"ios\" || target_os == \"android\";\n  alias(\"desktop\", !mobile);\n  alias(\"mobile\", mobile);\n\n  let out_dir = PathBuf::from(env::var(\"OUT_DIR\").unwrap());\n\n  let checked_features_out_path = out_dir.join(\"checked_features\");\n  std::fs::write(\n    checked_features_out_path,\n    CHECKED_FEATURES.get().unwrap().lock().unwrap().join(\",\"),\n  )\n  .expect(\"failed to write checked_features file\");\n\n  // workaround needed to prevent `STATUS_ENTRYPOINT_NOT_FOUND` error in tests\n  // see https://github.com/tauri-apps/tauri/pull/4383#issuecomment-1212221864\n  let target_env = std::env::var(\"CARGO_CFG_TARGET_ENV\");\n  let is_tauri_workspace = std::env::var(\"__TAURI_WORKSPACE__\").is_ok_and(|v| v == \"true\");\n  if is_tauri_workspace && target_os == \"windows\" && Ok(\"msvc\") == target_env.as_deref() {\n    embed_manifest_for_tests();\n  }\n\n  if target_os == \"android\" {\n    fn env_var(var: &str) -> String {\n      std::env::var(var).unwrap_or_else(|_| {\n        panic!(\"`{var}` is not set, which is needed to generate the kotlin files for android.\")\n      })\n    }\n\n    if let Ok(kotlin_out_dir) = std::env::var(\"WRY_ANDROID_KOTLIN_FILES_OUT_DIR\") {\n      let package = env_var(\"WRY_ANDROID_PACKAGE\");\n      let library = env_var(\"WRY_ANDROID_LIBRARY\");\n\n      let kotlin_out_dir = PathBuf::from(&kotlin_out_dir)\n        .canonicalize()\n        .unwrap_or_else(move |_| {\n          panic!(\"Failed to canonicalize `WRY_ANDROID_KOTLIN_FILES_OUT_DIR` path {kotlin_out_dir}\")\n        });\n\n      let kotlin_files_path =\n        PathBuf::from(env_var(\"CARGO_MANIFEST_DIR\")).join(\"mobile/android-codegen\");\n      println!(\"cargo:rerun-if-changed={}\", kotlin_files_path.display());\n      let kotlin_files =\n        fs::read_dir(kotlin_files_path).expect(\"failed to read Android codegen directory\");\n\n      for file in kotlin_files {\n        let file = file.unwrap();\n\n        let content = fs::read_to_string(file.path())\n          .expect(\"failed to read kotlin file as string\")\n          .replace(\"{{package}}\", &package)\n          .replace(\"{{library}}\", &library);\n\n        let out_path = kotlin_out_dir.join(file.file_name());\n        // Overwrite only if changed to not trigger rebuilds\n        write_if_changed(&out_path, &content).expect(\"Failed to write kotlin file\");\n\n        println!(\"cargo:rerun-if-changed={}\", out_path.display());\n      }\n    }\n\n    if let Some(project_dir) = env::var_os(\"TAURI_ANDROID_PROJECT_PATH\").map(PathBuf::from) {\n      let package_unescaped = env::var(\"TAURI_ANDROID_PACKAGE_UNESCAPED\")\n        .unwrap_or_else(|_| env_var(\"WRY_ANDROID_PACKAGE\").replace('`', \"\"));\n      let tauri_proguard =\n        include_str!(\"./mobile/proguard-tauri.pro\").replace(\"$PACKAGE\", &package_unescaped);\n      std::fs::write(\n        project_dir.join(\"app\").join(\"proguard-tauri.pro\"),\n        tauri_proguard,\n      )\n      .expect(\"failed to write proguard-tauri.pro\");\n    }\n\n    let lib_path =\n      PathBuf::from(std::env::var_os(\"CARGO_MANIFEST_DIR\").unwrap()).join(\"mobile/android\");\n    println!(\"cargo:android_library_path={}\", lib_path.display());\n  }\n\n  #[cfg(target_os = \"macos\")]\n  {\n    if target_os == \"ios\" {\n      let lib_path =\n        PathBuf::from(std::env::var_os(\"CARGO_MANIFEST_DIR\").unwrap()).join(\"mobile/ios-api\");\n      tauri_utils::build::link_apple_library(\"Tauri\", &lib_path);\n      println!(\"cargo:ios_library_path={}\", lib_path.display());\n    }\n  }\n\n  let tauri_global_scripts = PathBuf::from(\"./scripts/bundle.global.js\")\n    .canonicalize()\n    .expect(\"failed to canonicalize tauri global API script path\");\n  tauri_utils::plugin::define_global_api_script_path(&tauri_global_scripts);\n  // This should usually be done in `tauri-build`,\n  // but we need to do this here for the examples in this workspace to work as they don't have build scripts\n  if is_tauri_workspace {\n    tauri_utils::plugin::save_global_api_scripts_paths(&out_dir, Some(tauri_global_scripts));\n  }\n\n  let permissions = define_permissions(&out_dir);\n  tauri_utils::acl::build::generate_allowed_commands(&out_dir, None, permissions).unwrap();\n}\n\nconst LICENSE_HEADER: &str = r\"# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\";\n\nfn define_permissions(\n  out_dir: &Path,\n) -> BTreeMap<String, Vec<tauri_utils::acl::manifest::PermissionFile>> {\n  let mut all_permissions = BTreeMap::new();\n  for (plugin, commands) in PLUGINS {\n    let plugin_directory_name = plugin.strip_prefix(\"core:\").unwrap_or(plugin);\n    let permissions_out_dir = out_dir.join(\"permissions\").join(plugin_directory_name);\n    let autogenerated =\n      permissions_out_dir.join(tauri_utils::acl::build::AUTOGENERATED_FOLDER_NAME);\n    let commands_dir = autogenerated.join(\"commands\");\n\n    tauri_utils::acl::build::autogenerate_command_permissions(\n      &commands_dir,\n      &commands.iter().map(|(cmd, _)| *cmd).collect::<Vec<_>>(),\n      LICENSE_HEADER,\n      false,\n    );\n    let default_permissions: Vec<_> = commands.iter().filter(|(_cmd, default)| *default).collect();\n    let all_commands_enabled_by_default = commands.len() == default_permissions.len();\n    let default_permissions = default_permissions\n      .into_iter()\n      .map(|(cmd, _)| {\n        let slugified_command = cmd.replace('_', \"-\");\n        format!(\"\\\"allow-{slugified_command}\\\"\")\n      })\n      .collect::<Vec<_>>()\n      .join(\", \");\n\n    let all_enable_by_default = if all_commands_enabled_by_default {\n      \", which enables all commands\"\n    } else {\n      \"\"\n    };\n\n    let default_toml = format!(\n      r#\"{LICENSE_HEADER}# Automatically generated - DO NOT EDIT!\n\n[default]\ndescription = \"Default permissions for the plugin{all_enable_by_default}.\"\npermissions = [{default_permissions}]\n\"#,\n    );\n\n    let out_path = autogenerated.join(\"default.toml\");\n    write_if_changed(out_path, default_toml)\n      .unwrap_or_else(|_| panic!(\"unable to autogenerate default permissions\"));\n\n    let permissions = tauri_utils::acl::build::define_permissions(\n      &PathBuf::from(glob::Pattern::escape(\n        &permissions_out_dir.to_string_lossy(),\n      ))\n      .join(\"**\")\n      .join(\"*.toml\")\n      .to_string_lossy(),\n      &format!(\"tauri:{plugin}\"),\n      out_dir,\n      |_| true,\n    )\n    .unwrap_or_else(|e| panic!(\"failed to define permissions for {plugin}: {e}\"));\n\n    let docs_out_dir = Path::new(\"permissions\")\n      .join(plugin_directory_name)\n      .join(\"autogenerated\");\n    fs::create_dir_all(&docs_out_dir).expect(\"failed to create plugin documentation directory\");\n    tauri_utils::acl::build::generate_docs(\n      &permissions,\n      &docs_out_dir,\n      plugin.strip_prefix(\"tauri-plugin-\").unwrap_or(plugin),\n    )\n    .expect(\"failed to generate plugin documentation page\");\n    all_permissions.insert(plugin.to_string(), permissions);\n  }\n\n  let default_permissions = define_default_permission_set(out_dir);\n  all_permissions.insert(\"core\".to_string(), default_permissions);\n\n  all_permissions\n}\n\nfn define_default_permission_set(\n  out_dir: &Path,\n) -> Vec<tauri_utils::acl::manifest::PermissionFile> {\n  let permissions_out_dir = out_dir.join(\"permissions\");\n  fs::create_dir_all(&permissions_out_dir)\n    .expect(\"failed to create core:default permissions directory\");\n\n  let default_toml = permissions_out_dir.join(\"default.toml\");\n  let toml_content = format!(\n    r#\"{LICENSE_HEADER}\n\n[default]\ndescription = \"Default core plugins set.\"\npermissions = [{}]\n\"#,\n    PLUGINS\n      .iter()\n      .map(|(k, _)| format!(\"\\\"{k}:default\\\"\"))\n      .collect::<Vec<_>>()\n      .join(\",\")\n  );\n\n  write_if_changed(default_toml, toml_content)\n    .unwrap_or_else(|_| panic!(\"unable to autogenerate core:default set\"));\n\n  tauri_utils::acl::build::define_permissions(\n    &PathBuf::from(glob::Pattern::escape(\n      &permissions_out_dir.to_string_lossy(),\n    ))\n    .join(\"*.toml\")\n    .to_string_lossy(),\n    \"tauri:core\",\n    out_dir,\n    |_| true,\n  )\n  .unwrap_or_else(|e| panic!(\"failed to define permissions for `core:default` : {e}\"))\n}\n\nfn embed_manifest_for_tests() {\n  static WINDOWS_MANIFEST_FILE: &str = \"windows-app-manifest.xml\";\n\n  let manifest = std::env::current_dir()\n    .unwrap()\n    .join(\"../tauri-build/src\")\n    .join(WINDOWS_MANIFEST_FILE);\n\n  println!(\"cargo:rerun-if-changed={}\", manifest.display());\n  // Embed the Windows application manifest file.\n  println!(\"cargo:rustc-link-arg=/MANIFEST:EMBED\");\n  println!(\n    \"cargo:rustc-link-arg=/MANIFESTINPUT:{}\",\n    manifest.to_str().unwrap()\n  );\n  // Turn linker warnings into errors.\n  println!(\"cargo:rustc-link-arg=/WX\");\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/.gitignore",
    "content": "/build"
  },
  {
    "path": "crates/tauri/mobile/android/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n    id(\"org.jetbrains.kotlin.android\")\n}\n\nandroid {\n    namespace = \"app.tauri\"\n    compileSdk = 36\n\n    defaultConfig {\n        minSdk = 21\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles(\"proguard-rules.pro\")\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = false\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n    buildFeatures {\n        buildConfig = true\n    }\n}\n\ndependencies {\n\n    implementation(\"androidx.core:core-ktx:1.7.0\")\n    implementation(\"androidx.appcompat:appcompat:1.6.0\")\n    implementation(\"com.google.android.material:material:1.7.0\")\n    implementation(\"com.fasterxml.jackson.core:jackson-databind:2.15.3\")\n    testImplementation(\"junit:junit:4.13.2\")\n    androidTestImplementation(\"androidx.test.ext:junit:1.1.5\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.5.1\")\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/proguard-rules.pro",
    "content": "-keep class app.tauri.** {\n  @app.tauri.JniMethod public <methods>;\n  native <methods>;\n}\n\n-keep class app.tauri.plugin.JSArray {\n  public <init>(...);\n}\n\n-keepclassmembers class org.json.JSONArray {\n  public put(...);\n}\n\n-keep class app.tauri.plugin.JSObject {\n  public <init>(...);\n  public put(...);\n}\n\n-keep @app.tauri.annotation.TauriPlugin public class * {\n  @app.tauri.annotation.Command public <methods>;\n  @app.tauri.annotation.PermissionCallback <methods>;\n  @app.tauri.annotation.ActivityCallback <methods>;\n  @app.tauri.annotation.Permission <methods>;\n  public <init>(...);\n}\n\n-keep @app.tauri.annotation.InvokeArg public class * {\n  *;\n}\n\n-keep @com.fasterxml.jackson.databind.annotation.JsonDeserialize public class * {\n  *;\n}\n\n-keep @com.fasterxml.jackson.databind.annotation.JsonSerialize public class * {\n  *;\n}\n\n-keep class * extends com.fasterxml.jackson.databind.JsonDeserializer { *; }\n\n-keep class * extends com.fasterxml.jackson.databind.JsonSerializer { *; }\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/androidTest/java/app/tauri/ExampleInstrumentedTest.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.runners.AndroidJUnit4\n\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\nimport org.junit.Assert.*\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"app.tauri.test\", appContext.packageName)\n    }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n</manifest>\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/AppPlugin.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri\n\nimport android.app.Activity\nimport android.webkit.WebView\nimport androidx.activity.OnBackPressedCallback\nimport androidx.appcompat.app.AppCompatActivity\nimport app.tauri.annotation.Command\nimport app.tauri.annotation.TauriPlugin\nimport app.tauri.plugin.Plugin\nimport app.tauri.plugin.Invoke\nimport app.tauri.plugin.JSObject\n\n@TauriPlugin\nclass AppPlugin(private val activity: Activity): Plugin(activity) {\n  private val BACK_BUTTON_EVENT = \"back-button\"\n\n  private var webView: WebView? = null\n\n  override fun load(webView: WebView) {\n    this.webView = webView\n  }\n\n  init {\n    val callback = object : OnBackPressedCallback(true) {\n      override fun handleOnBackPressed() {\n        if (!hasListener(BACK_BUTTON_EVENT)) {\n          if (this@AppPlugin.webView?.canGoBack() == true) {\n            this@AppPlugin.webView!!.goBack()\n          } else {\n            this.isEnabled = false\n            this@AppPlugin.activity.onBackPressed()\n            this.isEnabled = true\n          }\n        } else {\n          val data = JSObject().apply {\n            put(\"canGoBack\", this@AppPlugin.webView?.canGoBack() ?: false)\n          }\n          trigger(BACK_BUTTON_EVENT, data)\n        }\n      }\n    }\n    (activity as AppCompatActivity).onBackPressedDispatcher.addCallback(activity, callback)\n  }\n\n  @Command\n  fun exit(invoke: Invoke) {\n    invoke.resolve()\n    activity.finish()\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/FsUtils.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri\n\nimport android.content.ContentUris\nimport android.content.Context\nimport android.content.res.AssetManager\nimport android.database.Cursor\nimport android.net.Uri\nimport android.os.Environment\nimport android.provider.DocumentsContract\nimport android.provider.MediaStore\nimport android.provider.OpenableColumns\nimport java.io.File\nimport java.io.FileOutputStream\nimport kotlin.math.min\n\ninternal class FsUtils {\n  companion object {\n    fun readAsset(assetManager: AssetManager, fileName: String): String {\n      assetManager.open(fileName).bufferedReader().use {\n        return it.readText()\n      }\n    }\n\n    fun getFileUrlForUri(context: Context, uri: Uri): String? {\n      // DocumentProvider\n      if (DocumentsContract.isDocumentUri(context, uri)) {\n        // ExternalStorageProvider\n        if (isExternalStorageDocument(uri)) {\n          val docId: String = DocumentsContract.getDocumentId(uri)\n          val split = docId.split(\":\".toRegex()).dropLastWhile { it.isEmpty() }\n            .toTypedArray()\n          val type = split[0]\n          if (\"primary\".equals(type, ignoreCase = true)) {\n            return legacyPrimaryPath(split[1])\n          } else {\n            val splitIndex = docId.indexOf(':', 1)\n            val tag = docId.substring(0, splitIndex)\n            val path = docId.substring(splitIndex + 1)\n            val nonPrimaryVolume = getPathToNonPrimaryVolume(context, tag)\n            if (nonPrimaryVolume != null) {\n              val result = \"$nonPrimaryVolume/$path\"\n              val file = File(result)\n              return if (file.exists() && file.canRead()) {\n                result\n              } else null\n            }\n          }\n        } else if (isDownloadsDocument(uri)) {\n          val id: String = DocumentsContract.getDocumentId(uri)\n          val contentUri: Uri = ContentUris.withAppendedId(\n            Uri.parse(\"content://downloads/public_downloads\"),\n            java.lang.Long.valueOf(id)\n          )\n          return getDataColumn(context, contentUri, null, null)\n        } else if (isMediaDocument(uri)) {\n          val docId: String = DocumentsContract.getDocumentId(uri)\n          val split = docId.split(\":\".toRegex()).dropLastWhile { it.isEmpty() }\n            .toTypedArray()\n          val type = split[0]\n          var contentUri: Uri? = null\n          when (type) {\n            \"image\" -> {\n              contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI\n            }\n            \"video\" -> {\n              contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI\n            }\n            \"audio\" -> {\n              contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI\n            }\n          }\n          val selection = \"_id=?\"\n          val selectionArgs = arrayOf(split[1])\n          if (contentUri != null) {\n            return getDataColumn(context, contentUri, selection, selectionArgs)\n          }\n        }\n      } else if (\"content\".equals(uri.scheme, ignoreCase = true)) {\n        // Return the remote address\n        return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(\n          context,\n          uri,\n          null,\n          null\n        )\n      } else if (\"file\".equals(uri.scheme, ignoreCase = true)) {\n        return uri.path\n      }\n      return null\n    }\n\n    /**\n     * Get the value of the data column for this Uri. This is useful for\n     * MediaStore Uris, and other file-based ContentProviders.\n     *\n     * @param context The context.\n     * @param uri The Uri to query.\n     * @param selection (Optional) Filter used in the query.\n     * @param selectionArgs (Optional) Selection arguments used in the query.\n     * @return The value of the _data column, which is typically a file path.\n     */\n    private fun getDataColumn(\n      context: Context,\n      uri: Uri,\n      selection: String?,\n      selectionArgs: Array<String>?\n    ): String? {\n      var path: String? = null\n      var cursor: Cursor? = null\n      val column = \"_data\"\n      val projection = arrayOf(column)\n      try {\n        cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)\n        if (cursor != null && cursor.moveToFirst()) {\n          val index = cursor.getColumnIndexOrThrow(column)\n          path = cursor.getString(index)\n        }\n      } catch (ex: IllegalArgumentException) {\n        return getCopyFilePath(uri, context)\n      } finally {\n        cursor?.close()\n      }\n      return path ?: getCopyFilePath(uri, context)\n    }\n\n    private fun getCopyFilePath(uri: Uri, context: Context): String? {\n      val cursor = context.contentResolver.query(uri, null, null, null, null)!!\n      val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)\n      cursor.moveToFirst()\n      val name = cursor.getString(nameIndex)\n      val file = File(context.filesDir, name)\n      try {\n        val inputStream = context.contentResolver.openInputStream(uri)\n        val outputStream = FileOutputStream(file)\n        var read: Int\n        val maxBufferSize = 1024 * 1024\n        val bufferSize = min(inputStream!!.available(), maxBufferSize)\n        val buffers = ByteArray(bufferSize)\n        while (inputStream.read(buffers).also { read = it } != -1) {\n          outputStream.write(buffers, 0, read)\n        }\n        inputStream.close()\n        outputStream.close()\n      } catch (e: Exception) {\n        return null\n      } finally {\n        cursor.close()\n      }\n      return file.path\n    }\n\n    private fun legacyPrimaryPath(pathPart: String): String {\n      return Environment.getExternalStorageDirectory().toString() + \"/\" + pathPart\n    }\n\n    /**\n     * @param uri The Uri to check.\n     * @return Whether the Uri authority is ExternalStorageProvider.\n     */\n    private fun isExternalStorageDocument(uri: Uri): Boolean {\n      return \"com.android.externalstorage.documents\" == uri.authority\n    }\n\n    /**\n     * @param uri The Uri to check.\n     * @return Whether the Uri authority is DownloadsProvider.\n     */\n    private fun isDownloadsDocument(uri: Uri): Boolean {\n      return \"com.android.providers.downloads.documents\" == uri.authority\n    }\n\n    /**\n     * @param uri The Uri to check.\n     * @return Whether the Uri authority is MediaProvider.\n     */\n    private fun isMediaDocument(uri: Uri): Boolean {\n      return \"com.android.providers.media.documents\" == uri.authority\n    }\n\n    /**\n     * @param uri The Uri to check.\n     * @return Whether the Uri authority is Google Photos.\n     */\n    private fun isGooglePhotosUri(uri: Uri): Boolean {\n      return \"com.google.android.apps.photos.content\" == uri.authority\n    }\n\n    private fun getPathToNonPrimaryVolume(context: Context, tag: String): String? {\n      val volumes = context.externalCacheDirs\n      if (volumes != null) {\n        for (volume in volumes) {\n          if (volume != null) {\n            val path = volume.absolutePath\n            val index = path.indexOf(tag)\n            if (index != -1) {\n              return path.substring(0, index) + tag\n            }\n          }\n        }\n      }\n      return null\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/JniMethod.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri\n\n@Retention(AnnotationRetention.RUNTIME)\ninternal annotation class JniMethod\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/Logger.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri\n\n// taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/Logger.java\n\nimport android.text.TextUtils;\nimport android.util.Log;\n\nclass Logger {\n  companion object {\n    private const val LOG_TAG_CORE = \"Tauri\"\n\n    fun tags(vararg subtags: String): String {\n      return if (subtags.isNotEmpty()) {\n        LOG_TAG_CORE + \"/\" + TextUtils.join(\"/\", subtags)\n      } else LOG_TAG_CORE\n    }\n\n    fun verbose(message: String) {\n      verbose(LOG_TAG_CORE, message)\n    }\n\n    fun verbose(tag: String, message: String) {\n      if (!shouldLog()) {\n        return\n      }\n      Log.v(tag, message)\n    }\n\n    fun debug(message: String) {\n      debug(LOG_TAG_CORE, message)\n    }\n\n    fun debug(tag: String, message: String) {\n      if (!shouldLog()) {\n        return\n      }\n      Log.d(tag, message)\n    }\n\n    fun info(message: String) {\n      info(LOG_TAG_CORE, message)\n    }\n\n    fun info(tag: String, message: String) {\n      if (!shouldLog()) {\n        return\n      }\n      Log.i(tag, message)\n    }\n\n    fun warn(message: String) {\n      warn(LOG_TAG_CORE, message)\n    }\n\n    fun warn(tag: String, message: String) {\n      if (!shouldLog()) {\n        return\n      }\n      Log.w(tag, message)\n    }\n\n    fun error(message: String) {\n      error(LOG_TAG_CORE, message, null)\n    }\n\n    fun error(message: String, e: Throwable?) {\n      error(LOG_TAG_CORE, message, e)\n    }\n\n    fun error(tag: String, message: String, e: Throwable?) {\n      if (!shouldLog()) {\n        return\n      }\n      Log.e(tag, message, e)\n    }\n\n    private fun shouldLog(): Boolean {\n      return BuildConfig.DEBUG\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/PathPlugin.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri\n\nimport android.app.Activity\nimport android.database.Cursor\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Environment\nimport android.provider.OpenableColumns\nimport app.tauri.annotation.Command\nimport app.tauri.annotation.InvokeArg\nimport app.tauri.annotation.TauriPlugin\nimport app.tauri.plugin.Plugin\nimport app.tauri.plugin.Invoke\nimport app.tauri.plugin.JSObject\n\nconst val TAURI_ASSETS_DIRECTORY_URI = \"asset://localhost/\"\n\n@InvokeArg\nclass GetFileNameFromUriArgs {\n  lateinit var uri: String\n}\n\n@TauriPlugin\nclass PathPlugin(private val activity: Activity): Plugin(activity) {\n    private fun resolvePath(invoke: Invoke, path: String?) {\n        val obj = JSObject()\n        obj.put(\"path\", path)\n        invoke.resolve(obj)\n    }\n\n    @Command\n    fun getFileNameFromUri(invoke: Invoke) {\n      val args = invoke.parseArgs(GetFileNameFromUriArgs::class.java)\n      val name = getRealNameFromURI(activity, Uri.parse(args.uri))\n      val res = JSObject()\n      res.put(\"name\", name)\n      invoke.resolve(res)\n    }\n\n    @Command\n    fun getAudioDir(invoke: Invoke) {\n        resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_MUSIC)?.absolutePath)\n    }\n\n    @Command\n    fun getExternalCacheDir(invoke: Invoke) {\n        resolvePath(invoke, activity.externalCacheDir?.absolutePath)\n    }\n\n    @Command\n    fun getConfigDir(invoke: Invoke) {\n      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n        resolvePath(invoke, activity.dataDir.absolutePath)\n      } else {\n        resolvePath(invoke, activity.applicationInfo.dataDir)\n      }\n    }\n\n    @Command\n    fun getDataDir(invoke: Invoke) {\n      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n        resolvePath(invoke, activity.dataDir.absolutePath)\n      } else {\n        resolvePath(invoke, activity.applicationInfo.dataDir)\n      }\n    }\n\n    @Command\n    fun getDocumentDir(invoke: Invoke) {\n        resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)?.absolutePath)\n    }\n\n    @Command\n    fun getDownloadDir(invoke: Invoke) {\n        resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath)\n    }\n\n    @Command\n    fun getPictureDir(invoke: Invoke) {\n        resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.absolutePath)\n    }\n\n    @Command\n    fun getPublicDir(invoke: Invoke) {\n        resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DCIM)?.absolutePath)\n    }\n\n    @Command\n    fun getVideoDir(invoke: Invoke) {\n        resolvePath(invoke, activity.externalCacheDir?.absolutePath)\n    }\n\n    @Command\n    fun getResourcesDir(invoke: Invoke) {\n        resolvePath(invoke, TAURI_ASSETS_DIRECTORY_URI)\n    }\n\n    @Command\n    fun getCacheDir(invoke: Invoke) {\n        resolvePath(invoke, activity.cacheDir.absolutePath)\n    }\n\n    @Command\n    fun getHomeDir(invoke: Invoke) {\n        resolvePath(invoke, Environment.getExternalStorageDirectory().absolutePath)\n    }\n}\n\nfun getRealNameFromURI(activity: Activity, contentUri: Uri): String? {\n    var cursor: Cursor? = null\n    try {\n        val projection = arrayOf(OpenableColumns.DISPLAY_NAME)\n        cursor = activity.contentResolver.query(contentUri, projection, null, null, null)\n\n        cursor?.let {\n            val columnIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)\n            if (it.moveToFirst()) {\n                return it.getString(columnIndex)\n            }\n        }\n    } catch (e: Exception) {\n        Logger.error(\"failed to get real name from URI $e\")\n    } finally {\n        cursor?.close()\n    }\n\n    return null // Return null if no file name could be resolved\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/PermissionHelper.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri\n\n// taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/PermissionHelper.java\n\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\nimport androidx.core.app.ActivityCompat;\nimport java.util.ArrayList;\n\nobject PermissionHelper {\n  /**\n   * Checks if a list of given permissions are all granted by the user\n   *\n   * @param permissions Permissions to check.\n   * @return True if all permissions are granted, false if at least one is not.\n   */\n  fun hasPermissions(context: Context?, permissions: Array<String>): Boolean {\n    for (perm in permissions) {\n      if (ActivityCompat.checkSelfPermission(\n          context!!,\n          perm\n        ) != PackageManager.PERMISSION_GRANTED\n      ) {\n        return false\n      }\n    }\n    return true\n  }\n\n  /**\n   * Check whether the given permission has been defined in the AndroidManifest.xml\n   *\n   * @param permission A permission to check.\n   * @return True if the permission has been defined in the Manifest, false if not.\n   */\n  fun hasDefinedPermission(context: Context, permission: String): Boolean {\n    var hasPermission = false\n    val requestedPermissions = getManifestPermissions(context)\n    if (requestedPermissions != null && requestedPermissions.isNotEmpty()) {\n      val requestedPermissionsList = listOf(*requestedPermissions)\n      val requestedPermissionsArrayList = ArrayList(requestedPermissionsList)\n      if (requestedPermissionsArrayList.contains(permission)) {\n        hasPermission = true\n      }\n    }\n    return hasPermission\n  }\n\n  /**\n   * Check whether all of the given permissions have been defined in the AndroidManifest.xml\n   * @param context the app context\n   * @param permissions a list of permissions\n   * @return true only if all permissions are defined in the AndroidManifest.xml\n   */\n  fun hasDefinedPermissions(context: Context, permissions: Array<String>): Boolean {\n    for (permission in permissions) {\n      if (!hasDefinedPermission(context, permission)) {\n        return false\n      }\n    }\n    return true\n  }\n\n  /**\n   * Get the permissions defined in AndroidManifest.xml\n   *\n   * @return The permissions defined in AndroidManifest.xml\n   */\n  private fun getManifestPermissions(context: Context): Array<String>? {\n    var requestedPermissions: Array<String>? = null\n    try {\n      val pm = context.packageManager\n      val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n        pm.getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong()))\n      } else {\n        @Suppress(\"DEPRECATION\")\n        pm.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)\n      }\n      if (packageInfo != null) {\n        requestedPermissions = packageInfo.requestedPermissions\n      }\n    } catch (_: Exception) {\n    }\n    return requestedPermissions\n  }\n\n  /**\n   * Given a list of permissions, return a new list with the ones not present in AndroidManifest.xml\n   *\n   * @param neededPermissions The permissions needed.\n   * @return The permissions not present in AndroidManifest.xml\n   */\n  fun getUndefinedPermissions(context: Context, neededPermissions: Array<String>): Array<String> {\n    val undefinedPermissions = ArrayList<String>()\n    val requestedPermissions = getManifestPermissions(context)\n    if (!requestedPermissions.isNullOrEmpty()) {\n      val requestedPermissionsList = listOf(*requestedPermissions)\n      val requestedPermissionsArrayList = ArrayList(requestedPermissionsList)\n      for (permission in neededPermissions) {\n        if (!requestedPermissionsArrayList.contains(permission)) {\n          undefinedPermissions.add(permission)\n        }\n      }\n      return undefinedPermissions.toTypedArray()\n    }\n    return neededPermissions\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/PermissionState.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri\n\nimport java.util.*\n\nenum class PermissionState(private val state: String) {\n  GRANTED(\"granted\"), DENIED(\"denied\"), PROMPT(\"prompt\"), PROMPT_WITH_RATIONALE(\"prompt-with-rationale\");\n\n  override fun toString(): String {\n    return state\n  }\n\n  companion object {\n    fun byState(state: String): PermissionState {\n      return valueOf(state.uppercase(Locale.ROOT).replace('-', '_'))\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/annotation/ActivityCallback.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.annotation\n\n@Retention(AnnotationRetention.RUNTIME)\n@Target(AnnotationTarget.FUNCTION)\nannotation class ActivityCallback\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/annotation/InvokeArg.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.annotation\n\n@Retention(AnnotationRetention.RUNTIME)\n@Target(AnnotationTarget.CLASS)\nannotation class InvokeArg\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/annotation/Permission.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.annotation\n\n@Retention(AnnotationRetention.RUNTIME)\nannotation class Permission(\n  /**\n   * An array of Android permission strings.\n   * Eg: {Manifest.permission.ACCESS_COARSE_LOCATION}\n   * or {\"android.permission.ACCESS_COARSE_LOCATION\"}\n   */\n  val strings: Array<String> = [],\n  /**\n   * An optional name to use instead of the Android permission string.\n   */\n  val alias: String = \"\"\n)\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/annotation/PermissionCallback.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.annotation\n\n@Retention(AnnotationRetention.RUNTIME)\n@Target(AnnotationTarget.FUNCTION)\nannotation class PermissionCallback\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/annotation/PluginMethod.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.annotation\n\n@Retention(AnnotationRetention.RUNTIME)\nannotation class Command\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/annotation/TauriPlugin.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.annotation\n\nimport app.tauri.annotation.Permission\n\n/**\n * Base annotation for all Plugins\n */\n@Retention(AnnotationRetention.RUNTIME)\nannotation class TauriPlugin(\n  /**\n   * Permissions this plugin needs, in order to make permission requests\n   * easy if the plugin only needs basic permission prompting\n   */\n  val permissions: Array<Permission> = []\n)\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/plugin/Channel.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.plugin\n\nimport com.fasterxml.jackson.core.JsonParser\nimport com.fasterxml.jackson.databind.DeserializationContext\nimport com.fasterxml.jackson.databind.JsonDeserializer\nimport com.fasterxml.jackson.databind.ObjectMapper\n\nconst val CHANNEL_PREFIX = \"__CHANNEL__:\"\n\ninternal class ChannelDeserializer(val sendChannelData: (channelId: Long, data: String) -> Unit, private val objectMapper: ObjectMapper): JsonDeserializer<Channel>() {\n  override fun deserialize(\n    jsonParser: JsonParser?,\n    deserializationContext: DeserializationContext\n  ): Channel {\n    val channelDef = deserializationContext.readValue(jsonParser, String::class.java)\n    val callback = channelDef.substring(CHANNEL_PREFIX.length).toLongOrNull() ?: throw Error(\"unexpected channel value $channelDef\")\n    return Channel(callback, { res -> sendChannelData(callback, res) }, objectMapper)\n  }\n}\n\nclass Channel(val id: Long, private val handler: (data: String) -> Unit, private val objectMapper: ObjectMapper) {\n  fun send(data: JSObject) {\n    handler(PluginResult(data).toString())\n  }\n\n  fun sendObject(data: Any) {\n    handler(objectMapper.writeValueAsString(data))\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/plugin/InvalidPluginMethodException.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.plugin\n\ninternal class InvalidCommandException : Exception {\n  constructor(s: String?) : super(s) {}\n  constructor(t: Throwable?) : super(t) {}\n  constructor(s: String?, t: Throwable?) : super(s, t) {}\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/plugin/Invoke.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.plugin\n\nimport app.tauri.Logger\nimport com.fasterxml.jackson.core.type.TypeReference\nimport com.fasterxml.jackson.databind.ObjectMapper\n\nclass Invoke(\n  val id: Long,\n  val command: String,\n  val callback: Long,\n  val error: Long,\n  private val sendResponse: (callback: Long, data: String) -> Unit,\n  private val argsJson: String,\n  private val jsonMapper: ObjectMapper\n) {\n  fun getRawArgs(): String {\n    return argsJson\n  }\n\n  fun getArgs(): JSObject {\n    return JSObject(argsJson)\n  }\n\n  fun<T> parseArgs(cls: Class<T>): T {\n    return jsonMapper.readValue(argsJson, cls)\n  }\n\n  fun<T> parseArgs(ref: TypeReference<T>): T {\n    return jsonMapper.readValue(argsJson, ref)\n  }\n\n  fun resolve(data: JSObject?) {\n    sendResponse(callback, PluginResult(data).toString())\n  }\n\n  fun resolveObject(data: Any) {\n    sendResponse(callback, jsonMapper.writeValueAsString(data))\n  }\n\n  fun resolve() {\n    sendResponse(callback, \"null\")\n  }\n\n  fun reject(msg: String?, code: String?, ex: Exception?, data: JSObject?) {\n    val errorResult = PluginResult()\n\n    if (ex != null) {\n      Logger.error(Logger.tags(\"Plugin\"), msg!!, ex)\n    }\n\n    errorResult.put(\"message\", msg)\n    if (code != null) {\n      errorResult.put(\"code\", code)\n    }\n    if (data != null) {\n      errorResult.put(\"data\", data)\n    }\n\n    sendResponse(error, errorResult.toString())\n  }\n\n  fun reject(msg: String?, ex: Exception?, data: JSObject?) {\n    reject(msg, null, ex, data)\n  }\n\n  fun reject(msg: String?, code: String?, data: JSObject?) {\n    reject(msg, code, null, data)\n  }\n\n  fun reject(msg: String?, code: String?, ex: Exception?) {\n    reject(msg, code, ex, null)\n  }\n\n  fun reject(msg: String?, data: JSObject?) {\n    reject(msg, null, null, data)\n  }\n\n  fun reject(msg: String?, ex: Exception?) {\n    reject(msg, null, ex, null)\n  }\n\n  fun reject(msg: String?, code: String?) {\n    reject(msg, code, null, null)\n  }\n\n  fun reject(msg: String?) {\n    reject(msg, null, null, null)\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/plugin/JSArray.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.plugin\n\nimport org.json.JSONArray\nimport org.json.JSONException\n\nclass JSArray : JSONArray {\n  constructor() : super() {}\n  constructor(json: String?) : super(json) {}\n  constructor(copyFrom: Collection<*>?) : super(copyFrom) {}\n  constructor(array: Any?) : super(array) {}\n\n  @Suppress(\"UNCHECKED_CAST\", \"ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE\")\n  @Throws(JSONException::class)\n  fun <E> toList(): List<E> {\n    val items: MutableList<E> = ArrayList()\n    var o: Any? = null\n    for (i in 0 until this.length()) {\n      this.get(i).also { o = it }\n      try {\n        items.add(this.get(i) as E)\n      } catch (ex: Exception) {\n        throw JSONException(\"Not all items are instances of the given type\")\n      }\n    }\n    return items\n  }\n\n  companion object {\n    /**\n     * Create a new JSArray without throwing a error\n     */\n    fun from(array: Any?): JSArray? {\n      try {\n        return JSArray(array)\n      } catch (ex: JSONException) {\n        //\n      }\n      return null\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/plugin/JSObject.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.plugin\n\nimport org.json.JSONException\nimport org.json.JSONObject\n\nclass JSObject : JSONObject {\n  constructor() : super()\n  constructor(json: String) : super(json)\n  constructor(obj: JSONObject, names: Array<String>) : super(obj, names)\n\n  override fun getString(key: String): String {\n    return getString(key, \"\")!!\n  }\n\n  fun getString(key: String, defaultValue: String?): String? {\n    try {\n      if (!super.isNull(key)) {\n        return super.getString(key)\n      }\n    } catch (_: JSONException) {\n    }\n    return defaultValue\n  }\n\n  fun getInteger(key: String): Int? {\n    return getIntegerInternal(key, null)\n  }\n\n  fun getInteger(key: String, defaultValue: Int): Int {\n    return getIntegerInternal(key, defaultValue)!!\n  }\n\n  private fun getIntegerInternal(key: String, defaultValue: Int?): Int? {\n    try {\n      return super.getInt(key)\n    } catch (_: JSONException) {\n    }\n    return defaultValue\n  }\n\n  override fun getBoolean(key: String): Boolean {\n    return getBooleanInternal(key, false)!!\n  }\n\n  fun getBoolean(key: String, defaultValue: Boolean?): Boolean {\n    return getBooleanInternal(key, defaultValue)!!\n  }\n\n  private fun getBooleanInternal(key: String, defaultValue: Boolean?): Boolean? {\n    try {\n      return super.getBoolean(key)\n    } catch (_: JSONException) {\n    }\n    return defaultValue\n  }\n\n  fun getJSObject(name: String): JSObject? {\n    try {\n      return getJSObjectInternal(name, null)\n    } catch (_: JSONException) {\n    }\n    return null\n  }\n\n  fun getJSObject(name: String, defaultValue: JSObject): JSObject {\n    return getJSObjectInternal(name, defaultValue)!!\n  }\n\n  private fun getJSObjectInternal(name: String, defaultValue: JSObject?): JSObject? {\n    try {\n      val obj = get(name)\n      if (obj is JSONObject) {\n        val keysIter = obj.keys()\n        val keys: MutableList<String> = ArrayList()\n        while (keysIter.hasNext()) {\n          keys.add(keysIter.next())\n        }\n        return JSObject(obj, keys.toTypedArray())\n      }\n    } catch (_: JSONException) {\n    }\n    return defaultValue\n  }\n\n  override fun put(key: String, value: Boolean): JSObject {\n    try {\n      super.put(key, value)\n    } catch (_: JSONException) {\n    }\n    return this\n  }\n\n  override fun put(key: String, value: Int): JSObject {\n    try {\n      super.put(key, value)\n    } catch (_: JSONException) {\n    }\n    return this\n  }\n\n  override fun put(key: String, value: Long): JSObject {\n    try {\n      super.put(key, value)\n    } catch (_: JSONException) {\n    }\n    return this\n  }\n\n  override fun put(key: String, value: Double): JSObject {\n    try {\n      super.put(key, value)\n    } catch (_: JSONException) {\n    }\n    return this\n  }\n\n  override fun put(key: String, value: Any?): JSObject {\n    try {\n      super.put(key, value)\n    } catch (_: JSONException) {\n    }\n    return this\n  }\n\n  fun put(key: String, value: String?): JSObject {\n    try {\n      super.put(key, value)\n    } catch (_: JSONException) {\n    }\n    return this\n  }\n\n  companion object {\n    /**\n     * Convert a pathetic JSONObject into a JSObject\n     * @param obj\n     */\n    @Throws(JSONException::class)\n    fun fromJSONObject(obj: JSONObject): JSObject {\n      val keysIter = obj.keys()\n      val keys: MutableList<String> = ArrayList()\n      while (keysIter.hasNext()) {\n        keys.add(keysIter.next())\n      }\n      return JSObject(obj, keys.toTypedArray())\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/plugin/Plugin.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.plugin\n\nimport android.app.Activity\nimport android.content.res.Configuration\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.net.Uri\nimport android.webkit.WebView\nimport androidx.activity.result.IntentSenderRequest\nimport androidx.core.app.ActivityCompat\nimport app.tauri.FsUtils\nimport app.tauri.Logger\nimport app.tauri.PermissionHelper\nimport app.tauri.PermissionState\nimport app.tauri.annotation.ActivityCallback\nimport app.tauri.annotation.Command\nimport app.tauri.annotation.InvokeArg\nimport app.tauri.annotation.PermissionCallback\nimport app.tauri.annotation.TauriPlugin\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport java.util.*\nimport java.util.concurrent.CopyOnWriteArrayList\n\n@InvokeArg\ninternal class RegisterListenerArgs {\n  lateinit var event: String\n  lateinit var handler: Channel\n}\n\n@InvokeArg\ninternal class RemoveListenerArgs {\n  lateinit var event: String\n  var channelId: Long = 0\n}\n\n@InvokeArg internal class RequestPermissionsArgs {\n  var permissions: List<String>? = null\n}\n\nabstract class Plugin(private val activity: Activity) {\n  var handle: PluginHandle? = null\n  private val listeners: MutableMap<String, MutableList<Channel>> = mutableMapOf()\n\n  open fun load(webView: WebView) {}\n\n  fun jsonMapper(): ObjectMapper {\n    return handle!!.jsonMapper\n  }\n\n  fun<T> getConfig(cls: Class<T>): T {\n    return jsonMapper().readValue(handle!!.config, cls)\n  }\n\n  /**\n   * Handle a new intent being received by the application\n   */\n  open fun onNewIntent(intent: Intent) {}\n\n\n  /**\n   * This event is called just before another activity comes into the foreground.\n   */\n  open fun onPause() {}\n\n  /**\n   * This event is called when the user returns to the activity. It is also called on cold starts.\n   */\n  open fun onResume() {}\n\n  /**\n   * This event is called after onStop() when the current activity is being re-displayed to the user (the user has navigated back to it).\n   * It will be followed by onStart() and then onResume().\n   */\n  open fun onRestart() {}\n\n  /**\n   * This event is called when the app is no longer visible to the user.\n   * You will next receive either onRestart(), onDestroy(), or nothing, depending on later user activity.\n   */\n  open fun onStop() {}\n\n  /**\n   * This event is called before the activity is destroyed.\n   */\n  open fun onDestroy() {}\n\n  /**\n   * This event is called when a configuration change occurs but the app does not recreate the activity.\n   */\n  open fun onConfigurationChanged(newConfig: Configuration) {}\n\n  /**\n   * Start activity for result with the provided Intent and resolve calling the provided callback method name.\n   *\n   * If there is no registered activity callback for the method name passed in, the call will\n   * be rejected. Make sure a valid activity result callback method is registered using the\n   * [ActivityCallback] annotation.\n   *\n   * @param invoke the invoke object\n   * @param intent the intent used to start an activity\n   * @param callbackName the name of the callback to run when the launched activity is finished\n   */\n  fun startActivityForResult(invoke: Invoke, intent: Intent, callbackName: String) {\n    handle!!.startActivityForResult(invoke, intent, callbackName)\n  }\n\n  /**\n   * Like startActivityForResult() but taking an IntentSender to describe the activity to be started.\n   *\n   * If there is no registered activity callback for the method name passed in, the call will\n   * be rejected. Make sure a valid activity result callback method is registered using the\n   * [ActivityCallback] annotation.\n   *\n   * @param invoke the invoke object\n   * @param intentSender the intent used to start an activity\n   * @param callbackName the name of the callback to run when the launched activity is finished\n   */\n  fun startIntentSenderForResult(invoke: Invoke, intentSender: IntentSenderRequest, callbackName: String) {\n    handle!!.startIntentSenderForResult(invoke, intentSender, callbackName)\n  }\n\n  /**\n   * Get the plugin log tags.\n   * @param subTags\n   */\n  protected fun getLogTag(vararg subTags: String): String {\n    return Logger.tags(*subTags)\n  }\n\n  /**\n   * Gets a log tag with the plugin's class name as subTag.\n   */\n  protected fun getLogTag(): String {\n    return Logger.tags(this.javaClass.simpleName)\n  }\n\n  /**\n   * Convert an URI to an URL that can be loaded by the webview.\n   */\n  fun assetUrl(u: Uri): String {\n    var path = FsUtils.getFileUrlForUri(activity, u)\n    if (path?.startsWith(\"file://\") == true) {\n      path = path.replace(\"file://\", \"\")\n    }\n    return \"asset://localhost$path\"\n  }\n\n  fun trigger(event: String, payload: JSObject) {\n    val eventListeners = listeners[event]\n    if (!eventListeners.isNullOrEmpty()) {\n      val listeners = CopyOnWriteArrayList(eventListeners)\n      for (channel in listeners) {\n        channel.send(payload)\n      }\n    }\n  }\n\n  fun triggerObject(event: String, payload: Any) {\n    val eventListeners = listeners[event]\n    if (!eventListeners.isNullOrEmpty()) {\n      val listeners = CopyOnWriteArrayList(eventListeners)\n      for (channel in listeners) {\n        channel.sendObject(payload)\n      }\n    }\n  }\n\n  fun hasListener(event: String): Boolean {\n    return !listeners[event].isNullOrEmpty()\n  }\n\n  @Command\n  open fun registerListener(invoke: Invoke) {\n    val args = invoke.parseArgs(RegisterListenerArgs::class.java)\n\n    val eventListeners = listeners[args.event]\n    if (eventListeners.isNullOrEmpty()) {\n      listeners[args.event] = mutableListOf(args.handler)\n    } else {\n      eventListeners.add(args.handler)\n    }\n\n    invoke.resolve()\n  }\n\n  @Command\n  open fun removeListener(invoke: Invoke) {\n    val args = invoke.parseArgs(RemoveListenerArgs::class.java)\n\n    val eventListeners = listeners[args.event]\n    if (!eventListeners.isNullOrEmpty()) {\n      val c = eventListeners.find { c -> c.id == args.channelId }\n      if (c != null) {\n        eventListeners.remove(c)\n      }\n    }\n\n    invoke.resolve()\n  }\n\n  /**\n   * Exported plugin method for checking the granted status for each permission\n   * declared on the plugin. This plugin call responds with a mapping of permissions to\n   * the associated granted status.\n   */\n  @Command\n  @PermissionCallback\n  open fun checkPermissions(invoke: Invoke) {\n    val permissionsResult: Map<String, PermissionState?> = getPermissionStates()\n    if (permissionsResult.isEmpty()) {\n      // if no permissions are defined on the plugin, resolve undefined\n      invoke.resolve()\n    } else {\n      val permissionsResultJSON = JSObject()\n      for ((key, value) in permissionsResult) {\n        permissionsResultJSON.put(key, value)\n      }\n      invoke.resolve(permissionsResultJSON)\n    }\n  }\n\n  /**\n   * Exported plugin method to request all permissions for this plugin.\n   * To manually request permissions within a plugin use:\n   * [.requestAllPermissions], or\n   * [.requestPermissionForAlias], or\n   * [.requestPermissionForAliases]\n   *\n   * @param invoke\n   */\n  @Command\n  open fun requestPermissions(invoke: Invoke) {\n    val annotation = handle?.annotation\n    if (annotation != null) {\n      // handle permission requests for plugins defined with @TauriPlugin\n      var permAliases: Array<String>? = null\n      val autoGrantPerms: MutableSet<String> = HashSet()\n\n      val args = invoke.parseArgs(RequestPermissionsArgs::class.java)\n\n      args.permissions?.let {\n        val aliasSet: MutableSet<String> = HashSet()\n\n        for (perm in annotation.permissions) {\n          if (it.contains(perm.alias)) {\n            aliasSet.add(perm.alias)\n          }\n        }\n        if (aliasSet.isEmpty()) {\n          invoke.reject(\"No valid permission alias was requested of this plugin.\")\n          return\n        } else {\n          permAliases = aliasSet.toTypedArray()\n        }\n      } ?: run {\n        val aliasSet: MutableSet<String> = HashSet()\n\n        for (perm in annotation.permissions) {\n          // If a permission is defined with no permission strings, separate it for auto-granting.\n          // Otherwise, the alias is added to the list to be requested.\n          if (perm.strings.isEmpty() || perm.strings.size == 1 && perm.strings[0]\n              .isEmpty()\n          ) {\n            if (perm.alias.isNotEmpty()) {\n              autoGrantPerms.add(perm.alias)\n            }\n          } else {\n            aliasSet.add(perm.alias)\n          }\n        }\n        permAliases = aliasSet.toTypedArray()\n      }\n\n      permAliases?.let {\n        // request permissions using provided aliases or all defined on the plugin\n        requestPermissionForAliases(it, invoke, \"checkPermissions\")\n      } ?: run {\n        if (autoGrantPerms.isNotEmpty()) {\n          // if the plugin only has auto-grant permissions, return all as GRANTED\n          val permissionsResults = JSObject()\n          for (perm in autoGrantPerms) {\n            permissionsResults.put(perm, PermissionState.GRANTED.toString())\n          }\n          invoke.resolve(permissionsResults)\n        } else {\n          // no permissions are defined on the plugin, resolve undefined\n          invoke.resolve()\n        }\n      }\n    }\n  }\n\n  /**\n   * Checks if the given permission alias is correctly declared in AndroidManifest.xml\n   * @param alias a permission alias defined on the plugin\n   * @return true only if all permissions associated with the given alias are declared in the manifest\n   */\n  fun isPermissionDeclared(alias: String): Boolean {\n    val annotation = handle?.annotation\n    if (annotation != null) {\n      for (perm in annotation.permissions) {\n        if (alias.equals(perm.alias, ignoreCase = true)) {\n          var result = true\n          for (permString in perm.strings) {\n            result = result && PermissionHelper.hasDefinedPermission(activity, permString)\n          }\n          return result\n        }\n      }\n    }\n    Logger.error(\n      String.format(\n        \"isPermissionDeclared: No alias defined for %s \" + \"or missing @TauriPlugin annotation.\",\n        alias\n      )\n    )\n    return false\n  }\n\n  private fun permissionActivityResult(\n    invoke: Invoke,\n    permissionStrings: Array<String>,\n    callbackName: String\n  ) {\n    handle!!.requestPermissions(invoke, permissionStrings, callbackName)\n  }\n\n  /**\n   * Request all of the specified permissions in the TauriPlugin annotation (if any)\n   *\n   * If there is no registered permission callback for the Invoke passed in, the call will\n   * be rejected. Make sure a valid permission callback method is registered using the\n   * [PermissionCallback] annotation.\n   *\n   * @param invoke\n   * @param callbackName the name of the callback to run when the permission request is complete\n   */\n  protected fun requestAllPermissions(\n    invoke: Invoke,\n    callbackName: String\n  ) {\n    val annotation = handle!!.annotation\n    if (annotation != null) {\n      val perms: HashSet<String> = HashSet()\n      for (perm in annotation.permissions) {\n        perms.addAll(perm.strings)\n      }\n      permissionActivityResult(invoke, perms.toArray(arrayOfNulls<String>(0)), callbackName)\n    }\n  }\n\n  /**\n   * Request permissions using an alias defined on the plugin.\n   *\n   * If there is no registered permission callback for the Invoke passed in, the call will\n   * be rejected. Make sure a valid permission callback method is registered using the\n   * [PermissionCallback] annotation.\n   *\n   * @param alias an alias defined on the plugin\n   * @param invoke the invoke involved in originating the request\n   * @param callbackName the name of the callback to run when the permission request is complete\n   */\n  protected fun requestPermissionForAlias(\n    alias: String,\n    invoke: Invoke,\n    callbackName: String\n  ) {\n    requestPermissionForAliases(arrayOf(alias), invoke, callbackName)\n  }\n\n  /**\n   * Request permissions using aliases defined on the plugin.\n   *\n   * If there is no registered permission callback for the Invoke passed in, the call will\n   * be rejected. Make sure a valid permission callback method is registered using the\n   * [PermissionCallback] annotation.\n   *\n   * @param aliases a set of aliases defined on the plugin\n   * @param invoke    the invoke involved in originating the request\n   * @param callbackName the name of the callback to run when the permission request is complete\n   */\n  fun requestPermissionForAliases(\n    aliases: Array<String>,\n    invoke: Invoke,\n    callbackName: String\n  ) {\n    if (aliases.isEmpty()) {\n      Logger.error(\"No permission alias was provided\")\n      return\n    }\n    val permissions = getPermissionStringsForAliases(aliases)\n    if (permissions.isNotEmpty()) {\n      permissionActivityResult(invoke, permissions, callbackName)\n    }\n  }\n\n  /**\n   * Gets the Android permission strings defined on the [TauriPlugin] annotation with\n   * the provided aliases.\n   *\n   * @param aliases aliases for permissions defined on the plugin\n   * @return Android permission strings associated with the provided aliases, if exists\n   */\n  private fun getPermissionStringsForAliases(aliases: Array<String>): Array<String> {\n    val annotation = handle?.annotation\n    val perms: HashSet<String> = HashSet()\n    if (annotation != null) {\n      for (perm in annotation.permissions) {\n        if (aliases.contains(perm.alias)) {\n          perms.addAll(perm.strings)\n        }\n      }\n    }\n    return perms.toArray(arrayOfNulls(0))\n  }\n\n  /**\n   * Get the permission state for the provided permission alias.\n   *\n   * @param alias the permission alias to get\n   * @return the state of the provided permission alias or null\n   */\n  fun getPermissionState(alias: String): PermissionState? {\n    return getPermissionStates()[alias]\n  }\n\n  /**\n   * Helper to check all permissions defined on a plugin and see the state of each.\n   *\n   * @return A mapping of permission aliases to the associated granted status.\n   */\n  open fun getPermissionStates(): Map<String, PermissionState> {\n    val permissionsResults: MutableMap<String, PermissionState> = HashMap()\n    val annotation = handle?.annotation\n    if (annotation != null) {\n      for (perm in annotation.permissions) {\n        // If a permission is defined with no permission constants, return GRANTED for it.\n        // Otherwise, get its true state.\n        if (perm.strings.isEmpty() || perm.strings.size == 1 && perm.strings[0]\n            .isEmpty()\n        ) {\n          val key = perm.alias\n          if (key.isNotEmpty()) {\n            val existingResult = permissionsResults[key]\n\n            // auto set permission state to GRANTED if the alias is empty.\n            if (existingResult == null) {\n              permissionsResults[key] = PermissionState.GRANTED\n            }\n          }\n        } else {\n          for (permString in perm.strings) {\n            val key = perm.alias.ifEmpty { permString }\n            var permissionStatus: PermissionState\n            if (ActivityCompat.checkSelfPermission(\n                activity,\n                permString\n              ) == PackageManager.PERMISSION_GRANTED\n            ) {\n              permissionStatus = PermissionState.GRANTED\n            } else {\n              permissionStatus = PermissionState.PROMPT\n\n              // Check if there is a cached permission state for the \"Never ask again\" state\n              val prefs =\n                activity.getSharedPreferences(\"PluginPermStates\", Activity.MODE_PRIVATE)\n              val state = prefs.getString(permString, null)\n              if (state != null) {\n                permissionStatus = PermissionState.byState(state)\n              }\n            }\n            val existingResult = permissionsResults[key]\n\n            // multiple permissions with the same alias must all be true, otherwise all false.\n            if (existingResult == null || existingResult === PermissionState.GRANTED) {\n              permissionsResults[key] = permissionStatus\n            }\n          }\n        }\n      }\n    }\n\n    return permissionsResults\n  }\n\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginHandle.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.plugin\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.content.SharedPreferences\nimport android.webkit.WebView\nimport androidx.activity.result.IntentSenderRequest\nimport androidx.core.app.ActivityCompat\nimport app.tauri.PermissionHelper\nimport app.tauri.PermissionState\nimport app.tauri.annotation.ActivityCallback\nimport app.tauri.annotation.Command\nimport app.tauri.annotation.PermissionCallback\nimport app.tauri.annotation.TauriPlugin\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport java.lang.reflect.Method\n\nclass PluginHandle(private val manager: PluginManager, val name: String, val instance: Plugin, val config: String, val jsonMapper: ObjectMapper) {\n  private val commands: HashMap<String, CommandData> = HashMap()\n  private val permissionCallbackMethods: HashMap<String, Method> = HashMap()\n  private val startActivityCallbackMethods: HashMap<String, Method> = HashMap()\n  var annotation: TauriPlugin?\n  var loaded = false\n\n  init {\n    indexMethods()\n    instance.handle = this\n    annotation = instance.javaClass.getAnnotation(TauriPlugin::class.java)\n  }\n\n  fun load(webView: WebView) {\n    instance.load(webView)\n    loaded = true\n  }\n\n  fun startActivityForResult(invoke: Invoke, intent: Intent, callbackName: String) {\n    manager.startActivityForResult(intent) { result ->\n      val method = startActivityCallbackMethods[callbackName]\n      if (method != null) {\n        method.isAccessible = true\n        method(instance, invoke, result)\n      }\n    }\n  }\n\n  fun startIntentSenderForResult(invoke: Invoke, intentSender: IntentSenderRequest, callbackName: String) {\n    manager.startIntentSenderForResult(intentSender) { result ->\n      val method = startActivityCallbackMethods[callbackName]\n      if (method != null) {\n        method.isAccessible = true\n        method(instance, invoke, result)\n      }\n    }\n  }\n\n  fun requestPermissions(\n    invoke: Invoke,\n    permissions: Array<String>,\n    callbackName: String\n  ) {\n    manager.requestPermissions(permissions) { result ->\n      if (validatePermissions(invoke, result)) {\n        val method = permissionCallbackMethods[callbackName]\n        if (method != null) {\n          method.isAccessible = true\n          method(instance, invoke)\n        }\n      }\n    }\n  }\n\n  /**\n   * Saves permission states and rejects if permissions were not correctly defined in\n   * the AndroidManifest.xml file.\n   *\n   * @param permissions\n   * @return true if permissions were saved and defined correctly, false if not\n   */\n  private fun validatePermissions(\n    invoke: Invoke,\n    permissions: Map<String, Boolean>\n  ): Boolean {\n    val activity = manager.activity\n    val prefs =\n      activity.getSharedPreferences(\"PluginPermStates\", Activity.MODE_PRIVATE)\n    for ((permString, isGranted) in permissions) {\n      if (isGranted) {\n        // Permission granted. If previously denied, remove cached state\n        val state = prefs.getString(permString, null)\n        if (state != null) {\n          val editor: SharedPreferences.Editor = prefs.edit()\n          editor.remove(permString)\n          editor.apply()\n        }\n      } else {\n        val editor: SharedPreferences.Editor = prefs.edit()\n        if (ActivityCompat.shouldShowRequestPermissionRationale(\n            activity,\n            permString\n          )\n        ) {\n          // Permission denied, can prompt again with rationale\n          editor.putString(permString, PermissionState.PROMPT_WITH_RATIONALE.toString())\n        } else {\n          // Permission denied permanently, store this state for future reference\n          editor.putString(permString, PermissionState.DENIED.toString())\n        }\n        editor.apply()\n      }\n    }\n    val permStrings = permissions.keys.toTypedArray()\n    if (!PermissionHelper.hasDefinedPermissions(activity, permStrings)) {\n      val builder = StringBuilder()\n      builder.append(\"Missing the following permissions in AndroidManifest.xml:\\n\")\n      val missing = PermissionHelper.getUndefinedPermissions(activity, permStrings)\n      for (perm in missing) {\n        builder.append(\n          \"\"\"\n                $perm\n\n                \"\"\".trimIndent()\n        )\n      }\n      invoke.reject(builder.toString())\n      return false\n    }\n    return true\n  }\n\n  @Throws(\n    InvalidCommandException::class,\n    IllegalAccessException::class\n  )\n  fun invoke(invoke: Invoke) {\n    val methodMeta = commands[invoke.command]\n      ?: throw InvalidCommandException(\"No command \" + invoke.command + \" found for plugin \" + instance.javaClass.name)\n    methodMeta.method.invoke(instance, invoke)\n  }\n\n  private fun indexMethods() {\n    val methods = mutableListOf<Method>()\n    var pluginCursor: Class<*> = instance.javaClass\n    while (pluginCursor.name != Any::class.java.name) {\n      methods.addAll(listOf(*pluginCursor.declaredMethods))\n      pluginCursor = pluginCursor.superclass\n    }\n\n    for (method in methods) {\n      if (method.isAnnotationPresent(Command::class.java)) {\n        val command = method.getAnnotation(Command::class.java) ?: continue\n        val methodMeta = CommandData(method, command)\n        commands[method.name] = methodMeta\n      }\n\n      if (method.isAnnotationPresent(ActivityCallback::class.java)) {\n        startActivityCallbackMethods[method.name] = method\n      }\n\n      if (method.isAnnotationPresent(PermissionCallback::class.java)) {\n        permissionCallbackMethods[method.name] = method\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginManager.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.plugin\n\nimport android.app.PendingIntent\nimport android.content.res.Configuration\nimport android.content.Context\nimport android.content.Intent\nimport android.webkit.WebView\nimport androidx.activity.result.ActivityResult\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.IntentSenderRequest\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AppCompatActivity\nimport app.tauri.annotation.InvokeArg\nimport app.tauri.FsUtils\nimport app.tauri.JniMethod\nimport app.tauri.Logger\nimport com.fasterxml.jackson.annotation.JsonAutoDetect\nimport com.fasterxml.jackson.annotation.PropertyAccessor\nimport com.fasterxml.jackson.databind.DeserializationFeature\nimport com.fasterxml.jackson.databind.JsonNode\nimport com.fasterxml.jackson.databind.ObjectMapper\nimport com.fasterxml.jackson.databind.module.SimpleModule\nimport java.lang.reflect.InvocationTargetException\n\nclass PluginManager(val activity: AppCompatActivity) {\n  fun interface RequestPermissionsCallback {\n    fun onResult(permissions: Map<String, Boolean>)\n  }\n\n  fun interface ActivityResultCallback {\n    fun onResult(result: ActivityResult)\n  }\n\n  private val plugins: HashMap<String, PluginHandle> = HashMap()\n  private val startActivityForResultLauncher: ActivityResultLauncher<Intent>\n  private val startIntentSenderForResultLauncher: ActivityResultLauncher<IntentSenderRequest>\n  private val requestPermissionsLauncher: ActivityResultLauncher<Array<String>>\n  private var requestPermissionsCallback: RequestPermissionsCallback? = null\n  private var startActivityForResultCallback: ActivityResultCallback? = null\n  private var startIntentSenderForResultCallback: ActivityResultCallback? = null\n  private var jsonMapper: ObjectMapper\n\n  init {\n    startActivityForResultLauncher =\n      activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()\n      ) { result ->\n        if (startActivityForResultCallback != null) {\n          startActivityForResultCallback!!.onResult(result)\n        }\n      }\n\n    startIntentSenderForResultLauncher =\n      activity.registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()\n      ) { result ->\n        if (startIntentSenderForResultCallback != null) {\n          startIntentSenderForResultCallback!!.onResult(result)\n        }\n      }\n\n    requestPermissionsLauncher =\n      activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()\n      ) { result ->\n        if (requestPermissionsCallback != null) {\n          requestPermissionsCallback!!.onResult(result)\n        }\n      }\n\n    jsonMapper = ObjectMapper()\n      .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)\n      .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)\n      .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)\n\n    val channelDeserializer = ChannelDeserializer({ channelId, payload ->\n      sendChannelData(channelId, payload)\n    }, jsonMapper)\n    jsonMapper\n      .registerModule(SimpleModule().addDeserializer(Channel::class.java, channelDeserializer))\n  }\n\n  fun onNewIntent(intent: Intent) {\n    for (plugin in plugins.values) {\n      plugin.instance.onNewIntent(intent)\n    }\n  }\n\n  fun onPause() {\n    for (plugin in plugins.values) {\n      plugin.instance.onPause()\n    }\n  }\n\n  fun onResume() {\n    for (plugin in plugins.values) {\n      plugin.instance.onResume()\n    }\n  }\n\n  fun onRestart() {\n    for (plugin in plugins.values) {\n      plugin.instance.onRestart()\n    }\n  }\n\n  fun onStop() {\n    for (plugin in plugins.values) {\n      plugin.instance.onStop()\n    }\n  }\n\n  fun onDestroy() {\n    for (plugin in plugins.values) {\n      plugin.instance.onDestroy()\n    }\n  }\n\n  fun onConfigurationChanged(newConfig: Configuration) {\n    for (plugin in plugins.values) {\n      plugin.instance.onConfigurationChanged(newConfig)\n    }\n  }\n\n  fun startActivityForResult(intent: Intent, callback: ActivityResultCallback) {\n    startActivityForResultCallback = callback\n    startActivityForResultLauncher.launch(intent)\n  }\n\n  fun startIntentSenderForResult(intent: IntentSenderRequest, callback: ActivityResultCallback) {\n    startIntentSenderForResultCallback = callback\n    startIntentSenderForResultLauncher.launch(intent)\n  }\n\n  fun requestPermissions(\n    permissionStrings: Array<String>,\n    callback: RequestPermissionsCallback\n  ) {\n    requestPermissionsCallback = callback\n    requestPermissionsLauncher.launch(permissionStrings)\n  }\n\n  @JniMethod\n  fun onWebViewCreated(webView: WebView) {\n    for ((_, plugin) in plugins) {\n      if (!plugin.loaded) {\n        plugin.load(webView)\n      }\n    }\n  }\n\n  @JniMethod\n  fun load(webView: WebView?, name: String, plugin: Plugin, config: String) {\n    val handle = PluginHandle(this, name, plugin, config, jsonMapper)\n    plugins[name] = handle\n    if (webView != null) {\n      plugin.load(webView)\n    }\n  }\n\n  @JniMethod\n  fun runCommand(id: Int, pluginId: String, command: String, data: String) {\n    val successId = 0L\n    val errorId = 1L\n    val invoke = Invoke(id.toLong(), command, successId, errorId, { fn, result ->\n      var success: String? = null\n      var error: String? = null\n      if (fn == successId) {\n        success = result\n      } else {\n        error = result\n      }\n      handlePluginResponse(id, success, error)\n    }, data, jsonMapper)\n\n    dispatchPluginMessage(invoke, pluginId)\n  }\n\n  private fun dispatchPluginMessage(invoke: Invoke, pluginId: String) {\n    Logger.verbose(\n      Logger.tags(\"Plugin\"),\n      \"Tauri plugin: pluginId: $pluginId, command: ${invoke.command}\"\n    )\n\n    try {\n      val plugin = plugins[pluginId]\n      if (plugin == null) {\n        invoke.reject(\"Plugin $pluginId not initialized\")\n      } else {\n        plugins[pluginId]?.invoke(invoke)\n      }\n    } catch (e: Exception) {\n      var exception: Throwable = e\n      if (exception.message?.isEmpty() != false) {\n        if (e is InvocationTargetException) {\n          exception = e.targetException\n        }\n      }\n      invoke.reject(if (exception.message?.isEmpty() != false) { exception.toString() } else { exception.message })\n    }\n  }\n\n  companion object {\n    fun<T> loadConfig(context: Context, plugin: String, cls: Class<T>): T {\n      val tauriConfigJson = FsUtils.readAsset(context.assets, \"tauri.conf.json\")\n      val mapper = ObjectMapper()\n        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)\n      val config = mapper.readValue(tauriConfigJson, Config::class.java)\n      return mapper.readValue(config.plugins[plugin].toString(), cls)\n    }\n  }\n\n  private external fun handlePluginResponse(id: Int, success: String?, error: String?)\n  private external fun sendChannelData(id: Long, data: String)\n}\n\n@InvokeArg\ninternal class Config {\n  lateinit var plugins: Map<String, JsonNode>\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginMethodData.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.plugin\n\nimport app.tauri.annotation.Command\nimport java.lang.reflect.Method\n\nclass CommandData(\n  val method: Method, methodDecorator: Command\n) {\n\n  // The name of the method\n  val name: String = method.name\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/main/java/app/tauri/plugin/PluginResult.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri.plugin\n\nimport android.annotation.SuppressLint\nimport app.tauri.Logger\nimport java.text.DateFormat\nimport java.text.SimpleDateFormat\nimport java.util.*\n\nclass PluginResult @JvmOverloads constructor(json: JSObject? = JSObject()) {\n  private val json: JSObject\n\n  init {\n    this.json = json ?: JSObject()\n  }\n\n  fun put(name: String, value: Boolean): PluginResult {\n    return jsonPut(name, value)\n  }\n\n  fun put(name: String, value: Double): PluginResult {\n    return jsonPut(name, value)\n  }\n\n  fun put(name: String, value: Int): PluginResult {\n    return jsonPut(name, value)\n  }\n\n  fun put(name: String, value: Long): PluginResult {\n    return jsonPut(name, value)\n  }\n\n  /**\n   * Format a date as an ISO string\n   */\n  @SuppressLint(\"SimpleDateFormat\")\n  fun put(name: String, value: Date): PluginResult {\n    val tz: TimeZone = TimeZone.getTimeZone(\"UTC\")\n    val df: DateFormat = SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm'Z'\")\n    df.timeZone = tz\n    return jsonPut(name, df.format(value))\n  }\n\n  fun put(name: String, value: Any?): PluginResult {\n    return jsonPut(name, value)\n  }\n\n  fun put(name: String, value: PluginResult): PluginResult {\n    return jsonPut(name, value.json)\n  }\n\n  private fun jsonPut(name: String, value: Any?): PluginResult {\n    try {\n      json.put(name, value)\n    } catch (ex: Exception) {\n      Logger.error(Logger.tags(\"Plugin\"), \"\", ex)\n    }\n    return this\n  }\n\n  override fun toString(): String {\n    return json.toString()\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android/src/test/java/app/tauri/ExampleUnitTest.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage app.tauri\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/android-codegen/TauriActivity.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/* THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!! */\n\npackage {{package}}\n\nimport android.content.Intent\nimport android.content.res.Configuration\nimport app.tauri.plugin.PluginManager\n\nabstract class TauriActivity : WryActivity() {\n  var pluginManager: PluginManager = PluginManager(this)\n  override val handleBackNavigation: Boolean = false\n\n  override fun onNewIntent(intent: Intent) {\n    super.onNewIntent(intent)\n    pluginManager.onNewIntent(intent)\n  }\n\n  override fun onResume() {\n    super.onResume()\n    pluginManager.onResume()\n  }\n\n  override fun onPause() {\n    super.onPause()\n    pluginManager.onPause()\n  }\n\n  override fun onRestart() {\n    super.onRestart()\n    pluginManager.onRestart()\n  }\n\n  override fun onStop() {\n    super.onStop()\n    pluginManager.onStop()\n  }\n\n  override fun onDestroy() {\n    super.onDestroy()\n    pluginManager.onDestroy()\n  }\n\n  override fun onConfigurationChanged(newConfig: Configuration) {\n    super.onConfigurationChanged(newConfig)\n    pluginManager.onConfigurationChanged(newConfig)\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/ios-api/.gitignore",
    "content": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\nDerivedData/\n.swiftpm/config/registries.json\n.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata\n.netrc\nPackage.resolved\n"
  },
  {
    "path": "crates/tauri/mobile/ios-api/Package.swift",
    "content": "// swift-tools-version:5.3\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport PackageDescription\n\nlet package = Package(\n  name: \"Tauri\",\n  platforms: [\n    .macOS(.v10_13),\n    .iOS(.v11),\n  ],\n  products: [\n    // Products define the executables and libraries a package produces, and make them visible to other packages.\n    .library(\n      name: \"Tauri\",\n      type: .static,\n      targets: [\"Tauri\"])\n  ],\n  dependencies: [\n    // Dependencies declare other packages that this package depends on.\n    .package(name: \"SwiftRs\", url: \"https://github.com/Brendonovich/swift-rs\", from: \"1.0.0\")\n  ],\n  targets: [\n    // Targets are the basic building blocks of a package. A target can define a module or a test suite.\n    // Targets can depend on other targets in this package, and on products in packages this package depends on.\n    .target(\n      name: \"Tauri\",\n      dependencies: [\n        .byName(name: \"SwiftRs\")\n      ],\n      path: \"Sources\"\n    ),\n    .testTarget(\n      name: \"TauriTests\",\n      dependencies: [\"Tauri\"]\n    ),\n  ]\n)\n"
  },
  {
    "path": "crates/tauri/mobile/ios-api/README.md",
    "content": "# Tauri\n\nTauri iOS API.\n"
  },
  {
    "path": "crates/tauri/mobile/ios-api/Sources/Tauri/Channel.swift",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport Foundation\n\nlet CHANNEL_PREFIX = \"__CHANNEL__:\"\nlet channelDataKey = CodingUserInfoKey(rawValue: \"sendChannelData\")!\n\npublic class Channel: Decodable {\n  public let id: UInt64\n  let handler: (UInt64, String) -> Void\n\n  public required init(from decoder: Decoder) throws {\n    let container = try decoder.singleValueContainer()\n    let channelDef = try container.decode(String.self)\n\n    let components = channelDef.components(separatedBy: CHANNEL_PREFIX)\n    if components.count < 2 {\n      throw DecodingError.dataCorruptedError(\n        in: container,\n        debugDescription: \"Invalid channel definition from \\(channelDef)\"\n      )\n\n    }\n    guard let channelId = UInt64(components[1]) else {\n      throw DecodingError.dataCorruptedError(\n        in: container,\n        debugDescription: \"Invalid channel ID from \\(channelDef)\"\n      )\n    }\n\n    guard let handler = decoder.userInfo[channelDataKey] as? (UInt64, String) -> Void else {\n      throw DecodingError.dataCorruptedError(\n        in: container,\n        debugDescription: \"missing userInfo for Channel handler. This is a Tauri issue\"\n      )\n    }\n\n    self.id = channelId\n    self.handler = handler\n  }\n\n  func serialize(_ data: JsonValue) -> String {\n    do {\n      return try data.jsonRepresentation() ?? \"\\\"Failed to serialize payload\\\"\"\n    } catch {\n      return \"\\\"\\(error)\\\"\"\n    }\n  }\n\n  public func send(_ data: JsonObject) {\n    send(.dictionary(data))\n  }\n\n  public func send(_ data: JsonValue) {\n    handler(id, serialize(data))\n  }\n\n  public func send<T: Encodable>(_ data: T) throws {\n    let json = try JSONEncoder().encode(data)\n    handler(id, String(decoding: json, as: UTF8.self))\n  }\n\n}\n"
  },
  {
    "path": "crates/tauri/mobile/ios-api/Sources/Tauri/Invoke.swift",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport Foundation\nimport UIKit\n\n@objc public class Invoke: NSObject {\n  public let command: String\n  let callback: UInt64\n  let error: UInt64\n  let data: String\n  let sendResponse: (UInt64, String?) -> Void\n  let sendChannelData: (UInt64, String) -> Void\n\n  public init(\n    command: String, callback: UInt64, error: UInt64,\n    sendResponse: @escaping (UInt64, String?) -> Void,\n    sendChannelData: @escaping (UInt64, String) -> Void, data: String\n  ) {\n    self.command = command\n    self.callback = callback\n    self.error = error\n    self.data = data\n    self.sendResponse = sendResponse\n    self.sendChannelData = sendChannelData\n  }\n\n  public func getRawArgs() -> String {\n    return self.data\n  }\n\n  public func getArgs() throws -> JSObject {\n    let jsonData = self.data.data(using: .utf8)!\n    let data = try JSONSerialization.jsonObject(with: jsonData, options: [])\n    return JSTypes.coerceDictionaryToJSObject(\n      (data as! NSDictionary), formattingDatesAsStrings: true)!\n  }\n\n  public func parseArgs<T: Decodable>(_ type: T.Type) throws -> T {\n    let jsonData = self.data.data(using: .utf8)!\n    let decoder = JSONDecoder()\n    decoder.userInfo[channelDataKey] = sendChannelData\n    return try decoder.decode(type, from: jsonData)\n  }\n\n  func serialize(_ data: JsonValue) -> String {\n    do {\n      return try data.jsonRepresentation() ?? \"\\\"Failed to serialize payload\\\"\"\n    } catch {\n      return \"\\\"\\(error)\\\"\"\n    }\n  }\n\n  public func resolve() {\n    sendResponse(callback, nil)\n  }\n\n  public func resolve(_ data: JsonObject) {\n    resolve(.dictionary(data))\n  }\n\n  public func resolve(_ data: JsonValue) {\n    sendResponse(callback, serialize(data))\n  }\n\n  public func resolve<T: Encodable>(_ data: T) {\n    do {\n      let json = try JSONEncoder().encode(data)\n      sendResponse(callback, String(decoding: json, as: UTF8.self))\n    } catch {\n      sendResponse(self.error, \"\\\"\\(error)\\\"\")\n    }\n  }\n\n  public func reject(\n    _ message: String, code: String? = nil, error: Error? = nil, data: JsonValue? = nil\n  ) {\n    let payload: NSMutableDictionary = [\n      \"message\": message\n    ]\n\n    if let code = code {\n      payload[\"code\"] = code\n    }\n\n    if let error = error {\n      payload[\"error\"] = error\n    }\n\n    if let data = data {\n      switch data {\n      case .dictionary(let dict):\n        for entry in dict {\n          payload[entry.key] = entry.value\n        }\n      }\n    }\n\n    sendResponse(self.error, serialize(.dictionary(payload as! JsonObject)))\n  }\n\n  public func unimplemented() {\n    unimplemented(\"not implemented\")\n  }\n\n  public func unimplemented(_ message: String) {\n    reject(message)\n  }\n\n  public func unavailable() {\n    unavailable(\"not available\")\n  }\n\n  public func unavailable(_ message: String) {\n    reject(message)\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/ios-api/Sources/Tauri/JSTypes.swift",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport Foundation\n\n// declare our empty protocol, and conformance, for typing\npublic protocol JSValue {}\nextension String: JSValue {}\nextension Bool: JSValue {}\nextension Int: JSValue {}\nextension Float: JSValue {}\nextension Double: JSValue {}\nextension NSNumber: JSValue {}\nextension NSNull: JSValue {}\nextension Array: JSValue {}\nextension Date: JSValue {}\nextension Dictionary: JSValue where Key == String, Value == JSValue {}\n\n// convenience aliases\npublic typealias JSObject = [String: JSValue]\npublic typealias JSArray = [JSValue]\n\nextension Dictionary where Key == String, Value == JSValue {\n  public func getValue(_ key: String) -> JSValue? {\n    return self[key]\n  }\n\n  public func getString(_ key: String) -> String? {\n    return self[key] as? String\n  }\n\n  public func getBool(_ key: String) -> Bool? {\n    return self[key] as? Bool\n  }\n\n  public func getInt(_ key: String) -> Int? {\n    return self[key] as? Int\n  }\n\n  public func getFloat(_ key: String) -> Float? {\n    if let floatValue = self[key] as? Float {\n      return floatValue\n    } else if let doubleValue = self[key] as? Double {\n      return Float(doubleValue)\n    }\n    return nil\n  }\n\n  public func getDouble(_ key: String) -> Double? {\n    return self[key] as? Double\n  }\n\n  public func getArray(_ key: String) -> JSArray? {\n    return self[key] as? JSArray\n  }\n\n  public func getObject(_ key: String) -> JSObject? {\n    return self[key] as? JSObject\n  }\n}\n\n/*\n Simply casting objects from foundation class clusters (such as __NSArrayM)\n doesn't work with the JSValue protocol and will always fail. So we need to\n recursively and explicitly convert each value in the dictionary.\n */\npublic enum JSTypes {}\nextension JSTypes {\n  public static func coerceDictionaryToJSObject(\n    _ dictionary: NSDictionary?, formattingDatesAsStrings: Bool = false\n  ) -> JSObject? {\n    return coerceToJSValue(dictionary, formattingDates: formattingDatesAsStrings) as? JSObject\n  }\n\n  public static func coerceDictionaryToJSObject(\n    _ dictionary: [AnyHashable: Any]?, formattingDatesAsStrings: Bool = false\n  ) -> JSObject? {\n    return coerceToJSValue(dictionary, formattingDates: formattingDatesAsStrings) as? JSObject\n  }\n}\n\nprivate let dateStringFormatter = ISO8601DateFormatter()\n\n// We need a large switch statement because we have a lot of types.\n// swiftlint:disable:next cyclomatic_complexity\nprivate func coerceToJSValue(_ value: Any?, formattingDates: Bool) -> JSValue? {\n  guard let value = value else {\n    return nil\n  }\n  switch value {\n  case let stringValue as String:\n    return stringValue\n  case let numberValue as NSNumber:\n    return numberValue\n  case let boolValue as Bool:\n    return boolValue\n  case let intValue as Int:\n    return intValue\n  case let floatValue as Float:\n    return floatValue\n  case let doubleValue as Double:\n    return doubleValue\n  case let dateValue as Date:\n    if formattingDates {\n      return dateStringFormatter.string(from: dateValue)\n    }\n    return dateValue\n  case let nullValue as NSNull:\n    return nullValue\n  case let arrayValue as NSArray:\n    return arrayValue.compactMap { coerceToJSValue($0, formattingDates: formattingDates) }\n  case let dictionaryValue as NSDictionary:\n    let keys = dictionaryValue.allKeys.compactMap { $0 as? String }\n    var result: JSObject = [:]\n    for key in keys {\n      result[key] = coerceToJSValue(dictionaryValue[key], formattingDates: formattingDates)\n    }\n    return result\n  default:\n    return nil\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/ios-api/Sources/Tauri/JsonValue.swift",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport Foundation\n\npublic typealias JsonObject = [String: Any?]\n\npublic enum JsonValue {\n\tcase dictionary(JsonObject)\n\n\tenum SerializationError: Error {\n\t\tcase invalidObject\n\t}\n\n\tpublic func jsonRepresentation(includingFields: JsonObject? = nil) throws -> String? {\n\t\tswitch self {\n\t\tcase .dictionary(var dictionary):\n\t\t\tif let fields = includingFields {\n\t\t\t\tdictionary.merge(fields) { (current, _) in current }\n\t\t\t}\n\t\t\tdictionary = prepare(dictionary: dictionary)\n\t\t\tguard JSONSerialization.isValidJSONObject(dictionary) else {\n\t\t\t\tthrow SerializationError.invalidObject\n\t\t\t}\n\t\t\tlet data = try JSONSerialization.data(withJSONObject: dictionary, options: [])\n\t\t\treturn String(data: data, encoding: .utf8)\n\t\t}\n\t}\n\n\tprivate static let formatter = ISO8601DateFormatter()\n\n\tprivate func prepare(dictionary: JsonObject) -> JsonObject {\n\t\treturn dictionary.mapValues { (value) -> Any in\n\t\t\tif let date = value as? Date {\n\t\t\t\treturn JsonValue.formatter.string(from: date)\n\t\t\t} else if let aDictionary = value as? JsonObject {\n\t\t\t\treturn prepare(dictionary: aDictionary)\n\t\t\t} else if let anArray = value as? [Any] {\n\t\t\t\treturn prepare(array: anArray)\n\t\t\t}\n\t\t\treturn value\n\t\t}\n\t}\n\n\tprivate func prepare(array: [Any]) -> [Any] {\n\t\treturn array.map { (value) -> Any in\n\t\t\tif let date = value as? Date {\n\t\t\t\treturn JsonValue.formatter.string(from: date)\n\t\t\t} else if let aDictionary = value as? JsonObject {\n\t\t\t\treturn prepare(dictionary: aDictionary)\n\t\t\t} else if let anArray = value as? [Any] {\n\t\t\t\treturn prepare(array: anArray)\n\t\t\t}\n\t\t\treturn value\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "crates/tauri/mobile/ios-api/Sources/Tauri/Logger.swift",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport os.log\nimport UIKit\nimport Foundation\n\nclass StdoutRedirector {\n  private var originalStdout: Int32 = -1\n  private var originalStderr: Int32 = -1\n  private var stdoutPipe: [Int32] = [-1, -1]\n  private var stderrPipe: [Int32] = [-1, -1]\n  private var stdoutReadSource: DispatchSourceRead?\n  private var stderrReadSource: DispatchSourceRead?\n    \n  func start() {\n    originalStdout = dup(STDOUT_FILENO)\n    originalStderr = dup(STDERR_FILENO)\n        \n    guard Darwin.pipe(&stdoutPipe) == 0,\n      Darwin.pipe(&stderrPipe) == 0 else {\n      Logger.error(\"Failed to create stdout/stderr pipes\")\n      return\n    }\n        \n    dup2(stdoutPipe[1], STDOUT_FILENO)\n    dup2(stderrPipe[1], STDERR_FILENO)\n    close(stdoutPipe[1])\n    close(stderrPipe[1])\n        \n    stdoutReadSource = createReader(\n      readPipe: stdoutPipe[0],\n      writeToOriginal: originalStdout,\n      label: \"stdout\"\n    )\n        \n    stderrReadSource = createReader(\n      readPipe: stderrPipe[0],\n      writeToOriginal: originalStderr,\n      label: \"stderr\"\n    )\n  }\n    \n  private func createReader(\n    readPipe: Int32,\n    writeToOriginal: Int32,\n    label: String\n  ) -> DispatchSourceRead {\n    let source = DispatchSource.makeReadSource(\n      fileDescriptor: readPipe,\n      queue: .global(qos: .utility)\n    )\n        \n    source.setEventHandler {\n      let bufferSize = 4096\n      var buffer = [UInt8](repeating: 0, count: bufferSize)\n      let bytesRead = read(readPipe, &buffer, bufferSize)\n            \n      if bytesRead > 0 {\n        let output = String(\n          bytes: buffer[0..<bytesRead],\n          encoding: .utf8\n        ) ?? \"\"\n                \n        let trimmed = output.trimmingCharacters(in: .newlines)\n        if !trimmed.isEmpty {\n          // we're sending stderr to oslog, so we need to avoid recursive calls\n          if trimmed.hasPrefix(\"OSLOG-\") {\n            // make sure the system can parse the oslogs\n            write(writeToOriginal, &buffer, bytesRead)\n          } else {\n            Logger.info(\"[\\(label)] \\(trimmed)\")\n          }\n        }\n      }\n    } \n        \n    source.setCancelHandler {\n      close(readPipe)\n    }\n        \n    source.resume()\n    return source\n  }\n}\n\n/// Wrapper class for os_log function\npublic class Logger {\n  private static var _enabled = false\n  public static var enabled: Bool {\n    get {\n      #if DEBUG\n      return true\n      #else\n      return _enabled\n      #endif\n    }\n    set {\n      Logger._enabled = newValue\n    }\n  }\n\n  static func log(_ items: [Any], category: String, type: OSLogType) {\n    if Logger.enabled {\n      var message = \"\"\n      let last = items.count - 1\n      for (index, item) in items.enumerated() {\n        message += \"\\(item)\"\n        if index != last {\n          message += \" \"\n        }\n      }\n\n      let log = OSLog(subsystem: Bundle.main.bundleIdentifier ?? \"-\", category: category)\n      os_log(\"%{public}@\", log: log, type: type, String(message.prefix(4068)))\n    }\n  }\n\n  public static func debug(_ items: Any..., category: String = \"app\") {\n    #if DEBUG\n    Logger.log(items, category: category, type: OSLogType.default)\n    #else\n    Logger.log(items, category: category, type: OSLogType.debug)\n    #endif\n  }\n\n  public static func info(_ items: Any..., category: String = \"app\") {\n    #if DEBUG\n    Logger.log(items, category: category, type: OSLogType.default)\n    #else\n    Logger.log(items, category: category, type: OSLogType.info)\n    #endif\n  }\n\n  public static func error(_ items: Any..., category: String = \"app\") {\n    Logger.log(items, category: category, type: OSLogType.error)\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/ios-api/Sources/Tauri/Plugin/Plugin.swift",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport WebKit\nimport os.log\n\nstruct RegisterListenerArgs: Decodable {\n  let event: String\n  let handler: Channel\n}\n\nstruct RemoveListenerArgs: Decodable {\n  let event: String\n  let channelId: UInt64\n}\n\nopen class Plugin: NSObject {\n  public let manager: PluginManager = PluginManager.shared\n  var config: String = \"{}\"\n  private var listeners = [String: [Channel]]()\n\n  internal func setConfig(_ config: String) {\n    self.config = config\n  }\n\n  public func parseConfig<T: Decodable>(_ type: T.Type) throws -> T {\n    let jsonData = self.config.data(using: .utf8)!\n    let decoder = JSONDecoder()\n    return try decoder.decode(type, from: jsonData)\n  }\n\n  @objc open func load(webview: WKWebView) {}\n\n  @objc open func checkPermissions(_ invoke: Invoke) {\n    invoke.resolve()\n  }\n\n  @objc open func requestPermissions(_ invoke: Invoke) {\n    invoke.resolve()\n  }\n\n  public func trigger(_ event: String, data: JSObject) {\n    if let eventListeners = listeners[event] {\n      for channel in eventListeners {\n        channel.send(data)\n      }\n    }\n  }\n\n  public func trigger<T: Encodable>(_ event: String, data: T) throws {\n    if let eventListeners = listeners[event] {\n      for channel in eventListeners {\n        try channel.send(data)\n      }\n    }\n  }\n\n  @objc func registerListener(_ invoke: Invoke) throws {\n    let args = try invoke.parseArgs(RegisterListenerArgs.self)\n\n    if var eventListeners = listeners[args.event] {\n      eventListeners.append(args.handler)\n      listeners[args.event] = eventListeners\n    } else {\n      listeners[args.event] = [args.handler]\n    }\n\n    invoke.resolve()\n  }\n\n  @objc func removeListener(_ invoke: Invoke) throws {\n    let args = try invoke.parseArgs(RemoveListenerArgs.self)\n\n    if let eventListeners = listeners[args.event] {\n\n      listeners[args.event] = eventListeners.filter { $0.id != args.channelId }\n    }\n\n    invoke.resolve()\n  }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/ios-api/Sources/Tauri/Tauri.swift",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport Foundation\nimport SwiftRs\nimport UIKit\nimport WebKit\nimport os.log\n\nclass PluginHandle {\n  var instance: Plugin\n  var loaded = false\n\n  init(plugin: Plugin) {\n    instance = plugin\n  }\n}\n\npublic class PluginManager {\n  static let shared: PluginManager = PluginManager()\n  public var viewController: UIViewController?\n  var plugins: [String: PluginHandle] = [:]\n  var ipcDispatchQueue = DispatchQueue(label: \"ipc\")\n  public var isSimEnvironment: Bool {\n    #if targetEnvironment(simulator)\n      return true\n    #else\n      return false\n    #endif\n  }\n\n  public func assetUrl(fromLocalURL url: URL?) -> URL? {\n    guard let inputURL = url else {\n      return nil\n    }\n\n    return URL(string: \"asset://localhost\")!.appendingPathComponent(inputURL.path)\n  }\n\n  func onWebviewCreated(_ webview: WKWebView) {\n    for (_, handle) in plugins {\n      if !handle.loaded {\n        handle.instance.load(webview: webview)\n      }\n    }\n  }\n\n  func load<P: Plugin>(name: String, plugin: P, config: String, webview: WKWebView?) {\n    plugin.setConfig(config)\n    let handle = PluginHandle(plugin: plugin)\n    if let webview = webview {\n      handle.instance.load(webview: webview)\n      handle.loaded = true\n    }\n    plugins[name] = handle\n  }\n\n  func invoke(name: String, invoke: Invoke) {\n    if let plugin = plugins[name] {\n      ipcDispatchQueue.async {\n        let selectorWithCompletionHandler = Selector((\"\\(invoke.command):completionHandler:\"))\n        let selectorWithThrows = Selector((\"\\(invoke.command):error:\"))\n\n        if plugin.instance.responds(to: selectorWithCompletionHandler) {\n          let completion: @convention(block) (NSError?) -> Void = { error in\n            if let error = error {\n              invoke.reject(\"\\(error)\")\n            }\n          }\n\n          let blockObj: AnyObject = unsafeBitCast(completion, to: AnyObject.self)\n          let imp = plugin.instance.method(for: selectorWithCompletionHandler)\n\n          typealias Fn = @convention(c) (AnyObject, Selector, Invoke, AnyObject) -> Void\n          let fn = unsafeBitCast(imp, to: Fn.self)\n          fn(plugin.instance, selectorWithCompletionHandler, invoke, blockObj)\n        } else if plugin.instance.responds(to: selectorWithThrows) {\n          var error: NSError? = nil\n          withUnsafeMutablePointer(to: &error) {\n            let methodIMP: IMP! = plugin.instance.method(for: selectorWithThrows)\n            unsafeBitCast(\n              methodIMP, to: (@convention(c) (Any?, Selector, Invoke, OpaquePointer) -> Void).self)(\n                plugin.instance, selectorWithThrows, invoke, OpaquePointer($0))\n          }\n          if let error = error {\n            invoke.reject(\"\\(error)\")\n            // TODO: app crashes without this leak\n            let _ = Unmanaged.passRetained(error)\n          }\n        } else {\n          let selector = Selector((\"\\(invoke.command):\"))\n          if plugin.instance.responds(to: selector) {\n            plugin.instance.perform(selector, with: invoke)\n          } else {\n            invoke.reject(\"No command \\(invoke.command) found for plugin \\(name)\")\n          }\n        }\n      }\n    } else {\n      invoke.reject(\"Plugin \\(name) not initialized\")\n    }\n  }\n}\n\nextension PluginManager: NSCopying {\n  public func copy(with zone: NSZone? = nil) -> Any {\n    return self\n  }\n}\n\nprivate var stdoutRedirector: StdoutRedirector?\n\n@_cdecl(\"log_stdout\")\nfunc logStdout() {\n  stdoutRedirector = StdoutRedirector()\n  stdoutRedirector!.start()\n}\n\n@_cdecl(\"register_plugin\")\nfunc registerPlugin(name: SRString, plugin: NSObject, config: SRString, webview: WKWebView?) {\n  PluginManager.shared.load(\n    name: name.toString(),\n    plugin: plugin as! Plugin,\n    config: config.toString(),\n    webview: webview\n  )\n}\n\n@_cdecl(\"on_webview_created\")\nfunc onWebviewCreated(webview: WKWebView, viewController: UIViewController) {\n  PluginManager.shared.viewController = viewController\n  PluginManager.shared.onWebviewCreated(webview)\n}\n\n@_cdecl(\"run_plugin_command\")\nfunc runCommand(\n  id: Int,\n  name: SRString,\n  command: SRString,\n  data: SRString,\n  callback: @escaping @convention(c) (Int, Bool, UnsafePointer<CChar>) -> Void,\n  sendChannelData: @escaping @convention(c) (UInt64, UnsafePointer<CChar>) -> Void\n) {\n  let callbackId: UInt64 = 0\n  let errorId: UInt64 = 1\n  let invoke = Invoke(\n    command: command.toString(), callback: callbackId, error: errorId,\n    sendResponse: { (fn: UInt64, payload: String?) -> Void in\n      let success = fn == callbackId\n      callback(id, success, payload ?? \"null\")\n    },\n    sendChannelData: { (id: UInt64, payload: String) -> Void in\n      sendChannelData(id, payload)\n    }, data: data.toString())\n  PluginManager.shared.invoke(name: name.toString(), invoke: invoke)\n}\n"
  },
  {
    "path": "crates/tauri/mobile/ios-api/Sources/Tauri/UiUtils.swift",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport UIKit\n\npublic class UIUtils {\n    public static func centerPopover(rootViewController: UIViewController?, popoverController: UIViewController) {\n        if let viewController = rootViewController {\n            popoverController.popoverPresentationController?.sourceRect = CGRect(x: viewController.view.center.x, y: viewController.view.center.y, width: 0, height: 0)\n            popoverController.popoverPresentationController?.sourceView = viewController.view\n            popoverController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up\n        }\n    }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/ios-api/Tests/TauriTests/TauriTests.swift",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport XCTest\n@testable import Tauri\n\nfinal class TauriTests: XCTestCase {\n    func testExample() throws {\n        // This is an example of a functional test case.\n        // Use XCTAssert and related functions to verify your tests produce the correct\n        // results.\n        XCTAssertEqual(Tauri().text, \"Hello, World!\")\n    }\n}\n"
  },
  {
    "path": "crates/tauri/mobile/proguard-tauri.pro",
    "content": "# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n\n-keep class $PACKAGE.TauriActivity {\n  public app.tauri.plugin.PluginManager getPluginManager();\n}\n"
  },
  {
    "path": "crates/tauri/permissions/app/autogenerated/reference.md",
    "content": "## Default Permission\n\nDefault permissions for the plugin.\n\n#### This default permission set includes the following:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n\n## Permission Table\n\n<table>\n<tr>\n<th>Identifier</th>\n<th>Description</th>\n</tr>\n\n\n<tr>\n<td>\n\n`core:app:allow-app-hide`\n\n</td>\n<td>\n\nEnables the app_hide command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-app-hide`\n\n</td>\n<td>\n\nDenies the app_hide command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-app-show`\n\n</td>\n<td>\n\nEnables the app_show command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-app-show`\n\n</td>\n<td>\n\nDenies the app_show command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-bundle-type`\n\n</td>\n<td>\n\nEnables the bundle_type command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-bundle-type`\n\n</td>\n<td>\n\nDenies the bundle_type command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-default-window-icon`\n\n</td>\n<td>\n\nEnables the default_window_icon command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-default-window-icon`\n\n</td>\n<td>\n\nDenies the default_window_icon command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-fetch-data-store-identifiers`\n\n</td>\n<td>\n\nEnables the fetch_data_store_identifiers command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-fetch-data-store-identifiers`\n\n</td>\n<td>\n\nDenies the fetch_data_store_identifiers command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-identifier`\n\n</td>\n<td>\n\nEnables the identifier command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-identifier`\n\n</td>\n<td>\n\nDenies the identifier command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-name`\n\n</td>\n<td>\n\nEnables the name command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-name`\n\n</td>\n<td>\n\nDenies the name command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-register-listener`\n\n</td>\n<td>\n\nEnables the register_listener command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-register-listener`\n\n</td>\n<td>\n\nDenies the register_listener command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-remove-data-store`\n\n</td>\n<td>\n\nEnables the remove_data_store command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-remove-data-store`\n\n</td>\n<td>\n\nDenies the remove_data_store command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-remove-listener`\n\n</td>\n<td>\n\nEnables the remove_listener command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-remove-listener`\n\n</td>\n<td>\n\nDenies the remove_listener command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-set-app-theme`\n\n</td>\n<td>\n\nEnables the set_app_theme command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-set-app-theme`\n\n</td>\n<td>\n\nDenies the set_app_theme command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-set-dock-visibility`\n\n</td>\n<td>\n\nEnables the set_dock_visibility command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-set-dock-visibility`\n\n</td>\n<td>\n\nDenies the set_dock_visibility command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-tauri-version`\n\n</td>\n<td>\n\nEnables the tauri_version command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-tauri-version`\n\n</td>\n<td>\n\nDenies the tauri_version command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:allow-version`\n\n</td>\n<td>\n\nEnables the version command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:app:deny-version`\n\n</td>\n<td>\n\nDenies the version command without any pre-configured scope.\n\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "crates/tauri/permissions/event/autogenerated/reference.md",
    "content": "## Default Permission\n\nDefault permissions for the plugin, which enables all commands.\n\n#### This default permission set includes the following:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`\n\n## Permission Table\n\n<table>\n<tr>\n<th>Identifier</th>\n<th>Description</th>\n</tr>\n\n\n<tr>\n<td>\n\n`core:event:allow-emit`\n\n</td>\n<td>\n\nEnables the emit command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:event:deny-emit`\n\n</td>\n<td>\n\nDenies the emit command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:event:allow-emit-to`\n\n</td>\n<td>\n\nEnables the emit_to command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:event:deny-emit-to`\n\n</td>\n<td>\n\nDenies the emit_to command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:event:allow-listen`\n\n</td>\n<td>\n\nEnables the listen command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:event:deny-listen`\n\n</td>\n<td>\n\nDenies the listen command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:event:allow-unlisten`\n\n</td>\n<td>\n\nEnables the unlisten command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:event:deny-unlisten`\n\n</td>\n<td>\n\nDenies the unlisten command without any pre-configured scope.\n\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "crates/tauri/permissions/image/autogenerated/reference.md",
    "content": "## Default Permission\n\nDefault permissions for the plugin, which enables all commands.\n\n#### This default permission set includes the following:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`\n\n## Permission Table\n\n<table>\n<tr>\n<th>Identifier</th>\n<th>Description</th>\n</tr>\n\n\n<tr>\n<td>\n\n`core:image:allow-from-bytes`\n\n</td>\n<td>\n\nEnables the from_bytes command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:image:deny-from-bytes`\n\n</td>\n<td>\n\nDenies the from_bytes command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:image:allow-from-path`\n\n</td>\n<td>\n\nEnables the from_path command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:image:deny-from-path`\n\n</td>\n<td>\n\nDenies the from_path command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:image:allow-new`\n\n</td>\n<td>\n\nEnables the new command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:image:deny-new`\n\n</td>\n<td>\n\nDenies the new command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:image:allow-rgba`\n\n</td>\n<td>\n\nEnables the rgba command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:image:deny-rgba`\n\n</td>\n<td>\n\nDenies the rgba command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:image:allow-size`\n\n</td>\n<td>\n\nEnables the size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:image:deny-size`\n\n</td>\n<td>\n\nDenies the size command without any pre-configured scope.\n\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "crates/tauri/permissions/menu/autogenerated/reference.md",
    "content": "## Default Permission\n\nDefault permissions for the plugin, which enables all commands.\n\n#### This default permission set includes the following:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`\n\n## Permission Table\n\n<table>\n<tr>\n<th>Identifier</th>\n<th>Description</th>\n</tr>\n\n\n<tr>\n<td>\n\n`core:menu:allow-append`\n\n</td>\n<td>\n\nEnables the append command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-append`\n\n</td>\n<td>\n\nDenies the append command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-create-default`\n\n</td>\n<td>\n\nEnables the create_default command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-create-default`\n\n</td>\n<td>\n\nDenies the create_default command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-get`\n\n</td>\n<td>\n\nEnables the get command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-get`\n\n</td>\n<td>\n\nDenies the get command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-insert`\n\n</td>\n<td>\n\nEnables the insert command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-insert`\n\n</td>\n<td>\n\nDenies the insert command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-is-checked`\n\n</td>\n<td>\n\nEnables the is_checked command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-is-checked`\n\n</td>\n<td>\n\nDenies the is_checked command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-is-enabled`\n\n</td>\n<td>\n\nEnables the is_enabled command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-is-enabled`\n\n</td>\n<td>\n\nDenies the is_enabled command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-items`\n\n</td>\n<td>\n\nEnables the items command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-items`\n\n</td>\n<td>\n\nDenies the items command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-new`\n\n</td>\n<td>\n\nEnables the new command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-new`\n\n</td>\n<td>\n\nDenies the new command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-popup`\n\n</td>\n<td>\n\nEnables the popup command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-popup`\n\n</td>\n<td>\n\nDenies the popup command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-prepend`\n\n</td>\n<td>\n\nEnables the prepend command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-prepend`\n\n</td>\n<td>\n\nDenies the prepend command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-remove`\n\n</td>\n<td>\n\nEnables the remove command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-remove`\n\n</td>\n<td>\n\nDenies the remove command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-remove-at`\n\n</td>\n<td>\n\nEnables the remove_at command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-remove-at`\n\n</td>\n<td>\n\nDenies the remove_at command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-set-accelerator`\n\n</td>\n<td>\n\nEnables the set_accelerator command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-set-accelerator`\n\n</td>\n<td>\n\nDenies the set_accelerator command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-set-as-app-menu`\n\n</td>\n<td>\n\nEnables the set_as_app_menu command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-set-as-app-menu`\n\n</td>\n<td>\n\nDenies the set_as_app_menu command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-set-as-help-menu-for-nsapp`\n\n</td>\n<td>\n\nEnables the set_as_help_menu_for_nsapp command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-set-as-help-menu-for-nsapp`\n\n</td>\n<td>\n\nDenies the set_as_help_menu_for_nsapp command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-set-as-window-menu`\n\n</td>\n<td>\n\nEnables the set_as_window_menu command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-set-as-window-menu`\n\n</td>\n<td>\n\nDenies the set_as_window_menu command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-set-as-windows-menu-for-nsapp`\n\n</td>\n<td>\n\nEnables the set_as_windows_menu_for_nsapp command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-set-as-windows-menu-for-nsapp`\n\n</td>\n<td>\n\nDenies the set_as_windows_menu_for_nsapp command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-set-checked`\n\n</td>\n<td>\n\nEnables the set_checked command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-set-checked`\n\n</td>\n<td>\n\nDenies the set_checked command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-set-enabled`\n\n</td>\n<td>\n\nEnables the set_enabled command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-set-enabled`\n\n</td>\n<td>\n\nDenies the set_enabled command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-set-icon`\n\n</td>\n<td>\n\nEnables the set_icon command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-set-icon`\n\n</td>\n<td>\n\nDenies the set_icon command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-set-text`\n\n</td>\n<td>\n\nEnables the set_text command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-set-text`\n\n</td>\n<td>\n\nDenies the set_text command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:allow-text`\n\n</td>\n<td>\n\nEnables the text command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:menu:deny-text`\n\n</td>\n<td>\n\nDenies the text command without any pre-configured scope.\n\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "crates/tauri/permissions/path/autogenerated/reference.md",
    "content": "## Default Permission\n\nDefault permissions for the plugin, which enables all commands.\n\n#### This default permission set includes the following:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`\n\n## Permission Table\n\n<table>\n<tr>\n<th>Identifier</th>\n<th>Description</th>\n</tr>\n\n\n<tr>\n<td>\n\n`core:path:allow-basename`\n\n</td>\n<td>\n\nEnables the basename command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:deny-basename`\n\n</td>\n<td>\n\nDenies the basename command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:allow-dirname`\n\n</td>\n<td>\n\nEnables the dirname command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:deny-dirname`\n\n</td>\n<td>\n\nDenies the dirname command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:allow-extname`\n\n</td>\n<td>\n\nEnables the extname command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:deny-extname`\n\n</td>\n<td>\n\nDenies the extname command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:allow-is-absolute`\n\n</td>\n<td>\n\nEnables the is_absolute command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:deny-is-absolute`\n\n</td>\n<td>\n\nDenies the is_absolute command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:allow-join`\n\n</td>\n<td>\n\nEnables the join command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:deny-join`\n\n</td>\n<td>\n\nDenies the join command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:allow-normalize`\n\n</td>\n<td>\n\nEnables the normalize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:deny-normalize`\n\n</td>\n<td>\n\nDenies the normalize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:allow-resolve`\n\n</td>\n<td>\n\nEnables the resolve command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:deny-resolve`\n\n</td>\n<td>\n\nDenies the resolve command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:allow-resolve-directory`\n\n</td>\n<td>\n\nEnables the resolve_directory command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:path:deny-resolve-directory`\n\n</td>\n<td>\n\nDenies the resolve_directory command without any pre-configured scope.\n\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "crates/tauri/permissions/resources/autogenerated/reference.md",
    "content": "## Default Permission\n\nDefault permissions for the plugin, which enables all commands.\n\n#### This default permission set includes the following:\n\n- `allow-close`\n\n## Permission Table\n\n<table>\n<tr>\n<th>Identifier</th>\n<th>Description</th>\n</tr>\n\n\n<tr>\n<td>\n\n`core:resources:allow-close`\n\n</td>\n<td>\n\nEnables the close command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:resources:deny-close`\n\n</td>\n<td>\n\nDenies the close command without any pre-configured scope.\n\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "crates/tauri/permissions/tray/autogenerated/reference.md",
    "content": "## Default Permission\n\nDefault permissions for the plugin, which enables all commands.\n\n#### This default permission set includes the following:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`\n\n## Permission Table\n\n<table>\n<tr>\n<th>Identifier</th>\n<th>Description</th>\n</tr>\n\n\n<tr>\n<td>\n\n`core:tray:allow-get-by-id`\n\n</td>\n<td>\n\nEnables the get_by_id command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:deny-get-by-id`\n\n</td>\n<td>\n\nDenies the get_by_id command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:allow-new`\n\n</td>\n<td>\n\nEnables the new command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:deny-new`\n\n</td>\n<td>\n\nDenies the new command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:allow-remove-by-id`\n\n</td>\n<td>\n\nEnables the remove_by_id command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:deny-remove-by-id`\n\n</td>\n<td>\n\nDenies the remove_by_id command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:allow-set-icon`\n\n</td>\n<td>\n\nEnables the set_icon command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:deny-set-icon`\n\n</td>\n<td>\n\nDenies the set_icon command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:allow-set-icon-as-template`\n\n</td>\n<td>\n\nEnables the set_icon_as_template command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:deny-set-icon-as-template`\n\n</td>\n<td>\n\nDenies the set_icon_as_template command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:allow-set-menu`\n\n</td>\n<td>\n\nEnables the set_menu command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:deny-set-menu`\n\n</td>\n<td>\n\nDenies the set_menu command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:allow-set-show-menu-on-left-click`\n\n</td>\n<td>\n\nEnables the set_show_menu_on_left_click command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:deny-set-show-menu-on-left-click`\n\n</td>\n<td>\n\nDenies the set_show_menu_on_left_click command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:allow-set-temp-dir-path`\n\n</td>\n<td>\n\nEnables the set_temp_dir_path command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:deny-set-temp-dir-path`\n\n</td>\n<td>\n\nDenies the set_temp_dir_path command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:allow-set-title`\n\n</td>\n<td>\n\nEnables the set_title command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:deny-set-title`\n\n</td>\n<td>\n\nDenies the set_title command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:allow-set-tooltip`\n\n</td>\n<td>\n\nEnables the set_tooltip command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:deny-set-tooltip`\n\n</td>\n<td>\n\nDenies the set_tooltip command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:allow-set-visible`\n\n</td>\n<td>\n\nEnables the set_visible command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:tray:deny-set-visible`\n\n</td>\n<td>\n\nDenies the set_visible command without any pre-configured scope.\n\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "crates/tauri/permissions/webview/autogenerated/reference.md",
    "content": "## Default Permission\n\nDefault permissions for the plugin.\n\n#### This default permission set includes the following:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`\n\n## Permission Table\n\n<table>\n<tr>\n<th>Identifier</th>\n<th>Description</th>\n</tr>\n\n\n<tr>\n<td>\n\n`core:webview:allow-clear-all-browsing-data`\n\n</td>\n<td>\n\nEnables the clear_all_browsing_data command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-clear-all-browsing-data`\n\n</td>\n<td>\n\nDenies the clear_all_browsing_data command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-create-webview`\n\n</td>\n<td>\n\nEnables the create_webview command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-create-webview`\n\n</td>\n<td>\n\nDenies the create_webview command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-create-webview-window`\n\n</td>\n<td>\n\nEnables the create_webview_window command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-create-webview-window`\n\n</td>\n<td>\n\nDenies the create_webview_window command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-get-all-webviews`\n\n</td>\n<td>\n\nEnables the get_all_webviews command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-get-all-webviews`\n\n</td>\n<td>\n\nDenies the get_all_webviews command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-internal-toggle-devtools`\n\n</td>\n<td>\n\nEnables the internal_toggle_devtools command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-internal-toggle-devtools`\n\n</td>\n<td>\n\nDenies the internal_toggle_devtools command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-print`\n\n</td>\n<td>\n\nEnables the print command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-print`\n\n</td>\n<td>\n\nDenies the print command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-reparent`\n\n</td>\n<td>\n\nEnables the reparent command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-reparent`\n\n</td>\n<td>\n\nDenies the reparent command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-set-webview-auto-resize`\n\n</td>\n<td>\n\nEnables the set_webview_auto_resize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-set-webview-auto-resize`\n\n</td>\n<td>\n\nDenies the set_webview_auto_resize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-set-webview-background-color`\n\n</td>\n<td>\n\nEnables the set_webview_background_color command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-set-webview-background-color`\n\n</td>\n<td>\n\nDenies the set_webview_background_color command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-set-webview-focus`\n\n</td>\n<td>\n\nEnables the set_webview_focus command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-set-webview-focus`\n\n</td>\n<td>\n\nDenies the set_webview_focus command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-set-webview-position`\n\n</td>\n<td>\n\nEnables the set_webview_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-set-webview-position`\n\n</td>\n<td>\n\nDenies the set_webview_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-set-webview-size`\n\n</td>\n<td>\n\nEnables the set_webview_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-set-webview-size`\n\n</td>\n<td>\n\nDenies the set_webview_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-set-webview-zoom`\n\n</td>\n<td>\n\nEnables the set_webview_zoom command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-set-webview-zoom`\n\n</td>\n<td>\n\nDenies the set_webview_zoom command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-webview-close`\n\n</td>\n<td>\n\nEnables the webview_close command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-webview-close`\n\n</td>\n<td>\n\nDenies the webview_close command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-webview-hide`\n\n</td>\n<td>\n\nEnables the webview_hide command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-webview-hide`\n\n</td>\n<td>\n\nDenies the webview_hide command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-webview-position`\n\n</td>\n<td>\n\nEnables the webview_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-webview-position`\n\n</td>\n<td>\n\nDenies the webview_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-webview-show`\n\n</td>\n<td>\n\nEnables the webview_show command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-webview-show`\n\n</td>\n<td>\n\nDenies the webview_show command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:allow-webview-size`\n\n</td>\n<td>\n\nEnables the webview_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:webview:deny-webview-size`\n\n</td>\n<td>\n\nDenies the webview_size command without any pre-configured scope.\n\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "crates/tauri/permissions/window/autogenerated/reference.md",
    "content": "## Default Permission\n\nDefault permissions for the plugin.\n\n#### This default permission set includes the following:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`\n\n## Permission Table\n\n<table>\n<tr>\n<th>Identifier</th>\n<th>Description</th>\n</tr>\n\n\n<tr>\n<td>\n\n`core:window:allow-available-monitors`\n\n</td>\n<td>\n\nEnables the available_monitors command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-available-monitors`\n\n</td>\n<td>\n\nDenies the available_monitors command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-center`\n\n</td>\n<td>\n\nEnables the center command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-center`\n\n</td>\n<td>\n\nDenies the center command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-close`\n\n</td>\n<td>\n\nEnables the close command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-close`\n\n</td>\n<td>\n\nDenies the close command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-create`\n\n</td>\n<td>\n\nEnables the create command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-create`\n\n</td>\n<td>\n\nDenies the create command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-current-monitor`\n\n</td>\n<td>\n\nEnables the current_monitor command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-current-monitor`\n\n</td>\n<td>\n\nDenies the current_monitor command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-cursor-position`\n\n</td>\n<td>\n\nEnables the cursor_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-cursor-position`\n\n</td>\n<td>\n\nDenies the cursor_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-destroy`\n\n</td>\n<td>\n\nEnables the destroy command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-destroy`\n\n</td>\n<td>\n\nDenies the destroy command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-get-all-windows`\n\n</td>\n<td>\n\nEnables the get_all_windows command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-get-all-windows`\n\n</td>\n<td>\n\nDenies the get_all_windows command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-hide`\n\n</td>\n<td>\n\nEnables the hide command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-hide`\n\n</td>\n<td>\n\nDenies the hide command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-inner-position`\n\n</td>\n<td>\n\nEnables the inner_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-inner-position`\n\n</td>\n<td>\n\nDenies the inner_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-inner-size`\n\n</td>\n<td>\n\nEnables the inner_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-inner-size`\n\n</td>\n<td>\n\nDenies the inner_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-internal-toggle-maximize`\n\n</td>\n<td>\n\nEnables the internal_toggle_maximize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-internal-toggle-maximize`\n\n</td>\n<td>\n\nDenies the internal_toggle_maximize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-is-always-on-top`\n\n</td>\n<td>\n\nEnables the is_always_on_top command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-is-always-on-top`\n\n</td>\n<td>\n\nDenies the is_always_on_top command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-is-closable`\n\n</td>\n<td>\n\nEnables the is_closable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-is-closable`\n\n</td>\n<td>\n\nDenies the is_closable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-is-decorated`\n\n</td>\n<td>\n\nEnables the is_decorated command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-is-decorated`\n\n</td>\n<td>\n\nDenies the is_decorated command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-is-enabled`\n\n</td>\n<td>\n\nEnables the is_enabled command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-is-enabled`\n\n</td>\n<td>\n\nDenies the is_enabled command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-is-focused`\n\n</td>\n<td>\n\nEnables the is_focused command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-is-focused`\n\n</td>\n<td>\n\nDenies the is_focused command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-is-fullscreen`\n\n</td>\n<td>\n\nEnables the is_fullscreen command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-is-fullscreen`\n\n</td>\n<td>\n\nDenies the is_fullscreen command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-is-maximizable`\n\n</td>\n<td>\n\nEnables the is_maximizable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-is-maximizable`\n\n</td>\n<td>\n\nDenies the is_maximizable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-is-maximized`\n\n</td>\n<td>\n\nEnables the is_maximized command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-is-maximized`\n\n</td>\n<td>\n\nDenies the is_maximized command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-is-minimizable`\n\n</td>\n<td>\n\nEnables the is_minimizable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-is-minimizable`\n\n</td>\n<td>\n\nDenies the is_minimizable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-is-minimized`\n\n</td>\n<td>\n\nEnables the is_minimized command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-is-minimized`\n\n</td>\n<td>\n\nDenies the is_minimized command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-is-resizable`\n\n</td>\n<td>\n\nEnables the is_resizable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-is-resizable`\n\n</td>\n<td>\n\nDenies the is_resizable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-is-visible`\n\n</td>\n<td>\n\nEnables the is_visible command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-is-visible`\n\n</td>\n<td>\n\nDenies the is_visible command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-maximize`\n\n</td>\n<td>\n\nEnables the maximize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-maximize`\n\n</td>\n<td>\n\nDenies the maximize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-minimize`\n\n</td>\n<td>\n\nEnables the minimize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-minimize`\n\n</td>\n<td>\n\nDenies the minimize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-monitor-from-point`\n\n</td>\n<td>\n\nEnables the monitor_from_point command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-monitor-from-point`\n\n</td>\n<td>\n\nDenies the monitor_from_point command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-outer-position`\n\n</td>\n<td>\n\nEnables the outer_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-outer-position`\n\n</td>\n<td>\n\nDenies the outer_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-outer-size`\n\n</td>\n<td>\n\nEnables the outer_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-outer-size`\n\n</td>\n<td>\n\nDenies the outer_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-primary-monitor`\n\n</td>\n<td>\n\nEnables the primary_monitor command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-primary-monitor`\n\n</td>\n<td>\n\nDenies the primary_monitor command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-request-user-attention`\n\n</td>\n<td>\n\nEnables the request_user_attention command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-request-user-attention`\n\n</td>\n<td>\n\nDenies the request_user_attention command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-scale-factor`\n\n</td>\n<td>\n\nEnables the scale_factor command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-scale-factor`\n\n</td>\n<td>\n\nDenies the scale_factor command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-always-on-bottom`\n\n</td>\n<td>\n\nEnables the set_always_on_bottom command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-always-on-bottom`\n\n</td>\n<td>\n\nDenies the set_always_on_bottom command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-always-on-top`\n\n</td>\n<td>\n\nEnables the set_always_on_top command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-always-on-top`\n\n</td>\n<td>\n\nDenies the set_always_on_top command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-background-color`\n\n</td>\n<td>\n\nEnables the set_background_color command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-background-color`\n\n</td>\n<td>\n\nDenies the set_background_color command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-badge-count`\n\n</td>\n<td>\n\nEnables the set_badge_count command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-badge-count`\n\n</td>\n<td>\n\nDenies the set_badge_count command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-badge-label`\n\n</td>\n<td>\n\nEnables the set_badge_label command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-badge-label`\n\n</td>\n<td>\n\nDenies the set_badge_label command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-closable`\n\n</td>\n<td>\n\nEnables the set_closable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-closable`\n\n</td>\n<td>\n\nDenies the set_closable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-content-protected`\n\n</td>\n<td>\n\nEnables the set_content_protected command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-content-protected`\n\n</td>\n<td>\n\nDenies the set_content_protected command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-cursor-grab`\n\n</td>\n<td>\n\nEnables the set_cursor_grab command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-cursor-grab`\n\n</td>\n<td>\n\nDenies the set_cursor_grab command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-cursor-icon`\n\n</td>\n<td>\n\nEnables the set_cursor_icon command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-cursor-icon`\n\n</td>\n<td>\n\nDenies the set_cursor_icon command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-cursor-position`\n\n</td>\n<td>\n\nEnables the set_cursor_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-cursor-position`\n\n</td>\n<td>\n\nDenies the set_cursor_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-cursor-visible`\n\n</td>\n<td>\n\nEnables the set_cursor_visible command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-cursor-visible`\n\n</td>\n<td>\n\nDenies the set_cursor_visible command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-decorations`\n\n</td>\n<td>\n\nEnables the set_decorations command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-decorations`\n\n</td>\n<td>\n\nDenies the set_decorations command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-effects`\n\n</td>\n<td>\n\nEnables the set_effects command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-effects`\n\n</td>\n<td>\n\nDenies the set_effects command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-enabled`\n\n</td>\n<td>\n\nEnables the set_enabled command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-enabled`\n\n</td>\n<td>\n\nDenies the set_enabled command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-focus`\n\n</td>\n<td>\n\nEnables the set_focus command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-focus`\n\n</td>\n<td>\n\nDenies the set_focus command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-focusable`\n\n</td>\n<td>\n\nEnables the set_focusable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-focusable`\n\n</td>\n<td>\n\nDenies the set_focusable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-fullscreen`\n\n</td>\n<td>\n\nEnables the set_fullscreen command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-fullscreen`\n\n</td>\n<td>\n\nDenies the set_fullscreen command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-icon`\n\n</td>\n<td>\n\nEnables the set_icon command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-icon`\n\n</td>\n<td>\n\nDenies the set_icon command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-ignore-cursor-events`\n\n</td>\n<td>\n\nEnables the set_ignore_cursor_events command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-ignore-cursor-events`\n\n</td>\n<td>\n\nDenies the set_ignore_cursor_events command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-max-size`\n\n</td>\n<td>\n\nEnables the set_max_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-max-size`\n\n</td>\n<td>\n\nDenies the set_max_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-maximizable`\n\n</td>\n<td>\n\nEnables the set_maximizable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-maximizable`\n\n</td>\n<td>\n\nDenies the set_maximizable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-min-size`\n\n</td>\n<td>\n\nEnables the set_min_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-min-size`\n\n</td>\n<td>\n\nDenies the set_min_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-minimizable`\n\n</td>\n<td>\n\nEnables the set_minimizable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-minimizable`\n\n</td>\n<td>\n\nDenies the set_minimizable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-overlay-icon`\n\n</td>\n<td>\n\nEnables the set_overlay_icon command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-overlay-icon`\n\n</td>\n<td>\n\nDenies the set_overlay_icon command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-position`\n\n</td>\n<td>\n\nEnables the set_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-position`\n\n</td>\n<td>\n\nDenies the set_position command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-progress-bar`\n\n</td>\n<td>\n\nEnables the set_progress_bar command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-progress-bar`\n\n</td>\n<td>\n\nDenies the set_progress_bar command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-resizable`\n\n</td>\n<td>\n\nEnables the set_resizable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-resizable`\n\n</td>\n<td>\n\nDenies the set_resizable command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-shadow`\n\n</td>\n<td>\n\nEnables the set_shadow command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-shadow`\n\n</td>\n<td>\n\nDenies the set_shadow command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-simple-fullscreen`\n\n</td>\n<td>\n\nEnables the set_simple_fullscreen command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-simple-fullscreen`\n\n</td>\n<td>\n\nDenies the set_simple_fullscreen command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-size`\n\n</td>\n<td>\n\nEnables the set_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-size`\n\n</td>\n<td>\n\nDenies the set_size command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-size-constraints`\n\n</td>\n<td>\n\nEnables the set_size_constraints command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-size-constraints`\n\n</td>\n<td>\n\nDenies the set_size_constraints command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-skip-taskbar`\n\n</td>\n<td>\n\nEnables the set_skip_taskbar command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-skip-taskbar`\n\n</td>\n<td>\n\nDenies the set_skip_taskbar command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-theme`\n\n</td>\n<td>\n\nEnables the set_theme command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-theme`\n\n</td>\n<td>\n\nDenies the set_theme command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-title`\n\n</td>\n<td>\n\nEnables the set_title command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-title`\n\n</td>\n<td>\n\nDenies the set_title command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-title-bar-style`\n\n</td>\n<td>\n\nEnables the set_title_bar_style command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-title-bar-style`\n\n</td>\n<td>\n\nDenies the set_title_bar_style command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-set-visible-on-all-workspaces`\n\n</td>\n<td>\n\nEnables the set_visible_on_all_workspaces command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-set-visible-on-all-workspaces`\n\n</td>\n<td>\n\nDenies the set_visible_on_all_workspaces command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-show`\n\n</td>\n<td>\n\nEnables the show command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-show`\n\n</td>\n<td>\n\nDenies the show command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-start-dragging`\n\n</td>\n<td>\n\nEnables the start_dragging command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-start-dragging`\n\n</td>\n<td>\n\nDenies the start_dragging command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-start-resize-dragging`\n\n</td>\n<td>\n\nEnables the start_resize_dragging command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-start-resize-dragging`\n\n</td>\n<td>\n\nDenies the start_resize_dragging command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-theme`\n\n</td>\n<td>\n\nEnables the theme command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-theme`\n\n</td>\n<td>\n\nDenies the theme command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-title`\n\n</td>\n<td>\n\nEnables the title command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-title`\n\n</td>\n<td>\n\nDenies the title command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-toggle-maximize`\n\n</td>\n<td>\n\nEnables the toggle_maximize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-toggle-maximize`\n\n</td>\n<td>\n\nDenies the toggle_maximize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-unmaximize`\n\n</td>\n<td>\n\nEnables the unmaximize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-unmaximize`\n\n</td>\n<td>\n\nDenies the unmaximize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:allow-unminimize`\n\n</td>\n<td>\n\nEnables the unminimize command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`core:window:deny-unminimize`\n\n</td>\n<td>\n\nDenies the unminimize command without any pre-configured scope.\n\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "crates/tauri/scripts/bundle.global.js",
    "content": "var __TAURI_IIFE__=function(e){\"use strict\";function n(e,n,t,i){if(\"a\"===t&&!i)throw new TypeError(\"Private accessor was defined without a getter\");if(\"function\"==typeof n?e!==n||!i:!n.has(e))throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");return\"m\"===t?i:\"a\"===t?i.call(e):i?i.value:n.get(e)}function t(e,n,t,i,r){if(\"m\"===i)throw new TypeError(\"Private method is not writable\");if(\"a\"===i&&!r)throw new TypeError(\"Private accessor was defined without a setter\");if(\"function\"==typeof n?e!==n||!r:!n.has(e))throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");return\"a\"===i?r.call(e,t):r?r.value=t:n.set(e,t),t}var i,r,s,a,l;\"function\"==typeof SuppressedError&&SuppressedError;const o=\"__TAURI_TO_IPC_KEY__\";function u(e,n=!1){return window.__TAURI_INTERNALS__.transformCallback(e,n)}class c{constructor(e){i.set(this,void 0),r.set(this,0),s.set(this,[]),a.set(this,void 0),t(this,i,e||(()=>{}),\"f\"),this.id=u(e=>{const l=e.index;if(\"end\"in e)return void(l==n(this,r,\"f\")?this.cleanupCallback():t(this,a,l,\"f\"));const o=e.message;if(l==n(this,r,\"f\")){for(n(this,i,\"f\").call(this,o),t(this,r,n(this,r,\"f\")+1,\"f\");n(this,r,\"f\")in n(this,s,\"f\");){const e=n(this,s,\"f\")[n(this,r,\"f\")];n(this,i,\"f\").call(this,e),delete n(this,s,\"f\")[n(this,r,\"f\")],t(this,r,n(this,r,\"f\")+1,\"f\")}n(this,r,\"f\")===n(this,a,\"f\")&&this.cleanupCallback()}else n(this,s,\"f\")[l]=o})}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(e){t(this,i,e,\"f\")}get onmessage(){return n(this,i,\"f\")}[(i=new WeakMap,r=new WeakMap,s=new WeakMap,a=new WeakMap,o)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[o]()}}class d{constructor(e,n,t){this.plugin=e,this.event=n,this.channelId=t}async unregister(){return h(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}}async function p(e,n,t){const i=new c(t);try{return await h(`plugin:${e}|register_listener`,{event:n,handler:i}),new d(e,n,i.id)}catch{return await h(`plugin:${e}|registerListener`,{event:n,handler:i}),new d(e,n,i.id)}}async function h(e,n={},t){return window.__TAURI_INTERNALS__.invoke(e,n,t)}class w{get rid(){return n(this,l,\"f\")}constructor(e){l.set(this,void 0),t(this,l,e,\"f\")}async close(){return h(\"plugin:resources|close\",{rid:this.rid})}}l=new WeakMap;var _=Object.freeze({__proto__:null,Channel:c,PluginListener:d,Resource:w,SERIALIZE_TO_IPC_FN:o,addPluginListener:p,checkPermissions:async function(e){return h(`plugin:${e}|check_permissions`)},convertFileSrc:function(e,n=\"asset\"){return window.__TAURI_INTERNALS__.convertFileSrc(e,n)},invoke:h,isTauri:function(){return!!(globalThis||window).isTauri},requestPermissions:async function(e){return h(`plugin:${e}|request_permissions`)},transformCallback:u});class y extends w{constructor(e){super(e)}static async new(e,n,t){return h(\"plugin:image|new\",{rgba:g(e),width:n,height:t}).then(e=>new y(e))}static async fromBytes(e){return h(\"plugin:image|from_bytes\",{bytes:g(e)}).then(e=>new y(e))}static async fromPath(e){return h(\"plugin:image|from_path\",{path:e}).then(e=>new y(e))}async rgba(){return h(\"plugin:image|rgba\",{rid:this.rid}).then(e=>new Uint8Array(e))}async size(){return h(\"plugin:image|size\",{rid:this.rid})}}function g(e){return null==e?null:\"string\"==typeof e?e:e instanceof y?e.rid:e}var b,m=Object.freeze({__proto__:null,Image:y,transformImage:g});!function(e){e.Nsis=\"nsis\",e.Msi=\"msi\",e.Deb=\"deb\",e.Rpm=\"rpm\",e.AppImage=\"appimage\",e.App=\"app\"}(b||(b={}));var f=Object.freeze({__proto__:null,get BundleType(){return b},defaultWindowIcon:async function(){return h(\"plugin:app|default_window_icon\").then(e=>e?new y(e):null)},fetchDataStoreIdentifiers:async function(){return h(\"plugin:app|fetch_data_store_identifiers\")},getBundleType:async function(){return h(\"plugin:app|bundle_type\")},getIdentifier:async function(){return h(\"plugin:app|identifier\")},getName:async function(){return h(\"plugin:app|name\")},getTauriVersion:async function(){return h(\"plugin:app|tauri_version\")},getVersion:async function(){return h(\"plugin:app|version\")},hide:async function(){return h(\"plugin:app|app_hide\")},onBackButtonPress:async function(e){return p(\"app\",\"back-button\",e)},removeDataStore:async function(e){return h(\"plugin:app|remove_data_store\",{uuid:e})},setDockVisibility:async function(e){return h(\"plugin:app|set_dock_visibility\",{visible:e})},setTheme:async function(e){return h(\"plugin:app|set_app_theme\",{theme:e})},show:async function(){return h(\"plugin:app|app_show\")}});class v{constructor(...e){this.type=\"Logical\",1===e.length?\"Logical\"in e[0]?(this.width=e[0].Logical.width,this.height=e[0].Logical.height):(this.width=e[0].width,this.height=e[0].height):(this.width=e[0],this.height=e[1])}toPhysical(e){return new k(this.width*e,this.height*e)}[o](){return{width:this.width,height:this.height}}toJSON(){return this[o]()}}class k{constructor(...e){this.type=\"Physical\",1===e.length?\"Physical\"in e[0]?(this.width=e[0].Physical.width,this.height=e[0].Physical.height):(this.width=e[0].width,this.height=e[0].height):(this.width=e[0],this.height=e[1])}toLogical(e){return new v(this.width/e,this.height/e)}[o](){return{width:this.width,height:this.height}}toJSON(){return this[o]()}}class A{constructor(e){this.size=e}toLogical(e){return this.size instanceof v?this.size:this.size.toLogical(e)}toPhysical(e){return this.size instanceof k?this.size:this.size.toPhysical(e)}[o](){return{[`${this.size.type}`]:{width:this.size.width,height:this.size.height}}}toJSON(){return this[o]()}}class T{constructor(...e){this.type=\"Logical\",1===e.length?\"Logical\"in e[0]?(this.x=e[0].Logical.x,this.y=e[0].Logical.y):(this.x=e[0].x,this.y=e[0].y):(this.x=e[0],this.y=e[1])}toPhysical(e){return new I(this.x*e,this.y*e)}[o](){return{x:this.x,y:this.y}}toJSON(){return this[o]()}}class I{constructor(...e){this.type=\"Physical\",1===e.length?\"Physical\"in e[0]?(this.x=e[0].Physical.x,this.y=e[0].Physical.y):(this.x=e[0].x,this.y=e[0].y):(this.x=e[0],this.y=e[1])}toLogical(e){return new T(this.x/e,this.y/e)}[o](){return{x:this.x,y:this.y}}toJSON(){return this[o]()}}class E{constructor(e){this.position=e}toLogical(e){return this.position instanceof T?this.position:this.position.toLogical(e)}toPhysical(e){return this.position instanceof I?this.position:this.position.toPhysical(e)}[o](){return{[`${this.position.type}`]:{x:this.position.x,y:this.position.y}}}toJSON(){return this[o]()}}var R,D=Object.freeze({__proto__:null,LogicalPosition:T,LogicalSize:v,PhysicalPosition:I,PhysicalSize:k,Position:E,Size:A});async function S(e,n){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(e,n),await h(\"plugin:event|unlisten\",{event:e,eventId:n})}async function N(e,n,t){var i;const r=\"string\"==typeof(null==t?void 0:t.target)?{kind:\"AnyLabel\",label:t.target}:null!==(i=null==t?void 0:t.target)&&void 0!==i?i:{kind:\"Any\"};return h(\"plugin:event|listen\",{event:e,target:r,handler:u(n)}).then(n=>async()=>S(e,n))}async function L(e,n,t){return N(e,t=>{S(e,t.id),n(t)},t)}async function C(e,n){await h(\"plugin:event|emit\",{event:e,payload:n})}async function x(e,n,t){const i=\"string\"==typeof e?{kind:\"AnyLabel\",label:e}:e;await h(\"plugin:event|emit_to\",{target:i,event:n,payload:t})}!function(e){e.WINDOW_RESIZED=\"tauri://resize\",e.WINDOW_MOVED=\"tauri://move\",e.WINDOW_CLOSE_REQUESTED=\"tauri://close-requested\",e.WINDOW_DESTROYED=\"tauri://destroyed\",e.WINDOW_FOCUS=\"tauri://focus\",e.WINDOW_BLUR=\"tauri://blur\",e.WINDOW_SCALE_FACTOR_CHANGED=\"tauri://scale-change\",e.WINDOW_THEME_CHANGED=\"tauri://theme-changed\",e.WINDOW_CREATED=\"tauri://window-created\",e.WEBVIEW_CREATED=\"tauri://webview-created\",e.DRAG_ENTER=\"tauri://drag-enter\",e.DRAG_OVER=\"tauri://drag-over\",e.DRAG_DROP=\"tauri://drag-drop\",e.DRAG_LEAVE=\"tauri://drag-leave\"}(R||(R={}));var P,z,W,O=Object.freeze({__proto__:null,get TauriEvent(){return R},emit:C,emitTo:x,listen:N,once:L});function U(e){var n;if(\"items\"in e)e.items=null===(n=e.items)||void 0===n?void 0:n.map(e=>\"rid\"in e?e:U(e));else if(\"action\"in e&&e.action){const n=new c;return n.onmessage=e.action,delete e.action,{...e,handler:n}}return e}async function F(e,n){const t=new c;if(n&&\"object\"==typeof n&&(\"action\"in n&&n.action&&(t.onmessage=n.action,delete n.action),\"item\"in n&&n.item&&\"object\"==typeof n.item&&\"About\"in n.item&&n.item.About&&\"object\"==typeof n.item.About&&\"icon\"in n.item.About&&n.item.About.icon&&(n.item.About.icon=g(n.item.About.icon)),\"icon\"in n&&n.icon&&(n.icon=g(n.icon)),\"items\"in n&&n.items)){function i(e){var n;return\"rid\"in e?[e.rid,e.kind]:(\"item\"in e&&\"object\"==typeof e.item&&(null===(n=e.item.About)||void 0===n?void 0:n.icon)&&(e.item.About.icon=g(e.item.About.icon)),\"icon\"in e&&e.icon&&(e.icon=g(e.icon)),\"items\"in e&&e.items&&(e.items=e.items.map(i)),U(e))}n.items=n.items.map(i)}return h(\"plugin:menu|new\",{kind:e,options:n,handler:t})}class M extends w{get id(){return n(this,P,\"f\")}get kind(){return n(this,z,\"f\")}constructor(e,n,i){super(e),P.set(this,void 0),z.set(this,void 0),t(this,P,n,\"f\"),t(this,z,i,\"f\")}}P=new WeakMap,z=new WeakMap;class B extends M{constructor(e,n){super(e,n,\"MenuItem\")}static async new(e){return F(\"MenuItem\",e).then(([e,n])=>new B(e,n))}async text(){return h(\"plugin:menu|text\",{rid:this.rid,kind:this.kind})}async setText(e){return h(\"plugin:menu|set_text\",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return h(\"plugin:menu|is_enabled\",{rid:this.rid,kind:this.kind})}async setEnabled(e){return h(\"plugin:menu|set_enabled\",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return h(\"plugin:menu|set_accelerator\",{rid:this.rid,kind:this.kind,accelerator:e})}}class V extends M{constructor(e,n){super(e,n,\"Check\")}static async new(e){return F(\"Check\",e).then(([e,n])=>new V(e,n))}async text(){return h(\"plugin:menu|text\",{rid:this.rid,kind:this.kind})}async setText(e){return h(\"plugin:menu|set_text\",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return h(\"plugin:menu|is_enabled\",{rid:this.rid,kind:this.kind})}async setEnabled(e){return h(\"plugin:menu|set_enabled\",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return h(\"plugin:menu|set_accelerator\",{rid:this.rid,kind:this.kind,accelerator:e})}async isChecked(){return h(\"plugin:menu|is_checked\",{rid:this.rid})}async setChecked(e){return h(\"plugin:menu|set_checked\",{rid:this.rid,checked:e})}}!function(e){e.Add=\"Add\",e.Advanced=\"Advanced\",e.Bluetooth=\"Bluetooth\",e.Bookmarks=\"Bookmarks\",e.Caution=\"Caution\",e.ColorPanel=\"ColorPanel\",e.ColumnView=\"ColumnView\",e.Computer=\"Computer\",e.EnterFullScreen=\"EnterFullScreen\",e.Everyone=\"Everyone\",e.ExitFullScreen=\"ExitFullScreen\",e.FlowView=\"FlowView\",e.Folder=\"Folder\",e.FolderBurnable=\"FolderBurnable\",e.FolderSmart=\"FolderSmart\",e.FollowLinkFreestanding=\"FollowLinkFreestanding\",e.FontPanel=\"FontPanel\",e.GoLeft=\"GoLeft\",e.GoRight=\"GoRight\",e.Home=\"Home\",e.IChatTheater=\"IChatTheater\",e.IconView=\"IconView\",e.Info=\"Info\",e.InvalidDataFreestanding=\"InvalidDataFreestanding\",e.LeftFacingTriangle=\"LeftFacingTriangle\",e.ListView=\"ListView\",e.LockLocked=\"LockLocked\",e.LockUnlocked=\"LockUnlocked\",e.MenuMixedState=\"MenuMixedState\",e.MenuOnState=\"MenuOnState\",e.MobileMe=\"MobileMe\",e.MultipleDocuments=\"MultipleDocuments\",e.Network=\"Network\",e.Path=\"Path\",e.PreferencesGeneral=\"PreferencesGeneral\",e.QuickLook=\"QuickLook\",e.RefreshFreestanding=\"RefreshFreestanding\",e.Refresh=\"Refresh\",e.Remove=\"Remove\",e.RevealFreestanding=\"RevealFreestanding\",e.RightFacingTriangle=\"RightFacingTriangle\",e.Share=\"Share\",e.Slideshow=\"Slideshow\",e.SmartBadge=\"SmartBadge\",e.StatusAvailable=\"StatusAvailable\",e.StatusNone=\"StatusNone\",e.StatusPartiallyAvailable=\"StatusPartiallyAvailable\",e.StatusUnavailable=\"StatusUnavailable\",e.StopProgressFreestanding=\"StopProgressFreestanding\",e.StopProgress=\"StopProgress\",e.TrashEmpty=\"TrashEmpty\",e.TrashFull=\"TrashFull\",e.User=\"User\",e.UserAccounts=\"UserAccounts\",e.UserGroup=\"UserGroup\",e.UserGuest=\"UserGuest\"}(W||(W={}));class G extends M{constructor(e,n){super(e,n,\"Icon\")}static async new(e){return F(\"Icon\",e).then(([e,n])=>new G(e,n))}async text(){return h(\"plugin:menu|text\",{rid:this.rid,kind:this.kind})}async setText(e){return h(\"plugin:menu|set_text\",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return h(\"plugin:menu|is_enabled\",{rid:this.rid,kind:this.kind})}async setEnabled(e){return h(\"plugin:menu|set_enabled\",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return h(\"plugin:menu|set_accelerator\",{rid:this.rid,kind:this.kind,accelerator:e})}async setIcon(e){return h(\"plugin:menu|set_icon\",{rid:this.rid,kind:this.kind,icon:g(e)})}}class j extends M{constructor(e,n){super(e,n,\"Predefined\")}static async new(e){return F(\"Predefined\",e).then(([e,n])=>new j(e,n))}async text(){return h(\"plugin:menu|text\",{rid:this.rid,kind:this.kind})}async setText(e){return h(\"plugin:menu|set_text\",{rid:this.rid,kind:this.kind,text:e})}}function H([e,n,t]){switch(t){case\"Submenu\":return new $(e,n);case\"Predefined\":return new j(e,n);case\"Check\":return new V(e,n);case\"Icon\":return new G(e,n);default:return new B(e,n)}}class $ extends M{constructor(e,n){super(e,n,\"Submenu\")}static async new(e){return F(\"Submenu\",e).then(([e,n])=>new $(e,n))}async text(){return h(\"plugin:menu|text\",{rid:this.rid,kind:this.kind})}async setText(e){return h(\"plugin:menu|set_text\",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return h(\"plugin:menu|is_enabled\",{rid:this.rid,kind:this.kind})}async setEnabled(e){return h(\"plugin:menu|set_enabled\",{rid:this.rid,kind:this.kind,enabled:e})}async append(e){return h(\"plugin:menu|append\",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map(e=>\"rid\"in e?[e.rid,e.kind]:e)})}async prepend(e){return h(\"plugin:menu|prepend\",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map(e=>\"rid\"in e?[e.rid,e.kind]:e)})}async insert(e,n){return h(\"plugin:menu|insert\",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map(e=>\"rid\"in e?[e.rid,e.kind]:e),position:n})}async remove(e){return h(\"plugin:menu|remove\",{rid:this.rid,kind:this.kind,item:[e.rid,e.kind]})}async removeAt(e){return h(\"plugin:menu|remove_at\",{rid:this.rid,kind:this.kind,position:e}).then(H)}async items(){return h(\"plugin:menu|items\",{rid:this.rid,kind:this.kind}).then(e=>e.map(H))}async get(e){return h(\"plugin:menu|get\",{rid:this.rid,kind:this.kind,id:e}).then(e=>e?H(e):null)}async popup(e,n){var t;return h(\"plugin:menu|popup\",{rid:this.rid,kind:this.kind,window:null!==(t=null==n?void 0:n.label)&&void 0!==t?t:null,at:e instanceof E?e:e?new E(e):null})}async setAsWindowsMenuForNSApp(){return h(\"plugin:menu|set_as_windows_menu_for_nsapp\",{rid:this.rid})}async setAsHelpMenuForNSApp(){return h(\"plugin:menu|set_as_help_menu_for_nsapp\",{rid:this.rid})}async setIcon(e){return h(\"plugin:menu|set_icon\",{rid:this.rid,kind:this.kind,icon:g(e)})}}class q extends M{constructor(e,n){super(e,n,\"Menu\")}static async new(e){return F(\"Menu\",e).then(([e,n])=>new q(e,n))}static async default(){return h(\"plugin:menu|create_default\").then(([e,n])=>new q(e,n))}async append(e){return h(\"plugin:menu|append\",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map(e=>\"rid\"in e?[e.rid,e.kind]:e)})}async prepend(e){return h(\"plugin:menu|prepend\",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map(e=>\"rid\"in e?[e.rid,e.kind]:e)})}async insert(e,n){return h(\"plugin:menu|insert\",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map(e=>\"rid\"in e?[e.rid,e.kind]:e),position:n})}async remove(e){return h(\"plugin:menu|remove\",{rid:this.rid,kind:this.kind,item:[e.rid,e.kind]})}async removeAt(e){return h(\"plugin:menu|remove_at\",{rid:this.rid,kind:this.kind,position:e}).then(H)}async items(){return h(\"plugin:menu|items\",{rid:this.rid,kind:this.kind}).then(e=>e.map(H))}async get(e){return h(\"plugin:menu|get\",{rid:this.rid,kind:this.kind,id:e}).then(e=>e?H(e):null)}async popup(e,n){var t;return h(\"plugin:menu|popup\",{rid:this.rid,kind:this.kind,window:null!==(t=null==n?void 0:n.label)&&void 0!==t?t:null,at:e instanceof E?e:e?new E(e):null})}async setAsAppMenu(){return h(\"plugin:menu|set_as_app_menu\",{rid:this.rid}).then(e=>e?new q(e[0],e[1]):null)}async setAsWindowMenu(e){var n;return h(\"plugin:menu|set_as_window_menu\",{rid:this.rid,window:null!==(n=null==e?void 0:e.label)&&void 0!==n?n:null}).then(e=>e?new q(e[0],e[1]):null)}}var J=Object.freeze({__proto__:null,CheckMenuItem:V,IconMenuItem:G,Menu:q,MenuItem:B,get NativeIcon(){return W},PredefinedMenuItem:j,Submenu:$,itemFromKind:H});function Q(){var e,n;window.__TAURI_INTERNALS__=null!==(e=window.__TAURI_INTERNALS__)&&void 0!==e?e:{},window.__TAURI_EVENT_PLUGIN_INTERNALS__=null!==(n=window.__TAURI_EVENT_PLUGIN_INTERNALS__)&&void 0!==n?n:{}}var Z,K=Object.freeze({__proto__:null,clearMocks:function(){\"object\"==typeof window.__TAURI_INTERNALS__&&(delete window.__TAURI_INTERNALS__.invoke,delete window.__TAURI_INTERNALS__.transformCallback,delete window.__TAURI_INTERNALS__.unregisterCallback,delete window.__TAURI_INTERNALS__.runCallback,delete window.__TAURI_INTERNALS__.callbacks,delete window.__TAURI_INTERNALS__.convertFileSrc,delete window.__TAURI_INTERNALS__.metadata,\"object\"==typeof window.__TAURI_EVENT_PLUGIN_INTERNALS__&&delete window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener)},mockConvertFileSrc:function(e){Q(),window.__TAURI_INTERNALS__.convertFileSrc=function(n,t=\"asset\"){const i=encodeURIComponent(n);return\"windows\"===e?`http://${t}.localhost/${i}`:`${t}://localhost/${i}`}},mockIPC:function(e,n){function t(e,n){switch(e){case\"plugin:event|listen\":return function(e){i.has(e.event)||i.set(e.event,[]);return i.get(e.event).push(e.handler),e.handler}(n);case\"plugin:event|emit\":return function(e){const n=i.get(e.event)||[];for(const t of n)a(t,e);return null}(n);case\"plugin:event|unlisten\":return function(e){const n=i.get(e.event);if(n){const t=n.indexOf(e.id);-1!==t&&n.splice(t,1)}}(n)}}Q();const i=new Map,r=new Map;function s(e){r.delete(e)}function a(e,n){const t=r.get(e);t?t(n):console.warn(`[TAURI] Couldn't find callback id ${e}. This might happen when the app is reloaded while Rust is running an asynchronous operation.`)}window.__TAURI_INTERNALS__.invoke=async function(i,r,s){return(null==n?void 0:n.shouldMockEvents)&&function(e){return e.startsWith(\"plugin:event|\")}(i)?t(i,r):e(i,r)},window.__TAURI_INTERNALS__.transformCallback=function(e,n=!1){const t=window.crypto.getRandomValues(new Uint32Array(1))[0];return r.set(t,i=>(n&&s(t),e&&e(i))),t},window.__TAURI_INTERNALS__.unregisterCallback=s,window.__TAURI_INTERNALS__.runCallback=a,window.__TAURI_INTERNALS__.callbacks=r,window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener=function(e,n){s(n)}},mockWindows:function(e,...n){Q(),window.__TAURI_INTERNALS__.metadata={currentWindow:{label:e},currentWebview:{windowLabel:e,label:e}}}});!function(e){e[e.Audio=1]=\"Audio\",e[e.Cache=2]=\"Cache\",e[e.Config=3]=\"Config\",e[e.Data=4]=\"Data\",e[e.LocalData=5]=\"LocalData\",e[e.Document=6]=\"Document\",e[e.Download=7]=\"Download\",e[e.Picture=8]=\"Picture\",e[e.Public=9]=\"Public\",e[e.Video=10]=\"Video\",e[e.Resource=11]=\"Resource\",e[e.Temp=12]=\"Temp\",e[e.AppConfig=13]=\"AppConfig\",e[e.AppData=14]=\"AppData\",e[e.AppLocalData=15]=\"AppLocalData\",e[e.AppCache=16]=\"AppCache\",e[e.AppLog=17]=\"AppLog\",e[e.Desktop=18]=\"Desktop\",e[e.Executable=19]=\"Executable\",e[e.Font=20]=\"Font\",e[e.Home=21]=\"Home\",e[e.Runtime=22]=\"Runtime\",e[e.Template=23]=\"Template\"}(Z||(Z={}));var Y=Object.freeze({__proto__:null,get BaseDirectory(){return Z},appCacheDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.AppCache})},appConfigDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.AppConfig})},appDataDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.AppData})},appLocalDataDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.AppLocalData})},appLogDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.AppLog})},audioDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Audio})},basename:async function(e,n){return h(\"plugin:path|basename\",{path:e,ext:n})},cacheDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Cache})},configDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Config})},dataDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Data})},delimiter:function(){return window.__TAURI_INTERNALS__.plugins.path.delimiter},desktopDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Desktop})},dirname:async function(e){return h(\"plugin:path|dirname\",{path:e})},documentDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Document})},downloadDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Download})},executableDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Executable})},extname:async function(e){return h(\"plugin:path|extname\",{path:e})},fontDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Font})},homeDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Home})},isAbsolute:async function(e){return h(\"plugin:path|is_absolute\",{path:e})},join:async function(...e){return h(\"plugin:path|join\",{paths:e})},localDataDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.LocalData})},normalize:async function(e){return h(\"plugin:path|normalize\",{path:e})},pictureDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Picture})},publicDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Public})},resolve:async function(...e){return h(\"plugin:path|resolve\",{paths:e})},resolveResource:async function(e){return h(\"plugin:path|resolve_directory\",{directory:Z.Resource,path:e})},resourceDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Resource})},runtimeDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Runtime})},sep:function(){return window.__TAURI_INTERNALS__.plugins.path.sep},tempDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Temp})},templateDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Template})},videoDir:async function(){return h(\"plugin:path|resolve_directory\",{directory:Z.Video})}});class X extends w{constructor(e,n){super(e),this.id=n}static async getById(e){return h(\"plugin:tray|get_by_id\",{id:e}).then(n=>n?new X(n,e):null)}static async removeById(e){return h(\"plugin:tray|remove_by_id\",{id:e})}static async new(e){(null==e?void 0:e.menu)&&(e.menu=[e.menu.rid,e.menu.kind]),(null==e?void 0:e.icon)&&(e.icon=g(e.icon));const n=new c;if(null==e?void 0:e.action){const t=e.action;n.onmessage=e=>t(function(e){const n=e;return n.position=new I(e.position),n.rect.position=new I(e.rect.position),n.rect.size=new k(e.rect.size),n}(e)),delete e.action}return h(\"plugin:tray|new\",{options:null!=e?e:{},handler:n}).then(([e,n])=>new X(e,n))}async setIcon(e){let n=null;return e&&(n=g(e)),h(\"plugin:tray|set_icon\",{rid:this.rid,icon:n})}async setMenu(e){return e&&(e=[e.rid,e.kind]),h(\"plugin:tray|set_menu\",{rid:this.rid,menu:e})}async setTooltip(e){return h(\"plugin:tray|set_tooltip\",{rid:this.rid,tooltip:e})}async setTitle(e){return h(\"plugin:tray|set_title\",{rid:this.rid,title:e})}async setVisible(e){return h(\"plugin:tray|set_visible\",{rid:this.rid,visible:e})}async setTempDirPath(e){return h(\"plugin:tray|set_temp_dir_path\",{rid:this.rid,path:e})}async setIconAsTemplate(e){return h(\"plugin:tray|set_icon_as_template\",{rid:this.rid,asTemplate:e})}async setMenuOnLeftClick(e){return h(\"plugin:tray|set_show_menu_on_left_click\",{rid:this.rid,onLeft:e})}async setShowMenuOnLeftClick(e){return h(\"plugin:tray|set_show_menu_on_left_click\",{rid:this.rid,onLeft:e})}}var ee,ne,te=Object.freeze({__proto__:null,TrayIcon:X});!function(e){e[e.Critical=1]=\"Critical\",e[e.Informational=2]=\"Informational\"}(ee||(ee={}));class ie{constructor(e){this._preventDefault=!1,this.event=e.event,this.id=e.id}preventDefault(){this._preventDefault=!0}isPreventDefault(){return this._preventDefault}}function re(){return new le(window.__TAURI_INTERNALS__.metadata.currentWindow.label,{skip:!0})}async function se(){return h(\"plugin:window|get_all_windows\").then(e=>e.map(e=>new le(e,{skip:!0})))}!function(e){e.None=\"none\",e.Normal=\"normal\",e.Indeterminate=\"indeterminate\",e.Paused=\"paused\",e.Error=\"error\"}(ne||(ne={}));const ae=[\"tauri://created\",\"tauri://error\"];class le{constructor(e,n={}){var t;this.label=e,this.listeners=Object.create(null),(null==n?void 0:n.skip)||h(\"plugin:window|create\",{options:{...n,parent:\"string\"==typeof n.parent?n.parent:null===(t=n.parent)||void 0===t?void 0:t.label,label:e}}).then(async()=>this.emit(\"tauri://created\")).catch(async e=>this.emit(\"tauri://error\",e))}static async getByLabel(e){var n;return null!==(n=(await se()).find(n=>n.label===e))&&void 0!==n?n:null}static getCurrent(){return re()}static async getAll(){return se()}static async getFocusedWindow(){for(const e of await se())if(await e.isFocused())return e;return null}async listen(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:N(e,n,{target:{kind:\"Window\",label:this.label}})}async once(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:L(e,n,{target:{kind:\"Window\",label:this.label}})}async emit(e,n){if(!ae.includes(e))return C(e,n);for(const t of this.listeners[e]||[])t({event:e,id:-1,payload:n})}async emitTo(e,n,t){if(!ae.includes(n))return x(e,n,t);for(const e of this.listeners[n]||[])e({event:n,id:-1,payload:t})}_handleTauriEvent(e,n){return!!ae.includes(e)&&(e in this.listeners?this.listeners[e].push(n):this.listeners[e]=[n],!0)}async scaleFactor(){return h(\"plugin:window|scale_factor\",{label:this.label})}async innerPosition(){return h(\"plugin:window|inner_position\",{label:this.label}).then(e=>new I(e))}async outerPosition(){return h(\"plugin:window|outer_position\",{label:this.label}).then(e=>new I(e))}async innerSize(){return h(\"plugin:window|inner_size\",{label:this.label}).then(e=>new k(e))}async outerSize(){return h(\"plugin:window|outer_size\",{label:this.label}).then(e=>new k(e))}async isFullscreen(){return h(\"plugin:window|is_fullscreen\",{label:this.label})}async isMinimized(){return h(\"plugin:window|is_minimized\",{label:this.label})}async isMaximized(){return h(\"plugin:window|is_maximized\",{label:this.label})}async isFocused(){return h(\"plugin:window|is_focused\",{label:this.label})}async isDecorated(){return h(\"plugin:window|is_decorated\",{label:this.label})}async isResizable(){return h(\"plugin:window|is_resizable\",{label:this.label})}async isMaximizable(){return h(\"plugin:window|is_maximizable\",{label:this.label})}async isMinimizable(){return h(\"plugin:window|is_minimizable\",{label:this.label})}async isClosable(){return h(\"plugin:window|is_closable\",{label:this.label})}async isVisible(){return h(\"plugin:window|is_visible\",{label:this.label})}async title(){return h(\"plugin:window|title\",{label:this.label})}async theme(){return h(\"plugin:window|theme\",{label:this.label})}async isAlwaysOnTop(){return h(\"plugin:window|is_always_on_top\",{label:this.label})}async center(){return h(\"plugin:window|center\",{label:this.label})}async requestUserAttention(e){let n=null;return e&&(n=e===ee.Critical?{type:\"Critical\"}:{type:\"Informational\"}),h(\"plugin:window|request_user_attention\",{label:this.label,value:n})}async setResizable(e){return h(\"plugin:window|set_resizable\",{label:this.label,value:e})}async setEnabled(e){return h(\"plugin:window|set_enabled\",{label:this.label,value:e})}async isEnabled(){return h(\"plugin:window|is_enabled\",{label:this.label})}async setMaximizable(e){return h(\"plugin:window|set_maximizable\",{label:this.label,value:e})}async setMinimizable(e){return h(\"plugin:window|set_minimizable\",{label:this.label,value:e})}async setClosable(e){return h(\"plugin:window|set_closable\",{label:this.label,value:e})}async setTitle(e){return h(\"plugin:window|set_title\",{label:this.label,value:e})}async maximize(){return h(\"plugin:window|maximize\",{label:this.label})}async unmaximize(){return h(\"plugin:window|unmaximize\",{label:this.label})}async toggleMaximize(){return h(\"plugin:window|toggle_maximize\",{label:this.label})}async minimize(){return h(\"plugin:window|minimize\",{label:this.label})}async unminimize(){return h(\"plugin:window|unminimize\",{label:this.label})}async show(){return h(\"plugin:window|show\",{label:this.label})}async hide(){return h(\"plugin:window|hide\",{label:this.label})}async close(){return h(\"plugin:window|close\",{label:this.label})}async destroy(){return h(\"plugin:window|destroy\",{label:this.label})}async setDecorations(e){return h(\"plugin:window|set_decorations\",{label:this.label,value:e})}async setShadow(e){return h(\"plugin:window|set_shadow\",{label:this.label,value:e})}async setEffects(e){return h(\"plugin:window|set_effects\",{label:this.label,value:e})}async clearEffects(){return h(\"plugin:window|set_effects\",{label:this.label,value:null})}async setAlwaysOnTop(e){return h(\"plugin:window|set_always_on_top\",{label:this.label,value:e})}async setAlwaysOnBottom(e){return h(\"plugin:window|set_always_on_bottom\",{label:this.label,value:e})}async setContentProtected(e){return h(\"plugin:window|set_content_protected\",{label:this.label,value:e})}async setSize(e){return h(\"plugin:window|set_size\",{label:this.label,value:e instanceof A?e:new A(e)})}async setMinSize(e){return h(\"plugin:window|set_min_size\",{label:this.label,value:e instanceof A?e:e?new A(e):null})}async setMaxSize(e){return h(\"plugin:window|set_max_size\",{label:this.label,value:e instanceof A?e:e?new A(e):null})}async setSizeConstraints(e){function n(e){return e?{Logical:e}:null}return h(\"plugin:window|set_size_constraints\",{label:this.label,value:{minWidth:n(null==e?void 0:e.minWidth),minHeight:n(null==e?void 0:e.minHeight),maxWidth:n(null==e?void 0:e.maxWidth),maxHeight:n(null==e?void 0:e.maxHeight)}})}async setPosition(e){return h(\"plugin:window|set_position\",{label:this.label,value:e instanceof E?e:new E(e)})}async setFullscreen(e){return h(\"plugin:window|set_fullscreen\",{label:this.label,value:e})}async setSimpleFullscreen(e){return h(\"plugin:window|set_simple_fullscreen\",{label:this.label,value:e})}async setFocus(){return h(\"plugin:window|set_focus\",{label:this.label})}async setFocusable(e){return h(\"plugin:window|set_focusable\",{label:this.label,value:e})}async setIcon(e){return h(\"plugin:window|set_icon\",{label:this.label,value:g(e)})}async setSkipTaskbar(e){return h(\"plugin:window|set_skip_taskbar\",{label:this.label,value:e})}async setCursorGrab(e){return h(\"plugin:window|set_cursor_grab\",{label:this.label,value:e})}async setCursorVisible(e){return h(\"plugin:window|set_cursor_visible\",{label:this.label,value:e})}async setCursorIcon(e){return h(\"plugin:window|set_cursor_icon\",{label:this.label,value:e})}async setBackgroundColor(e){return h(\"plugin:window|set_background_color\",{color:e})}async setCursorPosition(e){return h(\"plugin:window|set_cursor_position\",{label:this.label,value:e instanceof E?e:new E(e)})}async setIgnoreCursorEvents(e){return h(\"plugin:window|set_ignore_cursor_events\",{label:this.label,value:e})}async startDragging(){return h(\"plugin:window|start_dragging\",{label:this.label})}async startResizeDragging(e){return h(\"plugin:window|start_resize_dragging\",{label:this.label,value:e})}async setBadgeCount(e){return h(\"plugin:window|set_badge_count\",{label:this.label,value:e})}async setBadgeLabel(e){return h(\"plugin:window|set_badge_label\",{label:this.label,value:e})}async setOverlayIcon(e){return h(\"plugin:window|set_overlay_icon\",{label:this.label,value:e?g(e):void 0})}async setProgressBar(e){return h(\"plugin:window|set_progress_bar\",{label:this.label,value:e})}async setVisibleOnAllWorkspaces(e){return h(\"plugin:window|set_visible_on_all_workspaces\",{label:this.label,value:e})}async setTitleBarStyle(e){return h(\"plugin:window|set_title_bar_style\",{label:this.label,value:e})}async setTheme(e){return h(\"plugin:window|set_theme\",{label:this.label,value:e})}async onResized(e){return this.listen(R.WINDOW_RESIZED,n=>{n.payload=new k(n.payload),e(n)})}async onMoved(e){return this.listen(R.WINDOW_MOVED,n=>{n.payload=new I(n.payload),e(n)})}async onCloseRequested(e){return this.listen(R.WINDOW_CLOSE_REQUESTED,async n=>{const t=new ie(n);await e(t),t.isPreventDefault()||await this.destroy()})}async onDragDropEvent(e){const n=await this.listen(R.DRAG_ENTER,n=>{e({...n,payload:{type:\"enter\",paths:n.payload.paths,position:new I(n.payload.position)}})}),t=await this.listen(R.DRAG_OVER,n=>{e({...n,payload:{type:\"over\",position:new I(n.payload.position)}})}),i=await this.listen(R.DRAG_DROP,n=>{e({...n,payload:{type:\"drop\",paths:n.payload.paths,position:new I(n.payload.position)}})}),r=await this.listen(R.DRAG_LEAVE,n=>{e({...n,payload:{type:\"leave\"}})});return()=>{n(),i(),t(),r()}}async onFocusChanged(e){const n=await this.listen(R.WINDOW_FOCUS,n=>{e({...n,payload:!0})}),t=await this.listen(R.WINDOW_BLUR,n=>{e({...n,payload:!1})});return()=>{n(),t()}}async onScaleChanged(e){return this.listen(R.WINDOW_SCALE_FACTOR_CHANGED,e)}async onThemeChanged(e){return this.listen(R.WINDOW_THEME_CHANGED,e)}}var oe,ue,ce,de;function pe(e){return null===e?null:{name:e.name,scaleFactor:e.scaleFactor,position:new I(e.position),size:new k(e.size),workArea:{position:new I(e.workArea.position),size:new k(e.workArea.size)}}}!function(e){e.Disabled=\"disabled\",e.Throttle=\"throttle\",e.Suspend=\"suspend\"}(oe||(oe={})),function(e){e.Default=\"default\",e.FluentOverlay=\"fluentOverlay\"}(ue||(ue={})),function(e){e.AppearanceBased=\"appearanceBased\",e.Light=\"light\",e.Dark=\"dark\",e.MediumLight=\"mediumLight\",e.UltraDark=\"ultraDark\",e.Titlebar=\"titlebar\",e.Selection=\"selection\",e.Menu=\"menu\",e.Popover=\"popover\",e.Sidebar=\"sidebar\",e.HeaderView=\"headerView\",e.Sheet=\"sheet\",e.WindowBackground=\"windowBackground\",e.HudWindow=\"hudWindow\",e.FullScreenUI=\"fullScreenUI\",e.Tooltip=\"tooltip\",e.ContentBackground=\"contentBackground\",e.UnderWindowBackground=\"underWindowBackground\",e.UnderPageBackground=\"underPageBackground\",e.Mica=\"mica\",e.Blur=\"blur\",e.Acrylic=\"acrylic\",e.Tabbed=\"tabbed\",e.TabbedDark=\"tabbedDark\",e.TabbedLight=\"tabbedLight\"}(ce||(ce={})),function(e){e.FollowsWindowActiveState=\"followsWindowActiveState\",e.Active=\"active\",e.Inactive=\"inactive\"}(de||(de={}));var he=Object.freeze({__proto__:null,CloseRequestedEvent:ie,get Effect(){return ce},get EffectState(){return de},LogicalPosition:T,LogicalSize:v,PhysicalPosition:I,PhysicalSize:k,get ProgressBarStatus(){return ne},get UserAttentionType(){return ee},Window:le,availableMonitors:async function(){return h(\"plugin:window|available_monitors\").then(e=>e.map(pe))},currentMonitor:async function(){return h(\"plugin:window|current_monitor\").then(pe)},cursorPosition:async function(){return h(\"plugin:window|cursor_position\").then(e=>new I(e))},getAllWindows:se,getCurrentWindow:re,monitorFromPoint:async function(e,n){return h(\"plugin:window|monitor_from_point\",{x:e,y:n}).then(pe)},primaryMonitor:async function(){return h(\"plugin:window|primary_monitor\").then(pe)}});function we(){return new ge(re(),window.__TAURI_INTERNALS__.metadata.currentWebview.label,{skip:!0})}async function _e(){return h(\"plugin:webview|get_all_webviews\").then(e=>e.map(e=>new ge(new le(e.windowLabel,{skip:!0}),e.label,{skip:!0})))}const ye=[\"tauri://created\",\"tauri://error\"];class ge{constructor(e,n,t){this.window=e,this.label=n,this.listeners=Object.create(null),(null==t?void 0:t.skip)||h(\"plugin:webview|create_webview\",{windowLabel:e.label,options:{...t,label:n}}).then(async()=>this.emit(\"tauri://created\")).catch(async e=>this.emit(\"tauri://error\",e))}static async getByLabel(e){var n;return null!==(n=(await _e()).find(n=>n.label===e))&&void 0!==n?n:null}static getCurrent(){return we()}static async getAll(){return _e()}async listen(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:N(e,n,{target:{kind:\"Webview\",label:this.label}})}async once(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:L(e,n,{target:{kind:\"Webview\",label:this.label}})}async emit(e,n){if(!ye.includes(e))return C(e,n);for(const t of this.listeners[e]||[])t({event:e,id:-1,payload:n})}async emitTo(e,n,t){if(!ye.includes(n))return x(e,n,t);for(const e of this.listeners[n]||[])e({event:n,id:-1,payload:t})}_handleTauriEvent(e,n){return!!ye.includes(e)&&(e in this.listeners?this.listeners[e].push(n):this.listeners[e]=[n],!0)}async position(){return h(\"plugin:webview|webview_position\",{label:this.label}).then(e=>new I(e))}async size(){return h(\"plugin:webview|webview_size\",{label:this.label}).then(e=>new k(e))}async close(){return h(\"plugin:webview|webview_close\",{label:this.label})}async setSize(e){return h(\"plugin:webview|set_webview_size\",{label:this.label,value:e instanceof A?e:new A(e)})}async setPosition(e){return h(\"plugin:webview|set_webview_position\",{label:this.label,value:e instanceof E?e:new E(e)})}async setFocus(){return h(\"plugin:webview|set_webview_focus\",{label:this.label})}async setAutoResize(e){return h(\"plugin:webview|set_webview_auto_resize\",{label:this.label,value:e})}async hide(){return h(\"plugin:webview|webview_hide\",{label:this.label})}async show(){return h(\"plugin:webview|webview_show\",{label:this.label})}async setZoom(e){return h(\"plugin:webview|set_webview_zoom\",{label:this.label,value:e})}async reparent(e){return h(\"plugin:webview|reparent\",{label:this.label,window:\"string\"==typeof e?e:e.label})}async clearAllBrowsingData(){return h(\"plugin:webview|clear_all_browsing_data\")}async setBackgroundColor(e){return h(\"plugin:webview|set_webview_background_color\",{color:e})}async onDragDropEvent(e){const n=await this.listen(R.DRAG_ENTER,n=>{e({...n,payload:{type:\"enter\",paths:n.payload.paths,position:new I(n.payload.position)}})}),t=await this.listen(R.DRAG_OVER,n=>{e({...n,payload:{type:\"over\",position:new I(n.payload.position)}})}),i=await this.listen(R.DRAG_DROP,n=>{e({...n,payload:{type:\"drop\",paths:n.payload.paths,position:new I(n.payload.position)}})}),r=await this.listen(R.DRAG_LEAVE,n=>{e({...n,payload:{type:\"leave\"}})});return()=>{n(),i(),t(),r()}}}var be,me,fe=Object.freeze({__proto__:null,Webview:ge,getAllWebviews:_e,getCurrentWebview:we});function ve(){const e=we();return new Ae(e.label,{skip:!0})}async function ke(){return h(\"plugin:window|get_all_windows\").then(e=>e.map(e=>new Ae(e,{skip:!0})))}class Ae{constructor(e,n={}){var t;this.label=e,this.listeners=Object.create(null),(null==n?void 0:n.skip)||h(\"plugin:webview|create_webview_window\",{options:{...n,parent:\"string\"==typeof n.parent?n.parent:null===(t=n.parent)||void 0===t?void 0:t.label,label:e}}).then(async()=>this.emit(\"tauri://created\")).catch(async e=>this.emit(\"tauri://error\",e))}static async getByLabel(e){var n;const t=null!==(n=(await ke()).find(n=>n.label===e))&&void 0!==n?n:null;return t?new Ae(t.label,{skip:!0}):null}static getCurrent(){return ve()}static async getAll(){return ke()}async listen(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:N(e,n,{target:{kind:\"WebviewWindow\",label:this.label}})}async once(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:L(e,n,{target:{kind:\"WebviewWindow\",label:this.label}})}async setBackgroundColor(e){return h(\"plugin:window|set_background_color\",{color:e}).then(()=>h(\"plugin:webview|set_webview_background_color\",{color:e}))}}be=Ae,me=[le,ge],(Array.isArray(me)?me:[me]).forEach(e=>{Object.getOwnPropertyNames(e.prototype).forEach(n=>{var t;\"object\"==typeof be.prototype&&be.prototype&&n in be.prototype||Object.defineProperty(be.prototype,n,null!==(t=Object.getOwnPropertyDescriptor(e.prototype,n))&&void 0!==t?t:Object.create(null))})});var Te=Object.freeze({__proto__:null,WebviewWindow:Ae,getAllWebviewWindows:ke,getCurrentWebviewWindow:ve});return e.app=f,e.core=_,e.dpi=D,e.event=O,e.image=m,e.menu=J,e.mocks=K,e.path=Y,e.tray=te,e.webview=fe,e.webviewWindow=Te,e.window=he,e}({});window.__TAURI__=__TAURI_IIFE__;\n"
  },
  {
    "path": "crates/tauri/scripts/core.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n;(function () {\n  function uid() {\n    return window.crypto.getRandomValues(new Uint32Array(1))[0]\n  }\n\n  const osName = __TEMPLATE_os_name__\n  const protocolScheme = __TEMPLATE_protocol_scheme__\n\n  Object.defineProperty(window.__TAURI_INTERNALS__, 'convertFileSrc', {\n    value: function (filePath, protocol = 'asset') {\n      const path = encodeURIComponent(filePath)\n      return osName === 'windows' || osName === 'android'\n        ? `${protocolScheme}://${protocol}.localhost/${path}`\n        : `${protocol}://localhost/${path}`\n    }\n  })\n\n  const callbacks = new Map()\n\n  function registerCallback(callback, once) {\n    const identifier = uid()\n    callbacks.set(identifier, (data) => {\n      if (once) {\n        unregisterCallback(identifier)\n      }\n      return callback && callback(data)\n    })\n    return identifier\n  }\n\n  function unregisterCallback(id) {\n    callbacks.delete(id)\n  }\n\n  function runCallback(id, data) {\n    const callback = callbacks.get(id)\n    if (callback) {\n      callback(data)\n    } else {\n      console.warn(\n        `[TAURI] Couldn't find callback id ${id}. This might happen when the app is reloaded while Rust is running an asynchronous operation.`\n      )\n    }\n  }\n\n  // Maybe let's rename it to `registerCallback`?\n  Object.defineProperty(window.__TAURI_INTERNALS__, 'transformCallback', {\n    value: registerCallback\n  })\n\n  Object.defineProperty(window.__TAURI_INTERNALS__, 'unregisterCallback', {\n    value: unregisterCallback\n  })\n\n  Object.defineProperty(window.__TAURI_INTERNALS__, 'runCallback', {\n    value: runCallback\n  })\n\n  // This is just for the debugging purposes\n  Object.defineProperty(window.__TAURI_INTERNALS__, 'callbacks', {\n    value: callbacks\n  })\n\n  const ipcQueue = []\n  let isWaitingForIpc = false\n\n  function waitForIpc() {\n    if ('ipc' in window.__TAURI_INTERNALS__) {\n      for (const action of ipcQueue) {\n        action()\n      }\n    } else {\n      setTimeout(waitForIpc, 50)\n    }\n  }\n\n  Object.defineProperty(window.__TAURI_INTERNALS__, 'invoke', {\n    value: function (cmd, payload = {}, options) {\n      return new Promise(function (resolve, reject) {\n        const callback = registerCallback((r) => {\n          resolve(r)\n          unregisterCallback(error)\n        }, true)\n        const error = registerCallback((e) => {\n          reject(e)\n          unregisterCallback(callback)\n        }, true)\n\n        const action = () => {\n          window.__TAURI_INTERNALS__.ipc({\n            cmd,\n            callback,\n            error,\n            payload,\n            options\n          })\n        }\n        if ('ipc' in window.__TAURI_INTERNALS__) {\n          action()\n        } else {\n          ipcQueue.push(action)\n          if (!isWaitingForIpc) {\n            waitForIpc()\n            isWaitingForIpc = true\n          }\n        }\n      })\n    }\n  })\n})()\n"
  },
  {
    "path": "crates/tauri/scripts/freeze_prototype.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nObject.freeze(Object.prototype)\n"
  },
  {
    "path": "crates/tauri/scripts/init.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n;(function () {\n  __RAW_freeze_prototype__\n\n  __RAW_pattern_script__\n\n  __RAW_ipc_script__\n\n  __RAW_core_script__\n\n  __RAW_event_initialization_script__\n})()\n"
  },
  {
    "path": "crates/tauri/scripts/ipc-protocol.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n;(function () {\n  /**\n   * A runtime generated key to ensure an IPC call comes from an initialized frame.\n   *\n   * This is declared outside the `window.__TAURI_INVOKE__` definition to prevent\n   * the key from being leaked by `window.__TAURI_INVOKE__.toString()`.\n   */\n  const __TAURI_INVOKE_KEY__ = __TEMPLATE_invoke_key__\n\n  const processIpcMessage = __RAW_process_ipc_message_fn__\n  const osName = __TEMPLATE_os_name__\n  const fetchChannelDataCommand = __TEMPLATE_fetch_channel_data_command__\n  let customProtocolIpcFailed = false\n\n  // on Android we never use it because Android does not have support to reading the request body\n  const canUseCustomProtocol = osName !== 'android'\n\n  function sendIpcMessage(message) {\n    const { cmd, callback, error, payload, options } = message\n\n    if (\n      !customProtocolIpcFailed\n      && (canUseCustomProtocol || cmd === fetchChannelDataCommand)\n    ) {\n      const { contentType, data } = processIpcMessage(payload)\n\n      const headers = new Headers((options && options.headers) || {})\n      headers.set('Content-Type', contentType)\n      headers.set('Tauri-Callback', callback)\n      headers.set('Tauri-Error', error)\n      headers.set('Tauri-Invoke-Key', __TAURI_INVOKE_KEY__)\n\n      fetch(window.__TAURI_INTERNALS__.convertFileSrc(cmd, 'ipc'), {\n        method: 'POST',\n        body: data,\n        headers\n      })\n        .then((response) => {\n          const callbackId =\n            response.headers.get('Tauri-Response') === 'ok' ? callback : error\n          // we need to split here because on Android the content-type gets duplicated\n          switch ((response.headers.get('content-type') || '').split(',')[0]) {\n            case 'application/json':\n              return response.json().then((r) => [callbackId, r])\n            case 'text/plain':\n              return response.text().then((r) => [callbackId, r])\n            default:\n              return response.arrayBuffer().then((r) => [callbackId, r])\n          }\n        })\n        .then(\n          ([callbackId, data]) => {\n            window.__TAURI_INTERNALS__.runCallback(callbackId, data)\n          },\n          (e) => {\n            console.warn(\n              'IPC custom protocol failed, Tauri will now use the postMessage interface instead',\n              e\n            )\n            // failed to use the custom protocol IPC (either the webview blocked a custom protocol or it was a CSP error)\n            // so we need to fallback to the postMessage interface\n            customProtocolIpcFailed = true\n            sendIpcMessage(message)\n          }\n        )\n    } else {\n      // otherwise use the postMessage interface\n      const { data } = processIpcMessage({\n        cmd,\n        callback,\n        error,\n        options: {\n          ...options,\n          customProtocolIpcBlocked: customProtocolIpcFailed\n        },\n        payload,\n        __TAURI_INVOKE_KEY__\n      })\n      // `window.ipc.postMessage` came from `tauri-runtime-wry` > `wry` [`with_ipc_handler`](https://github.com/tauri-apps/wry/blob/a0403b9e2f1ff9d73be7dce1184f058afcaa1d82/src/lib.rs#L1130)\n      window.ipc.postMessage(data)\n    }\n  }\n\n  Object.defineProperty(window.__TAURI_INTERNALS__, 'postMessage', {\n    value: sendIpcMessage\n  })\n})()\n"
  },
  {
    "path": "crates/tauri/scripts/ipc.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/**\n * @typedef {{callback: string, error: string, data: *}} IsolationPayload - a valid isolation payload\n */\n\n;(function () {\n  /**\n   * @type {string}\n   */\n  const pattern = window.__TAURI_INTERNALS__.__TAURI_PATTERN__.pattern\n\n  /**\n   * @type {string}\n   */\n  const isolationOrigin = __TEMPLATE_isolation_origin__\n\n  /**\n   * @type {{queue: object[], ready: boolean, frame: HTMLElement | null}}\n   */\n  const isolation = Object.create(null)\n  isolation.queue = []\n  isolation.ready = false\n  isolation.frame = null\n\n  /**\n   * Detects if a message event is a valid isolation message.\n   *\n   * @param {MessageEvent<object>} event - a message event that is expected to be an isolation message\n   * @return {boolean} - if the event was a valid isolation message\n   */\n  function isIsolationMessage(event) {\n    if (\n      typeof event.data === 'object'\n      && typeof event.data.payload === 'object'\n    ) {\n      const keys = Object.keys(event.data.payload || {})\n      return (\n        keys.length > 0\n        && keys.every(\n          (key) => key === 'contentType' || key === 'nonce' || key === 'payload'\n        )\n      )\n    }\n    return false\n  }\n\n  /**\n   * Detects if data is able to transform into an isolation payload.\n   *\n   * @param {object} data - object that is expected to contain at least a callback and error identifier\n   * @return {boolean} - if the data is able to transform into an isolation payload\n   */\n  function isIsolationPayload(data) {\n    return (\n      typeof data === 'object'\n      && 'callback' in data\n      && 'error' in data\n      && !isIsolationMessage(data)\n    )\n  }\n\n  /**\n   * Sends a properly formatted message to the isolation frame.\n   *\n   * @param {IsolationPayload} data - data that has been validated to be an isolation payload\n   */\n  function sendIsolationMessage(data) {\n    // set the frame dom element if it's not been set before\n    if (!isolation.frame) {\n      const frame = document.querySelector('iframe#__tauri_isolation__')\n      if (frame.src.startsWith(isolationOrigin)) {\n        isolation.frame = frame\n      } else {\n        console.error(\n          'Tauri IPC found an isolation iframe, but it had the wrong origin'\n        )\n      }\n    }\n\n    // ensure we have the target to send the message to\n    if (!isolation.frame || !isolation.frame.contentWindow) {\n      console.error(\n        'Tauri \"Isolation\" Pattern could not find the Isolation iframe window'\n      )\n      return\n    }\n\n    // `postMessage` uses `structuredClone` to serialize the data before sending it\n    // unlike `JSON.stringify`, we can't extend a type with a method similar to `toJSON`\n    // so that `structuredClone` would use, so until https://github.com/whatwg/html/issues/7428\n    // we manually call `toIPC`\n    function serializeIpcPayload(data) {\n      // if this value changes, make sure to update it in:\n      // 1. process-ipc-message-fn.js\n      // 2. core.ts\n      const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__'\n\n      if (\n        typeof data === 'object'\n        && data !== null\n        && 'constructor' in data\n        && data.constructor === Array\n      ) {\n        return data.map((v) => serializeIpcPayload(v))\n      }\n\n      if (\n        typeof data === 'object'\n        && data !== null\n        && SERIALIZE_TO_IPC_FN in data\n      ) {\n        return data[SERIALIZE_TO_IPC_FN]()\n      }\n\n      if (\n        typeof data === 'object'\n        && data !== null\n        && 'constructor' in data\n        && data.constructor === Object\n      ) {\n        const acc = {}\n        Object.entries(data).forEach(([k, v]) => {\n          acc[k] = serializeIpcPayload(v)\n        })\n        return acc\n      }\n\n      return data\n    }\n\n    data.payload = serializeIpcPayload(data.payload)\n\n    isolation.frame.contentWindow.postMessage(\n      data,\n      '*' /* todo: set this to the secure origin */\n    )\n  }\n\n  Object.defineProperty(window.__TAURI_INTERNALS__, 'ipc', {\n    // todo: JSDoc this function\n    value: Object.freeze((message) => {\n      switch (pattern) {\n        case 'brownfield':\n          window.__TAURI_INTERNALS__.postMessage(message)\n          break\n\n        case 'isolation':\n          if (!isIsolationPayload(message)) {\n            console.error(\n              'Tauri \"Isolation\" Pattern found an invalid isolation message payload',\n              message\n            )\n            break\n          }\n\n          if (isolation.ready) {\n            sendIsolationMessage(message)\n          } else {\n            isolation.queue.push(message)\n          }\n\n          break\n\n        case 'error':\n          console.error(\n            'Tauri IPC found a Tauri Pattern, but it was an error. Check for other log messages to find the cause.'\n          )\n          break\n\n        default:\n          console.error(\n            'Tauri IPC did not find a Tauri Pattern that it understood.'\n          )\n          break\n      }\n    })\n  })\n\n  /**\n   * IMPORTANT: See isolation_secure.js for the isolation frame implementation.\n   * main frame -> isolation frame = isolation payload\n   * isolation frame -> main frame = isolation message\n   */\n  if (pattern === 'isolation') {\n    window.addEventListener(\n      'message',\n      (event) => {\n        // watch for the isolation frame being ready and flush any queued messages\n        if (event.data === '__TAURI_ISOLATION_READY__') {\n          isolation.ready = true\n\n          for (const message of isolation.queue) {\n            sendIsolationMessage(message)\n          }\n\n          isolation.queue = []\n          return\n        }\n\n        if (isIsolationMessage(event)) {\n          window.__TAURI_INTERNALS__.postMessage(event.data)\n        }\n      },\n      false\n    )\n  }\n})()\n"
  },
  {
    "path": "crates/tauri/scripts/isolation.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nif (location.href !== __TEMPLATE_isolation_src__) {\n  window.addEventListener('DOMContentLoaded', () => {\n    let style = document.createElement('style')\n    style.textContent = __TEMPLATE_style__\n    document.head.append(style)\n\n    let iframe = document.createElement('iframe')\n    iframe.id = '__tauri_isolation__'\n    iframe.sandbox.add('allow-scripts')\n    iframe.src = __TEMPLATE_isolation_src__\n    document.body.append(iframe)\n  })\n}\n"
  },
  {
    "path": "crates/tauri/scripts/pattern.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n;(function () {\n  function __tauriDeepFreeze(object) {\n    const props = Object.getOwnPropertyNames(object)\n\n    for (const prop of props) {\n      if (typeof object[prop] === 'object') {\n        __tauriDeepFreeze(object[prop])\n      }\n    }\n\n    return Object.freeze(object)\n  }\n\n  Object.defineProperty(window.__TAURI_INTERNALS__, '__TAURI_PATTERN__', {\n    value: __tauriDeepFreeze(__TEMPLATE_pattern__)\n  })\n})()\n"
  },
  {
    "path": "crates/tauri/scripts/process-ipc-message-fn.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// this is a function and not an iife so use it carefully\n\n(function (message) {\n  if (\n    message instanceof ArrayBuffer\n    || ArrayBuffer.isView(message)\n    || Array.isArray(message)\n  ) {\n    return {\n      contentType: 'application/octet-stream',\n      data: message\n    }\n  } else {\n    const data = JSON.stringify(message, (_k, val) => {\n      // if this value changes, make sure to update it in:\n      // 1. ipc.js\n      // 2. core.ts\n      const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__'\n\n      if (val instanceof Map) {\n        return Object.fromEntries(val.entries())\n      } else if (val instanceof Uint8Array) {\n        return Array.from(val)\n      } else if (val instanceof ArrayBuffer) {\n        return Array.from(new Uint8Array(val))\n      } else if (\n        typeof val === 'object'\n        && val !== null\n        && SERIALIZE_TO_IPC_FN in val\n      ) {\n        return val[SERIALIZE_TO_IPC_FN]()\n      } else {\n        return val\n      }\n    })\n\n    return {\n      contentType: 'application/json',\n      data\n    }\n  }\n})\n"
  },
  {
    "path": "crates/tauri/src/app/plugin.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse tauri_utils::{config::BundleType, Theme};\n\nuse crate::{\n  command,\n  plugin::{Builder, TauriPlugin},\n  AppHandle, Manager, ResourceId, Runtime, Webview,\n};\n\n#[command(root = \"crate\")]\npub fn version<R: Runtime>(app: AppHandle<R>) -> String {\n  app.package_info().version.to_string()\n}\n\n#[command(root = \"crate\")]\npub fn name<R: Runtime>(app: AppHandle<R>) -> String {\n  app.package_info().name.clone()\n}\n\n#[command(root = \"crate\")]\npub fn tauri_version() -> &'static str {\n  crate::VERSION\n}\n\n#[command(root = \"crate\")]\npub fn identifier<R: Runtime>(app: AppHandle<R>) -> String {\n  app.config().identifier.clone()\n}\n\n#[command(root = \"crate\")]\n#[allow(unused_variables)]\npub fn app_show<R: Runtime>(app: AppHandle<R>) -> crate::Result<()> {\n  #[cfg(target_os = \"macos\")]\n  app.show()?;\n  Ok(())\n}\n\n#[command(root = \"crate\")]\n#[allow(unused_variables)]\npub fn app_hide<R: Runtime>(app: AppHandle<R>) -> crate::Result<()> {\n  #[cfg(target_os = \"macos\")]\n  app.hide()?;\n  Ok(())\n}\n\n#[command(root = \"crate\")]\n#[allow(unused_variables)]\npub async fn fetch_data_store_identifiers<R: Runtime>(\n  app: AppHandle<R>,\n) -> crate::Result<Vec<[u8; 16]>> {\n  #[cfg(target_vendor = \"apple\")]\n  return app.fetch_data_store_identifiers().await;\n  #[cfg(not(target_vendor = \"apple\"))]\n  return Ok(Vec::new());\n}\n\n#[command(root = \"crate\")]\n#[allow(unused_variables)]\npub async fn remove_data_store<R: Runtime>(app: AppHandle<R>, uuid: [u8; 16]) -> crate::Result<()> {\n  #[cfg(target_vendor = \"apple\")]\n  app.remove_data_store(uuid).await?;\n  #[cfg(not(target_vendor = \"apple\"))]\n  let _ = uuid;\n  Ok(())\n}\n\n#[command(root = \"crate\")]\npub fn default_window_icon<R: Runtime>(\n  webview: Webview<R>,\n  app: AppHandle<R>,\n) -> Option<ResourceId> {\n  app.default_window_icon().cloned().map(|icon| {\n    let mut resources_table = webview.resources_table();\n    resources_table.add(icon.to_owned())\n  })\n}\n\n#[command(root = \"crate\")]\npub async fn set_app_theme<R: Runtime>(app: AppHandle<R>, theme: Option<Theme>) {\n  app.set_theme(theme);\n}\n\n#[command(root = \"crate\")]\npub async fn set_dock_visibility<R: Runtime>(\n  app: AppHandle<R>,\n  visible: bool,\n) -> crate::Result<()> {\n  #[cfg(target_os = \"macos\")]\n  {\n    let mut focused_window = None;\n    for window in app.manager.windows().into_values() {\n      if window.is_focused().unwrap_or_default() {\n        focused_window.replace(window);\n        break;\n      }\n    }\n\n    app.set_dock_visibility(visible)?;\n\n    // retain focus\n    if let Some(focused_window) = focused_window {\n      let _ = focused_window.set_focus();\n    }\n  }\n  #[cfg(not(target_os = \"macos\"))]\n  let (_app, _visible) = (app, visible);\n  Ok(())\n}\n\n#[command(root = \"crate\")]\npub fn bundle_type() -> Option<BundleType> {\n  tauri_utils::platform::bundle_type()\n}\n\npub fn init<R: Runtime>() -> TauriPlugin<R> {\n  Builder::new(\"app\")\n    .invoke_handler(crate::generate_handler![\n      #![plugin(app)]\n      version,\n      name,\n      tauri_version,\n      identifier,\n      app_show,\n      app_hide,\n      fetch_data_store_identifiers,\n      remove_data_store,\n      default_window_icon,\n      set_app_theme,\n      set_dock_visibility,\n      bundle_type,\n    ])\n    .setup(|_app, _api| {\n      #[cfg(target_os = \"android\")]\n      {\n        let handle = _api.register_android_plugin(\"app.tauri\", \"AppPlugin\")?;\n        _app.manage(AppPlugin(handle));\n      }\n      Ok(())\n    })\n    .build()\n}\n\n#[cfg(target_os = \"android\")]\npub(crate) struct AppPlugin<R: Runtime>(pub crate::plugin::PluginHandle<R>);\n"
  },
  {
    "path": "crates/tauri/src/app.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  image::Image,\n  ipc::{\n    channel::ChannelDataIpcQueue, CallbackFn, CommandArg, CommandItem, Invoke, InvokeError,\n    InvokeHandler, InvokeResponseBody,\n  },\n  manager::{webview::UriSchemeProtocol, AppManager, Asset},\n  plugin::{Plugin, PluginStore},\n  resources::ResourceTable,\n  runtime::{\n    window::{WebviewEvent as RuntimeWebviewEvent, WindowEvent as RuntimeWindowEvent},\n    ExitRequestedEventAction, RunEvent as RuntimeRunEvent,\n  },\n  sealed::{ManagerBase, RuntimeOrDispatch},\n  utils::{config::Config, Env},\n  webview::PageLoadPayload,\n  Context, DeviceEventFilter, Emitter, EventLoopMessage, EventName, Listener, Manager, Monitor,\n  Runtime, Scopes, StateManager, Theme, Webview, WebviewWindowBuilder, Window,\n};\n\n#[cfg(desktop)]\nuse crate::menu::{Menu, MenuEvent};\n#[cfg(all(desktop, feature = \"tray-icon\"))]\nuse crate::tray::{TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconId};\nuse raw_window_handle::HasDisplayHandle;\nuse serialize_to_javascript::{default_template, DefaultTemplate, Template};\nuse tauri_macros::default_runtime;\n#[cfg(desktop)]\nuse tauri_runtime::EventLoopProxy;\nuse tauri_runtime::{\n  dpi::{PhysicalPosition, PhysicalSize},\n  window::DragDropEvent,\n  RuntimeInitArgs,\n};\nuse tauri_utils::{assets::AssetsIter, PackageInfo};\n\nuse std::{\n  borrow::Cow,\n  collections::HashMap,\n  fmt,\n  sync::{atomic, mpsc::Sender, Arc, Mutex, MutexGuard},\n  thread::ThreadId,\n  time::Duration,\n};\n\nuse crate::{event::EventId, runtime::RuntimeHandle, Event, EventTarget};\n\n#[cfg(target_os = \"macos\")]\nuse crate::ActivationPolicy;\n\npub(crate) mod plugin;\n\n#[cfg(desktop)]\npub(crate) type GlobalMenuEventListener<T> = Box<dyn Fn(&T, crate::menu::MenuEvent) + Send + Sync>;\n#[cfg(all(desktop, feature = \"tray-icon\"))]\npub(crate) type GlobalTrayIconEventListener<T> =\n  Box<dyn Fn(&T, crate::tray::TrayIconEvent) + Send + Sync>;\npub(crate) type GlobalWindowEventListener<R> = Box<dyn Fn(&Window<R>, &WindowEvent) + Send + Sync>;\npub(crate) type GlobalWebviewEventListener<R> =\n  Box<dyn Fn(&Webview<R>, &WebviewEvent) + Send + Sync>;\n/// A closure that is run when the Tauri application is setting up.\npub type SetupHook<R> =\n  Box<dyn FnOnce(&mut App<R>) -> std::result::Result<(), Box<dyn std::error::Error>> + Send>;\n/// A closure that is run every time a page starts or finishes loading.\npub type OnPageLoad<R> = dyn Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static;\npub type ChannelInterceptor<R> =\n  Box<dyn Fn(&Webview<R>, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static>;\n\n/// The exit code on [`RunEvent::ExitRequested`] when [`AppHandle#method.restart`] is called.\npub const RESTART_EXIT_CODE: i32 = i32::MAX;\n\n/// Api exposed on the `ExitRequested` event.\n#[derive(Debug, Clone)]\npub struct ExitRequestApi {\n  tx: Sender<ExitRequestedEventAction>,\n  code: Option<i32>,\n}\n\nimpl ExitRequestApi {\n  /// Prevents the app from exiting.\n  ///\n  /// **Note:** This is ignored when using [`AppHandle#method.restart`].\n  pub fn prevent_exit(&self) {\n    if self.code != Some(RESTART_EXIT_CODE) {\n      self.tx.send(ExitRequestedEventAction::Prevent).unwrap();\n    }\n  }\n}\n\n/// Api exposed on the `CloseRequested` event.\n#[derive(Debug, Clone)]\npub struct CloseRequestApi(Sender<bool>);\n\nimpl CloseRequestApi {\n  /// Prevents the window from being closed.\n  pub fn prevent_close(&self) {\n    self.0.send(true).unwrap();\n  }\n}\n\n/// An event from a window.\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub enum WindowEvent {\n  /// The size of the window has changed. Contains the client area's new dimensions.\n  Resized(PhysicalSize<u32>),\n  /// The position of the window has changed. Contains the window's new position.\n  Moved(PhysicalPosition<i32>),\n  /// The window has been requested to close.\n  #[non_exhaustive]\n  CloseRequested {\n    /// An API modify the behavior of the close requested event.\n    api: CloseRequestApi,\n  },\n  /// The window has been destroyed.\n  Destroyed,\n  /// The window gained or lost focus.\n  ///\n  /// The parameter is true if the window has gained focus, and false if it has lost focus.\n  Focused(bool),\n  /// The window's scale factor has changed.\n  ///\n  /// The following user actions can cause DPI changes:\n  ///\n  /// - Changing the display's resolution.\n  /// - Changing the display's scale factor (e.g. in Control Panel on Windows).\n  /// - Moving the window to a display with a different scale factor.\n  #[non_exhaustive]\n  ScaleFactorChanged {\n    /// The new scale factor.\n    scale_factor: f64,\n    /// The window inner size.\n    new_inner_size: PhysicalSize<u32>,\n  },\n  /// An event associated with the drag and drop action.\n  DragDrop(DragDropEvent),\n  /// The system window theme has changed. Only delivered if the window [`theme`](`crate::window::WindowBuilder#method.theme`) is `None`.\n  ///\n  /// Applications might wish to react to this to change the theme of the content of the window when the system changes the window theme.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux**: Not supported.\n  ThemeChanged(Theme),\n}\n\nimpl From<RuntimeWindowEvent> for WindowEvent {\n  fn from(event: RuntimeWindowEvent) -> Self {\n    match event {\n      RuntimeWindowEvent::Resized(size) => Self::Resized(size),\n      RuntimeWindowEvent::Moved(position) => Self::Moved(position),\n      RuntimeWindowEvent::CloseRequested { signal_tx } => Self::CloseRequested {\n        api: CloseRequestApi(signal_tx),\n      },\n      RuntimeWindowEvent::Destroyed => Self::Destroyed,\n      RuntimeWindowEvent::Focused(flag) => Self::Focused(flag),\n      RuntimeWindowEvent::ScaleFactorChanged {\n        scale_factor,\n        new_inner_size,\n      } => Self::ScaleFactorChanged {\n        scale_factor,\n        new_inner_size,\n      },\n      RuntimeWindowEvent::DragDrop(event) => Self::DragDrop(event),\n      RuntimeWindowEvent::ThemeChanged(theme) => Self::ThemeChanged(theme),\n    }\n  }\n}\n\n/// An event from a window.\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub enum WebviewEvent {\n  /// An event associated with the drag and drop action.\n  DragDrop(DragDropEvent),\n}\n\nimpl From<RuntimeWebviewEvent> for WebviewEvent {\n  fn from(event: RuntimeWebviewEvent) -> Self {\n    match event {\n      RuntimeWebviewEvent::DragDrop(e) => Self::DragDrop(e),\n    }\n  }\n}\n\n/// An application event, triggered from the event loop.\n///\n/// See [`App::run`](crate::App#method.run) for usage examples.\n#[derive(Debug)]\n#[non_exhaustive]\npub enum RunEvent {\n  /// Event loop is exiting.\n  Exit,\n  /// The app is about to exit\n  #[non_exhaustive]\n  ExitRequested {\n    /// Exit code.\n    /// [`Option::None`] when the exit is requested by user interaction,\n    /// [`Option::Some`] when requested programmatically via [`AppHandle#method.exit`] and [`AppHandle#method.restart`].\n    code: Option<i32>,\n    /// Event API\n    api: ExitRequestApi,\n  },\n  /// An event associated with a window.\n  #[non_exhaustive]\n  WindowEvent {\n    /// The window label.\n    label: String,\n    /// The detailed event.\n    event: WindowEvent,\n  },\n  /// An event associated with a webview.\n  #[non_exhaustive]\n  WebviewEvent {\n    /// The window label.\n    label: String,\n    /// The detailed event.\n    event: WebviewEvent,\n  },\n  /// Application ready.\n  Ready,\n  /// Sent if the event loop is being resumed.\n  Resumed,\n  /// Emitted when all of the event loop's input events have been processed and redraw processing is about to begin.\n  ///\n  /// This event is useful as a place to put your code that should be run after all state-changing events have been handled and you want to do stuff (updating state, performing calculations, etc) that happens as the \"main body\" of your event loop.\n  MainEventsCleared,\n  /// Emitted when the user wants to open the specified resource with the app.\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(target_os = \"macos\", feature = \"ios\"))))]\n  Opened {\n    /// The URL of the resources that is being open.\n    urls: Vec<url::Url>,\n  },\n  /// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu.\n  #[cfg(desktop)]\n  #[cfg_attr(docsrs, doc(cfg(desktop)))]\n  MenuEvent(crate::menu::MenuEvent),\n  /// An event from a tray icon.\n  #[cfg(all(desktop, feature = \"tray-icon\"))]\n  #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = \"tray-icon\"))))]\n  TrayIconEvent(crate::tray::TrayIconEvent),\n  /// Emitted when the NSApplicationDelegate's applicationShouldHandleReopen gets called\n  #[non_exhaustive]\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  Reopen {\n    /// Indicates whether the NSApplication object found any visible windows in your application.\n    has_visible_windows: bool,\n  },\n}\n\nimpl From<EventLoopMessage> for RunEvent {\n  fn from(event: EventLoopMessage) -> Self {\n    match event {\n      #[cfg(desktop)]\n      EventLoopMessage::MenuEvent(e) => Self::MenuEvent(e),\n      #[cfg(all(desktop, feature = \"tray-icon\"))]\n      EventLoopMessage::TrayIconEvent(e) => Self::TrayIconEvent(e),\n    }\n  }\n}\n\n/// The asset resolver is a helper to access the [`tauri_utils::assets::Assets`] interface.\n#[derive(Debug, Clone)]\npub struct AssetResolver<R: Runtime> {\n  manager: Arc<AppManager<R>>,\n}\n\nimpl<R: Runtime> AssetResolver<R> {\n  /// Gets the app asset associated with the given path.\n  ///\n  /// By default it tries to infer your application's URL scheme in production by checking if all webviews\n  /// were configured with [`crate::webview::WebviewBuilder::use_https_scheme`] or `tauri.conf.json > app > windows > useHttpsScheme`.\n  /// If you are resolving an asset for a webview with a more dynamic configuration, see [`AssetResolver::get_for_scheme`].\n  ///\n  /// In production, this resolves to the embedded asset bundled in the app executable\n  /// which contains your frontend assets in [`frontendDist`](https://v2.tauri.app/reference/config/#frontenddist) during build time.\n  ///\n  /// In dev mode, if [`devUrl`](https://v2.tauri.app/reference/config/#devurl) is set, we don't bundle the assets to reduce re-builds,\n  /// and this will fall back to read from `frontendDist` directly.\n  /// Note that the dist directory must exist so you might need to build your frontend assets first.\n  pub fn get(&self, path: String) -> Option<Asset> {\n    let use_https_scheme = self\n      .manager\n      .webviews()\n      .values()\n      .all(|webview| webview.use_https_scheme());\n    self.get_for_scheme(path, use_https_scheme)\n  }\n\n  ///  Same as [`AssetResolver::get`] but resolves the custom protocol scheme based on a parameter.\n  ///\n  /// - `use_https_scheme`: If `true` when using [`Pattern::Isolation`](crate::Pattern::Isolation),\n  ///   the csp header will contain `https://tauri.localhost` instead of `http://tauri.localhost`\n  pub fn get_for_scheme(&self, path: String, use_https_scheme: bool) -> Option<Asset> {\n    #[cfg(dev)]\n    {\n      // We don't bundle the assets when in dev mode and `devUrl` is set, so fall back to read from `frontendDist` directly\n      // TODO: Maybe handle `FrontendDist::Files` as well\n      if let (Some(_), Some(crate::utils::config::FrontendDist::Directory(dist_path))) = (\n        &self.manager.config().build.dev_url,\n        &self.manager.config().build.frontend_dist,\n      ) {\n        let asset_path = std::path::PathBuf::from(&path)\n          .components()\n          .filter(|c| !matches!(c, std::path::Component::RootDir))\n          .collect::<std::path::PathBuf>();\n\n        let asset_path = self\n          .manager\n          .config_parent()\n          .map(|p| p.join(dist_path).join(&asset_path))\n          .unwrap_or_else(|| dist_path.join(&asset_path));\n        return std::fs::read(asset_path).ok().map(|bytes| {\n          let mime_type = crate::utils::mime_type::MimeType::parse(&bytes, &path);\n          Asset {\n            bytes,\n            mime_type,\n            csp_header: None,\n          }\n        });\n      }\n    }\n\n    self.manager.get_asset(path, use_https_scheme).ok()\n  }\n\n  /// Iterate on all assets.\n  pub fn iter(&self) -> Box<AssetsIter<'_>> {\n    self.manager.assets.iter()\n  }\n}\n\n/// A handle to the currently running application.\n///\n/// This type implements [`Manager`] which allows for manipulation of global application items.\n#[default_runtime(crate::Wry, wry)]\n#[derive(Debug)]\npub struct AppHandle<R: Runtime> {\n  pub(crate) runtime_handle: R::Handle,\n  pub(crate) manager: Arc<AppManager<R>>,\n  event_loop: Arc<Mutex<EventLoop>>,\n}\n\n/// Not the real event loop, only contains the main thread id of the event loop\n#[derive(Debug)]\nstruct EventLoop {\n  main_thread_id: ThreadId,\n}\n\n/// APIs specific to the wry runtime.\n#[cfg(feature = \"wry\")]\nimpl AppHandle<crate::Wry> {\n  /// Create a new tao window using a callback. The event loop must be running at this point.\n  pub fn create_tao_window<\n    F: FnOnce() -> (String, tauri_runtime_wry::TaoWindowBuilder) + Send + 'static,\n  >(\n    &self,\n    f: F,\n  ) -> crate::Result<std::sync::Weak<tauri_runtime_wry::Window>> {\n    self.runtime_handle.create_tao_window(f).map_err(Into::into)\n  }\n\n  /// Sends a window message to the event loop.\n  pub fn send_tao_window_event(\n    &self,\n    window_id: tauri_runtime_wry::TaoWindowId,\n    message: tauri_runtime_wry::WindowMessage,\n  ) -> crate::Result<()> {\n    self\n      .runtime_handle\n      .send_event(tauri_runtime_wry::Message::Window(\n        self.runtime_handle.window_id(window_id),\n        message,\n      ))\n      .map_err(Into::into)\n  }\n}\n\n#[cfg(target_vendor = \"apple\")]\nimpl<R: Runtime> AppHandle<R> {\n  /// Fetches all Data Store Identifiers by this app\n  ///\n  /// Needs to be called from Main Thread\n  pub async fn fetch_data_store_identifiers(&self) -> crate::Result<Vec<[u8; 16]>> {\n    let (tx, rx) = tokio::sync::oneshot::channel::<Result<Vec<[u8; 16]>, tauri_runtime::Error>>();\n    let lock: Arc<Mutex<Option<_>>> = Arc::new(Mutex::new(Some(tx)));\n    let runtime_handle = self.runtime_handle.clone();\n\n    self.run_on_main_thread(move || {\n      let cloned_lock = lock.clone();\n      if let Err(err) = runtime_handle.fetch_data_store_identifiers(move |ids| {\n        if let Some(tx) = cloned_lock.lock().unwrap().take() {\n          let _ = tx.send(Ok(ids));\n        }\n      }) {\n        if let Some(tx) = lock.lock().unwrap().take() {\n          let _ = tx.send(Err(err));\n        }\n      }\n    })?;\n\n    rx.await?.map_err(Into::into)\n  }\n  /// Deletes a Data Store of this app\n  ///\n  /// Needs to be called from Main Thread\n  pub async fn remove_data_store(&self, uuid: [u8; 16]) -> crate::Result<()> {\n    let (tx, rx) = tokio::sync::oneshot::channel::<Result<(), tauri_runtime::Error>>();\n    let lock: Arc<Mutex<Option<_>>> = Arc::new(Mutex::new(Some(tx)));\n    let runtime_handle = self.runtime_handle.clone();\n\n    self.run_on_main_thread(move || {\n      let cloned_lock = lock.clone();\n      if let Err(err) = runtime_handle.remove_data_store(uuid, move |result| {\n        if let Some(tx) = cloned_lock.lock().unwrap().take() {\n          let _ = tx.send(result);\n        }\n      }) {\n        if let Some(tx) = lock.lock().unwrap().take() {\n          let _ = tx.send(Err(err));\n        }\n      }\n    })?;\n    rx.await?.map_err(Into::into)\n  }\n}\n\nimpl<R: Runtime> Clone for AppHandle<R> {\n  fn clone(&self) -> Self {\n    Self {\n      runtime_handle: self.runtime_handle.clone(),\n      manager: self.manager.clone(),\n      event_loop: self.event_loop.clone(),\n    }\n  }\n}\n\nimpl<'de, R: Runtime> CommandArg<'de, R> for AppHandle<R> {\n  /// Grabs the [`Window`] from the [`CommandItem`] and returns the associated [`AppHandle`]. This will never fail.\n  fn from_command(command: CommandItem<'de, R>) -> std::result::Result<Self, InvokeError> {\n    Ok(command.message.webview().app_handle)\n  }\n}\n\nimpl<R: Runtime> AppHandle<R> {\n  /// Runs the given closure on the main thread.\n  pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {\n    self\n      .runtime_handle\n      .run_on_main_thread(f)\n      .map_err(Into::into)\n  }\n\n  /// Adds a Tauri application plugin.\n  /// This function can be used to register a plugin that is loaded dynamically e.g. after login.\n  /// For plugins that are created when the app is started, prefer [`Builder::plugin`].\n  ///\n  /// See [`Builder::plugin`] for more information.\n  ///\n  /// # Examples\n  ///\n  /// ```\n  /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, Runtime};\n  ///\n  /// fn init_plugin<R: Runtime>() -> TauriPlugin<R> {\n  ///   PluginBuilder::new(\"dummy\").build()\n  /// }\n  ///\n  /// tauri::Builder::default()\n  ///   .setup(move |app| {\n  ///     let handle = app.handle().clone();\n  ///     std::thread::spawn(move || {\n  ///       handle.plugin(init_plugin());\n  ///     });\n  ///\n  ///     Ok(())\n  ///   });\n  /// ```\n  pub fn plugin<P: Plugin<R> + 'static>(&self, plugin: P) -> crate::Result<()> {\n    self.plugin_boxed(Box::new(plugin))\n  }\n\n  /// Adds a Tauri application plugin.\n  ///\n  /// This method is similar to [`Self::plugin`],\n  /// but accepts a boxed trait object instead of a generic type.\n  #[cfg_attr(feature = \"tracing\", tracing::instrument(name = \"app::plugin::register\", skip(plugin), fields(name = plugin.name())))]\n  pub fn plugin_boxed(&self, mut plugin: Box<dyn Plugin<R>>) -> crate::Result<()> {\n    let mut store = self.manager().plugins.lock().unwrap();\n    store.initialize(&mut plugin, self, &self.config().plugins)?;\n    store.register(plugin);\n\n    Ok(())\n  }\n\n  /// Removes the plugin with the given name.\n  ///\n  /// # Examples\n  ///\n  /// ```\n  /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin, Plugin}, Runtime};\n  ///\n  /// fn init_plugin<R: Runtime>() -> TauriPlugin<R> {\n  ///   PluginBuilder::new(\"dummy\").build()\n  /// }\n  ///\n  /// let plugin = init_plugin();\n  /// // `.name()` requires the `Plugin` trait import\n  /// let plugin_name = plugin.name();\n  /// tauri::Builder::default()\n  ///   .plugin(plugin)\n  ///   .setup(move |app| {\n  ///     let handle = app.handle().clone();\n  ///     std::thread::spawn(move || {\n  ///       handle.remove_plugin(plugin_name);\n  ///     });\n  ///\n  ///     Ok(())\n  ///   });\n  /// ```\n  pub fn remove_plugin(&self, plugin: &str) -> bool {\n    self.manager().plugins.lock().unwrap().unregister(plugin)\n  }\n\n  /// Exits the app by triggering [`RunEvent::ExitRequested`] and [`RunEvent::Exit`].\n  pub fn exit(&self, exit_code: i32) {\n    if let Err(e) = self.runtime_handle.request_exit(exit_code) {\n      log::error!(\"failed to exit: {}\", e);\n      self.cleanup_before_exit();\n      std::process::exit(exit_code);\n    }\n  }\n\n  /// Restarts the app by triggering [`RunEvent::ExitRequested`] with code [`RESTART_EXIT_CODE`](crate::RESTART_EXIT_CODE) and [`RunEvent::Exit`].\n  ///\n  /// When this function is called on the main thread, we cannot guarantee the delivery of those events,\n  /// so we skip them and directly restart the process.\n  ///\n  /// If you want to trigger them reliably, use [`Self::request_restart`] instead\n  pub fn restart(&self) -> ! {\n    if self.event_loop.lock().unwrap().main_thread_id == std::thread::current().id() {\n      log::debug!(\"restart triggered on the main thread\");\n      self.cleanup_before_exit();\n      crate::process::restart(&self.env());\n    } else {\n      log::debug!(\"restart triggered from a separate thread\");\n      // we're running on a separate thread, so we must trigger the exit request and wait for it to finish\n      self\n        .manager\n        .restart_on_exit\n        .store(true, atomic::Ordering::Relaxed);\n      // We'll be restarting when we receive the next `RuntimeRunEvent::Exit` event in `App::run` if this call succeed\n      match self.runtime_handle.request_exit(RESTART_EXIT_CODE) {\n        Ok(()) => loop {\n          std::thread::sleep(Duration::MAX);\n        },\n        Err(e) => {\n          log::error!(\"failed to request exit: {e}\");\n          self.cleanup_before_exit();\n          crate::process::restart(&self.env());\n        }\n      }\n    }\n  }\n\n  /// Restarts the app by triggering [`RunEvent::ExitRequested`] with code [`RESTART_EXIT_CODE`] and [`RunEvent::Exit`].\n  pub fn request_restart(&self) {\n    self\n      .manager\n      .restart_on_exit\n      .store(true, atomic::Ordering::Relaxed);\n    // We'll be restarting when we receive the next `RuntimeRunEvent::Exit` event in `App::run` if this call succeed\n    if self.runtime_handle.request_exit(RESTART_EXIT_CODE).is_err() {\n      self.cleanup_before_exit();\n      crate::process::restart(&self.env());\n    }\n  }\n\n  /// Sets the activation policy for the application. It is set to `NSApplicationActivationPolicyRegular` by default.\n  ///\n  /// # Examples\n  /// ```,no_run\n  /// tauri::Builder::default()\n  ///   .setup(move |app| {\n  ///     #[cfg(target_os = \"macos\")]\n  ///     app.handle().set_activation_policy(tauri::ActivationPolicy::Accessory);\n  ///     Ok(())\n  ///   });\n  /// ```\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  pub fn set_activation_policy(&self, activation_policy: ActivationPolicy) -> crate::Result<()> {\n    self\n      .runtime_handle\n      .set_activation_policy(activation_policy)\n      .map_err(Into::into)\n  }\n\n  /// Sets the dock visibility for the application.\n  ///\n  /// # Examples\n  /// ```,no_run\n  /// tauri::Builder::default()\n  ///   .setup(move |app| {\n  ///     #[cfg(target_os = \"macos\")]\n  ///     app.handle().set_dock_visibility(false);\n  ///     Ok(())\n  ///   });\n  /// ```\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  pub fn set_dock_visibility(&self, visible: bool) -> crate::Result<()> {\n    self\n      .runtime_handle\n      .set_dock_visibility(visible)\n      .map_err(Into::into)\n  }\n\n  /// Change the device event filter mode.\n  ///\n  /// See [App::set_device_event_filter] for details.\n  ///\n  /// ## Platform-specific\n  ///\n  /// See [App::set_device_event_filter] for details.\n  pub fn set_device_event_filter(&self, filter: DeviceEventFilter) {\n    self.runtime_handle.set_device_event_filter(filter);\n  }\n}\n\nimpl<R: Runtime> Manager<R> for AppHandle<R> {\n  fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {\n    self.manager.resources_table()\n  }\n}\n\nimpl<R: Runtime> ManagerBase<R> for AppHandle<R> {\n  fn manager(&self) -> &AppManager<R> {\n    &self.manager\n  }\n\n  fn manager_owned(&self) -> Arc<AppManager<R>> {\n    self.manager.clone()\n  }\n\n  fn runtime(&self) -> RuntimeOrDispatch<'_, R> {\n    RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone())\n  }\n\n  fn managed_app_handle(&self) -> &AppHandle<R> {\n    self\n  }\n}\n\n/// The instance of the currently running application.\n///\n/// This type implements [`Manager`] which allows for manipulation of global application items.\n#[default_runtime(crate::Wry, wry)]\npub struct App<R: Runtime> {\n  runtime: Option<R>,\n  setup: Option<SetupHook<R>>,\n  manager: Arc<AppManager<R>>,\n  handle: AppHandle<R>,\n  ran_setup: bool,\n}\n\nimpl<R: Runtime> fmt::Debug for App<R> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"App\")\n      .field(\"runtime\", &self.runtime)\n      .field(\"manager\", &self.manager)\n      .field(\"handle\", &self.handle)\n      .finish()\n  }\n}\n\nimpl<R: Runtime> Manager<R> for App<R> {\n  fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {\n    self.manager.resources_table()\n  }\n}\n\nimpl<R: Runtime> ManagerBase<R> for App<R> {\n  fn manager(&self) -> &AppManager<R> {\n    &self.manager\n  }\n\n  fn manager_owned(&self) -> Arc<AppManager<R>> {\n    self.manager.clone()\n  }\n\n  fn runtime(&self) -> RuntimeOrDispatch<'_, R> {\n    if let Some(runtime) = self.runtime.as_ref() {\n      RuntimeOrDispatch::Runtime(runtime)\n    } else {\n      self.handle.runtime()\n    }\n  }\n\n  fn managed_app_handle(&self) -> &AppHandle<R> {\n    self.handle()\n  }\n}\n\n/// APIs specific to the wry runtime.\n#[cfg(feature = \"wry\")]\nimpl App<crate::Wry> {\n  /// Adds a [`tauri_runtime_wry::Plugin`] using its [`tauri_runtime_wry::PluginBuilder`].\n  ///\n  /// # Stability\n  ///\n  /// This API is unstable.\n  pub fn wry_plugin<P: tauri_runtime_wry::PluginBuilder<EventLoopMessage> + Send + 'static>(\n    &mut self,\n    plugin: P,\n  ) where\n    <P as tauri_runtime_wry::PluginBuilder<EventLoopMessage>>::Plugin: Send,\n  {\n    self.handle.runtime_handle.plugin(plugin);\n  }\n}\n\nmacro_rules! shared_app_impl {\n  ($app: ty) => {\n    impl<R: Runtime> $app {\n      /// Registers a global menu event listener.\n      #[cfg(desktop)]\n      pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Send + Sync + 'static>(\n        &self,\n        handler: F,\n      ) {\n        self.manager.menu.on_menu_event(handler)\n      }\n\n      /// Registers a global tray icon menu event listener.\n      #[cfg(all(desktop, feature = \"tray-icon\"))]\n      #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = \"tray-icon\"))))]\n      pub fn on_tray_icon_event<F: Fn(&AppHandle<R>, TrayIconEvent) + Send + Sync + 'static>(\n        &self,\n        handler: F,\n      ) {\n        self.manager.tray.on_tray_icon_event(handler)\n      }\n\n      /// Gets a tray icon using the provided id.\n      #[cfg(all(desktop, feature = \"tray-icon\"))]\n      #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = \"tray-icon\"))))]\n      pub fn tray_by_id<'a, I>(&self, id: &'a I) -> Option<TrayIcon<R>>\n      where\n        I: ?Sized,\n        TrayIconId: PartialEq<&'a I>,\n      {\n        self.manager.tray.tray_by_id(self.app_handle(), id)\n      }\n\n      /// Removes a tray icon using the provided id from tauri's internal state and returns it.\n      ///\n      /// Note that dropping the returned icon, may cause the tray icon to disappear\n      /// if it wasn't cloned somewhere else or referenced by JS.\n      #[cfg(all(desktop, feature = \"tray-icon\"))]\n      #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = \"tray-icon\"))))]\n      pub fn remove_tray_by_id<'a, I>(&self, id: &'a I) -> Option<TrayIcon<R>>\n      where\n        I: ?Sized,\n        TrayIconId: PartialEq<&'a I>,\n      {\n        self.manager.tray.remove_tray_by_id(self.app_handle(), id)\n      }\n\n      /// Gets the app's configuration, defined on the `tauri.conf.json` file.\n      pub fn config(&self) -> &Config {\n        self.manager.config()\n      }\n\n      /// Gets the app's package information.\n      pub fn package_info(&self) -> &PackageInfo {\n        self.manager.package_info()\n      }\n\n      /// The application's asset resolver.\n      pub fn asset_resolver(&self) -> AssetResolver<R> {\n        AssetResolver {\n          manager: self.manager.clone(),\n        }\n      }\n\n      /// Returns the primary monitor of the system.\n      ///\n      /// Returns None if it can't identify any monitor as a primary one.\n      pub fn primary_monitor(&self) -> crate::Result<Option<Monitor>> {\n        Ok(match self.runtime() {\n          RuntimeOrDispatch::Runtime(h) => h.primary_monitor().map(Into::into),\n          RuntimeOrDispatch::RuntimeHandle(h) => h.primary_monitor().map(Into::into),\n          _ => unreachable!(),\n        })\n      }\n\n      /// Returns the monitor that contains the given point.\n      pub fn monitor_from_point(&self, x: f64, y: f64) -> crate::Result<Option<Monitor>> {\n        Ok(match self.runtime() {\n          RuntimeOrDispatch::Runtime(h) => h.monitor_from_point(x, y).map(Into::into),\n          RuntimeOrDispatch::RuntimeHandle(h) => h.monitor_from_point(x, y).map(Into::into),\n          _ => unreachable!(),\n        })\n      }\n\n      /// Returns the list of all the monitors available on the system.\n      pub fn available_monitors(&self) -> crate::Result<Vec<Monitor>> {\n        Ok(match self.runtime() {\n          RuntimeOrDispatch::Runtime(h) => {\n            h.available_monitors().into_iter().map(Into::into).collect()\n          }\n          RuntimeOrDispatch::RuntimeHandle(h) => {\n            h.available_monitors().into_iter().map(Into::into).collect()\n          }\n          _ => unreachable!(),\n        })\n      }\n\n      /// Get the cursor position relative to the top-left hand corner of the desktop.\n      ///\n      /// Note that the top-left hand corner of the desktop is not necessarily the same as the screen.\n      /// If the user uses a desktop with multiple monitors,\n      /// the top-left hand corner of the desktop is the top-left hand corner of the main monitor on Windows and macOS\n      /// or the top-left of the leftmost monitor on X11.\n      ///\n      /// The coordinates can be negative if the top-left hand corner of the window is outside of the visible screen region.\n      pub fn cursor_position(&self) -> crate::Result<PhysicalPosition<f64>> {\n        Ok(match self.runtime() {\n          RuntimeOrDispatch::Runtime(h) => h.cursor_position()?,\n          RuntimeOrDispatch::RuntimeHandle(h) => h.cursor_position()?,\n          _ => unreachable!(),\n        })\n      }\n\n      /// Sets the app theme.\n      ///\n      /// ## Platform-specific\n      ///\n      /// - **iOS / Android:** Unsupported.\n      pub fn set_theme(&self, theme: Option<Theme>) {\n        #[cfg(windows)]\n        for window in self.manager.windows().values() {\n          if let (Some(menu), Ok(hwnd)) = (window.menu(), window.hwnd()) {\n            let raw_hwnd = hwnd.0 as isize;\n            let _ = self.run_on_main_thread(move || {\n              let _ = unsafe {\n                menu.inner().set_theme_for_hwnd(\n                  raw_hwnd,\n                  theme\n                    .map(crate::menu::map_to_menu_theme)\n                    .unwrap_or(muda::MenuTheme::Auto),\n                )\n              };\n            });\n          };\n        }\n        match self.runtime() {\n          RuntimeOrDispatch::Runtime(h) => h.set_theme(theme),\n          RuntimeOrDispatch::RuntimeHandle(h) => h.set_theme(theme),\n          _ => unreachable!(),\n        }\n      }\n\n      /// Returns the default window icon.\n      pub fn default_window_icon(&self) -> Option<&Image<'_>> {\n        self.manager.window.default_icon.as_ref()\n      }\n\n      /// Returns the app-wide menu.\n      #[cfg(desktop)]\n      pub fn menu(&self) -> Option<Menu<R>> {\n        self.manager.menu.menu_lock().clone()\n      }\n\n      /// Sets the app-wide menu and returns the previous one.\n      ///\n      /// If a window was not created with an explicit menu or had one set explicitly,\n      /// this menu will be assigned to it.\n      #[cfg(desktop)]\n      pub fn set_menu(&self, menu: Menu<R>) -> crate::Result<Option<Menu<R>>> {\n        let prev_menu = self.remove_menu()?;\n\n        self.manager.menu.insert_menu_into_stash(&menu);\n\n        self.manager.menu.menu_lock().replace(menu.clone());\n\n        // set it on all windows that don't have one or previously had the app-wide menu\n        #[cfg(not(target_os = \"macos\"))]\n        {\n          for window in self.manager.windows().values() {\n            let has_app_wide_menu = window.has_app_wide_menu() || window.menu().is_none();\n            if has_app_wide_menu {\n              window.set_menu(menu.clone())?;\n              window.menu_lock().replace(crate::window::WindowMenu {\n                is_app_wide: true,\n                menu: menu.clone(),\n              });\n            }\n          }\n        }\n\n        // set it app-wide for macos\n        #[cfg(target_os = \"macos\")]\n        {\n          let menu_ = menu.clone();\n          self.run_on_main_thread(move || {\n            let _ = init_app_menu(&menu_);\n          })?;\n        }\n\n        Ok(prev_menu)\n      }\n\n      /// Remove the app-wide menu and returns it.\n      ///\n      /// If a window was not created with an explicit menu or had one set explicitly,\n      /// this will remove the menu from it.\n      #[cfg(desktop)]\n      pub fn remove_menu(&self) -> crate::Result<Option<Menu<R>>> {\n        let menu = self.manager.menu.menu_lock().as_ref().cloned();\n        #[allow(unused_variables)]\n        if let Some(menu) = menu {\n          // remove from windows that have the app-wide menu\n          #[cfg(not(target_os = \"macos\"))]\n          {\n            for window in self.manager.windows().values() {\n              let has_app_wide_menu = window.has_app_wide_menu();\n              if has_app_wide_menu {\n                window.remove_menu()?;\n                *window.menu_lock() = None;\n              }\n            }\n          }\n\n          // remove app-wide for macos\n          #[cfg(target_os = \"macos\")]\n          {\n            self.run_on_main_thread(move || {\n              menu.inner().remove_for_nsapp();\n            })?;\n          }\n        }\n\n        let prev_menu = self.manager.menu.menu_lock().take();\n\n        self\n          .manager\n          .remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id()));\n\n        Ok(prev_menu)\n      }\n\n      /// Hides the app-wide menu from windows that have it.\n      ///\n      /// If a window was not created with an explicit menu or had one set explicitly,\n      /// this will hide the menu from it.\n      #[cfg(desktop)]\n      pub fn hide_menu(&self) -> crate::Result<()> {\n        #[cfg(not(target_os = \"macos\"))]\n        {\n          let is_app_menu_set = self.manager.menu.menu_lock().is_some();\n          if is_app_menu_set {\n            for window in self.manager.windows().values() {\n              if window.has_app_wide_menu() {\n                window.hide_menu()?;\n              }\n            }\n          }\n        }\n\n        Ok(())\n      }\n\n      /// Shows the app-wide menu for windows that have it.\n      ///\n      /// If a window was not created with an explicit menu or had one set explicitly,\n      /// this will show the menu for it.\n      #[cfg(desktop)]\n      pub fn show_menu(&self) -> crate::Result<()> {\n        #[cfg(not(target_os = \"macos\"))]\n        {\n          let is_app_menu_set = self.manager.menu.menu_lock().is_some();\n          if is_app_menu_set {\n            for window in self.manager.windows().values() {\n              if window.has_app_wide_menu() {\n                window.show_menu()?;\n              }\n            }\n          }\n        }\n\n        Ok(())\n      }\n\n      /// Shows the application, but does not automatically focus it.\n      #[cfg(target_os = \"macos\")]\n      pub fn show(&self) -> crate::Result<()> {\n        match self.runtime() {\n          RuntimeOrDispatch::Runtime(r) => r.show(),\n          RuntimeOrDispatch::RuntimeHandle(h) => h.show()?,\n          _ => unreachable!(),\n        }\n        Ok(())\n      }\n\n      /// Hides the application.\n      #[cfg(target_os = \"macos\")]\n      pub fn hide(&self) -> crate::Result<()> {\n        match self.runtime() {\n          RuntimeOrDispatch::Runtime(r) => r.hide(),\n          RuntimeOrDispatch::RuntimeHandle(h) => h.hide()?,\n          _ => unreachable!(),\n        }\n        Ok(())\n      }\n\n      /// Runs necessary cleanup tasks before exiting the process.\n      /// **You should always exit the tauri app immediately after this function returns and not use any tauri-related APIs.**\n      pub fn cleanup_before_exit(&self) {\n        #[cfg(all(desktop, feature = \"tray-icon\"))]\n        self.manager.tray.icons.lock().unwrap().clear();\n        self.manager.resources_table().clear();\n        for (_, window) in self.manager.windows() {\n          window.resources_table().clear();\n          #[cfg(windows)]\n          let _ = window.hide();\n        }\n        for (_, webview) in self.manager.webviews() {\n          webview.resources_table().clear();\n        }\n      }\n\n      /// Gets the invoke key that must be referenced when using [`crate::webview::InvokeRequest`].\n      ///\n      /// # Security\n      ///\n      /// DO NOT expose this key to third party scripts as might grant access to the backend from external URLs and iframes.\n      pub fn invoke_key(&self) -> &str {\n        self.manager.invoke_key()\n      }\n    }\n\n    impl<R: Runtime> Listener<R> for $app {\n      /// Listen to an event on this app.\n      ///\n      /// # Examples\n      ///\n      /// ```\n      /// use tauri::Listener;\n      ///\n      /// tauri::Builder::default()\n      ///   .setup(|app| {\n      ///     app.listen(\"component-loaded\", move |event| {\n      ///       println!(\"window just loaded a component\");\n      ///     });\n      ///\n      ///     Ok(())\n      ///   });\n      /// ```\n      fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId\n      where\n        F: Fn(Event) + Send + 'static,\n      {\n        let event = EventName::new(event.into()).unwrap();\n        self.manager.listen(event, EventTarget::App, handler)\n      }\n\n      /// Listen to an event on this app only once.\n      ///\n      /// See [`Self::listen`] for more information.\n      fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId\n      where\n        F: FnOnce(Event) + Send + 'static,\n      {\n        let event = EventName::new(event.into()).unwrap();\n        self.manager.once(event, EventTarget::App, handler)\n      }\n\n      /// Unlisten to an event on this app.\n      ///\n      /// # Examples\n      ///\n      /// ```\n      /// use tauri::Listener;\n      ///\n      /// tauri::Builder::default()\n      ///   .setup(|app| {\n      ///     let handler = app.listen(\"component-loaded\", move |event| {\n      ///       println!(\"app just loaded a component\");\n      ///     });\n      ///\n      ///     // stop listening to the event when you do not need it anymore\n      ///     app.unlisten(handler);\n      ///\n      ///     Ok(())\n      ///   });\n      /// ```\n      fn unlisten(&self, id: EventId) {\n        self.manager.unlisten(id)\n      }\n    }\n\n    impl<R: Runtime> Emitter<R> for $app {}\n  };\n}\n\nshared_app_impl!(App<R>);\nshared_app_impl!(AppHandle<R>);\n\nimpl<R: Runtime> App<R> {\n  #[cfg_attr(\n    feature = \"tracing\",\n    tracing::instrument(name = \"app::core_plugins::register\")\n  )]\n  fn register_core_plugins(&self) -> crate::Result<()> {\n    self.handle.plugin(crate::path::plugin::init())?;\n    self.handle.plugin(crate::event::plugin::init(self))?;\n    self.handle.plugin(crate::window::plugin::init())?;\n    self.handle.plugin(crate::webview::plugin::init())?;\n    self.handle.plugin(crate::app::plugin::init())?;\n    self.handle.plugin(crate::resources::plugin::init())?;\n    self.handle.plugin(crate::image::plugin::init())?;\n    #[cfg(desktop)]\n    self.handle.plugin(crate::menu::plugin::init())?;\n    #[cfg(all(desktop, feature = \"tray-icon\"))]\n    self.handle.plugin(crate::tray::plugin::init())?;\n    Ok(())\n  }\n\n  /// Runs the given closure on the main thread.\n  pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {\n    self.app_handle().run_on_main_thread(f)\n  }\n\n  /// Gets a handle to the application instance.\n  pub fn handle(&self) -> &AppHandle<R> {\n    &self.handle\n  }\n\n  /// Sets the activation policy for the application. It is set to `NSApplicationActivationPolicyRegular` by default.\n  ///\n  /// # Examples\n  /// ```,no_run\n  /// tauri::Builder::default()\n  ///   .setup(move |app| {\n  ///     #[cfg(target_os = \"macos\")]\n  ///     app.set_activation_policy(tauri::ActivationPolicy::Accessory);\n  ///     Ok(())\n  ///   });\n  /// ```\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  pub fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {\n    if let Some(runtime) = self.runtime.as_mut() {\n      runtime.set_activation_policy(activation_policy);\n    } else {\n      let _ = self.app_handle().set_activation_policy(activation_policy);\n    }\n  }\n\n  /// Sets the dock visibility for the application.\n  ///\n  /// # Examples\n  /// ```,no_run\n  /// tauri::Builder::default()\n  ///   .setup(move |app| {\n  ///     #[cfg(target_os = \"macos\")]\n  ///     app.set_dock_visibility(false);\n  ///     Ok(())\n  ///   });\n  /// ```\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  pub fn set_dock_visibility(&mut self, visible: bool) {\n    if let Some(runtime) = self.runtime.as_mut() {\n      runtime.set_dock_visibility(visible);\n    } else {\n      let _ = self.app_handle().set_dock_visibility(visible);\n    }\n  }\n\n  /// Change the device event filter mode.\n  ///\n  /// Since the DeviceEvent capture can lead to high CPU usage for unfocused windows, [`tao`]\n  /// will ignore them by default for unfocused windows on Windows. This method allows changing\n  /// the filter to explicitly capture them again.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - ** Linux / macOS / iOS / Android**: Unsupported.\n  ///\n  /// # Examples\n  /// ```,no_run\n  /// let mut app = tauri::Builder::default()\n  ///   // on an actual app, remove the string argument\n  ///   .build(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n  ///   .expect(\"error while building tauri application\");\n  /// app.set_device_event_filter(tauri::DeviceEventFilter::Always);\n  /// app.run(|_app_handle, _event| {});\n  /// ```\n  ///\n  /// [`tao`]: https://crates.io/crates/tao\n  pub fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {\n    self\n      .runtime\n      .as_mut()\n      .unwrap()\n      .set_device_event_filter(filter);\n  }\n\n  /// Runs the application.\n  ///\n  /// This function never returns. When the application finishes, the process is exited directly using [`std::process::exit`].\n  /// See [`run_return`](Self::run_return) if you need to run code after the application event loop exits.\n  ///\n  /// # Panics\n  ///\n  /// This function will panic if the setup-function supplied in [`Builder::setup`] fails.\n  ///\n  /// # Examples\n  /// ```,no_run\n  /// let app = tauri::Builder::default()\n  ///   // on an actual app, remove the string argument\n  ///   .build(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n  ///   .expect(\"error while building tauri application\");\n  /// app.run(|_app_handle, event| match event {\n  ///   tauri::RunEvent::ExitRequested { api, .. } => {\n  ///     api.prevent_exit();\n  ///   }\n  ///   _ => {}\n  /// });\n  /// ```\n  pub fn run<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, callback: F) {\n    self.handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();\n\n    self\n      .runtime\n      .take()\n      .unwrap()\n      .run(self.make_run_event_loop_callback(callback));\n  }\n\n  /// Runs the application, returning its intended exit code.\n  ///\n  /// Note when using [`AppHandle::restart`] and [`AppHandle::request_restart`],\n  /// this function will handle the restart request, exit and restart the app without returning\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS**: Unsupported. The application will fallback to [`run`](Self::run).\n  ///\n  /// # Panics\n  ///\n  /// This function will panic if the setup-function supplied in [`Builder::setup`] fails.\n  ///\n  /// # Examples\n  /// ```,no_run\n  /// let app = tauri::Builder::default()\n  ///   // on an actual app, remove the string argument\n  ///   .build(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n  ///   .expect(\"error while building tauri application\");\n  /// let exit_code = app\n  ///   .run_return(|_app_handle, event| match event {\n  ///     tauri::RunEvent::ExitRequested { api, .. } => {\n  ///      api.prevent_exit();\n  ///     }\n  ///      _ => {}\n  ///   });\n  ///\n  /// std::process::exit(exit_code);\n  /// ```\n  pub fn run_return<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, callback: F) -> i32 {\n    self.handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();\n\n    self\n      .runtime\n      .take()\n      .unwrap()\n      .run_return(self.make_run_event_loop_callback(callback))\n  }\n\n  fn make_run_event_loop_callback<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(\n    mut self,\n    mut callback: F,\n  ) -> impl FnMut(RuntimeRunEvent<EventLoopMessage>) {\n    let app_handle = self.handle().clone();\n    let manager = self.manager.clone();\n\n    move |event| match event {\n      RuntimeRunEvent::Ready => {\n        if let Err(e) = setup(&mut self) {\n          panic!(\"Failed to setup app: {e}\");\n        }\n        let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Ready, &manager);\n        callback(&app_handle, event);\n      }\n      RuntimeRunEvent::Exit => {\n        let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Exit, &manager);\n        callback(&app_handle, event);\n        app_handle.cleanup_before_exit();\n        if self.manager.restart_on_exit.load(atomic::Ordering::Relaxed) {\n          crate::process::restart(&self.env());\n        }\n      }\n      _ => {\n        let event = on_event_loop_event(&app_handle, event, &manager);\n        callback(&app_handle, event);\n      }\n    }\n  }\n\n  /// Runs an iteration of the runtime event loop and immediately return.\n  ///\n  /// Note that when using this API, app cleanup is not automatically done.\n  /// The cleanup calls [`App::cleanup_before_exit`] so you may want to call that function before exiting the application.\n  ///\n  /// # Examples\n  /// ```no_run\n  /// use tauri::Manager;\n  ///\n  /// let mut app = tauri::Builder::default()\n  ///   // on an actual app, remove the string argument\n  ///   .build(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n  ///   .expect(\"error while building tauri application\");\n  ///\n  /// loop {\n  ///   app.run_iteration(|_app, _event| {});\n  ///   if app.webview_windows().is_empty() {\n  ///     app.cleanup_before_exit();\n  ///     break;\n  ///   }\n  /// }\n  /// ```\n  #[cfg(desktop)]\n  #[deprecated(\n    note = \"When called in a loop (as suggested by the name), this function will busy-loop. To re-gain control of control flow after the app has exited, use `App::run_return` instead.\"\n  )]\n  pub fn run_iteration<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(&mut self, mut callback: F) {\n    let manager = self.manager.clone();\n    let app_handle = self.handle().clone();\n\n    if !self.ran_setup {\n      if let Err(e) = setup(self) {\n        panic!(\"Failed to setup app: {e}\");\n      }\n    }\n\n    app_handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();\n\n    self.runtime.as_mut().unwrap().run_iteration(move |event| {\n      let event = on_event_loop_event(&app_handle, event, &manager);\n      callback(&app_handle, event);\n    })\n  }\n}\n\n/// Builds a Tauri application.\n///\n/// # Examples\n/// ```,no_run\n/// tauri::Builder::default()\n///   // on an actual app, remove the string argument\n///   .run(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n///  .expect(\"error while running tauri application\");\n/// ```\n#[allow(clippy::type_complexity)]\npub struct Builder<R: Runtime> {\n  /// A flag indicating that the runtime must be started on an environment that supports the event loop not on the main thread.\n  #[cfg(any(windows, target_os = \"linux\"))]\n  runtime_any_thread: bool,\n\n  /// The JS message handler.\n  invoke_handler: Box<InvokeHandler<R>>,\n\n  /// The script that initializes the `window.__TAURI_INTERNALS__.postMessage` function.\n  pub(crate) invoke_initialization_script: String,\n\n  channel_interceptor: Option<ChannelInterceptor<R>>,\n\n  /// The setup hook.\n  setup: SetupHook<R>,\n\n  /// Page load hook.\n  on_page_load: Option<Arc<OnPageLoad<R>>>,\n\n  /// All passed plugins\n  plugins: PluginStore<R>,\n\n  /// The webview protocols available to all windows.\n  uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,\n\n  /// App state.\n  state: StateManager,\n\n  /// A closure that returns the menu set to all windows.\n  #[cfg(desktop)]\n  menu: Option<Box<dyn FnOnce(&AppHandle<R>) -> crate::Result<Menu<R>> + Send>>,\n\n  /// Menu event listeners for any menu event.\n  #[cfg(desktop)]\n  menu_event_listeners: Vec<GlobalMenuEventListener<AppHandle<R>>>,\n\n  /// Tray event listeners for any tray icon event.\n  #[cfg(all(desktop, feature = \"tray-icon\"))]\n  tray_icon_event_listeners: Vec<GlobalTrayIconEventListener<AppHandle<R>>>,\n\n  /// Enable macOS default menu creation.\n  #[allow(unused)]\n  enable_macos_default_menu: bool,\n\n  /// Window event handlers that listens to all windows.\n  window_event_listeners: Vec<GlobalWindowEventListener<R>>,\n\n  /// Webview event handlers that listens to all webviews.\n  webview_event_listeners: Vec<GlobalWebviewEventListener<R>>,\n\n  /// The device event filter.\n  device_event_filter: DeviceEventFilter,\n\n  pub(crate) invoke_key: String,\n}\n\n#[derive(Template)]\n#[default_template(\"../scripts/ipc-protocol.js\")]\npub(crate) struct InvokeInitializationScript<'a> {\n  /// The function that processes the IPC message.\n  #[raw]\n  pub(crate) process_ipc_message_fn: &'a str,\n  pub(crate) os_name: &'a str,\n  pub(crate) fetch_channel_data_command: &'a str,\n  pub(crate) invoke_key: &'a str,\n}\n\n/// Make `Wry` the default `Runtime` for `Builder`\n#[cfg(feature = \"wry\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"wry\")))]\nimpl Default for Builder<crate::Wry> {\n  fn default() -> Self {\n    Self::new()\n  }\n}\n\n#[cfg(not(feature = \"wry\"))]\n#[cfg_attr(docsrs, doc(cfg(not(feature = \"wry\"))))]\nimpl<R: Runtime> Default for Builder<R> {\n  fn default() -> Self {\n    Self::new()\n  }\n}\n\nimpl<R: Runtime> Builder<R> {\n  /// Creates a new App builder.\n  pub fn new() -> Self {\n    let invoke_key = crate::generate_invoke_key().unwrap();\n\n    Self {\n      #[cfg(any(windows, target_os = \"linux\"))]\n      runtime_any_thread: false,\n      setup: Box::new(|_| Ok(())),\n      invoke_handler: Box::new(|_| false),\n      invoke_initialization_script: InvokeInitializationScript {\n        process_ipc_message_fn: crate::manager::webview::PROCESS_IPC_MESSAGE_FN,\n        os_name: std::env::consts::OS,\n        fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND,\n        invoke_key: &invoke_key.clone(),\n      }\n      .render_default(&Default::default())\n      .unwrap()\n      .into_string(),\n      channel_interceptor: None,\n      on_page_load: None,\n      plugins: PluginStore::default(),\n      uri_scheme_protocols: Default::default(),\n      state: StateManager::new(),\n      #[cfg(desktop)]\n      menu: None,\n      #[cfg(desktop)]\n      menu_event_listeners: Vec::new(),\n      #[cfg(all(desktop, feature = \"tray-icon\"))]\n      tray_icon_event_listeners: Vec::new(),\n      enable_macos_default_menu: true,\n      window_event_listeners: Vec::new(),\n      webview_event_listeners: Vec::new(),\n      device_event_filter: Default::default(),\n      invoke_key,\n    }\n  }\n}\n\nimpl<R: Runtime> Builder<R> {\n  /// Builds a new Tauri application running on any thread, bypassing the main thread requirement.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** on macOS the application *must* be executed on the main thread, so this function is not exposed.\n  #[cfg(any(windows, target_os = \"linux\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(windows, target_os = \"linux\"))))]\n  #[must_use]\n  pub fn any_thread(mut self) -> Self {\n    self.runtime_any_thread = true;\n    self\n  }\n\n  /// Defines the JS message handler callback.\n  ///\n  /// # Examples\n  /// ```\n  /// #[tauri::command]\n  /// fn command_1() -> String {\n  ///   return \"hello world\".to_string();\n  /// }\n  /// tauri::Builder::default()\n  ///   .invoke_handler(tauri::generate_handler![\n  ///     command_1,\n  ///     // etc...\n  ///   ]);\n  /// ```\n  #[must_use]\n  pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self\n  where\n    F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,\n  {\n    self.invoke_handler = Box::new(invoke_handler);\n    self\n  }\n\n  /// Defines a custom JS message system.\n  ///\n  /// The `initialization_script` is a script that initializes `window.__TAURI_INTERNALS__.postMessage`.\n  /// That function must take the `(message: object, options: object)` arguments and send it to the backend.\n  ///\n  /// Additionally, the script must include a `__INVOKE_KEY__` token that is replaced with a value that must be sent with the IPC payload\n  /// to check the integrity of the message by the [`crate::WebviewWindow::on_message`] API, e.g.\n  ///\n  /// ```js\n  /// const invokeKey = __INVOKE_KEY__;\n  /// fetch('my-impl://command', {\n  ///   headers: {\n  ///     'Tauri-Invoke-Key': invokeKey,\n  ///   }\n  /// })\n  /// ```\n  ///\n  /// Note that the implementation details is up to your implementation.\n  #[must_use]\n  pub fn invoke_system(mut self, initialization_script: impl AsRef<str>) -> Self {\n    self.invoke_initialization_script = initialization_script\n      .as_ref()\n      .replace(\"__INVOKE_KEY__\", &format!(\"\\\"{}\\\"\", self.invoke_key));\n    self\n  }\n\n  /// Registers a channel interceptor that can overwrite the default channel implementation.\n  ///\n  /// If the event has been consumed, it must return `true`.\n  ///\n  /// The channel automatically orders the messages, so the third closure argument represents the message number.\n  /// The payload expected by the channel receiver is in the form of `{ id: usize, message: T }`.\n  pub fn channel_interceptor<\n    F: Fn(&Webview<R>, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static,\n  >(\n    mut self,\n    interceptor: F,\n  ) -> Self {\n    self.channel_interceptor.replace(Box::new(interceptor));\n    self\n  }\n\n  /// Append a custom initialization script.\n  ///\n  /// Allow to append custom initialization script instead of replacing entire invoke system.\n  ///\n  /// # Examples\n  ///\n  /// ```\n  /// let custom_script = r#\"\n  /// // A custom call system bridge build on top of tauri invoke system.\n  /// async function invoke(cmd, args = {}) {\n  ///   if (!args) args = {};\n  ///\n  ///   let prefix = \"\";\n  ///\n  ///   if (args?.__module) {\n  ///     prefix = `plugin:hybridcall.${args.__module}|`;\n  ///   }\n  ///\n  ///   const command = `${prefix}tauri_${cmd}`;\n  ///\n  ///   const invoke = window.__TAURI_INTERNALS__.invoke;\n  ///\n  ///   return invoke(command, args).then(result => {\n  ///     if (window.build.debug) {\n  ///       console.log(`call: ${command}`);\n  ///       console.log(`args: ${JSON.stringify(args)}`);\n  ///       console.log(`return: ${JSON.stringify(result)}`);\n  ///     }\n  ///\n  ///     return result;\n  ///   });\n  /// }\n  /// \"#;\n  ///\n  /// tauri::Builder::default()\n  ///   .append_invoke_initialization_script(custom_script);\n  /// ```\n  pub fn append_invoke_initialization_script(\n    mut self,\n    initialization_script: impl AsRef<str>,\n  ) -> Self {\n    self\n      .invoke_initialization_script\n      .push_str(initialization_script.as_ref());\n    self\n  }\n\n  /// Defines the setup hook.\n  ///\n  /// # Examples\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\nuse tauri::Manager;\ntauri::Builder::default()\n  .setup(|app| {\n    let main_window = app.get_webview_window(\"main\").unwrap();\n    main_window.set_title(\"Tauri!\")?;\n    Ok(())\n  });\n```\n  \"####\n  )]\n  #[must_use]\n  pub fn setup<F>(mut self, setup: F) -> Self\n  where\n    F: FnOnce(&mut App<R>) -> std::result::Result<(), Box<dyn std::error::Error>> + Send + 'static,\n  {\n    self.setup = Box::new(setup);\n    self\n  }\n\n  /// Defines the page load hook.\n  #[must_use]\n  pub fn on_page_load<F>(mut self, on_page_load: F) -> Self\n  where\n    F: Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static,\n  {\n    self.on_page_load.replace(Arc::new(on_page_load));\n    self\n  }\n\n  /// Adds a Tauri application plugin.\n  ///\n  /// A plugin is created using the [`crate::plugin::Builder`] struct.Check its documentation for more information.\n  ///\n  /// # Examples\n  ///\n  /// ```\n  /// mod plugin {\n  ///   use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, RunEvent, Runtime};\n  ///\n  ///   // this command can be called in the frontend using `invoke('plugin:window|do_something')`.\n  ///   #[tauri::command]\n  ///   async fn do_something<R: Runtime>(app: tauri::AppHandle<R>, window: tauri::Window<R>) -> Result<(), String> {\n  ///     println!(\"command called\");\n  ///     Ok(())\n  ///   }\n  ///   pub fn init<R: Runtime>() -> TauriPlugin<R> {\n  ///     PluginBuilder::new(\"window\")\n  ///       .setup(|app, api| {\n  ///         // initialize the plugin here\n  ///         Ok(())\n  ///       })\n  ///       .on_event(|app, event| {\n  ///         match event {\n  ///           RunEvent::Ready => {\n  ///             println!(\"app is ready\");\n  ///           }\n  ///           RunEvent::WindowEvent { label, event, .. } => {\n  ///             println!(\"window {} received an event: {:?}\", label, event);\n  ///           }\n  ///           _ => (),\n  ///         }\n  ///       })\n  ///       .invoke_handler(tauri::generate_handler![do_something])\n  ///       .build()\n  ///   }\n  /// }\n  ///\n  /// tauri::Builder::default()\n  ///   .plugin(plugin::init());\n  /// ```\n  #[must_use]\n  pub fn plugin<P: Plugin<R> + 'static>(self, plugin: P) -> Self {\n    self.plugin_boxed(Box::new(plugin))\n  }\n\n  /// Adds a Tauri application plugin.\n  ///\n  /// This method is similar to [`Self::plugin`],\n  /// but accepts a boxed trait object instead of a generic type.\n  #[must_use]\n  pub fn plugin_boxed(mut self, plugin: Box<dyn Plugin<R>>) -> Self {\n    self.plugins.register(plugin);\n    self\n  }\n\n  /// Add `state` to the state managed by the application.\n  ///\n  /// This method can be called any number of times as long as each call\n  /// refers to a different `T`.\n  ///\n  /// Managed state can be retrieved by any command handler via the\n  /// [`crate::State`] guard. In particular, if a value of type `T`\n  /// is managed by Tauri, adding `State<T>` to the list of arguments in a\n  /// command handler instructs Tauri to retrieve the managed value.\n  /// Additionally, [`state`](crate::Manager#method.state) can be used to retrieve the value manually.\n  ///\n  /// # Panics\n  ///\n  /// Panics if state of type `T` is already being managed.\n  ///\n  /// # Mutability\n  ///\n  /// Since the managed state is global and must be [`Send`] + [`Sync`], mutations can only happen through interior mutability:\n  ///\n  /// ```,no_run\n  /// use std::{collections::HashMap, sync::Mutex};\n  /// use tauri::State;\n  /// // here we use Mutex to achieve interior mutability\n  /// struct Storage {\n  ///   store: Mutex<HashMap<u64, String>>,\n  /// }\n  /// struct Connection;\n  /// struct DbConnection {\n  ///   db: Mutex<Option<Connection>>,\n  /// }\n  ///\n  /// #[tauri::command]\n  /// fn connect(connection: State<DbConnection>) {\n  ///   // initialize the connection, mutating the state with interior mutability\n  ///   *connection.db.lock().unwrap() = Some(Connection {});\n  /// }\n  ///\n  /// #[tauri::command]\n  /// fn storage_insert(key: u64, value: String, storage: State<Storage>) {\n  ///   // mutate the storage behind the Mutex\n  ///   storage.store.lock().unwrap().insert(key, value);\n  /// }\n  ///\n  /// tauri::Builder::default()\n  ///   .manage(Storage { store: Default::default() })\n  ///   .manage(DbConnection { db: Default::default() })\n  ///   .invoke_handler(tauri::generate_handler![connect, storage_insert])\n  ///   // on an actual app, remove the string argument\n  ///   .run(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n  ///   .expect(\"error while running tauri application\");\n  /// ```\n  ///\n  /// # Examples\n  ///\n  /// ```,no_run\n  /// use tauri::State;\n  ///\n  /// struct MyInt(isize);\n  /// struct MyString(String);\n  ///\n  /// #[tauri::command]\n  /// fn int_command(state: State<MyInt>) -> String {\n  ///     format!(\"The stateful int is: {}\", state.0)\n  /// }\n  ///\n  /// #[tauri::command]\n  /// fn string_command<'r>(state: State<'r, MyString>) {\n  ///     println!(\"state: {}\", state.inner().0);\n  /// }\n  ///\n  /// tauri::Builder::default()\n  ///   .manage(MyInt(10))\n  ///   .manage(MyString(\"Hello, managed state!\".to_string()))\n  ///   .invoke_handler(tauri::generate_handler![int_command, string_command])\n  ///   // on an actual app, remove the string argument\n  ///   .run(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n  ///   .expect(\"error while running tauri application\");\n  /// ```\n  #[must_use]\n  pub fn manage<T>(self, state: T) -> Self\n  where\n    T: Send + Sync + 'static,\n  {\n    let type_name = std::any::type_name::<T>();\n    assert!(\n      self.state.set(state),\n      \"state for type '{type_name}' is already being managed\",\n    );\n    self\n  }\n\n  /// Sets the menu to use on all windows.\n  ///\n  /// # Examples\n  /// ```\n  /// use tauri::menu::{Menu, MenuItem, PredefinedMenuItem, Submenu};\n  ///\n  /// tauri::Builder::default()\n  ///   .menu(|handle| Menu::with_items(handle, &[\n  ///     &Submenu::with_items(\n  ///       handle,\n  ///       \"File\",\n  ///       true,\n  ///       &[\n  ///         &PredefinedMenuItem::close_window(handle, None)?,\n  ///         #[cfg(target_os = \"macos\")]\n  ///         &MenuItem::new(handle, \"Hello\", true, None::<&str>)?,\n  ///       ],\n  ///     )?\n  ///   ]));\n  /// ```\n  #[must_use]\n  #[cfg(desktop)]\n  pub fn menu<F: FnOnce(&AppHandle<R>) -> crate::Result<Menu<R>> + Send + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.menu.replace(Box::new(f));\n    self\n  }\n\n  /// Registers an event handler for any menu event.\n  ///\n  /// # Examples\n  /// ```\n  /// use tauri::menu::*;\n  ///\n  /// tauri::Builder::default()\n  ///   .on_menu_event(|app, event| {\n  ///      if event.id() == \"quit\" {\n  ///        app.exit(0);\n  ///      }\n  ///   });\n  /// ```\n  #[must_use]\n  #[cfg(desktop)]\n  pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Send + Sync + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.menu_event_listeners.push(Box::new(f));\n    self\n  }\n\n  /// Registers an event handler for any tray icon event.\n  ///\n  /// # Examples\n  /// ```\n  /// use tauri::Manager;\n  ///\n  /// tauri::Builder::default()\n  ///   .on_tray_icon_event(|app, event| {\n  ///      let tray = app.tray_by_id(event.id()).expect(\"can't find tray icon\");\n  ///      let _ = tray.set_visible(false);\n  ///   });\n  /// ```\n  #[must_use]\n  #[cfg(all(desktop, feature = \"tray-icon\"))]\n  #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = \"tray-icon\"))))]\n  pub fn on_tray_icon_event<F: Fn(&AppHandle<R>, TrayIconEvent) + Send + Sync + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.tray_icon_event_listeners.push(Box::new(f));\n    self\n  }\n\n  /// Enable or disable the default menu on macOS. Enabled by default.\n  ///\n  /// # Examples\n  /// ```\n  /// tauri::Builder::default()\n  ///   .enable_macos_default_menu(false);\n  /// ```\n  #[must_use]\n  pub fn enable_macos_default_menu(mut self, enable: bool) -> Self {\n    self.enable_macos_default_menu = enable;\n    self\n  }\n\n  /// Registers a window event handler for all windows.\n  ///\n  /// # Examples\n  /// ```\n  /// tauri::Builder::default()\n  ///   .on_window_event(|window, event| match event {\n  ///     tauri::WindowEvent::Focused(focused) => {\n  ///       // hide window whenever it loses focus\n  ///       if !focused {\n  ///         window.hide().unwrap();\n  ///       }\n  ///     }\n  ///     _ => {}\n  ///   });\n  /// ```\n  #[must_use]\n  pub fn on_window_event<F: Fn(&Window<R>, &WindowEvent) + Send + Sync + 'static>(\n    mut self,\n    handler: F,\n  ) -> Self {\n    self.window_event_listeners.push(Box::new(handler));\n    self\n  }\n\n  /// Registers a webview event handler for all webviews.\n  ///\n  /// # Examples\n  /// ```\n  /// tauri::Builder::default()\n  ///   .on_webview_event(|window, event| match event {\n  ///     tauri::WebviewEvent::DragDrop(event) => {\n  ///       println!(\"{:?}\", event);\n  ///     }\n  ///     _ => {}\n  ///   });\n  /// ```\n  #[must_use]\n  pub fn on_webview_event<F: Fn(&Webview<R>, &WebviewEvent) + Send + Sync + 'static>(\n    mut self,\n    handler: F,\n  ) -> Self {\n    self.webview_event_listeners.push(Box::new(handler));\n    self\n  }\n\n  /// Registers a URI scheme protocol available to all webviews.\n  ///\n  /// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,\n  /// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows\n  /// and [webkit-web-context-register-uri-scheme](https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-register-uri-scheme) on Linux.\n  ///\n  /// # Arguments\n  ///\n  /// * `uri_scheme` The URI scheme to register, such as `example`.\n  /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes a request and returns a response.\n  ///\n  /// # Examples\n  /// ```\n  /// tauri::Builder::default()\n  ///   .register_uri_scheme_protocol(\"app-files\", |_ctx, request| {\n  ///     // skip leading `/`\n  ///     if let Ok(data) = std::fs::read(&request.uri().path()[1..]) {\n  ///       http::Response::builder()\n  ///         .body(data)\n  ///         .unwrap()\n  ///     } else {\n  ///       http::Response::builder()\n  ///         .status(http::StatusCode::BAD_REQUEST)\n  ///         .header(http::header::CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())\n  ///         .body(\"failed to read file\".as_bytes().to_vec())\n  ///         .unwrap()\n  ///     }\n  ///   });\n  /// ```\n  ///\n  /// # Warning\n  ///\n  /// Pages loaded from a custom protocol will have a different Origin on different platforms.\n  /// Servers which enforce CORS will need to add the exact same Origin header (or `*`) in `Access-Control-Allow-Origin`\n  /// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the\n  /// different Origin headers across platforms:\n  ///\n  /// - macOS, iOS and Linux: `<scheme_name>://localhost/<path>` (so it will be `my-scheme://localhost/path/to/page).\n  /// - Windows and Android: `http://<scheme_name>.localhost/<path>` by default (so it will be `http://my-scheme.localhost/path/to/page`).\n  ///   To use `https` instead of `http`, use [`super::webview::WebviewBuilder::use_https_scheme`].\n  #[must_use]\n  pub fn register_uri_scheme_protocol<\n    N: Into<String>,\n    T: Into<Cow<'static, [u8]>>,\n    H: Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>) -> http::Response<T>\n      + Send\n      + Sync\n      + 'static,\n  >(\n    mut self,\n    uri_scheme: N,\n    protocol_handler: H,\n  ) -> Self {\n    self.uri_scheme_protocols.insert(\n      uri_scheme.into(),\n      Arc::new(UriSchemeProtocol {\n        handler: Box::new(move |ctx, request, responder| {\n          responder.respond(protocol_handler(ctx, request))\n        }),\n      }),\n    );\n    self\n  }\n\n  /// Similar to [`Self::register_uri_scheme_protocol`] but with an asynchronous responder that allows you\n  /// to process the request in a separate thread and respond asynchronously.\n  ///\n  /// # Arguments\n  ///\n  /// * `uri_scheme` The URI scheme to register, such as `example`.\n  /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.\n  ///\n  /// # Examples\n  /// ```\n  /// tauri::Builder::default()\n  ///   .register_asynchronous_uri_scheme_protocol(\"app-files\", |_ctx, request, responder| {\n  ///     // skip leading `/`\n  ///     let path = request.uri().path()[1..].to_string();\n  ///     std::thread::spawn(move || {\n  ///       if let Ok(data) = std::fs::read(path) {\n  ///         responder.respond(\n  ///           http::Response::builder()\n  ///             .body(data)\n  ///             .unwrap()\n  ///         );\n  ///       } else {\n  ///         responder.respond(\n  ///           http::Response::builder()\n  ///             .status(http::StatusCode::BAD_REQUEST)\n  ///             .header(http::header::CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())\n  ///             .body(\"failed to read file\".as_bytes().to_vec())\n  ///             .unwrap()\n  ///         );\n  ///       }\n  ///     });\n  ///   });\n  /// ```\n  ///\n  /// # Warning\n  ///\n  /// Pages loaded from a custom protocol will have a different Origin on different platforms.\n  /// Servers which enforce CORS will need to add the exact same Origin header (or `*`) in `Access-Control-Allow-Origin`\n  /// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the\n  /// different Origin headers across platforms:\n  ///\n  /// - macOS, iOS and Linux: `<scheme_name>://localhost/<path>` (so it will be `my-scheme://localhost/path/to/page).\n  /// - Windows and Android: `http://<scheme_name>.localhost/<path>` by default (so it will be `http://my-scheme.localhost/path/to/page`).\n  ///   To use `https` instead of `http`, use [`super::webview::WebviewBuilder::use_https_scheme`].\n  #[must_use]\n  pub fn register_asynchronous_uri_scheme_protocol<\n    N: Into<String>,\n    H: Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync + 'static,\n  >(\n    mut self,\n    uri_scheme: N,\n    protocol_handler: H,\n  ) -> Self {\n    self.uri_scheme_protocols.insert(\n      uri_scheme.into(),\n      Arc::new(UriSchemeProtocol {\n        handler: Box::new(protocol_handler),\n      }),\n    );\n    self\n  }\n\n  /// Change the device event filter mode.\n  ///\n  /// Since the DeviceEvent capture can lead to high CPU usage for unfocused windows, [`tao`]\n  /// will ignore them by default for unfocused windows on Windows. This method allows changing\n  /// the filter to explicitly capture them again.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - ** Linux / macOS / iOS / Android**: Unsupported.\n  ///\n  /// # Examples\n  /// ```,no_run\n  /// tauri::Builder::default()\n  ///   .device_event_filter(tauri::DeviceEventFilter::Always);\n  /// ```\n  ///\n  /// [`tao`]: https://crates.io/crates/tao\n  pub fn device_event_filter(mut self, filter: DeviceEventFilter) -> Self {\n    self.device_event_filter = filter;\n    self\n  }\n\n  /// Builds the application.\n  #[allow(clippy::type_complexity, unused_mut)]\n  #[cfg_attr(\n    feature = \"tracing\",\n    tracing::instrument(name = \"app::build\", skip_all)\n  )]\n  pub fn build(mut self, context: Context<R>) -> crate::Result<App<R>> {\n    #[cfg(target_os = \"macos\")]\n    if self.menu.is_none() && self.enable_macos_default_menu {\n      self.menu = Some(Box::new(|app_handle| {\n        crate::menu::Menu::default(app_handle)\n      }));\n    }\n\n    let manager = Arc::new(AppManager::with_handlers(\n      context,\n      self.plugins,\n      self.invoke_handler,\n      self.on_page_load,\n      self.uri_scheme_protocols,\n      self.state,\n      #[cfg(desktop)]\n      self.menu_event_listeners,\n      #[cfg(all(desktop, feature = \"tray-icon\"))]\n      self.tray_icon_event_listeners,\n      self.window_event_listeners,\n      self.webview_event_listeners,\n      #[cfg(desktop)]\n      HashMap::new(),\n      self.invoke_initialization_script,\n      self.channel_interceptor,\n      self.invoke_key,\n    ));\n\n    #[cfg(any(\n      target_os = \"linux\",\n      target_os = \"dragonfly\",\n      target_os = \"freebsd\",\n      target_os = \"netbsd\",\n      target_os = \"openbsd\"\n    ))]\n    let app_id = if manager.config.app.enable_gtk_app_id {\n      Some(manager.config.identifier.clone())\n    } else {\n      None\n    };\n\n    let runtime_args = RuntimeInitArgs {\n      #[cfg(any(\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n      ))]\n      app_id,\n\n      #[cfg(windows)]\n      msg_hook: {\n        let menus = manager.menu.menus.clone();\n        Some(Box::new(move |msg| {\n          use windows::Win32::UI::WindowsAndMessaging::{TranslateAcceleratorW, HACCEL, MSG};\n          unsafe {\n            let msg = msg as *const MSG;\n            for menu in menus.lock().unwrap().values() {\n              let translated =\n                TranslateAcceleratorW((*msg).hwnd, HACCEL(menu.inner().haccel() as _), msg);\n              if translated == 1 {\n                return true;\n              }\n            }\n\n            false\n          }\n        }))\n      },\n    };\n\n    // The env var must be set before the Runtime is created so that GetAvailableBrowserVersionString picks it up.\n    #[cfg(windows)]\n    {\n      if let crate::utils::config::WebviewInstallMode::FixedRuntime { path } =\n        &manager.config.bundle.windows.webview_install_mode\n      {\n        if let Some(exe_dir) = crate::utils::platform::current_exe()\n          .ok()\n          .and_then(|p| p.parent().map(|p| p.to_path_buf()))\n        {\n          std::env::set_var(\"WEBVIEW2_BROWSER_EXECUTABLE_FOLDER\", exe_dir.join(path));\n        } else {\n          #[cfg(debug_assertions)]\n          eprintln!(\n            \"failed to resolve resource directory; fallback to the installed Webview2 runtime.\"\n          );\n        }\n      }\n    }\n\n    #[cfg(any(windows, target_os = \"linux\"))]\n    let mut runtime = if self.runtime_any_thread {\n      R::new_any_thread(runtime_args)?\n    } else {\n      R::new(runtime_args)?\n    };\n    #[cfg(not(any(windows, target_os = \"linux\")))]\n    let mut runtime = R::new(runtime_args)?;\n\n    #[cfg(desktop)]\n    {\n      // setup menu event handler\n      let proxy = runtime.create_proxy();\n      muda::MenuEvent::set_event_handler(Some(move |e: muda::MenuEvent| {\n        let _ = proxy.send_event(EventLoopMessage::MenuEvent(e.into()));\n      }));\n\n      // setup tray event handler\n      #[cfg(feature = \"tray-icon\")]\n      {\n        let proxy = runtime.create_proxy();\n        tray_icon::TrayIconEvent::set_event_handler(Some(move |e: tray_icon::TrayIconEvent| {\n          let _ = proxy.send_event(EventLoopMessage::TrayIconEvent(e.into()));\n        }));\n      }\n    }\n\n    runtime.set_device_event_filter(self.device_event_filter);\n\n    let runtime_handle = runtime.handle();\n\n    #[allow(unused_mut)]\n    let mut app = App {\n      runtime: Some(runtime),\n      setup: Some(self.setup),\n      manager: manager.clone(),\n      handle: AppHandle {\n        runtime_handle,\n        manager,\n        event_loop: Arc::new(Mutex::new(EventLoop {\n          main_thread_id: std::thread::current().id(),\n        })),\n      },\n      ran_setup: false,\n    };\n\n    #[cfg(desktop)]\n    if let Some(menu) = self.menu {\n      let menu = menu(&app.handle)?;\n      app\n        .manager\n        .menu\n        .menus_stash_lock()\n        .insert(menu.id().clone(), menu.clone());\n\n      #[cfg(target_os = \"macos\")]\n      init_app_menu(&menu)?;\n\n      app.manager.menu.menu_lock().replace(menu);\n    }\n\n    app.register_core_plugins()?;\n\n    let env = Env::default();\n    app.manage(env);\n\n    app.manage(Scopes {\n      #[cfg(feature = \"protocol-asset\")]\n      asset_protocol: crate::scope::fs::Scope::new(\n        &app,\n        &app.config().app.security.asset_protocol.scope,\n      )?,\n    });\n\n    app.manage(ChannelDataIpcQueue::default());\n    app.handle.plugin(crate::ipc::channel::plugin())?;\n\n    let handle = app.handle();\n\n    // initialize default tray icon if defined\n    #[cfg(all(desktop, feature = \"tray-icon\"))]\n    {\n      let config = app.config();\n      if let Some(tray_config) = &config.app.tray_icon {\n        #[allow(deprecated)]\n        let mut tray =\n          TrayIconBuilder::with_id(tray_config.id.clone().unwrap_or_else(|| \"main\".into()))\n            .icon_as_template(tray_config.icon_as_template)\n            .menu_on_left_click(tray_config.menu_on_left_click)\n            .show_menu_on_left_click(tray_config.show_menu_on_left_click);\n        if let Some(icon) = &app.manager.tray.icon {\n          tray = tray.icon(icon.clone());\n        }\n        if let Some(title) = &tray_config.title {\n          tray = tray.title(title);\n        }\n        if let Some(tooltip) = &tray_config.tooltip {\n          tray = tray.tooltip(tooltip);\n        }\n        tray.build(handle)?;\n      }\n    }\n\n    app.manager.initialize_plugins(handle)?;\n\n    Ok(app)\n  }\n\n  /// Builds the configured application and runs it.\n  ///\n  /// This is a shorthand for [`Self::build`] followed by [`App::run`].\n  /// For more flexibility, consider using those functions manually.\n  pub fn run(self, context: Context<R>) -> crate::Result<()> {\n    self.build(context)?.run(|_, _| {});\n    Ok(())\n  }\n}\n\npub(crate) type UriSchemeResponderFn = Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>;\n\n/// Async uri scheme protocol responder.\npub struct UriSchemeResponder(pub(crate) UriSchemeResponderFn);\n\nimpl UriSchemeResponder {\n  /// Resolves the request with the given response.\n  pub fn respond<T: Into<Cow<'static, [u8]>>>(self, response: http::Response<T>) {\n    let (parts, body) = response.into_parts();\n    (self.0)(http::Response::from_parts(parts, body.into()))\n  }\n}\n\n/// Uri scheme protocol context\npub struct UriSchemeContext<'a, R: Runtime> {\n  pub(crate) app_handle: &'a AppHandle<R>,\n  pub(crate) webview_label: &'a str,\n}\n\nimpl<'a, R: Runtime> UriSchemeContext<'a, R> {\n  /// Get a reference to an [`AppHandle`].\n  pub fn app_handle(&self) -> &'a AppHandle<R> {\n    self.app_handle\n  }\n\n  /// Get the webview label that made the uri scheme request.\n  pub fn webview_label(&self) -> &'a str {\n    self.webview_label\n  }\n}\n\n#[cfg(target_os = \"macos\")]\nfn init_app_menu<R: Runtime>(menu: &Menu<R>) -> crate::Result<()> {\n  menu.inner().init_for_nsapp();\n\n  if let Some(window_menu) = menu.get(crate::menu::WINDOW_SUBMENU_ID) {\n    if let Some(m) = window_menu.as_submenu() {\n      m.set_as_windows_menu_for_nsapp()?;\n    }\n  }\n  if let Some(help_menu) = menu.get(crate::menu::HELP_SUBMENU_ID) {\n    if let Some(m) = help_menu.as_submenu() {\n      m.set_as_help_menu_for_nsapp()?;\n    }\n  }\n\n  Ok(())\n}\n\nimpl<R: Runtime> HasDisplayHandle for AppHandle<R> {\n  fn display_handle(\n    &self,\n  ) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {\n    self.runtime_handle.display_handle()\n  }\n}\n\nimpl<R: Runtime> HasDisplayHandle for App<R> {\n  fn display_handle(\n    &self,\n  ) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {\n    self.handle.display_handle()\n  }\n}\n\n#[cfg_attr(feature = \"tracing\", tracing::instrument(name = \"app::setup\"))]\nfn setup<R: Runtime>(app: &mut App<R>) -> crate::Result<()> {\n  app.ran_setup = true;\n\n  for window_config in app.config().app.windows.iter().filter(|w| w.create) {\n    WebviewWindowBuilder::from_config(app.handle(), window_config)?.build()?;\n  }\n\n  app.manager.assets.setup(app);\n\n  if let Some(setup) = app.setup.take() {\n    (setup)(app).map_err(|e| crate::Error::Setup(e.into()))?;\n  }\n\n  Ok(())\n}\n\nfn on_event_loop_event<R: Runtime>(\n  app_handle: &AppHandle<R>,\n  event: RuntimeRunEvent<EventLoopMessage>,\n  manager: &AppManager<R>,\n) -> RunEvent {\n  if let RuntimeRunEvent::WindowEvent {\n    label,\n    event: RuntimeWindowEvent::Destroyed,\n  } = &event\n  {\n    manager.on_window_close(label);\n  }\n\n  let event = match event {\n    RuntimeRunEvent::Exit => RunEvent::Exit,\n    RuntimeRunEvent::ExitRequested { code, tx } => RunEvent::ExitRequested {\n      code,\n      api: ExitRequestApi { tx, code },\n    },\n    RuntimeRunEvent::WindowEvent { label, event } => RunEvent::WindowEvent {\n      label,\n      event: event.into(),\n    },\n    RuntimeRunEvent::WebviewEvent { label, event } => RunEvent::WebviewEvent {\n      label,\n      event: event.into(),\n    },\n    RuntimeRunEvent::Ready => {\n      // set the app icon in development\n      #[cfg(all(dev, target_os = \"macos\"))]\n      {\n        use objc2::{AllocAnyThread, MainThreadMarker};\n        use objc2_app_kit::{NSApplication, NSImage};\n        use objc2_foundation::NSData;\n\n        if let Some(icon) = app_handle.manager.app_icon.clone() {\n          // TODO: Enable this check.\n          let mtm = unsafe { MainThreadMarker::new_unchecked() };\n          let app = NSApplication::sharedApplication(mtm);\n          let data = NSData::with_bytes(&icon);\n          let app_icon = NSImage::initWithData(NSImage::alloc(), &data).expect(\"creating icon\");\n          unsafe { app.setApplicationIconImage(Some(&app_icon)) };\n        }\n      }\n      RunEvent::Ready\n    }\n    RuntimeRunEvent::Resumed => RunEvent::Resumed,\n    RuntimeRunEvent::MainEventsCleared => RunEvent::MainEventsCleared,\n    RuntimeRunEvent::UserEvent(t) => {\n      match t {\n        #[cfg(desktop)]\n        EventLoopMessage::MenuEvent(ref e) => {\n          for listener in &*app_handle\n            .manager\n            .menu\n            .global_event_listeners\n            .lock()\n            .unwrap()\n          {\n            listener(app_handle, e.clone());\n          }\n          for (label, listener) in &*app_handle.manager.menu.event_listeners.lock().unwrap() {\n            if let Some(w) = app_handle.manager().get_window(label) {\n              listener(&w, e.clone());\n            }\n          }\n        }\n        #[cfg(all(desktop, feature = \"tray-icon\"))]\n        EventLoopMessage::TrayIconEvent(ref e) => {\n          for listener in &*app_handle\n            .manager\n            .tray\n            .global_event_listeners\n            .lock()\n            .unwrap()\n          {\n            listener(app_handle, e.clone());\n          }\n\n          for (id, listener) in &*app_handle.manager.tray.event_listeners.lock().unwrap() {\n            if e.id() == id {\n              if let Some(tray) = app_handle.tray_by_id(id) {\n                listener(&tray, e.clone());\n              }\n            }\n          }\n        }\n      }\n\n      #[allow(unreachable_code)]\n      t.into()\n    }\n    #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n    RuntimeRunEvent::Opened { urls } => RunEvent::Opened { urls },\n    #[cfg(target_os = \"macos\")]\n    RuntimeRunEvent::Reopen {\n      has_visible_windows,\n    } => RunEvent::Reopen {\n      has_visible_windows,\n    },\n    _ => unimplemented!(),\n  };\n\n  manager\n    .plugins\n    .lock()\n    .expect(\"poisoned plugin store\")\n    .on_event(app_handle, &event);\n\n  event\n}\n\n#[cfg(test)]\nmod tests {\n  #[test]\n  fn is_send_sync() {\n    crate::test_utils::assert_send::<super::AppHandle>();\n    crate::test_utils::assert_sync::<super::AppHandle>();\n\n    #[cfg(feature = \"wry\")]\n    {\n      crate::test_utils::assert_send::<super::AssetResolver<crate::Wry>>();\n      crate::test_utils::assert_sync::<super::AssetResolver<crate::Wry>>();\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/async_runtime.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The singleton async runtime used by Tauri and exposed to users.\n//!\n//! Tauri uses [`tokio`] Runtime to initialize code, such as\n//! [`Plugin::initialize`](../plugin/trait.Plugin.html#method.initialize) and [`crate::Builder::setup`] hooks.\n//! This module also re-export some common items most developers need from [`tokio`]. If there's\n//! one you need isn't here, you could use types in [`tokio`] directly.\n//! For custom command handlers, it's recommended to use a plain `async fn` command.\n\npub use tokio::{\n  runtime::{Handle as TokioHandle, Runtime as TokioRuntime},\n  sync::{\n    mpsc::{channel, Receiver, Sender},\n    Mutex, RwLock,\n  },\n  task::JoinHandle as TokioJoinHandle,\n};\n\nuse std::{\n  future::Future,\n  pin::Pin,\n  sync::OnceLock,\n  task::{Context, Poll},\n};\n\nstatic RUNTIME: OnceLock<GlobalRuntime> = OnceLock::new();\n\nstruct GlobalRuntime {\n  runtime: Option<Runtime>,\n  handle: RuntimeHandle,\n}\n\nimpl GlobalRuntime {\n  fn handle(&self) -> RuntimeHandle {\n    if let Some(r) = &self.runtime {\n      r.handle()\n    } else {\n      self.handle.clone()\n    }\n  }\n\n  fn spawn<F>(&self, task: F) -> JoinHandle<F::Output>\n  where\n    F: Future + Send + 'static,\n    F::Output: Send + 'static,\n  {\n    if let Some(r) = &self.runtime {\n      r.spawn(task)\n    } else {\n      self.handle.spawn(task)\n    }\n  }\n\n  pub fn spawn_blocking<F, R>(&self, func: F) -> JoinHandle<R>\n  where\n    F: FnOnce() -> R + Send + 'static,\n    R: Send + 'static,\n  {\n    if let Some(r) = &self.runtime {\n      r.spawn_blocking(func)\n    } else {\n      self.handle.spawn_blocking(func)\n    }\n  }\n\n  fn block_on<F: Future>(&self, task: F) -> F::Output {\n    if let Some(r) = &self.runtime {\n      r.block_on(task)\n    } else {\n      self.handle.block_on(task)\n    }\n  }\n}\n\n/// A runtime used to execute asynchronous tasks.\npub enum Runtime {\n  /// The tokio runtime.\n  Tokio(TokioRuntime),\n}\n\nimpl Runtime {\n  /// Gets a reference to the [`TokioRuntime`].\n  pub fn inner(&self) -> &TokioRuntime {\n    let Self::Tokio(r) = self;\n    r\n  }\n\n  /// Returns a handle of the async runtime.\n  pub fn handle(&self) -> RuntimeHandle {\n    match self {\n      Self::Tokio(r) => RuntimeHandle::Tokio(r.handle().clone()),\n    }\n  }\n\n  /// Spawns a future onto the runtime.\n  pub fn spawn<F>(&self, task: F) -> JoinHandle<F::Output>\n  where\n    F: Future + Send + 'static,\n    F::Output: Send + 'static,\n  {\n    match self {\n      Self::Tokio(r) => {\n        let _guard = r.enter();\n        JoinHandle::Tokio(tokio::spawn(task))\n      }\n    }\n  }\n\n  /// Runs the provided function on an executor dedicated to blocking operations.\n  pub fn spawn_blocking<F, R>(&self, func: F) -> JoinHandle<R>\n  where\n    F: FnOnce() -> R + Send + 'static,\n    R: Send + 'static,\n  {\n    match self {\n      Self::Tokio(r) => JoinHandle::Tokio(r.spawn_blocking(func)),\n    }\n  }\n\n  /// Runs a future to completion on runtime.\n  pub fn block_on<F: Future>(&self, task: F) -> F::Output {\n    match self {\n      Self::Tokio(r) => r.block_on(task),\n    }\n  }\n}\n\n/// An owned permission to join on a task (await its termination).\n#[derive(Debug)]\npub enum JoinHandle<T> {\n  /// The tokio JoinHandle.\n  Tokio(TokioJoinHandle<T>),\n}\n\nimpl<T> JoinHandle<T> {\n  /// Gets a reference to the [`TokioJoinHandle`].\n  pub fn inner(&self) -> &TokioJoinHandle<T> {\n    let Self::Tokio(t) = self;\n    t\n  }\n\n  /// Abort the task associated with the handle.\n  ///\n  /// Awaiting a cancelled task might complete as usual if the task was\n  /// already completed at the time it was cancelled, but most likely it\n  /// will fail with a cancelled `JoinError`.\n  pub fn abort(&self) {\n    match self {\n      Self::Tokio(t) => t.abort(),\n    }\n  }\n}\n\nimpl<T> Future for JoinHandle<T> {\n  type Output = crate::Result<T>;\n  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {\n    match self.get_mut() {\n      Self::Tokio(t) => Pin::new(t).poll(cx).map_err(Into::into),\n    }\n  }\n}\n\n/// A handle to the async runtime\n#[derive(Clone)]\npub enum RuntimeHandle {\n  /// The tokio handle.\n  Tokio(TokioHandle),\n}\n\nimpl RuntimeHandle {\n  /// Gets a reference to the [`TokioHandle`].\n  pub fn inner(&self) -> &TokioHandle {\n    let Self::Tokio(h) = self;\n    h\n  }\n\n  /// Runs the provided function on an executor dedicated to blocking operations.\n  pub fn spawn_blocking<F, R>(&self, func: F) -> JoinHandle<R>\n  where\n    F: FnOnce() -> R + Send + 'static,\n    R: Send + 'static,\n  {\n    match self {\n      Self::Tokio(h) => JoinHandle::Tokio(h.spawn_blocking(func)),\n    }\n  }\n\n  /// Spawns a future onto the runtime.\n  pub fn spawn<F>(&self, task: F) -> JoinHandle<F::Output>\n  where\n    F: Future + Send + 'static,\n    F::Output: Send + 'static,\n  {\n    match self {\n      Self::Tokio(h) => {\n        let _guard = h.enter();\n        JoinHandle::Tokio(tokio::spawn(task))\n      }\n    }\n  }\n\n  /// Runs a future to completion on runtime.\n  pub fn block_on<F: Future>(&self, task: F) -> F::Output {\n    match self {\n      Self::Tokio(h) => h.block_on(task),\n    }\n  }\n}\n\nfn default_runtime() -> GlobalRuntime {\n  let runtime = Runtime::Tokio(TokioRuntime::new().unwrap());\n  let handle = runtime.handle();\n  GlobalRuntime {\n    runtime: Some(runtime),\n    handle,\n  }\n}\n\n/// Sets the runtime to use to execute asynchronous tasks.\n/// For convenience, this method takes a [`TokioHandle`].\n/// Note that you cannot drop the underlying [`TokioRuntime`].\n///\n/// # Examples\n///\n/// ```rust\n/// #[tokio::main]\n/// async fn main() {\n///   // perform some async task before initializing the app\n///   do_something().await;\n///   // share the current runtime with Tauri\n///   tauri::async_runtime::set(tokio::runtime::Handle::current());\n///\n///   // bootstrap the tauri app...\n///   // tauri::Builder::default().run().unwrap();\n/// }\n///\n/// async fn do_something() {}\n/// ```\n///\n/// # Panics\n///\n/// Panics if the runtime is already set.\npub fn set(handle: TokioHandle) {\n  RUNTIME\n    .set(GlobalRuntime {\n      runtime: None,\n      handle: RuntimeHandle::Tokio(handle),\n    })\n    .unwrap_or_else(|_| panic!(\"runtime already initialized\"))\n}\n\n/// Returns a handle of the async runtime.\npub fn handle() -> RuntimeHandle {\n  let runtime = RUNTIME.get_or_init(default_runtime);\n  runtime.handle()\n}\n\n/// Runs a future to completion on runtime.\npub fn block_on<F: Future>(task: F) -> F::Output {\n  let runtime = RUNTIME.get_or_init(default_runtime);\n  runtime.block_on(task)\n}\n\n/// Spawns a future onto the runtime.\npub fn spawn<F>(task: F) -> JoinHandle<F::Output>\nwhere\n  F: Future + Send + 'static,\n  F::Output: Send + 'static,\n{\n  let runtime = RUNTIME.get_or_init(default_runtime);\n  runtime.spawn(task)\n}\n\n/// Runs the provided function on an executor dedicated to blocking operations.\npub fn spawn_blocking<F, R>(func: F) -> JoinHandle<R>\nwhere\n  F: FnOnce() -> R + Send + 'static,\n  R: Send + 'static,\n{\n  let runtime = RUNTIME.get_or_init(default_runtime);\n  runtime.spawn_blocking(func)\n}\n\n#[allow(dead_code)]\npub(crate) fn safe_block_on<F>(task: F) -> F::Output\nwhere\n  F: Future + Send + 'static,\n  F::Output: Send + 'static,\n{\n  if let Ok(handle) = tokio::runtime::Handle::try_current() {\n    let (tx, rx) = std::sync::mpsc::sync_channel(1);\n    let handle_ = handle.clone();\n    handle.spawn_blocking(move || {\n      tx.send(handle_.block_on(task)).unwrap();\n    });\n    rx.recv().unwrap()\n  } else {\n    block_on(task)\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use super::*;\n\n  #[tokio::test]\n  async fn runtime_spawn() {\n    let join = spawn(async { 5 });\n    assert_eq!(join.await.unwrap(), 5);\n  }\n\n  #[test]\n  fn runtime_block_on() {\n    assert_eq!(block_on(async { 0 }), 0);\n  }\n\n  #[tokio::test]\n  async fn handle_spawn() {\n    let handle = handle();\n    let join = handle.spawn(async { 5 });\n    assert_eq!(join.await.unwrap(), 5);\n  }\n\n  #[test]\n  fn handle_block_on() {\n    let handle = handle();\n    assert_eq!(handle.block_on(async { 0 }), 0);\n  }\n\n  #[tokio::test]\n  async fn handle_abort() {\n    let handle = handle();\n    let join = handle.spawn(async {\n      // Here we sleep 1 second to ensure this task to be uncompleted when abort() invoked.\n      tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;\n      5\n    });\n    join.abort();\n    if let crate::Error::JoinError(raw_error) = join.await.unwrap_err() {\n      assert!(raw_error.is_cancelled());\n    } else {\n      panic!(\"Abort did not result in the expected `JoinError`\");\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/error.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::fmt;\n\n/// A generic boxed error.\n#[derive(Debug)]\npub struct SetupError(Box<dyn std::error::Error>);\n\nimpl From<Box<dyn std::error::Error>> for SetupError {\n  fn from(error: Box<dyn std::error::Error>) -> Self {\n    Self(error)\n  }\n}\n\n// safety: the setup error is only used on the main thread\n// and we exit the process immediately.\nunsafe impl Send for SetupError {}\nunsafe impl Sync for SetupError {}\n\nimpl fmt::Display for SetupError {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    self.0.fmt(f)\n  }\n}\n\nimpl std::error::Error for SetupError {}\n\n/// Runtime errors that can happen inside a Tauri application.\n#[derive(Debug, thiserror::Error)]\n#[non_exhaustive]\npub enum Error {\n  /// Runtime error.\n  #[error(\"runtime error: {0}\")]\n  Runtime(#[from] tauri_runtime::Error),\n  /// Window label must be unique.\n  #[error(\"a window with label `{0}` already exists\")]\n  WindowLabelAlreadyExists(String),\n  /// Webview label must be unique.\n  #[error(\"a webview with label `{0}` already exists\")]\n  WebviewLabelAlreadyExists(String),\n  /// Cannot use the webview reparent function on webview windows.\n  #[error(\"cannot reparent when using a WebviewWindow\")]\n  CannotReparentWebviewWindow,\n  /// Embedded asset not found.\n  #[error(\"asset not found: {0}\")]\n  AssetNotFound(String),\n  /// Failed to serialize/deserialize.\n  #[error(\"JSON error: {0}\")]\n  Json(#[from] serde_json::Error),\n  /// IO error.\n  #[error(\"{0}\")]\n  Io(#[from] std::io::Error),\n  /// Failed to load window icon.\n  #[error(\"invalid icon: {0}\")]\n  InvalidIcon(std::io::Error),\n  /// Invalid args when running a command.\n  #[error(\"invalid args `{1}` for command `{0}`: {2}\")]\n  InvalidArgs(&'static str, &'static str, serde_json::Error),\n  /// Encountered an error in the setup hook,\n  #[error(\"error encountered during setup hook: {0}\")]\n  Setup(SetupError),\n  /// Error initializing plugin.\n  #[error(\"failed to initialize plugin `{0}`: {1}\")]\n  PluginInitialization(String, String),\n  /// A part of the URL is malformed or invalid. This may occur when parsing and combining\n  /// user-provided URLs and paths.\n  #[error(\"invalid url: {0}\")]\n  InvalidUrl(url::ParseError),\n  /// Task join error.\n  #[error(transparent)]\n  JoinError(#[from] tokio::task::JoinError),\n  /// An error happened inside the isolation pattern.\n  #[cfg(feature = \"isolation\")]\n  #[error(\"isolation pattern error: {0}\")]\n  IsolationPattern(#[from] tauri_utils::pattern::isolation::Error),\n  /// An invalid window URL was provided. Includes details about the error.\n  #[error(\"invalid window url: {0}\")]\n  InvalidWebviewUrl(&'static str),\n  /// Invalid glob pattern.\n  #[error(\"invalid glob pattern: {0}\")]\n  GlobPattern(#[from] glob::PatternError),\n  /// Image error.\n  #[cfg(any(feature = \"image-png\", feature = \"image-ico\"))]\n  #[error(\"failed to process image: {0}\")]\n  Image(#[from] image::error::ImageError),\n  /// The Window's raw handle is invalid for the platform.\n  #[error(\"Unexpected `raw_window_handle` for the current platform\")]\n  InvalidWindowHandle,\n  /// JNI error.\n  #[cfg(target_os = \"android\")]\n  #[error(\"jni error: {0}\")]\n  Jni(#[from] jni::errors::Error),\n  /// Failed to receive message .\n  #[error(\"failed to receive message\")]\n  FailedToReceiveMessage,\n  /// Menu error.\n  #[error(\"menu error: {0}\")]\n  #[cfg(desktop)]\n  Menu(#[from] muda::Error),\n  /// Bad menu icon error.\n  #[error(transparent)]\n  #[cfg(desktop)]\n  BadMenuIcon(#[from] muda::BadIcon),\n  /// Tray icon error.\n  #[error(\"tray icon error: {0}\")]\n  #[cfg(all(desktop, feature = \"tray-icon\"))]\n  #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = \"tray-icon\"))))]\n  Tray(#[from] tray_icon::Error),\n  /// Bad tray icon error.\n  #[error(transparent)]\n  #[cfg(all(desktop, feature = \"tray-icon\"))]\n  #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = \"tray-icon\"))))]\n  BadTrayIcon(#[from] tray_icon::BadIcon),\n  /// Path does not have a parent.\n  #[error(\"path does not have a parent\")]\n  NoParent,\n  /// Path does not have an extension.\n  #[error(\"path does not have an extension\")]\n  NoExtension,\n  /// Path does not have a basename.\n  #[error(\"path does not have a basename\")]\n  NoBasename,\n  /// Cannot resolve current directory.\n  #[error(\"failed to read current dir: {0}\")]\n  CurrentDir(std::io::Error),\n  /// Unknown path.\n  #[cfg(not(target_os = \"android\"))]\n  #[error(\"unknown path\")]\n  UnknownPath,\n  /// Failed to invoke mobile plugin.\n  #[cfg(target_os = \"android\")]\n  #[error(transparent)]\n  PluginInvoke(#[from] crate::plugin::mobile::PluginInvokeError),\n  /// window not found.\n  #[error(\"window not found\")]\n  WindowNotFound,\n  /// The resource id is invalid.\n  #[error(\"The resource id {0} is invalid.\")]\n  BadResourceId(crate::resources::ResourceId),\n  /// The anyhow crate error.\n  #[error(transparent)]\n  Anyhow(#[from] anyhow::Error),\n  /// webview not found.\n  #[error(\"webview not found\")]\n  WebviewNotFound,\n  /// API requires the unstable feature flag.\n  #[error(\"this feature requires the `unstable` flag on Cargo.toml\")]\n  UnstableFeatureNotSupported,\n  /// Failed to deserialize scope object.\n  #[error(\"error deserializing scope: {0}\")]\n  CannotDeserializeScope(Box<dyn std::error::Error + Send + Sync>),\n  /// Failed to get a raw handle.\n  #[error(transparent)]\n  RawHandleError(#[from] raw_window_handle::HandleError),\n  /// Something went wrong with the CSPRNG.\n  #[error(\"unable to generate random bytes from the operating system: {0}\")]\n  Csprng(getrandom::Error),\n  /// Bad `__TAURI_INVOKE_KEY__` value received in ipc message.\n  #[error(\"bad __TAURI_INVOKE_KEY__ value received in ipc message\")]\n  InvokeKey,\n  /// Illegal event name.\n  #[error(\"only alphanumeric, '-', '/', ':', '_' permitted for event names: {0:?}\")]\n  IllegalEventName(String),\n  /// tokio oneshot channel failed to receive message\n  #[error(transparent)]\n  TokioOneshotRecv(#[from] tokio::sync::oneshot::error::RecvError),\n}\n\nimpl From<getrandom::Error> for Error {\n  fn from(value: getrandom::Error) -> Self {\n    Self::Csprng(value)\n  }\n}\n\n/// `Result<T, ::tauri::Error>`\npub type Result<T> = std::result::Result<T, Error>;\n\n#[cfg(test)]\nmod tests {\n  #[test]\n  fn error_is_send_sync() {\n    crate::test_utils::assert_send::<super::Error>();\n    crate::test_utils::assert_sync::<super::Error>();\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/event/event-system-spec.md",
    "content": "# Tauri Event System Specification\n\n## Emitters\n\nEmitters can emit to any and all listeners.\n\n- `App` and `AppHandle`\n- `Window`\n- `Webview`\n- `WebviewWindow`\n- Any type that implements `Manager` trait.\n\n## Emit functions\n\n- `emit`: emits an event to all listeners.\n- `emit_to`: emits an event to a specified target.\n- `emit_filter`: emits an event to targets based on a filtering callback.\n\n## Listeners\n\nEmitters can emit to any and all listeners.\n\n- `App` and `AppHandle`\n- `Window`\n- `Webview`\n- `WebviewWindow`\n- Any type that implements `Manager` trait but is limited to only using `listen_any/once_any`.\n\n## Listen functions\n\n- `listen`: Listens to all events targeting this listener type only.\n- `once`: Listens to a single event targeting this listener type only.\n- `listen_any` (available only through `Manager` trait): Listens to all events to any target (aka event sniffer).\n- `once_any` (available only through `Manager` trait): Listens to a single event to any target (aka event sniffer).\n"
  },
  {
    "path": "crates/tauri/src/event/event_name.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::{Deserialize, Deserializer};\n\n/// Checks if an event name is valid.\nfn is_event_name_valid(event: &str) -> bool {\n  event\n    .chars()\n    .all(|c| c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_')\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub(crate) struct EventName<S = String>(S);\n\nimpl Copy for EventName<&str> {}\n\nimpl<S: AsRef<str>> EventName<S> {\n  pub(crate) fn new(s: S) -> crate::Result<EventName<S>> {\n    if !is_event_name_valid(s.as_ref()) {\n      return Err(crate::Error::IllegalEventName(s.as_ref().to_string()));\n    }\n    Ok(EventName(s))\n  }\n\n  pub(crate) fn as_str_event(&self) -> EventName<&str> {\n    EventName(self.0.as_ref())\n  }\n\n  pub(crate) fn as_str(&self) -> &str {\n    self.0.as_ref()\n  }\n}\n\nimpl<S: std::fmt::Display> std::fmt::Display for EventName<S> {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    self.0.fmt(f)\n  }\n}\n\nimpl EventName<&'static str> {\n  // this convenience method is for using in const contexts to discharge the preconditions\n  // &'static prevents using this function accidentally with dynamically built string slices\n  pub(crate) const fn from_str(s: &'static str) -> EventName<&'static str> {\n    EventName(s)\n  }\n}\n\nimpl EventName<&str> {\n  pub fn into_owned(self) -> EventName {\n    EventName(self.0.to_string())\n  }\n}\n\nimpl<'de> Deserialize<'de> for EventName {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let event_id = String::deserialize(deserializer)?;\n    if is_event_name_valid(&event_id) {\n      Ok(EventName(event_id))\n    } else {\n      Err(serde::de::Error::custom(\n        \"Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`.\",\n      ))\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/event/init.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// eslint-disable-next-line\nObject.defineProperty(window, '__TAURI_EVENT_PLUGIN_INTERNALS__', {\n  value: {\n    unregisterListener: __RAW_unregister_listener_function__\n  }\n})\n"
  },
  {
    "path": "crates/tauri/src/event/listener.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{Runtime, Webview};\n\nuse super::{EmitArgs, Event, EventId, EventTarget};\n\nuse std::{\n  boxed::Box,\n  cell::Cell,\n  collections::{HashMap, HashSet},\n  sync::{\n    atomic::{AtomicU32, Ordering},\n    Arc, Mutex,\n  },\n};\n\n/// What to do with the pending handler when resolving it?\nenum Pending {\n  Unlisten(EventId),\n  Listen {\n    id: EventId,\n    event: crate::EventName,\n    handler: Handler,\n  },\n  Emit(EmitArgs),\n}\n\n/// Stored in [`Listeners`] to be called upon, when the event that stored it, is triggered.\nstruct Handler {\n  target: EventTarget,\n  callback: Box<dyn Fn(Event) + Send>,\n}\n\nimpl Handler {\n  fn new<F: Fn(Event) + Send + 'static>(target: EventTarget, callback: F) -> Self {\n    Self {\n      target,\n      callback: Box::new(callback),\n    }\n  }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\nstruct JsHandler {\n  target: EventTarget,\n  id: EventId,\n}\n\nimpl JsHandler {\n  fn new(target: EventTarget, id: EventId) -> Self {\n    Self { target, id }\n  }\n}\n\ntype WebviewLabel = String;\n\n/// Holds event handlers and pending event handlers, along with the salts associating them.\nstruct InnerListeners {\n  pending: Mutex<Vec<Pending>>,\n  handlers: Mutex<HashMap<crate::EventName, HashMap<EventId, Handler>>>,\n  js_event_listeners: Mutex<HashMap<WebviewLabel, HashMap<crate::EventName, HashSet<JsHandler>>>>,\n  function_name: &'static str,\n  listeners_object_name: &'static str,\n  next_event_id: Arc<AtomicU32>,\n}\n\n/// A self-contained event manager.\n#[derive(Clone)]\npub struct Listeners {\n  inner: Arc<InnerListeners>,\n}\n\nimpl Default for Listeners {\n  fn default() -> Self {\n    Self {\n      inner: Arc::new(InnerListeners {\n        pending: Mutex::default(),\n        handlers: Mutex::default(),\n        js_event_listeners: Mutex::default(),\n        function_name: \"__internal_unstable_listeners_function_id__\",\n        listeners_object_name: \"__internal_unstable_listeners_object_id__\",\n        next_event_id: Default::default(),\n      }),\n    }\n  }\n}\n\nimpl Listeners {\n  pub(crate) fn next_event_id(&self) -> EventId {\n    self.inner.next_event_id.fetch_add(1, Ordering::Relaxed)\n  }\n\n  /// Function name to represent the JavaScript event function.\n  pub(crate) fn function_name(&self) -> &str {\n    self.inner.function_name\n  }\n\n  /// Listener object name to represent the JavaScript event listener object.\n  pub(crate) fn listeners_object_name(&self) -> &str {\n    self.inner.listeners_object_name\n  }\n\n  /// Insert a pending event action to the queue.\n  fn insert_pending(&self, action: Pending) {\n    self\n      .inner\n      .pending\n      .lock()\n      .expect(\"poisoned pending event queue\")\n      .push(action)\n  }\n\n  /// Finish all pending event actions.\n  fn flush_pending(&self) -> crate::Result<()> {\n    let pending = {\n      let mut lock = self\n        .inner\n        .pending\n        .lock()\n        .expect(\"poisoned pending event queue\");\n      std::mem::take(&mut *lock)\n    };\n\n    for action in pending {\n      match action {\n        Pending::Unlisten(id) => self.unlisten(id),\n        Pending::Listen { id, event, handler } => self.listen_with_id(id, event, handler),\n        Pending::Emit(args) => self.emit(args)?,\n      }\n    }\n\n    Ok(())\n  }\n\n  fn listen_with_id(&self, id: EventId, event: crate::EventName, handler: Handler) {\n    match self.inner.handlers.try_lock() {\n      Err(_) => self.insert_pending(Pending::Listen { id, event, handler }),\n      Ok(mut lock) => {\n        lock.entry(event).or_default().insert(id, handler);\n      }\n    }\n  }\n\n  /// Adds an event listener.\n  pub(crate) fn listen<F: Fn(Event) + Send + 'static>(\n    &self,\n    event: crate::EventName,\n    target: EventTarget,\n    handler: F,\n  ) -> EventId {\n    let id = self.next_event_id();\n    let handler = Handler::new(target, handler);\n    self.listen_with_id(id, event, handler);\n    id\n  }\n\n  /// Listen to an event and immediately unlisten.\n  pub(crate) fn once<F: FnOnce(Event) + Send + 'static>(\n    &self,\n    event: crate::EventName,\n    target: EventTarget,\n    handler: F,\n  ) -> EventId {\n    let self_ = self.clone();\n    let handler = Cell::new(Some(handler));\n\n    self.listen(event, target, move |event| {\n      let id = event.id;\n      let handler = handler\n        .take()\n        .expect(\"attempted to call handler more than once\");\n      handler(event);\n      self_.unlisten(id);\n    })\n  }\n\n  /// Removes an event listener.\n  pub(crate) fn unlisten(&self, id: EventId) {\n    match self.inner.handlers.try_lock() {\n      Err(_) => self.insert_pending(Pending::Unlisten(id)),\n      Ok(mut lock) => lock.values_mut().for_each(|handler| {\n        handler.remove(&id);\n      }),\n    }\n  }\n\n  /// Emits the given event with its payload based on a filter.\n  pub(crate) fn emit_filter<F>(&self, emit_args: EmitArgs, filter: Option<F>) -> crate::Result<()>\n  where\n    F: Fn(&EventTarget) -> bool,\n  {\n    let mut maybe_pending = false;\n\n    match self.inner.handlers.try_lock() {\n      Err(_) => self.insert_pending(Pending::Emit(emit_args)),\n      Ok(lock) => {\n        if let Some(handlers) = lock.get(&emit_args.event) {\n          let handlers = handlers.iter();\n          let handlers = handlers.filter(|(_, h)| match_any_or_filter(&h.target, &filter));\n          for (&id, Handler { callback, .. }) in handlers {\n            maybe_pending = true;\n            (callback)(Event::new(id, emit_args.payload.clone()))\n          }\n        }\n      }\n    }\n\n    if maybe_pending {\n      self.flush_pending()?;\n    }\n\n    Ok(())\n  }\n\n  /// Emits the given event with its payload.\n  pub(crate) fn emit(&self, emit_args: EmitArgs) -> crate::Result<()> {\n    self.emit_filter(emit_args, None::<&dyn Fn(&EventTarget) -> bool>)\n  }\n\n  pub(crate) fn listen_js(\n    &self,\n    event: crate::EventName<&str>,\n    source_webview_label: &str,\n    target: EventTarget,\n    id: EventId,\n  ) {\n    let event = event.into_owned();\n    let mut listeners = self.inner.js_event_listeners.lock().unwrap();\n    listeners\n      .entry(source_webview_label.to_string())\n      .or_default()\n      .entry(event)\n      .or_default()\n      .insert(JsHandler::new(target, id));\n  }\n\n  pub(crate) fn unlisten_js(&self, event: crate::EventName<&str>, id: EventId) {\n    let event = event.into_owned();\n    let mut js_listeners = self.inner.js_event_listeners.lock().unwrap();\n    let js_listeners = js_listeners.values_mut();\n    for js_listeners in js_listeners {\n      if let Some(handlers) = js_listeners.get_mut(&event) {\n        handlers.retain(|h| h.id != id);\n\n        if handlers.is_empty() {\n          js_listeners.remove(&event);\n        }\n      }\n    }\n  }\n\n  pub(crate) fn has_js_listener<F: Fn(&EventTarget) -> bool>(\n    &self,\n    event: crate::EventName<&str>,\n    filter: F,\n  ) -> bool {\n    let event = event.into_owned();\n    let js_listeners = self.inner.js_event_listeners.lock().unwrap();\n    js_listeners.values().any(|events| {\n      events\n        .get(&event)\n        .map(|handlers| handlers.iter().any(|handler| filter(&handler.target)))\n        .unwrap_or(false)\n    })\n  }\n\n  pub(crate) fn emit_js_filter<'a, R, I, F>(\n    &self,\n    mut webviews: I,\n    emit_args: &EmitArgs,\n    filter: Option<F>,\n  ) -> crate::Result<()>\n  where\n    R: Runtime,\n    I: Iterator<Item = &'a Webview<R>>,\n    F: Fn(&EventTarget) -> bool,\n  {\n    let event = &emit_args.event;\n    let js_listeners = self.inner.js_event_listeners.lock().unwrap();\n    webviews.try_for_each(|webview| {\n      if let Some(handlers) = js_listeners.get(webview.label()).and_then(|s| s.get(event)) {\n        let ids = handlers\n          .iter()\n          .filter(|handler| match_any_or_filter(&handler.target, &filter))\n          .map(|handler| handler.id)\n          .collect::<Vec<_>>();\n        webview.emit_js(emit_args, &ids)?;\n      }\n\n      Ok(())\n    })\n  }\n\n  pub(crate) fn emit_js<'a, R, I>(&self, webviews: I, emit_args: &EmitArgs) -> crate::Result<()>\n  where\n    R: Runtime,\n    I: Iterator<Item = &'a Webview<R>>,\n  {\n    self.emit_js_filter(webviews, emit_args, None::<&dyn Fn(&EventTarget) -> bool>)\n  }\n}\n\n#[inline(always)]\nfn match_any_or_filter<F: Fn(&EventTarget) -> bool>(\n  target: &EventTarget,\n  filter: &Option<F>,\n) -> bool {\n  *target == EventTarget::Any || filter.as_ref().map(|f| f(target)).unwrap_or(true)\n}\n\n#[cfg(test)]\nmod test {\n  use super::*;\n  use crate::event::EventTarget;\n  use proptest::prelude::*;\n\n  // dummy event handler function\n  fn event_fn(s: Event) {\n    println!(\"{s:?}\");\n  }\n\n  proptest! {\n    #![proptest_config(ProptestConfig::with_cases(10000))]\n\n    // check to see if listen() is properly passing keys into the LISTENERS map\n    #[test]\n    fn listeners_check_key(e in \"[a-z]+\") {\n      let listeners: Listeners = Default::default();\n      // clone e as the key\n      let key = crate::EventName::new(e).unwrap();\n      // pass e and an dummy func into listen\n      listeners.listen(key.clone(), EventTarget::Any, event_fn);\n\n      // lock mutex\n      let l = listeners.inner.handlers.lock().unwrap();\n\n      // check if the generated key is in the map\n      assert!(l.contains_key(&key));\n    }\n\n    // check to see if listen inputs a handler function properly into the LISTENERS map.\n    #[test]\n    fn listeners_check_fn(e in \"[a-z]+\") {\n      let listeners: Listeners = Default::default();\n      let key = crate::EventName::new(e).unwrap();\n       // pass e and an dummy func into listen\n       listeners.listen(key.clone(), EventTarget::Any, event_fn);\n\n       // lock mutex\n       let mut l = listeners.inner.handlers.lock().unwrap();\n\n       // check if l contains key\n       if l.contains_key(&key) {\n        // grab key if it exists\n        let handler = l.get_mut(&key);\n        // check to see if we get back a handler or not\n        match handler {\n          // pass on Some(handler)\n          Some(_) => {},\n          // Fail on None\n          None => panic!(\"handler is None\")\n        }\n      }\n    }\n\n    // check to see if on_event properly grabs the stored function from listen.\n    #[test]\n    fn check_on_event(e in \"[a-z]+\", d in \"[a-z]+\") {\n      let listeners: Listeners = Default::default();\n      let key = crate::EventName::new(e).unwrap();\n      // call listen with key and the event_fn dummy func\n      listeners.listen(key.clone(), EventTarget::Any, event_fn);\n      // call on event with key and d.\n      listeners.emit(EmitArgs::new(key.as_str_event(), &d)?)?;\n\n      // lock the mutex\n      let l = listeners.inner.handlers.lock().unwrap();\n\n      // assert that the key is contained in the listeners map\n      assert!(l.contains_key(&key));\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/event/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nmod listener;\npub(crate) mod plugin;\nuse std::{convert::Infallible, str::FromStr};\n\npub(crate) use listener::Listeners;\nuse serde::{Deserialize, Serialize};\n\nmod event_name;\n\npub(crate) use event_name::EventName;\n\nuse crate::ipc::CallbackFn;\n\n/// Unique id of an event.\npub type EventId = u32;\n\n/// Event Target\n#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]\n#[serde(tag = \"kind\")]\n#[non_exhaustive]\npub enum EventTarget {\n  /// Any and all event targets.\n  Any,\n\n  /// Any [`Window`](crate::Window), [`Webview`](crate::Webview) or [`WebviewWindow`](crate::WebviewWindow) that have this label.\n  AnyLabel {\n    /// Target label.\n    label: String,\n  },\n\n  /// [`App`](crate::App) and [`AppHandle`](crate::AppHandle) targets.\n  App,\n\n  /// [`Window`](crate::Window) target.\n  Window {\n    /// window label.\n    label: String,\n  },\n\n  /// [`Webview`](crate::Webview) target.\n  Webview {\n    /// webview label.\n    label: String,\n  },\n\n  /// [`WebviewWindow`](crate::WebviewWindow) target.\n  WebviewWindow {\n    /// webview window label.\n    label: String,\n  },\n}\n\nimpl EventTarget {\n  /// [`Self::Any`] target.\n  pub fn any() -> Self {\n    Self::Any\n  }\n\n  /// [`Self::App`] target.\n  pub fn app() -> Self {\n    Self::App\n  }\n\n  /// [`Self::AnyLabel`] target.\n  pub fn labeled(label: impl Into<String>) -> Self {\n    Self::AnyLabel {\n      label: label.into(),\n    }\n  }\n\n  /// [`Self::Window`] target.\n  pub fn window(label: impl Into<String>) -> Self {\n    Self::Window {\n      label: label.into(),\n    }\n  }\n\n  /// [`Self::Webview`] target.\n  pub fn webview(label: impl Into<String>) -> Self {\n    Self::Webview {\n      label: label.into(),\n    }\n  }\n\n  /// [`Self::WebviewWindow`] target.\n  pub fn webview_window(label: impl Into<String>) -> Self {\n    Self::WebviewWindow {\n      label: label.into(),\n    }\n  }\n}\n\nimpl<T: AsRef<str>> From<T> for EventTarget {\n  fn from(value: T) -> Self {\n    Self::AnyLabel {\n      label: value.as_ref().to_string(),\n    }\n  }\n}\n\nimpl FromStr for EventTarget {\n  type Err = Infallible;\n\n  fn from_str(s: &str) -> Result<Self, Self::Err> {\n    Ok(Self::AnyLabel {\n      label: s.to_string(),\n    })\n  }\n}\n\n/// Serialized emit arguments.\n#[derive(Clone)]\npub struct EmitArgs {\n  /// event name.\n  event: EventName,\n  /// Serialized payload.\n  payload: String,\n}\n\nimpl EmitArgs {\n  pub fn new<S: Serialize>(event: EventName<&str>, payload: &S) -> crate::Result<Self> {\n    #[cfg(feature = \"tracing\")]\n    let _span = tracing::debug_span!(\"window::emit::serialize\").entered();\n    Ok(EmitArgs {\n      event: event.into_owned(),\n      payload: serde_json::to_string(payload)?,\n    })\n  }\n\n  pub fn new_str(event: EventName<&str>, payload: String) -> crate::Result<Self> {\n    #[cfg(feature = \"tracing\")]\n    let _span = tracing::debug_span!(\"window::emit::json\").entered();\n    Ok(EmitArgs {\n      event: event.into_owned(),\n      payload,\n    })\n  }\n}\n\n/// An event that was emitted.\n#[derive(Debug, Clone)]\npub struct Event {\n  id: EventId,\n  data: String,\n}\n\nimpl Event {\n  fn new(id: EventId, data: String) -> Self {\n    Self { id, data }\n  }\n\n  /// The [`EventId`] of the handler that was triggered.\n  pub fn id(&self) -> EventId {\n    self.id\n  }\n\n  /// The event payload.\n  pub fn payload(&self) -> &str {\n    &self.data\n  }\n}\n\npub(crate) fn listen_js_script(\n  listeners_object_name: &str,\n  serialized_target: &str,\n  event: EventName<&str>,\n  event_id: EventId,\n  handler: CallbackFn,\n) -> String {\n  let handler_id = handler.0;\n  format!(\n    \"(function () {{\n      if (window['{listeners_object_name}'] === void 0) {{\n        Object.defineProperty(window, '{listeners_object_name}', {{ value: Object.create(null) }});\n      }}\n      if (window['{listeners_object_name}']['{event}'] === void 0) {{\n        Object.defineProperty(window['{listeners_object_name}'], '{event}', {{ value: Object.create(null) }});\n      }}\n      const eventListeners = window['{listeners_object_name}']['{event}']\n      const listener = {{\n        target: {serialized_target},\n        handlerId: {handler_id}\n      }};\n      Object.defineProperty(eventListeners, '{event_id}', {{ value: listener, configurable: true }});\n    }})()\n  \",\n  )\n}\n\npub(crate) fn emit_js_script(\n  event_emit_function_name: &str,\n  emit_args: &EmitArgs,\n  serialized_ids: &str,\n) -> crate::Result<String> {\n  Ok(format!(\n    \"(function () {{ const fn = window['{}']; fn && fn({{event: '{}', payload: {}}}, {ids}) }})()\",\n    event_emit_function_name,\n    emit_args.event,\n    emit_args.payload,\n    ids = serialized_ids,\n  ))\n}\n\npub(crate) fn unlisten_js_script(\n  listeners_object_name: &str,\n  event_arg: &str,\n  event_id_arg: &str,\n) -> String {\n  format!(\n    \"(function () {{\n        const listeners = (window['{listeners_object_name}'] || {{}})[{event_arg}]\n        if (listeners) {{\n          window.__TAURI_INTERNALS__.unregisterCallback(listeners[{event_id_arg}].handlerId)\n        }}\n      }})()\n    \",\n  )\n}\n\npub(crate) fn event_initialization_script(function_name: &str, listeners: &str) -> String {\n  format!(\n    \"Object.defineProperty(window, '{function_name}', {{\n      value: function (eventData, ids) {{\n        const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []\n        for (const id of ids) {{\n          const listener = listeners[id]\n          if (listener) {{\n            eventData.id = id\n            window.__TAURI_INTERNALS__.runCallback(listener.handlerId, eventData)\n          }}\n        }}\n      }}\n    }});\n  \"\n  )\n}\n\n#[cfg(test)]\nmod tests {\n  use super::*;\n  #[test]\n  fn test_illegal_event_name() {\n    let s = EventName::new(\"some\\r illegal event name\")\n      .unwrap_err()\n      .to_string();\n    assert_eq!(\"only alphanumeric, '-', '/', ':', '_' permitted for event names: \\\"some\\\\r illegal event name\\\"\", s);\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/event/plugin.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\nuse serde_json::Value as JsonValue;\nuse serialize_to_javascript::{default_template, DefaultTemplate, Template};\n\nuse crate::plugin::{Builder, TauriPlugin};\nuse crate::{command, ipc::CallbackFn, EventId, Result, Runtime};\nuse crate::{AppHandle, Emitter, Manager, Webview};\n\nuse super::EventName;\nuse super::EventTarget;\n\n#[command(root = \"crate\")]\nasync fn listen<R: Runtime>(\n  webview: Webview<R>,\n  event: EventName,\n  target: EventTarget,\n  handler: CallbackFn,\n) -> Result<EventId> {\n  webview.listen_js(event.as_str_event(), target, handler)\n}\n\n#[command(root = \"crate\")]\nasync fn unlisten<R: Runtime>(\n  webview: Webview<R>,\n  event: EventName,\n  event_id: EventId,\n) -> Result<()> {\n  webview.unlisten_js(event.as_str_event(), event_id)\n}\n\n#[command(root = \"crate\")]\nasync fn emit<R: Runtime>(\n  app: AppHandle<R>,\n  event: EventName,\n  payload: Option<JsonValue>,\n) -> Result<()> {\n  app.emit(event.as_str(), payload)\n}\n\n#[command(root = \"crate\")]\nasync fn emit_to<R: Runtime>(\n  app: AppHandle<R>,\n  target: EventTarget,\n  event: EventName,\n  payload: Option<JsonValue>,\n) -> Result<()> {\n  app.emit_to(target, event.as_str(), payload)\n}\n\n/// Initializes the event plugin.\npub(crate) fn init<R: Runtime, M: Manager<R>>(manager: &M) -> TauriPlugin<R> {\n  let listeners = manager.manager().listeners();\n\n  #[derive(Template)]\n  #[default_template(\"./init.js\")]\n  struct InitJavascript {\n    #[raw]\n    unregister_listener_function: String,\n  }\n\n  let init_script = InitJavascript {\n    unregister_listener_function: format!(\n      \"(event, eventId) => {}\",\n      crate::event::unlisten_js_script(listeners.listeners_object_name(), \"event\", \"eventId\")\n    ),\n  };\n\n  Builder::new(\"event\")\n    .invoke_handler(crate::generate_handler![\n      #![plugin(event)]\n      listen, unlisten, emit, emit_to\n    ])\n    .js_init_script(\n      init_script\n        .render_default(&Default::default())\n        .unwrap()\n        .to_string(),\n    )\n    .build()\n}\n"
  },
  {
    "path": "crates/tauri/src/image/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Image types used by this crate and also referenced by the JavaScript API layer.\n\npub(crate) mod plugin;\n\nuse std::borrow::Cow;\nuse std::sync::Arc;\n\nuse crate::{Resource, ResourceId, ResourceTable};\n\n/// An RGBA Image in row-major order from top to bottom.\n#[derive(Clone)]\npub struct Image<'a> {\n  rgba: Cow<'a, [u8]>,\n  width: u32,\n  height: u32,\n}\n\nimpl std::fmt::Debug for Image<'_> {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    f.debug_struct(\"Image\")\n      .field(\n        \"rgba\",\n        // Reduces the debug size compared to the derived default, as the default\n        // would format the raw bytes as numbers `[0, 0, 0, 0]` for 1 pixel.\n        // The custom format doesn't grow as much with larger images:\n        // `Image { rgba: Cow::Borrowed([u8; 4096]), width: 32, height: 32 }`\n        &format_args!(\n          \"Cow::{}([u8; {}])\",\n          match &self.rgba {\n            Cow::Borrowed(_) => \"Borrowed\",\n            Cow::Owned(_) => \"Owned\",\n          },\n          self.rgba.len()\n        ),\n      )\n      .field(\"width\", &self.width)\n      .field(\"height\", &self.height)\n      .finish()\n  }\n}\n\nimpl Resource for Image<'static> {}\n\nimpl Image<'static> {\n  /// Creates a new Image using RGBA data, in row-major order from top to bottom, and with specified width and height.\n  ///\n  /// Similar to [`Self::new`] but avoids cloning the rgba data to get an owned Image.\n  pub const fn new_owned(rgba: Vec<u8>, width: u32, height: u32) -> Self {\n    Self {\n      rgba: Cow::Owned(rgba),\n      width,\n      height,\n    }\n  }\n}\n\nimpl<'a> Image<'a> {\n  /// Creates a new Image using RGBA data, in row-major order from top to bottom, and with specified width and height.\n  pub const fn new(rgba: &'a [u8], width: u32, height: u32) -> Self {\n    Self {\n      rgba: Cow::Borrowed(rgba),\n      width,\n      height,\n    }\n  }\n\n  /// Creates a new image using the provided bytes.\n  ///\n  /// Only `ico` and `png` are supported (based on activated feature flag).\n  #[cfg(any(feature = \"image-ico\", feature = \"image-png\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(feature = \"image-ico\", feature = \"image-png\"))))]\n  pub fn from_bytes(bytes: &[u8]) -> crate::Result<Self> {\n    use image::GenericImageView;\n\n    let img = image::load_from_memory(bytes)?;\n    let pixels = img\n      .pixels()\n      .flat_map(|(_, _, pixel)| pixel.0)\n      .collect::<Vec<_>>();\n    Ok(Self {\n      rgba: Cow::Owned(pixels),\n      width: img.width(),\n      height: img.height(),\n    })\n  }\n\n  /// Creates a new image using the provided path.\n  ///\n  /// Only `ico` and `png` are supported (based on activated feature flag).\n  #[cfg(any(feature = \"image-ico\", feature = \"image-png\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(feature = \"image-ico\", feature = \"image-png\"))))]\n  pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> crate::Result<Self> {\n    let bytes = std::fs::read(path)?;\n    Self::from_bytes(&bytes)\n  }\n\n  /// Returns the RGBA data for this image, in row-major order from top to bottom.\n  pub fn rgba(&'a self) -> &'a [u8] {\n    &self.rgba\n  }\n\n  /// Returns the width of this image.\n  pub fn width(&self) -> u32 {\n    self.width\n  }\n\n  /// Returns the height of this image.\n  pub fn height(&self) -> u32 {\n    self.height\n  }\n\n  /// Convert into a 'static owned [`Image`].\n  /// This will allocate.\n  pub fn to_owned(self) -> Image<'static> {\n    Image {\n      rgba: match self.rgba {\n        Cow::Owned(v) => Cow::Owned(v),\n        Cow::Borrowed(v) => Cow::Owned(v.to_vec()),\n      },\n      height: self.height,\n      width: self.width,\n    }\n  }\n}\n\nimpl<'a> From<Image<'a>> for crate::runtime::Icon<'a> {\n  fn from(img: Image<'a>) -> Self {\n    Self {\n      rgba: img.rgba,\n      width: img.width,\n      height: img.height,\n    }\n  }\n}\n\n#[cfg(desktop)]\nimpl TryFrom<Image<'_>> for muda::Icon {\n  type Error = crate::Error;\n\n  fn try_from(img: Image<'_>) -> Result<Self, Self::Error> {\n    muda::Icon::from_rgba(img.rgba.to_vec(), img.width, img.height).map_err(Into::into)\n  }\n}\n\n#[cfg(all(desktop, feature = \"tray-icon\"))]\nimpl TryFrom<Image<'_>> for tray_icon::Icon {\n  type Error = crate::Error;\n\n  fn try_from(img: Image<'_>) -> Result<Self, Self::Error> {\n    tray_icon::Icon::from_rgba(img.rgba.to_vec(), img.width, img.height).map_err(Into::into)\n  }\n}\n\n/// An image type that accepts file paths, raw bytes, previously loaded images and image objects.\n///\n/// This type is meant to be used along the [transformImage](https://v2.tauri.app/reference/javascript/api/namespaceimage/#transformimage) API.\n///\n/// # Stability\n///\n/// The stability of the variants are not guaranteed, and matching against them is not recommended.\n/// Use [`JsImage::into_img`] instead.\n#[derive(serde::Deserialize)]\n#[serde(untagged)]\n#[non_exhaustive]\npub enum JsImage {\n  /// A reference to a image in the filesystem.\n  #[non_exhaustive]\n  Path(std::path::PathBuf),\n  /// Image from raw bytes.\n  #[non_exhaustive]\n  Bytes(Vec<u8>),\n  /// An image that was previously loaded with the API and is stored in the resource table.\n  #[non_exhaustive]\n  Resource(ResourceId),\n  /// Raw RGBA definition of an image.\n  #[non_exhaustive]\n  Rgba {\n    /// Image bytes.\n    rgba: Vec<u8>,\n    /// Image width.\n    width: u32,\n    /// Image height.\n    height: u32,\n  },\n}\n\nimpl JsImage {\n  /// Converts this intermediate image format into an actual [`Image`].\n  ///\n  /// This will retrieve the image from the passed [`ResourceTable`] if it is [`JsImage::Resource`]\n  /// and will return an error if it doesn't exist in the passed [`ResourceTable`] so make sure\n  /// the passed [`ResourceTable`] is the same one used to store the image, usually this should be\n  /// the webview [resources table](crate::webview::Webview::resources_table).\n  pub fn into_img(self, resources_table: &ResourceTable) -> crate::Result<Arc<Image<'_>>> {\n    match self {\n      Self::Resource(rid) => resources_table.get::<Image<'static>>(rid),\n      #[cfg(any(feature = \"image-ico\", feature = \"image-png\"))]\n      Self::Path(path) => Image::from_path(path).map(Arc::new),\n\n      #[cfg(any(feature = \"image-ico\", feature = \"image-png\"))]\n      Self::Bytes(bytes) => Image::from_bytes(&bytes).map(Arc::new),\n\n      Self::Rgba {\n        rgba,\n        width,\n        height,\n      } => Ok(Arc::new(Image::new_owned(rgba, width, height))),\n\n      #[cfg(not(any(feature = \"image-ico\", feature = \"image-png\")))]\n      _ => Err(\n        std::io::Error::new(\n          std::io::ErrorKind::InvalidInput,\n          format!(\n            \"expected RGBA image data, found {}\",\n            match self {\n              JsImage::Path(_) => \"a file path\",\n              JsImage::Bytes(_) => \"raw bytes\",\n              _ => unreachable!(),\n            }\n          ),\n        )\n        .into(),\n      ),\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/image/plugin.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::Serialize;\n\nuse crate::plugin::{Builder, TauriPlugin};\nuse crate::Manager;\nuse crate::{command, image::Image, ResourceId, Runtime, Webview};\n\n#[command(root = \"crate\")]\nfn new<R: Runtime>(\n  webview: Webview<R>,\n  rgba: Vec<u8>,\n  width: u32,\n  height: u32,\n) -> crate::Result<ResourceId> {\n  let image = Image::new_owned(rgba, width, height);\n  let mut resources_table = webview.resources_table();\n  let rid = resources_table.add(image);\n  Ok(rid)\n}\n\n#[cfg(any(feature = \"image-ico\", feature = \"image-png\"))]\n#[command(root = \"crate\")]\nfn from_bytes<R: Runtime>(webview: Webview<R>, bytes: Vec<u8>) -> crate::Result<ResourceId> {\n  let image = Image::from_bytes(&bytes)?.to_owned();\n  let mut resources_table = webview.resources_table();\n  let rid = resources_table.add(image);\n  Ok(rid)\n}\n\n#[cfg(not(any(feature = \"image-ico\", feature = \"image-png\")))]\n#[command(root = \"crate\")]\nfn from_bytes() -> std::result::Result<(), &'static str> {\n  Err(\"from_bytes is only supported if the `image-ico` or `image-png` Cargo features are enabled\")\n}\n\n#[cfg(any(feature = \"image-ico\", feature = \"image-png\"))]\n#[command(root = \"crate\")]\nfn from_path<R: Runtime>(\n  webview: Webview<R>,\n  path: std::path::PathBuf,\n) -> crate::Result<ResourceId> {\n  let image = Image::from_path(path)?.to_owned();\n  let mut resources_table = webview.resources_table();\n  let rid = resources_table.add(image);\n  Ok(rid)\n}\n\n#[cfg(not(any(feature = \"image-ico\", feature = \"image-png\")))]\n#[command(root = \"crate\")]\nfn from_path() -> std::result::Result<(), &'static str> {\n  Err(\"from_path is only supported if the `image-ico` or `image-png` Cargo features are enabled\")\n}\n\n#[command(root = \"crate\")]\nfn rgba<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> crate::Result<Vec<u8>> {\n  let resources_table = webview.resources_table();\n  let image = resources_table.get::<Image<'_>>(rid)?;\n  Ok(image.rgba().to_vec())\n}\n\n#[derive(Serialize)]\nstruct Size {\n  width: u32,\n  height: u32,\n}\n\n#[command(root = \"crate\")]\nfn size<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> crate::Result<Size> {\n  let resources_table = webview.resources_table();\n  let image = resources_table.get::<Image<'_>>(rid)?;\n  Ok(Size {\n    width: image.width(),\n    height: image.height(),\n  })\n}\n\n/// Initializes the plugin.\npub fn init<R: Runtime>() -> TauriPlugin<R> {\n  Builder::new(\"image\")\n    .invoke_handler(crate::generate_handler![\n      #![plugin(image)]\n      new, from_bytes, from_path, rgba, size\n    ])\n    .build()\n}\n"
  },
  {
    "path": "crates/tauri/src/ios.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse swift_rs::{swift, SRString, SwiftArg};\n\nuse std::{\n  ffi::c_void,\n  os::raw::{c_char, c_int, c_ulonglong},\n};\n\ntype PluginMessageCallbackFn = unsafe extern \"C\" fn(c_int, c_int, *const c_char);\npub struct PluginMessageCallback(pub PluginMessageCallbackFn);\n\nimpl<'a> SwiftArg<'a> for PluginMessageCallback {\n  type ArgType = PluginMessageCallbackFn;\n\n  unsafe fn as_arg(&'a self) -> Self::ArgType {\n    self.0\n  }\n}\n\ntype ChannelSendDataCallbackFn = unsafe extern \"C\" fn(c_ulonglong, *const c_char);\npub struct ChannelSendDataCallback(pub ChannelSendDataCallbackFn);\n\nimpl<'a> SwiftArg<'a> for ChannelSendDataCallback {\n  type ArgType = ChannelSendDataCallbackFn;\n\n  unsafe fn as_arg(&'a self) -> Self::ArgType {\n    self.0\n  }\n}\n\nswift!(pub fn run_plugin_command(\n  id: i32,\n  name: &SRString,\n  method: &SRString,\n  data: &SRString,\n  callback: PluginMessageCallback,\n  send_channel_data_callback: ChannelSendDataCallback\n));\nswift!(pub fn register_plugin(\n  name: &SRString,\n  plugin: *const c_void,\n  config: &SRString,\n  webview: *const c_void\n));\nswift!(pub fn on_webview_created(webview: *const c_void, controller: *const c_void));\nswift!(pub fn log_stdout());\n"
  },
  {
    "path": "crates/tauri/src/ipc/authority.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::collections::BTreeMap;\nuse std::fmt::{Debug, Display};\nuse std::sync::Arc;\n\nuse serde::de::DeserializeOwned;\n\n#[cfg(feature = \"dynamic-acl\")]\nuse tauri_utils::acl::capability::CapabilityFile;\n#[cfg(any(feature = \"dynamic-acl\", debug_assertions))]\nuse tauri_utils::acl::manifest::Manifest;\nuse tauri_utils::acl::{\n  resolved::{Resolved, ResolvedCommand, ResolvedScope, ScopeKey},\n  ExecutionContext, Value, APP_ACL_KEY,\n};\n\nuse url::Url;\n\nuse crate::{ipc::InvokeError, sealed::ManagerBase, Runtime};\nuse crate::{AppHandle, Manager, StateManager, Webview};\n\nuse super::{CommandArg, CommandItem};\n\n/// The runtime authority used to authorize IPC execution based on the Access Control List.\npub struct RuntimeAuthority {\n  #[cfg(any(feature = \"dynamic-acl\", debug_assertions))]\n  acl: BTreeMap<String, Manifest>,\n  has_app_acl: bool,\n  allowed_commands: BTreeMap<String, Vec<ResolvedCommand>>,\n  denied_commands: BTreeMap<String, Vec<ResolvedCommand>>,\n  pub(crate) scope_manager: ScopeManager,\n}\n\n/// The origin trying to access the IPC.\npub enum Origin {\n  /// Local app origin.\n  Local,\n  /// Remote origin.\n  Remote {\n    /// Remote URL.\n    url: Url,\n  },\n}\n\nimpl Display for Origin {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    match self {\n      Self::Local => write!(f, \"local\"),\n      Self::Remote { url } => write!(f, \"remote: {url}\"),\n    }\n  }\n}\n\nimpl Origin {\n  fn matches(&self, context: &ExecutionContext) -> bool {\n    match (self, context) {\n      (Self::Local, ExecutionContext::Local) => true,\n      (Self::Remote { url }, ExecutionContext::Remote { url: url_pattern }) => {\n        url_pattern.test(url)\n      }\n      _ => false,\n    }\n  }\n}\n\n/// This is used internally by [`crate::generate_handler!`] for constructing [`RuntimeAuthority`]\n/// to only include the raw ACL when it's needed\n///\n/// ## Stability\n///\n/// The output of this macro is managed internally by Tauri,\n/// and should not be accessed directly on normal applications.\n/// It may have breaking changes in the future.\n#[cfg(any(feature = \"dynamic-acl\", debug_assertions))]\n#[doc(hidden)]\n#[macro_export]\nmacro_rules! runtime_authority {\n  ($acl:expr, $resolved_acl:expr) => {\n    $crate::ipc::RuntimeAuthority::new($acl, $resolved_acl)\n  };\n}\n\n/// This is used internally by [`crate::generate_handler!`] for constructing [`RuntimeAuthority`]\n/// to only include the raw ACL when it's needed\n///\n/// ## Stability\n///\n/// The output of this macro is managed internally by Tauri,\n/// and should not be accessed directly on normal applications.\n/// It may have breaking changes in the future.\n#[cfg(not(any(feature = \"dynamic-acl\", debug_assertions)))]\n#[doc(hidden)]\n#[macro_export]\nmacro_rules! runtime_authority {\n  ($_acl:expr, $resolved_acl:expr) => {\n    $crate::ipc::RuntimeAuthority::new($resolved_acl)\n  };\n}\n\nimpl RuntimeAuthority {\n  /// Contruct a new [`RuntimeAuthority`] from the ACL\n  ///\n  /// **Please prefer using the [`runtime_authority`] macro instead of calling this directly**\n  #[doc(hidden)]\n  pub fn new(\n    #[cfg(any(feature = \"dynamic-acl\", debug_assertions))] acl: BTreeMap<String, Manifest>,\n    resolved_acl: Resolved,\n  ) -> Self {\n    let command_cache = resolved_acl\n      .command_scope\n      .keys()\n      .map(|key| (*key, StateManager::new()))\n      .collect();\n    Self {\n      #[cfg(any(feature = \"dynamic-acl\", debug_assertions))]\n      acl,\n      has_app_acl: resolved_acl.has_app_acl,\n      allowed_commands: resolved_acl.allowed_commands,\n      denied_commands: resolved_acl.denied_commands,\n      scope_manager: ScopeManager {\n        command_scope: resolved_acl.command_scope,\n        global_scope: resolved_acl.global_scope,\n        command_cache,\n        global_scope_cache: StateManager::new(),\n      },\n    }\n  }\n\n  pub(crate) fn has_app_manifest(&self) -> bool {\n    self.has_app_acl\n  }\n\n  #[doc(hidden)]\n  pub fn __allow_command(&mut self, command: String, context: ExecutionContext) {\n    self.allowed_commands.insert(\n      command,\n      vec![ResolvedCommand {\n        context,\n        windows: vec![\"*\".parse().unwrap()],\n        ..Default::default()\n      }],\n    );\n  }\n\n  /// Adds the given capability to the runtime authority.\n  #[cfg(feature = \"dynamic-acl\")]\n  pub fn add_capability(&mut self, capability: impl super::RuntimeCapability) -> crate::Result<()> {\n    self.add_capability_inner(capability.build())\n  }\n\n  #[cfg(feature = \"dynamic-acl\")]\n  fn add_capability_inner(&mut self, capability: CapabilityFile) -> crate::Result<()> {\n    let mut capabilities = BTreeMap::new();\n    match capability {\n      CapabilityFile::Capability(c) => {\n        capabilities.insert(c.identifier.clone(), c);\n      }\n\n      CapabilityFile::List(capabilities_list)\n      | CapabilityFile::NamedList {\n        capabilities: capabilities_list,\n      } => {\n        capabilities.extend(\n          capabilities_list\n            .into_iter()\n            .map(|c| (c.identifier.clone(), c)),\n        );\n      }\n    }\n\n    let resolved = Resolved::resolve(\n      &self.acl,\n      capabilities,\n      tauri_utils::platform::Target::current(),\n    )\n    .unwrap();\n\n    // fill global scope\n    for (plugin, global_scope) in resolved.global_scope {\n      let global_scope_entry = self.scope_manager.global_scope.entry(plugin).or_default();\n\n      global_scope_entry.allow.extend(global_scope.allow);\n      global_scope_entry.deny.extend(global_scope.deny);\n\n      self.scope_manager.global_scope_cache = StateManager::new();\n    }\n\n    // denied commands\n    for (cmd_key, resolved_cmds) in resolved.denied_commands {\n      let entry = self.denied_commands.entry(cmd_key).or_default();\n      entry.extend(resolved_cmds);\n    }\n\n    // allowed commands\n    for (cmd_key, resolved_cmds) in resolved.allowed_commands {\n      // fill command scope\n      for resolved_cmd in &resolved_cmds {\n        if let Some(scope_id) = resolved_cmd.scope_id {\n          let command_scope = resolved.command_scope.get(&scope_id).unwrap();\n\n          let command_scope_entry = self\n            .scope_manager\n            .command_scope\n            .entry(scope_id)\n            .or_default();\n          command_scope_entry\n            .allow\n            .extend(command_scope.allow.clone());\n          command_scope_entry.deny.extend(command_scope.deny.clone());\n\n          self\n            .scope_manager\n            .command_cache\n            .insert(scope_id, StateManager::new());\n        }\n      }\n\n      let entry = self.allowed_commands.entry(cmd_key).or_default();\n      entry.extend(resolved_cmds);\n    }\n\n    Ok(())\n  }\n\n  #[cfg(debug_assertions)]\n  pub(crate) fn resolve_access_message(\n    &self,\n    key: &str,\n    command_name: &str,\n    window: &str,\n    webview: &str,\n    origin: &Origin,\n  ) -> String {\n    fn print_references(resolved: &[ResolvedCommand]) -> String {\n      resolved\n        .iter()\n        .map(|r| {\n          format!(\n            \"capability: {}, permission: {}\",\n            r.referenced_by.capability, r.referenced_by.permission\n          )\n        })\n        .collect::<Vec<_>>()\n        .join(\" || \")\n    }\n\n    fn print_allowed_on(resolved: &[ResolvedCommand]) -> String {\n      if resolved.is_empty() {\n        \"command not allowed on any window/webview/URL context\".to_string()\n      } else {\n        let mut s = \"allowed on: \".to_string();\n\n        let last_index = resolved.len() - 1;\n        for (index, cmd) in resolved.iter().enumerate() {\n          let windows = cmd\n            .windows\n            .iter()\n            .map(|w| format!(\"\\\"{}\\\"\", w.as_str()))\n            .collect::<Vec<_>>()\n            .join(\", \");\n          let webviews = cmd\n            .webviews\n            .iter()\n            .map(|w| format!(\"\\\"{}\\\"\", w.as_str()))\n            .collect::<Vec<_>>()\n            .join(\", \");\n\n          s.push('[');\n\n          if !windows.is_empty() {\n            s.push_str(&format!(\"windows: {windows}, \"));\n          }\n\n          if !webviews.is_empty() {\n            s.push_str(&format!(\"webviews: {webviews}, \"));\n          }\n\n          match &cmd.context {\n            ExecutionContext::Local => s.push_str(\"URL: local\"),\n            ExecutionContext::Remote { url } => s.push_str(&format!(\"URL: {}\", url.as_str())),\n          }\n\n          s.push(']');\n\n          if index != last_index {\n            s.push_str(\", \");\n          }\n        }\n\n        s\n      }\n    }\n\n    fn has_permissions_allowing_command(\n      manifest: &Manifest,\n      set: &crate::utils::acl::PermissionSet,\n      command: &str,\n    ) -> bool {\n      for permission_id in &set.permissions {\n        if permission_id == \"default\" {\n          if let Some(default) = &manifest.default_permission {\n            if has_permissions_allowing_command(manifest, default, command) {\n              return true;\n            }\n          }\n        } else if let Some(ref_set) = manifest.permission_sets.get(permission_id) {\n          if has_permissions_allowing_command(manifest, ref_set, command) {\n            return true;\n          }\n        } else if let Some(permission) = manifest.permissions.get(permission_id) {\n          if permission.commands.allow.contains(&command.into()) {\n            return true;\n          }\n        }\n      }\n      false\n    }\n\n    let command = if key == APP_ACL_KEY {\n      command_name.to_string()\n    } else {\n      format!(\"plugin:{key}|{command_name}\")\n    };\n\n    let command_pretty_name = if key == APP_ACL_KEY {\n      command_name.to_string()\n    } else {\n      format!(\"{key}.{command_name}\")\n    };\n\n    if let Some(resolved) = self.denied_commands.get(&command) {\n      format!(\n        \"{command_pretty_name} explicitly denied on origin {origin}\\n\\nreferenced by: {}\",\n        print_references(resolved)\n      )\n    } else {\n      let command_matches = self.allowed_commands.get(&command);\n\n      if let Some(resolved) = self.allowed_commands.get(&command) {\n        let resolved_matching_origin = resolved\n          .iter()\n          .filter(|cmd| origin.matches(&cmd.context))\n          .collect::<Vec<&ResolvedCommand>>();\n        if resolved_matching_origin\n          .iter()\n          .any(|cmd| cmd.webviews.iter().any(|w| w.matches(webview)))\n          || resolved_matching_origin\n            .iter()\n            .any(|cmd| cmd.windows.iter().any(|w| w.matches(window)))\n        {\n          \"allowed\".to_string()\n        } else {\n          format!(\"{command_pretty_name} not allowed on window \\\"{window}\\\", webview \\\"{webview}\\\", URL: {}\\n\\n{}\\n\\nreferenced by: {}\",\n            match origin {\n              Origin::Local => \"local\",\n              Origin::Remote { url } => url.as_str()\n            },\n            print_allowed_on(resolved),\n            print_references(resolved)\n          )\n        }\n      } else {\n        let permission_error_detail = if let Some((key, manifest)) = self\n          .acl\n          .get_key_value(key)\n          .or_else(|| self.acl.get_key_value(&format!(\"core:{key}\")))\n        {\n          let mut permissions_referencing_command = Vec::new();\n\n          if let Some(default) = &manifest.default_permission {\n            if has_permissions_allowing_command(manifest, default, command_name) {\n              permissions_referencing_command.push(\"default\".into());\n            }\n          }\n          for set in manifest.permission_sets.values() {\n            if has_permissions_allowing_command(manifest, set, command_name) {\n              permissions_referencing_command.push(set.identifier.clone());\n            }\n          }\n          for permission in manifest.permissions.values() {\n            if permission.commands.allow.contains(&command_name.into()) {\n              permissions_referencing_command.push(permission.identifier.clone());\n            }\n          }\n\n          permissions_referencing_command.sort();\n\n          let associated_permissions = permissions_referencing_command\n            .into_iter()\n            .map(|permission| {\n              if key == APP_ACL_KEY {\n                permission\n              } else {\n                format!(\"{key}:{permission}\")\n              }\n            })\n            .collect::<Vec<_>>()\n            .join(\", \");\n\n          if associated_permissions.is_empty() {\n            \"Command not found\".to_string()\n          } else {\n            format!(\"Permissions associated with this command: {associated_permissions}\")\n          }\n        } else {\n          \"Plugin not found\".to_string()\n        };\n\n        if let Some(resolved_cmds) = command_matches {\n          format!(\n            \"{command_pretty_name} not allowed on origin [{origin}]. Please create a capability that has this origin on the context field.\\n\\nFound matches for: {}\\n\\n{permission_error_detail}\",\n            resolved_cmds\n              .iter()\n              .map(|resolved| {\n                let context = match &resolved.context {\n                  ExecutionContext::Local => \"[local]\".to_string(),\n                  ExecutionContext::Remote { url } => format!(\"[remote: {}]\", url.as_str()),\n                };\n                format!(\n                  \"- context: {context}, referenced by: capability: {}, permission: {}\",\n                  resolved.referenced_by.capability,\n                  resolved.referenced_by.permission\n                )\n              })\n              .collect::<Vec<_>>()\n              .join(\"\\n\")\n          )\n        } else {\n          format!(\"{command_pretty_name} not allowed. {permission_error_detail}\")\n        }\n      }\n    }\n  }\n\n  /// Checks if the given IPC execution is allowed and returns the [`ResolvedCommand`] if it is.\n  pub fn resolve_access(\n    &self,\n    command: &str,\n    window: &str,\n    webview: &str,\n    origin: &Origin,\n  ) -> Option<Vec<ResolvedCommand>> {\n    if self\n      .denied_commands\n      .get(command)\n      .map(|resolved| resolved.iter().any(|cmd| origin.matches(&cmd.context)))\n      .is_some()\n    {\n      None\n    } else {\n      self.allowed_commands.get(command).and_then(|resolved| {\n        let resolved_cmds = resolved\n          .iter()\n          .filter(|cmd| {\n            origin.matches(&cmd.context)\n              && (cmd.webviews.iter().any(|w| w.matches(webview))\n                || cmd.windows.iter().any(|w| w.matches(window)))\n          })\n          .cloned()\n          .collect::<Vec<_>>();\n        if resolved_cmds.is_empty() {\n          None\n        } else {\n          Some(resolved_cmds)\n        }\n      })\n    }\n  }\n}\n\n/// List of allowed and denied objects that match either the command-specific or plugin global scope criteria.\n#[derive(Debug)]\npub struct ScopeValue<T: ScopeObject> {\n  allow: Arc<Vec<Arc<T>>>,\n  deny: Arc<Vec<Arc<T>>>,\n}\n\nimpl<T: ScopeObject> ScopeValue<T> {\n  fn clone(&self) -> Self {\n    Self {\n      allow: self.allow.clone(),\n      deny: self.deny.clone(),\n    }\n  }\n\n  /// What this access scope allows.\n  pub fn allows(&self) -> &Vec<Arc<T>> {\n    &self.allow\n  }\n\n  /// What this access scope denies.\n  pub fn denies(&self) -> &Vec<Arc<T>> {\n    &self.deny\n  }\n}\n\n/// Access scope for a command that can be retrieved directly in the command function.\n#[derive(Debug)]\npub struct CommandScope<T: ScopeObject> {\n  allow: Vec<Arc<T>>,\n  deny: Vec<Arc<T>>,\n}\n\nimpl<T: ScopeObject> CommandScope<T> {\n  pub(crate) fn resolve<R: Runtime>(\n    webview: &Webview<R>,\n    scope_ids: Vec<u64>,\n  ) -> crate::Result<Self> {\n    let mut allow = Vec::new();\n    let mut deny = Vec::new();\n\n    for scope_id in scope_ids {\n      let scope = webview\n        .manager()\n        .runtime_authority\n        .lock()\n        .unwrap()\n        .scope_manager\n        .get_command_scope_typed::<R, T>(webview.app_handle(), &scope_id)?;\n\n      for s in scope.allows() {\n        allow.push(s.clone());\n      }\n      for s in scope.denies() {\n        deny.push(s.clone());\n      }\n    }\n\n    Ok(CommandScope { allow, deny })\n  }\n\n  /// What this access scope allows.\n  pub fn allows(&self) -> &Vec<Arc<T>> {\n    &self.allow\n  }\n\n  /// What this access scope denies.\n  pub fn denies(&self) -> &Vec<Arc<T>> {\n    &self.deny\n  }\n}\n\nimpl<T: ScopeObjectMatch> CommandScope<T> {\n  /// Ensure all deny scopes were not matched and any allow scopes were.\n  ///\n  /// This **WILL** return `true` if the allow scopes are empty and the deny\n  /// scopes did not trigger. If you require at least one allow scope, then\n  /// ensure the allow scopes are not empty before calling this method.\n  ///\n  /// ```\n  /// # use tauri::ipc::CommandScope;\n  /// # fn command(scope: CommandScope<()>) -> Result<(), &'static str> {\n  /// if scope.allows().is_empty() {\n  ///   return Err(\"you need to specify at least 1 allow scope!\");\n  /// }\n  /// # Ok(())\n  /// # }\n  /// ```\n  ///\n  /// # Example\n  ///\n  /// ```\n  /// # use serde::{Serialize, Deserialize};\n  /// # use url::Url;\n  /// # use tauri::{ipc::{CommandScope, ScopeObjectMatch}, command};\n  /// #\n  /// #[derive(Debug, Clone, Serialize, Deserialize)]\n  /// # pub struct Scope;\n  /// #\n  /// # impl ScopeObjectMatch for Scope {\n  /// #   type Input = str;\n  /// #\n  /// #   fn matches(&self, input: &str) -> bool {\n  /// #     true\n  /// #   }\n  /// # }\n  /// #\n  /// # fn do_work(_: String) -> Result<String, &'static str> {\n  /// #   Ok(\"Output\".into())\n  /// # }\n  /// #\n  /// #[command]\n  /// fn my_command(scope: CommandScope<Scope>, input: String) -> Result<String, &'static str> {\n  ///   if scope.matches(&input) {\n  ///     do_work(input)\n  ///   } else {\n  ///     Err(\"Scope didn't match input\")\n  ///   }\n  /// }\n  /// ```\n  pub fn matches(&self, input: &T::Input) -> bool {\n    // first make sure the input doesn't match any existing deny scope\n    if self.deny.iter().any(|s| s.matches(input)) {\n      return false;\n    }\n\n    // if there are allow scopes, ensure the input matches at least 1\n    if self.allow.is_empty() {\n      true\n    } else {\n      self.allow.iter().any(|s| s.matches(input))\n    }\n  }\n}\n\nimpl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope<T> {\n  /// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`CommandScope`].\n  fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {\n    let scope_ids = command.acl.as_ref().map(|resolved| {\n      resolved\n        .iter()\n        .filter_map(|cmd| cmd.scope_id)\n        .collect::<Vec<_>>()\n    });\n    if let Some(scope_ids) = scope_ids {\n      CommandScope::resolve(&command.message.webview, scope_ids).map_err(Into::into)\n    } else {\n      Ok(CommandScope {\n        allow: Default::default(),\n        deny: Default::default(),\n      })\n    }\n  }\n}\n\n/// Global access scope that can be retrieved directly in the command function.\n#[derive(Debug)]\npub struct GlobalScope<T: ScopeObject>(ScopeValue<T>);\n\nimpl<T: ScopeObject> GlobalScope<T> {\n  pub(crate) fn resolve<R: Runtime>(webview: &Webview<R>, plugin: &str) -> crate::Result<Self> {\n    webview\n      .manager()\n      .runtime_authority\n      .lock()\n      .unwrap()\n      .scope_manager\n      .get_global_scope_typed(webview.app_handle(), plugin)\n      .map(Self)\n  }\n\n  /// What this access scope allows.\n  pub fn allows(&self) -> &Vec<Arc<T>> {\n    &self.0.allow\n  }\n\n  /// What this access scope denies.\n  pub fn denies(&self) -> &Vec<Arc<T>> {\n    &self.0.deny\n  }\n}\n\nimpl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for GlobalScope<T> {\n  /// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`GlobalScope`].\n  fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {\n    GlobalScope::resolve(\n      &command.message.webview,\n      command.plugin.unwrap_or(APP_ACL_KEY),\n    )\n    .map_err(InvokeError::from_error)\n  }\n}\n\n#[derive(Debug)]\npub struct ScopeManager {\n  command_scope: BTreeMap<ScopeKey, ResolvedScope>,\n  global_scope: BTreeMap<String, ResolvedScope>,\n  command_cache: BTreeMap<ScopeKey, StateManager>,\n  global_scope_cache: StateManager,\n}\n\n/// Marks a type as a scope object.\n///\n/// Usually you will just rely on [`serde::de::DeserializeOwned`] instead of implementing it manually,\n/// though this is useful if you need to do some initialization logic on the type itself.\npub trait ScopeObject: Sized + Send + Sync + Debug + 'static {\n  /// The error type.\n  type Error: std::error::Error + Send + Sync;\n  /// Deserialize the raw scope value.\n  fn deserialize<R: Runtime>(app: &AppHandle<R>, raw: Value) -> Result<Self, Self::Error>;\n}\n\nimpl<T: Send + Sync + Debug + DeserializeOwned + 'static> ScopeObject for T {\n  type Error = serde_json::Error;\n  fn deserialize<R: Runtime>(_app: &AppHandle<R>, raw: Value) -> Result<Self, Self::Error> {\n    serde_json::from_value(raw.into())\n  }\n}\n\n/// A [`ScopeObject`] whose validation can be represented as a `bool`.\n///\n/// # Example\n///\n/// ```\n/// # use serde::{Deserialize, Serialize};\n/// # use tauri::{ipc::ScopeObjectMatch, Url};\n/// #\n/// #[derive(Debug, Clone, Serialize, Deserialize)]\n/// #[serde(rename_all = \"camelCase\")]\n/// pub enum Scope {\n///   Domain(Url),\n///   StartsWith(String),\n/// }\n///\n/// impl ScopeObjectMatch for Scope {\n///   type Input = str;\n///\n///   fn matches(&self, input: &str) -> bool {\n///     match self {\n///       Scope::Domain(url) => {\n///         let parsed: Url = match input.parse() {\n///           Ok(parsed) => parsed,\n///           Err(_) => return false,\n///         };\n///\n///         let domain = parsed.domain();\n///\n///         domain.is_some() && domain == url.domain()\n///       }\n///       Scope::StartsWith(start) => input.starts_with(start),\n///     }\n///   }\n/// }\n/// ```\npub trait ScopeObjectMatch: ScopeObject {\n  /// The type of input expected to validate against the scope.\n  ///\n  /// This will be borrowed, so if you want to match on a `&str` this type should be `str`.\n  type Input: ?Sized;\n\n  /// Check if the input matches against the scope.\n  fn matches(&self, input: &Self::Input) -> bool;\n}\n\nimpl ScopeManager {\n  pub(crate) fn get_global_scope_typed<R: Runtime, T: ScopeObject>(\n    &self,\n    app: &AppHandle<R>,\n    key: &str,\n  ) -> crate::Result<ScopeValue<T>> {\n    match self.global_scope_cache.try_get::<ScopeValue<T>>() {\n      Some(cached) => Ok(cached.inner().clone()),\n      None => {\n        let mut allow = Vec::new();\n        let mut deny = Vec::new();\n\n        if let Some(global_scope) = self.global_scope.get(key) {\n          for allowed in &global_scope.allow {\n            allow\n              .push(Arc::new(T::deserialize(app, allowed.clone()).map_err(\n                |e| crate::Error::CannotDeserializeScope(Box::new(e)),\n              )?));\n          }\n          for denied in &global_scope.deny {\n            deny\n              .push(Arc::new(T::deserialize(app, denied.clone()).map_err(\n                |e| crate::Error::CannotDeserializeScope(Box::new(e)),\n              )?));\n          }\n        }\n\n        let scope = ScopeValue {\n          allow: Arc::new(allow),\n          deny: Arc::new(deny),\n        };\n        self.global_scope_cache.set(scope.clone());\n        Ok(scope)\n      }\n    }\n  }\n\n  fn get_command_scope_typed<R: Runtime, T: ScopeObject>(\n    &self,\n    app: &AppHandle<R>,\n    key: &ScopeKey,\n  ) -> crate::Result<ScopeValue<T>> {\n    let cache = self.command_cache.get(key).unwrap();\n    match cache.try_get::<ScopeValue<T>>() {\n      Some(cached) => Ok(cached.inner().clone()),\n      None => {\n        let resolved_scope = self\n          .command_scope\n          .get(key)\n          .unwrap_or_else(|| panic!(\"missing command scope for key {key}\"));\n\n        let mut allow = Vec::new();\n        let mut deny = Vec::new();\n\n        for allowed in &resolved_scope.allow {\n          allow\n            .push(Arc::new(T::deserialize(app, allowed.clone()).map_err(\n              |e| crate::Error::CannotDeserializeScope(Box::new(e)),\n            )?));\n        }\n        for denied in &resolved_scope.deny {\n          deny\n            .push(Arc::new(T::deserialize(app, denied.clone()).map_err(\n              |e| crate::Error::CannotDeserializeScope(Box::new(e)),\n            )?));\n        }\n\n        let value = ScopeValue {\n          allow: Arc::new(allow),\n          deny: Arc::new(deny),\n        };\n\n        let _ = cache.set(value.clone());\n        Ok(value)\n      }\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use glob::Pattern;\n  use tauri_utils::acl::{\n    resolved::{Resolved, ResolvedCommand},\n    ExecutionContext,\n  };\n\n  use crate::ipc::Origin;\n\n  use super::RuntimeAuthority;\n\n  #[test]\n  fn window_glob_pattern_matches() {\n    let command = \"my-command\";\n    let window = \"main-*\";\n    let webview = \"other-*\";\n\n    let resolved_cmd = vec![ResolvedCommand {\n      windows: vec![Pattern::new(window).unwrap()],\n      ..Default::default()\n    }];\n    let allowed_commands = [(command.to_string(), resolved_cmd.clone())]\n      .into_iter()\n      .collect();\n\n    let authority = RuntimeAuthority::new(\n      Default::default(),\n      Resolved {\n        allowed_commands,\n        ..Default::default()\n      },\n    );\n\n    assert_eq!(\n      authority.resolve_access(\n        command,\n        &window.replace('*', \"something\"),\n        webview,\n        &Origin::Local\n      ),\n      Some(resolved_cmd)\n    );\n  }\n\n  #[test]\n  fn webview_glob_pattern_matches() {\n    let command = \"my-command\";\n    let window = \"other-*\";\n    let webview = \"main-*\";\n\n    let resolved_cmd = vec![ResolvedCommand {\n      windows: vec![Pattern::new(window).unwrap()],\n      webviews: vec![Pattern::new(webview).unwrap()],\n      ..Default::default()\n    }];\n    let allowed_commands = [(command.to_string(), resolved_cmd.clone())]\n      .into_iter()\n      .collect();\n\n    let authority = RuntimeAuthority::new(\n      Default::default(),\n      Resolved {\n        allowed_commands,\n        ..Default::default()\n      },\n    );\n\n    assert_eq!(\n      authority.resolve_access(\n        command,\n        window,\n        &webview.replace('*', \"something\"),\n        &Origin::Local\n      ),\n      Some(resolved_cmd)\n    );\n  }\n\n  #[test]\n  fn remote_domain_matches() {\n    let url = \"https://tauri.app\";\n    let command = \"my-command\";\n    let window = \"main\";\n    let webview = \"main\";\n\n    let resolved_cmd = vec![ResolvedCommand {\n      windows: vec![Pattern::new(window).unwrap()],\n      context: ExecutionContext::Remote {\n        url: url.parse().unwrap(),\n      },\n      ..Default::default()\n    }];\n    let allowed_commands = [(command.to_string(), resolved_cmd.clone())]\n      .into_iter()\n      .collect();\n\n    let authority = RuntimeAuthority::new(\n      Default::default(),\n      Resolved {\n        allowed_commands,\n        ..Default::default()\n      },\n    );\n\n    assert_eq!(\n      authority.resolve_access(\n        command,\n        window,\n        webview,\n        &Origin::Remote {\n          url: url.parse().unwrap()\n        }\n      ),\n      Some(resolved_cmd)\n    );\n  }\n\n  #[test]\n  fn remote_domain_glob_pattern_matches() {\n    let url = \"http://tauri.*\";\n    let command = \"my-command\";\n    let window = \"main\";\n    let webview = \"main\";\n\n    let resolved_cmd = vec![ResolvedCommand {\n      windows: vec![Pattern::new(window).unwrap()],\n      context: ExecutionContext::Remote {\n        url: url.parse().unwrap(),\n      },\n      ..Default::default()\n    }];\n    let allowed_commands = [(command.to_string(), resolved_cmd.clone())]\n      .into_iter()\n      .collect();\n\n    let authority = RuntimeAuthority::new(\n      Default::default(),\n      Resolved {\n        allowed_commands,\n        ..Default::default()\n      },\n    );\n\n    assert_eq!(\n      authority.resolve_access(\n        command,\n        window,\n        webview,\n        &Origin::Remote {\n          url: url.replace('*', \"studio\").parse().unwrap()\n        }\n      ),\n      Some(resolved_cmd)\n    );\n  }\n\n  #[test]\n  fn remote_context_denied() {\n    let command = \"my-command\";\n    let window = \"main\";\n    let webview = \"main\";\n\n    let resolved_cmd = vec![ResolvedCommand {\n      windows: vec![Pattern::new(window).unwrap()],\n      ..Default::default()\n    }];\n    let allowed_commands = [(command.to_string(), resolved_cmd)].into_iter().collect();\n\n    let authority = RuntimeAuthority::new(\n      Default::default(),\n      Resolved {\n        allowed_commands,\n        ..Default::default()\n      },\n    );\n\n    assert!(authority\n      .resolve_access(\n        command,\n        window,\n        webview,\n        &Origin::Remote {\n          url: \"https://tauri.app\".parse().unwrap()\n        }\n      )\n      .is_none());\n  }\n\n  #[test]\n  fn denied_command_takes_precendence() {\n    let command = \"my-command\";\n    let window = \"main\";\n    let webview = \"main\";\n    let windows = vec![Pattern::new(window).unwrap()];\n    let allowed_commands = [(\n      command.to_string(),\n      vec![ResolvedCommand {\n        windows: windows.clone(),\n        ..Default::default()\n      }],\n    )]\n    .into_iter()\n    .collect();\n    let denied_commands = [(\n      command.to_string(),\n      vec![ResolvedCommand {\n        windows,\n        ..Default::default()\n      }],\n    )]\n    .into_iter()\n    .collect();\n\n    let authority = RuntimeAuthority::new(\n      Default::default(),\n      Resolved {\n        allowed_commands,\n        denied_commands,\n        ..Default::default()\n      },\n    );\n\n    assert!(authority\n      .resolve_access(command, window, webview, &Origin::Local)\n      .is_none());\n  }\n\n  #[cfg(debug_assertions)]\n  #[test]\n  fn resolve_access_message() {\n    use tauri_utils::acl::manifest::Manifest;\n\n    let plugin_name = \"myplugin\";\n    let command_allowed_on_window = \"my-command-window\";\n    let command_allowed_on_webview_window = \"my-command-webview-window\";\n    let window = \"main-*\";\n    let webview = \"webview-*\";\n    let remote_url = \"http://localhost:8080\";\n\n    let referenced_by = tauri_utils::acl::resolved::ResolvedCommandReference {\n      capability: \"maincap\".to_string(),\n      permission: \"allow-command\".to_string(),\n    };\n\n    let resolved_window_cmd = ResolvedCommand {\n      windows: vec![Pattern::new(window).unwrap()],\n      referenced_by: referenced_by.clone(),\n      ..Default::default()\n    };\n    let resolved_webview_window_cmd = ResolvedCommand {\n      windows: vec![Pattern::new(window).unwrap()],\n      webviews: vec![Pattern::new(webview).unwrap()],\n      referenced_by: referenced_by.clone(),\n      ..Default::default()\n    };\n    let resolved_webview_window_remote_cmd = ResolvedCommand {\n      windows: vec![Pattern::new(window).unwrap()],\n      webviews: vec![Pattern::new(webview).unwrap()],\n      referenced_by,\n      context: ExecutionContext::Remote {\n        url: remote_url.parse().unwrap(),\n      },\n      ..Default::default()\n    };\n\n    let allowed_commands = [\n      (\n        format!(\"plugin:{plugin_name}|{command_allowed_on_window}\"),\n        vec![resolved_window_cmd],\n      ),\n      (\n        format!(\"plugin:{plugin_name}|{command_allowed_on_webview_window}\"),\n        vec![\n          resolved_webview_window_cmd,\n          resolved_webview_window_remote_cmd,\n        ],\n      ),\n    ]\n    .into_iter()\n    .collect();\n\n    let authority = RuntimeAuthority::new(\n      [(\n        plugin_name.to_string(),\n        Manifest {\n          default_permission: None,\n          permissions: Default::default(),\n          permission_sets: Default::default(),\n          global_scope_schema: None,\n        },\n      )]\n      .into_iter()\n      .collect(),\n      Resolved {\n        allowed_commands,\n        ..Default::default()\n      },\n    );\n\n    // unknown plugin\n    assert_eq!(\n      authority.resolve_access_message(\n        \"unknown-plugin\",\n        command_allowed_on_window,\n        window,\n        webview,\n        &Origin::Local\n      ),\n      \"unknown-plugin.my-command-window not allowed. Plugin not found\"\n    );\n\n    // unknown command\n    assert_eq!(\n      authority.resolve_access_message(\n        plugin_name,\n        \"unknown-command\",\n        window,\n        webview,\n        &Origin::Local\n      ),\n      \"myplugin.unknown-command not allowed. Command not found\"\n    );\n\n    // window/webview do not match\n    assert_eq!(\n      authority.resolve_access_message(\n        plugin_name,\n        command_allowed_on_window,\n        \"other-window\",\n        \"any-webview\",\n        &Origin::Local\n      ),\n      \"myplugin.my-command-window not allowed on window \\\"other-window\\\", webview \\\"any-webview\\\", URL: local\\n\\nallowed on: [windows: \\\"main-*\\\", URL: local]\\n\\nreferenced by: capability: maincap, permission: allow-command\"\n    );\n\n    // window matches, but not origin\n    assert_eq!(\n      authority.resolve_access_message(\n        plugin_name,\n        command_allowed_on_window,\n        window,\n        \"any-webview\",\n        &Origin::Remote {\n          url: \"http://localhst\".parse().unwrap()\n        }\n      ),\n      \"myplugin.my-command-window not allowed on window \\\"main-*\\\", webview \\\"any-webview\\\", URL: http://localhst/\\n\\nallowed on: [windows: \\\"main-*\\\", URL: local]\\n\\nreferenced by: capability: maincap, permission: allow-command\"\n    );\n\n    // window/webview do not match\n    assert_eq!(\n      authority.resolve_access_message(\n        plugin_name,\n        command_allowed_on_webview_window,\n        \"other-window\",\n        \"other-webview\",\n        &Origin::Local\n      ),\n      \"myplugin.my-command-webview-window not allowed on window \\\"other-window\\\", webview \\\"other-webview\\\", URL: local\\n\\nallowed on: [windows: \\\"main-*\\\", webviews: \\\"webview-*\\\", URL: local], [windows: \\\"main-*\\\", webviews: \\\"webview-*\\\", URL: http://localhost:8080]\\n\\nreferenced by: capability: maincap, permission: allow-command || capability: maincap, permission: allow-command\"\n    );\n\n    // window/webview matches, but not origin\n    assert_eq!(\n      authority.resolve_access_message(\n        plugin_name,\n        command_allowed_on_webview_window,\n        window,\n        webview,\n        &Origin::Remote {\n          url: \"http://localhost:123\".parse().unwrap()\n        }\n      ),\n      \"myplugin.my-command-webview-window not allowed on window \\\"main-*\\\", webview \\\"webview-*\\\", URL: http://localhost:123/\\n\\nallowed on: [windows: \\\"main-*\\\", webviews: \\\"webview-*\\\", URL: local], [windows: \\\"main-*\\\", webviews: \\\"webview-*\\\", URL: http://localhost:8080]\\n\\nreferenced by: capability: maincap, permission: allow-command || capability: maincap, permission: allow-command\"\n    );\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/ipc/capability_builder.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::Serialize;\nuse tauri_utils::{\n  acl::{\n    capability::{Capability, CapabilityFile, PermissionEntry},\n    Scopes,\n  },\n  platform::Target,\n};\n\n/// A capability that can be added at runtime.\npub trait RuntimeCapability {\n  /// Creates the capability file.\n  fn build(self) -> CapabilityFile;\n}\n\nimpl<T: AsRef<str>> RuntimeCapability for T {\n  fn build(self) -> CapabilityFile {\n    self.as_ref().parse().expect(\"invalid capability\")\n  }\n}\n\n/// A builder for a [`Capability`].\npub struct CapabilityBuilder(Capability);\n\nimpl CapabilityBuilder {\n  /// Creates a new capability builder with a unique identifier.\n  pub fn new(identifier: impl Into<String>) -> Self {\n    Self(Capability {\n      identifier: identifier.into(),\n      description: \"\".into(),\n      remote: None,\n      local: true,\n      windows: Vec::new(),\n      webviews: Vec::new(),\n      permissions: Vec::new(),\n      platforms: None,\n    })\n  }\n\n  /// Allows this capability to be used by a remote URL.\n  pub fn remote(mut self, url: String) -> Self {\n    self\n      .0\n      .remote\n      .get_or_insert_with(Default::default)\n      .urls\n      .push(url);\n    self\n  }\n\n  /// Whether this capability is applied on local app URLs or not. Defaults to `true`.\n  pub fn local(mut self, local: bool) -> Self {\n    self.0.local = local;\n    self\n  }\n\n  /// Link this capability to the given window label.\n  pub fn window(mut self, window: impl Into<String>) -> Self {\n    self.0.windows.push(window.into());\n    self\n  }\n\n  /// Link this capability to the a list of window labels.\n  pub fn windows(mut self, windows: impl IntoIterator<Item = impl Into<String>>) -> Self {\n    self.0.windows.extend(windows.into_iter().map(|w| w.into()));\n    self\n  }\n\n  /// Link this capability to the given webview label.\n  pub fn webview(mut self, webview: impl Into<String>) -> Self {\n    self.0.webviews.push(webview.into());\n    self\n  }\n\n  /// Link this capability to the a list of window labels.\n  pub fn webviews(mut self, webviews: impl IntoIterator<Item = impl Into<String>>) -> Self {\n    self\n      .0\n      .webviews\n      .extend(webviews.into_iter().map(|w| w.into()));\n    self\n  }\n\n  /// Add a new permission to this capability.\n  pub fn permission(mut self, permission: impl Into<String>) -> Self {\n    let permission = permission.into();\n    self.0.permissions.push(PermissionEntry::PermissionRef(\n      permission\n        .clone()\n        .try_into()\n        .unwrap_or_else(|_| panic!(\"invalid permission identifier '{permission}'\")),\n    ));\n    self\n  }\n\n  /// Add a new scoped permission to this capability.\n  pub fn permission_scoped<T: Serialize>(\n    mut self,\n    permission: impl Into<String>,\n    allowed: Vec<T>,\n    denied: Vec<T>,\n  ) -> Self {\n    let permission = permission.into();\n    let identifier = permission\n      .clone()\n      .try_into()\n      .unwrap_or_else(|_| panic!(\"invalid permission identifier '{permission}'\"));\n\n    let allowed_scope = allowed\n      .into_iter()\n      .map(|a| {\n        serde_json::to_value(a)\n          .expect(\"failed to serialize scope\")\n          .into()\n      })\n      .collect();\n    let denied_scope = denied\n      .into_iter()\n      .map(|a| {\n        serde_json::to_value(a)\n          .expect(\"failed to serialize scope\")\n          .into()\n      })\n      .collect();\n    let scope = Scopes {\n      allow: Some(allowed_scope),\n      deny: Some(denied_scope),\n    };\n\n    self\n      .0\n      .permissions\n      .push(PermissionEntry::ExtendedPermission { identifier, scope });\n    self\n  }\n\n  /// Adds a target platform for this capability.\n  ///\n  /// By default all platforms are applied.\n  pub fn platform(mut self, platform: Target) -> Self {\n    self\n      .0\n      .platforms\n      .get_or_insert_with(Default::default)\n      .push(platform);\n    self\n  }\n\n  /// Adds target platforms for this capability.\n  ///\n  /// By default all platforms are applied.\n  pub fn platforms(mut self, platforms: impl IntoIterator<Item = Target>) -> Self {\n    self\n      .0\n      .platforms\n      .get_or_insert_with(Default::default)\n      .extend(platforms);\n    self\n  }\n}\n\nimpl RuntimeCapability for CapabilityBuilder {\n  fn build(self) -> CapabilityFile {\n    CapabilityFile::Capability(self.0)\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/ipc/channel.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  collections::HashMap,\n  str::FromStr,\n  sync::{\n    atomic::{AtomicU32, AtomicUsize, Ordering},\n    Arc, Mutex,\n  },\n};\n\nuse serde::{Deserialize, Deserializer, Serialize, Serializer};\n\nuse crate::{\n  command,\n  ipc::{CommandArg, CommandItem},\n  plugin::{Builder as PluginBuilder, TauriPlugin},\n  Manager, Runtime, State, Webview,\n};\n\nuse super::{\n  format_callback::format_raw_js, CallbackFn, InvokeError, InvokeResponseBody, IpcResponse,\n  Request, Response,\n};\n\npub const IPC_PAYLOAD_PREFIX: &str = \"__CHANNEL__:\";\n// TODO: Change this to `channel` in v3\npub const CHANNEL_PLUGIN_NAME: &str = \"__TAURI_CHANNEL__\";\n// TODO: Change this to `plugin:channel|fetch` in v3\npub const FETCH_CHANNEL_DATA_COMMAND: &str = \"plugin:__TAURI_CHANNEL__|fetch\";\nconst CHANNEL_ID_HEADER_NAME: &str = \"Tauri-Channel-Id\";\n\n/// Maximum size a JSON we should send directly without going through the fetch process\n// 8192 byte JSON payload runs roughly 2x faster through eval than through fetch on WebView2 v135\nconst MAX_JSON_DIRECT_EXECUTE_THRESHOLD: usize = 8192;\n// 1024 byte payload runs  roughly 30% faster through eval than through fetch on macOS\nconst MAX_RAW_DIRECT_EXECUTE_THRESHOLD: usize = 1024;\n\nstatic CHANNEL_COUNTER: AtomicU32 = AtomicU32::new(0);\nstatic CHANNEL_DATA_COUNTER: AtomicU32 = AtomicU32::new(0);\n\n/// Maps a channel id to a pending data that must be send to the JavaScript side via the IPC.\n#[derive(Default, Clone)]\npub struct ChannelDataIpcQueue(Arc<Mutex<HashMap<u32, InvokeResponseBody>>>);\n\n/// An IPC channel.\npub struct Channel<TSend = InvokeResponseBody> {\n  inner: Arc<ChannelInner>,\n  phantom: std::marker::PhantomData<TSend>,\n}\n\n#[cfg(feature = \"specta\")]\nconst _: () = {\n  #[derive(specta::Type)]\n  #[specta(remote = super::Channel)]\n  #[allow(dead_code, non_camel_case_types)]\n  struct TAURI_CHANNEL<TSend>(std::marker::PhantomData<TSend>);\n};\n\nimpl<TSend> Clone for Channel<TSend> {\n  fn clone(&self) -> Self {\n    Self {\n      inner: self.inner.clone(),\n      phantom: self.phantom,\n    }\n  }\n}\n\ntype OnDropFn = Option<Box<dyn Fn() + Send + Sync + 'static>>;\ntype OnMessageFn = Box<dyn Fn(InvokeResponseBody) -> crate::Result<()> + Send + Sync>;\n\nstruct ChannelInner {\n  id: u32,\n  on_message: OnMessageFn,\n  on_drop: OnDropFn,\n}\n\nimpl Drop for ChannelInner {\n  fn drop(&mut self) {\n    if let Some(on_drop) = &self.on_drop {\n      on_drop();\n    }\n  }\n}\n\nimpl<TSend> Serialize for Channel<TSend> {\n  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    serializer.serialize_str(&format!(\"{IPC_PAYLOAD_PREFIX}{}\", self.inner.id))\n  }\n}\n\n/// The ID of a channel that was defined on the JavaScript layer.\n///\n/// Useful when expecting [`Channel`] as part of a JSON object instead of a top-level command argument.\n///\n/// # Examples\n///\n/// ```rust\n/// use tauri::{ipc::JavaScriptChannelId, Runtime, Webview};\n///\n/// #[derive(serde::Deserialize)]\n/// #[serde(rename_all = \"camelCase\")]\n/// struct Button {\n///   label: String,\n///   on_click: JavaScriptChannelId,\n/// }\n///\n/// #[tauri::command]\n/// fn add_button<R: Runtime>(webview: Webview<R>, button: Button) {\n///   let channel = button.on_click.channel_on(webview);\n///   channel.send(\"clicked\").unwrap();\n/// }\n/// ```\npub struct JavaScriptChannelId(CallbackFn);\n\nimpl FromStr for JavaScriptChannelId {\n  type Err = &'static str;\n\n  fn from_str(s: &str) -> Result<Self, Self::Err> {\n    s.strip_prefix(IPC_PAYLOAD_PREFIX)\n      .ok_or(\"invalid channel string\")\n      .and_then(|id| id.parse().map_err(|_| \"invalid channel ID\"))\n      .map(|id| Self(CallbackFn(id)))\n  }\n}\n\nimpl JavaScriptChannelId {\n  /// Gets a [`Channel`] for this channel ID on the given [`Webview`].\n  pub fn channel_on<R: Runtime, TSend>(&self, webview: Webview<R>) -> Channel<TSend> {\n    let callback_fn = self.0;\n    let callback_id = callback_fn.0;\n\n    let counter = Arc::new(AtomicUsize::new(0));\n    let counter_clone = counter.clone();\n    let webview_clone = webview.clone();\n\n    Channel::new_with_id(\n      callback_id,\n      Box::new(move |body| {\n        let current_index = counter.fetch_add(1, Ordering::Relaxed);\n\n        if let Some(interceptor) = &webview.manager.channel_interceptor {\n          if interceptor(&webview, callback_fn, current_index, &body) {\n            return Ok(());\n          }\n        }\n\n        match body {\n          // Don't go through the fetch process if the payload is small\n          InvokeResponseBody::Json(json_string)\n            if json_string.len() < MAX_JSON_DIRECT_EXECUTE_THRESHOLD =>\n          {\n            webview.eval(format_raw_js(\n              callback_id,\n              format!(\"{{ message: {json_string}, index: {current_index} }}\"),\n            ))?;\n          }\n          InvokeResponseBody::Raw(bytes) if bytes.len() < MAX_RAW_DIRECT_EXECUTE_THRESHOLD => {\n            let bytes_as_json_array = serde_json::to_string(&bytes)?;\n            webview.eval(format_raw_js(callback_id, format!(\"{{ message: new Uint8Array({bytes_as_json_array}).buffer, index: {current_index} }}\")))?;\n          }\n          // use the fetch API to speed up larger response payloads\n          _ => {\n            let data_id = CHANNEL_DATA_COUNTER.fetch_add(1, Ordering::Relaxed);\n\n            webview\n              .state::<ChannelDataIpcQueue>()\n              .0\n              .lock()\n              .unwrap()\n              .insert(data_id, body);\n\n            webview.eval(format!(\n              \"window.__TAURI_INTERNALS__.invoke('{FETCH_CHANNEL_DATA_COMMAND}', null, {{ headers: {{ '{CHANNEL_ID_HEADER_NAME}': '{data_id}' }} }}).then((response) => window.__TAURI_INTERNALS__.runCallback({callback_id}, {{ message: response, index: {current_index} }})).catch(console.error)\",\n            ))?;\n          }\n        }\n\n        Ok(())\n      }),\n      Some(Box::new(move || {\n        let current_index = counter_clone.load(Ordering::Relaxed);\n        let _ = webview_clone.eval(format_raw_js(\n          callback_id,\n          format!(\"{{ end: true, index: {current_index} }}\"),\n        ));\n      })),\n    )\n  }\n}\n\nimpl<'de> Deserialize<'de> for JavaScriptChannelId {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let value: String = Deserialize::deserialize(deserializer)?;\n    Self::from_str(&value).map_err(|_| {\n      serde::de::Error::custom(format!(\n        \"invalid channel value `{value}`, expected a string in the `{IPC_PAYLOAD_PREFIX}ID` format\"\n      ))\n    })\n  }\n}\n\nimpl<TSend> Channel<TSend> {\n  /// Creates a new channel with the given message handler.\n  pub fn new<F: Fn(InvokeResponseBody) -> crate::Result<()> + Send + Sync + 'static>(\n    on_message: F,\n  ) -> Self {\n    Self::new_with_id(\n      CHANNEL_COUNTER.fetch_add(1, Ordering::Relaxed),\n      Box::new(on_message),\n      None,\n    )\n  }\n\n  fn new_with_id(id: u32, on_message: OnMessageFn, on_drop: OnDropFn) -> Self {\n    #[allow(clippy::let_and_return)]\n    let channel = Self {\n      inner: Arc::new(ChannelInner {\n        id,\n        on_message,\n        on_drop,\n      }),\n      phantom: Default::default(),\n    };\n\n    #[cfg(mobile)]\n    crate::plugin::mobile::register_channel(Channel {\n      inner: channel.inner.clone(),\n      phantom: Default::default(),\n    });\n\n    channel\n  }\n\n  // This is used from the IPC handler\n  pub(crate) fn from_callback_fn<R: Runtime>(webview: Webview<R>, callback: CallbackFn) -> Self {\n    let callback_id = callback.0;\n    Channel::new_with_id(\n      callback_id,\n      Box::new(move |body| {\n        match body {\n          // Don't go through the fetch process if the payload is small\n          InvokeResponseBody::Json(json_string)\n            if json_string.len() < MAX_JSON_DIRECT_EXECUTE_THRESHOLD =>\n          {\n            webview.eval(format_raw_js(callback_id, json_string))?;\n          }\n          InvokeResponseBody::Raw(bytes) if bytes.len() < MAX_RAW_DIRECT_EXECUTE_THRESHOLD => {\n            let bytes_as_json_array = serde_json::to_string(&bytes)?;\n            webview.eval(format_raw_js(\n              callback_id,\n              format!(\"new Uint8Array({bytes_as_json_array}).buffer\"),\n            ))?;\n          }\n          // use the fetch API to speed up larger response payloads\n          _ => {\n            let data_id = CHANNEL_DATA_COUNTER.fetch_add(1, Ordering::Relaxed);\n\n            webview\n              .state::<ChannelDataIpcQueue>()\n              .0\n              .lock()\n              .unwrap()\n              .insert(data_id, body);\n\n            webview.eval(format!(\n              \"window.__TAURI_INTERNALS__.invoke('{FETCH_CHANNEL_DATA_COMMAND}', null, {{ headers: {{ '{CHANNEL_ID_HEADER_NAME}': '{data_id}' }} }}).then((response) => window.__TAURI_INTERNALS__.runCallback({callback_id}, response)).catch(console.error)\",\n            ))?;\n          }\n        }\n\n        Ok(())\n      }),\n      None,\n    )\n  }\n\n  /// The channel identifier.\n  pub fn id(&self) -> u32 {\n    self.inner.id\n  }\n\n  /// Sends the given data through the channel.\n  pub fn send(&self, data: TSend) -> crate::Result<()>\n  where\n    TSend: IpcResponse,\n  {\n    (self.inner.on_message)(data.body()?)\n  }\n}\n\nimpl<'de, R: Runtime, TSend> CommandArg<'de, R> for Channel<TSend> {\n  /// Grabs the [`Webview`] from the [`CommandItem`] and returns the associated [`Channel`].\n  fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {\n    let name = command.name;\n    let arg = command.key;\n    let webview = command.message.webview();\n    let value: String =\n      Deserialize::deserialize(command).map_err(|e| crate::Error::InvalidArgs(name, arg, e))?;\n    JavaScriptChannelId::from_str(&value)\n      .map(|id| id.channel_on(webview))\n      .map_err(|_| {\n        InvokeError::from(format!(\n\t        \"invalid channel value `{value}`, expected a string in the `{IPC_PAYLOAD_PREFIX}ID` format\"\n\t      ))\n      })\n  }\n}\n\n#[command(root = \"crate\")]\nfn fetch(\n  request: Request<'_>,\n  cache: State<'_, ChannelDataIpcQueue>,\n) -> Result<Response, &'static str> {\n  if let Some(id) = request\n    .headers()\n    .get(CHANNEL_ID_HEADER_NAME)\n    .and_then(|v| v.to_str().ok())\n    .and_then(|id| id.parse().ok())\n  {\n    if let Some(data) = cache.0.lock().unwrap().remove(&id) {\n      Ok(Response::new(data))\n    } else {\n      Err(\"data not found\")\n    }\n  } else {\n    Err(\"missing channel id header\")\n  }\n}\n\npub fn plugin<R: Runtime>() -> TauriPlugin<R> {\n  PluginBuilder::new(CHANNEL_PLUGIN_NAME)\n    .invoke_handler(crate::generate_handler![\n      #![plugin(__TAURI_CHANNEL__)]\n      fetch\n    ])\n    .build()\n}\n"
  },
  {
    "path": "crates/tauri/src/ipc/command.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The Tauri custom commands types and traits.\n//!\n//! You usually don't need to create these items yourself. These are created from [command](../attr.command.html)\n//! attribute macro along the way and used by [`crate::generate_handler`] macro.\n\nuse crate::{\n  ipc::{InvokeBody, InvokeError, InvokeMessage},\n  Runtime,\n};\nuse serde::{\n  de::{Error, Visitor},\n  Deserialize, Deserializer,\n};\n\nuse tauri_utils::acl::resolved::ResolvedCommand;\n\n/// Represents a custom command.\npub struct CommandItem<'a, R: Runtime> {\n  /// Name of the plugin if this command targets one.\n  pub plugin: Option<&'static str>,\n\n  /// The name of the command, e.g. `handler` on `#[command] fn handler(value: u64)`\n  pub name: &'static str,\n\n  /// The key of the command item, e.g. `value` on `#[command] fn handler(value: u64)`\n  pub key: &'static str,\n\n  /// The [`InvokeMessage`] that was passed to this command.\n  pub message: &'a InvokeMessage<R>,\n\n  /// The resolved ACL for this command.\n  pub acl: &'a Option<Vec<ResolvedCommand>>,\n}\n\n/// Trait implemented by command arguments to derive a value from a [`CommandItem`].\n///\n/// # Command Arguments\n///\n/// A command argument is any type that represents an item parsable from a [`CommandItem`]. Most\n/// implementations will use the data stored in [`InvokeMessage`] since [`CommandItem`] is mostly a\n/// wrapper around it.\n///\n/// # Provided Implementations\n///\n/// Tauri implements [`CommandArg`] automatically for a number of types.\n/// * [`crate::Window`]\n/// * [`crate::State`]\n/// * `T where T: serde::Deserialize`\n///   * Any type that implements `Deserialize` can automatically be used as a [`CommandArg`].\npub trait CommandArg<'de, R: Runtime>: Sized {\n  /// Derives an instance of `Self` from the [`CommandItem`].\n  ///\n  /// If the derivation fails, the corresponding message will be rejected using [`InvokeMessage#reject`].\n  fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError>;\n}\n\n/// Automatically implement [`CommandArg`] for any type that can be deserialized.\nimpl<'de, D: Deserialize<'de>, R: Runtime> CommandArg<'de, R> for D {\n  fn from_command(command: CommandItem<'de, R>) -> Result<D, InvokeError> {\n    let name = command.name;\n    let arg = command.key;\n    #[cfg(feature = \"tracing\")]\n    let _span = tracing::trace_span!(\"ipc::request::deserialize_arg\", arg = arg).entered();\n    Self::deserialize(command).map_err(|e| crate::Error::InvalidArgs(name, arg, e).into())\n  }\n}\n\n/// Pass the result of [`serde_json::Value::get`] into [`serde_json::Value`]'s deserializer.\n///\n/// Returns an error if the [`CommandItem`]'s key does not exist in the value.\nmacro_rules! pass {\n  ($fn:ident, $($arg:ident: $argt:ty),+) => {\n    fn $fn<V: Visitor<'de>>(self, $($arg: $argt),*) -> Result<V::Value, Self::Error> {\n      self.deserialize_json()?.$fn($($arg),*)\n    }\n  }\n}\n\nimpl<'a, R: Runtime> CommandItem<'a, R> {\n  fn deserialize_json(self) -> serde_json::Result<&'a serde_json::Value> {\n    if self.key.is_empty() {\n      return Err(serde_json::Error::custom(format!(\n        \"command {} has an argument with no name with a non-optional value\",\n        self.name\n      )));\n    }\n\n    match &self.message.payload {\n      InvokeBody::Raw(_body) => Err(serde_json::Error::custom(format!(\n        \"command {} expected a value for key {} but the IPC call used a bytes payload\",\n        self.name, self.key\n      ))),\n      InvokeBody::Json(v) => match v.get(self.key) {\n        Some(value) => Ok(value),\n        None => Err(serde_json::Error::custom(format!(\n          \"command {} missing required key {}\",\n          self.name, self.key\n        ))),\n      },\n    }\n  }\n}\n\n/// A [`Deserializer`] wrapper around [`CommandItem`].\n///\n/// If the key doesn't exist, an error will be returned if the deserialized type is not expecting\n/// an optional item. If the key does exist, the value will be called with\n/// [`Value`](serde_json::Value)'s [`Deserializer`] implementation.\nimpl<'de, R: Runtime> Deserializer<'de> for CommandItem<'de, R> {\n  type Error = serde_json::Error;\n\n  pass!(deserialize_any, visitor: V);\n  pass!(deserialize_bool, visitor: V);\n  pass!(deserialize_i8, visitor: V);\n  pass!(deserialize_i16, visitor: V);\n  pass!(deserialize_i32, visitor: V);\n  pass!(deserialize_i64, visitor: V);\n  pass!(deserialize_u8, visitor: V);\n  pass!(deserialize_u16, visitor: V);\n  pass!(deserialize_u32, visitor: V);\n  pass!(deserialize_u64, visitor: V);\n  pass!(deserialize_f32, visitor: V);\n  pass!(deserialize_f64, visitor: V);\n  pass!(deserialize_char, visitor: V);\n  pass!(deserialize_str, visitor: V);\n  pass!(deserialize_string, visitor: V);\n  pass!(deserialize_bytes, visitor: V);\n  pass!(deserialize_byte_buf, visitor: V);\n\n  fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {\n    match &self.message.payload {\n      InvokeBody::Raw(_body) => Err(serde_json::Error::custom(format!(\n        \"command {} expected a value for key {} but the IPC call used a bytes payload\",\n        self.name, self.key\n      ))),\n      InvokeBody::Json(v) => match v.get(self.key) {\n        Some(value) => value.deserialize_option(visitor),\n        None => visitor.visit_none(),\n      },\n    }\n  }\n\n  pass!(deserialize_unit, visitor: V);\n  pass!(deserialize_unit_struct, name: &'static str, visitor: V);\n  pass!(deserialize_newtype_struct, name: &'static str, visitor: V);\n  pass!(deserialize_seq, visitor: V);\n  pass!(deserialize_tuple, len: usize, visitor: V);\n\n  pass!(\n    deserialize_tuple_struct,\n    name: &'static str,\n    len: usize,\n    visitor: V\n  );\n\n  pass!(deserialize_map, visitor: V);\n\n  pass!(\n    deserialize_struct,\n    name: &'static str,\n    fields: &'static [&'static str],\n    visitor: V\n  );\n\n  pass!(\n    deserialize_enum,\n    name: &'static str,\n    fields: &'static [&'static str],\n    visitor: V\n  );\n\n  pass!(deserialize_identifier, visitor: V);\n  pass!(deserialize_ignored_any, visitor: V);\n}\n\n/// [Autoref-based stable specialization](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md)\n///\n/// Nothing in this module is considered stable.\n#[doc(hidden)]\npub mod private {\n  use crate::{\n    ipc::{InvokeError, InvokeResolver, InvokeResponseBody, IpcResponse},\n    Runtime,\n  };\n  use std::future::Future;\n  #[cfg(feature = \"tracing\")]\n  pub use tracing;\n\n  // ===== impl IpcResponse =====\n\n  pub struct ResponseTag;\n\n  pub trait ResponseKind {\n    #[inline(always)]\n    fn blocking_kind(&self) -> ResponseTag {\n      ResponseTag\n    }\n\n    #[inline(always)]\n    fn async_kind(&self) -> ResponseTag {\n      ResponseTag\n    }\n  }\n\n  impl<T: IpcResponse> ResponseKind for &T {}\n\n  impl ResponseTag {\n    #[inline(always)]\n    pub fn block<R, T>(self, value: T, resolver: InvokeResolver<R>)\n    where\n      R: Runtime,\n      T: IpcResponse,\n    {\n      resolver.respond(Ok(value))\n    }\n\n    #[inline(always)]\n    pub async fn future<T>(self, value: T) -> Result<InvokeResponseBody, InvokeError>\n    where\n      T: IpcResponse,\n    {\n      Ok(value.body()?)\n    }\n  }\n\n  // ===== Result<impl Serialize, impl Into<InvokeError>> =====\n\n  pub struct ResultTag;\n\n  pub trait ResultKind {\n    #[inline(always)]\n    fn blocking_kind(&self) -> ResultTag {\n      ResultTag\n    }\n\n    #[inline(always)]\n    fn async_kind(&self) -> ResultTag {\n      ResultTag\n    }\n  }\n\n  impl<T: IpcResponse, E: Into<InvokeError>> ResultKind for Result<T, E> {}\n\n  impl ResultTag {\n    #[inline(always)]\n    pub fn block<R, T, E>(self, value: Result<T, E>, resolver: InvokeResolver<R>)\n    where\n      R: Runtime,\n      T: IpcResponse,\n      E: Into<InvokeError>,\n    {\n      resolver.respond(value.map_err(Into::into))\n    }\n\n    #[inline(always)]\n    pub async fn future<T, E>(self, value: Result<T, E>) -> Result<InvokeResponseBody, InvokeError>\n    where\n      T: IpcResponse,\n      E: Into<InvokeError>,\n    {\n      Ok(value.map_err(Into::into)?.body()?)\n    }\n  }\n\n  // ===== Future<Output = impl IpcResponse> =====\n\n  pub struct FutureTag;\n\n  pub trait FutureKind {\n    #[inline(always)]\n    fn async_kind(&self) -> FutureTag {\n      FutureTag\n    }\n  }\n  impl<T: IpcResponse, F: Future<Output = T>> FutureKind for &F {}\n\n  impl FutureTag {\n    #[inline(always)]\n    pub async fn future<T, F>(self, value: F) -> Result<InvokeResponseBody, InvokeError>\n    where\n      T: IpcResponse,\n      F: Future<Output = T> + Send + 'static,\n    {\n      Ok(value.await.body()?)\n    }\n  }\n\n  // ===== Future<Output = Result<impl Serialize, impl Into<InvokeError>>> =====\n\n  pub struct ResultFutureTag;\n\n  pub trait ResultFutureKind {\n    #[inline(always)]\n    fn async_kind(&self) -> ResultFutureTag {\n      ResultFutureTag\n    }\n  }\n\n  impl<T: IpcResponse, E: Into<InvokeError>, F: Future<Output = Result<T, E>>> ResultFutureKind\n    for F\n  {\n  }\n\n  impl ResultFutureTag {\n    #[inline(always)]\n    pub async fn future<T, E, F>(self, value: F) -> Result<InvokeResponseBody, InvokeError>\n    where\n      T: IpcResponse,\n      E: Into<InvokeError>,\n      F: Future<Output = Result<T, E>> + Send,\n    {\n      let response = value.await.map_err(Into::into)?;\n      Ok(response.body()?)\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/ipc/format_callback.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::Serialize;\nuse serde_json::value::RawValue;\nuse serialize_to_javascript::Serialized;\n\nuse super::CallbackFn;\n\n/// The information about this is quite limited. On Chrome/Edge and Firefox, [the maximum string size is approximately 1 GB](https://stackoverflow.com/a/34958490).\n///\n/// [From MDN:](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length#description)\n///\n/// ECMAScript 2016 (ed. 7) established a maximum length of 2^53 - 1 elements. Previously, no maximum length was specified.\n///\n/// In Firefox, strings have a maximum length of 2\\*\\*30 - 2 (~1GB). In versions prior to Firefox 65, the maximum length was 2\\*\\*28 - 1 (~256MB).\nconst MAX_JSON_STR_LEN: usize = usize::pow(2, 30) - 2;\n\n/// Minimum size JSON needs to be in order to convert it to JSON.parse with [`format_json`].\n// TODO: this number should be benchmarked and checked for optimal range, I set 10 KiB arbitrarily\n// we don't want to lose the gained object parsing time to extra allocations preparing it\nconst MIN_JSON_PARSE_LEN: usize = 10_240;\n\n/// Transforms & escapes a JSON value.\n///\n/// If it's an object or array, JSON.parse('{json}') is used, with the '{json}' string properly escaped.\n/// The return value of this function can be safely used on [`eval`](crate::Window#method.eval) calls.\n///\n/// Single quotes chosen because double quotes are already used in JSON. With single quotes, we only\n/// need to escape strings that include backslashes or single quotes. If we used double quotes, then\n/// there would be no cases that a string doesn't need escaping.\n///\n/// The function takes a closure to handle the escaped string in order to avoid unnecessary allocations.\n///\n/// # Safety\n///\n/// The ability to safely escape JSON into a JSON.parse('{json}') relies entirely on 2 things.\n///\n/// 1. `serde_json`'s ability to correctly escape and format json into a string.\n/// 2. JavaScript engines not accepting anything except another unescaped, literal single quote\n///    character to end a string that was opened with it.\nfn serialize_js_with<F: FnOnce(&str) -> String>(\n  json_string: String,\n  options: serialize_to_javascript::Options,\n  cb: F,\n) -> crate::Result<String> {\n  // get a raw &str representation of a serialized json value.\n\n  let raw = RawValue::from_string(json_string)?;\n\n  // from here we know json.len() > 1 because an empty string is not a valid json value.\n  let json = raw.get();\n  let first = json.as_bytes()[0];\n\n  #[cfg(debug_assertions)]\n  if first == b'\"' {\n    assert!(\n      json.len() < MAX_JSON_STR_LEN,\n      \"passing a string larger than the max JavaScript literal string size\"\n    )\n  }\n\n  let return_val = if json.len() > MIN_JSON_PARSE_LEN && (first == b'{' || first == b'[') {\n    let serialized = Serialized::new(&raw, &options).into_string();\n    // only use JSON.parse('{arg}') for arrays and objects less than the limit\n    // smaller literals do not benefit from being parsed from json\n    if serialized.len() < MAX_JSON_STR_LEN {\n      cb(&serialized)\n    } else {\n      cb(json)\n    }\n  } else {\n    cb(json)\n  };\n\n  Ok(return_val)\n}\n\n/// Formats a function name and a serializable argument to be evaluated as callback.\n///\n/// See [`format_raw`] for more information.\npub fn format<T: Serialize>(function_name: CallbackFn, arg: &T) -> crate::Result<String> {\n  format_raw(function_name, serde_json::to_string(arg)?)\n}\n\n/// Formats a function name and a raw JSON string argument to be evaluated as callback.\n///\n/// This will serialize primitive JSON types (e.g. booleans, strings, numbers, etc.) as JavaScript literals,\n/// but will serialize arrays and objects whose serialized JSON string is smaller than 1 GB and larger\n/// than 10 KiB with `JSON.parse('...')`.\n/// See [json-parse-benchmark](https://github.com/GoogleChromeLabs/json-parse-benchmark).\npub fn format_raw(function_name: CallbackFn, json_string: String) -> crate::Result<String> {\n  let callback_id = function_name.0;\n  serialize_js_with(json_string, Default::default(), |arg| {\n    format_raw_js(callback_id, arg)\n  })\n}\n\n/// Formats a function name and a JavaScript string argument to be evaluated as callback.\npub fn format_raw_js(callback_id: u32, js: impl AsRef<str>) -> String {\n  fn format_inner(callback_id: u32, js: &str) -> String {\n    format!(\"window.__TAURI_INTERNALS__.runCallback({callback_id}, {js})\")\n  }\n  format_inner(callback_id, js.as_ref())\n}\n\n/// Formats a serializable Result type to its Promise response.\n///\n/// See [`format_result_raw`] for more information.\npub fn format_result<T: Serialize, E: Serialize>(\n  result: Result<T, E>,\n  success_callback: CallbackFn,\n  error_callback: CallbackFn,\n) -> crate::Result<String> {\n  match result {\n    Ok(res) => format(success_callback, &res),\n    Err(err) => format(error_callback, &err),\n  }\n}\n\n/// Formats a Result type of raw JSON strings to its Promise response.\n/// Useful for Promises handling.\n/// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.\n/// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.\n///\n/// * `result` the Result to check\n/// * `success_callback` the function name of the Ok callback. Usually the `resolve` of the JS Promise.\n/// * `error_callback` the function name of the Err callback. Usually the `reject` of the JS Promise.\n///\n/// Note that the callback strings are automatically generated by the `invoke` helper.\npub fn format_result_raw(\n  raw_result: Result<String, String>,\n  success_callback: CallbackFn,\n  error_callback: CallbackFn,\n) -> crate::Result<String> {\n  match raw_result {\n    Ok(res) => format_raw(success_callback, res),\n    Err(err) => format_raw(error_callback, err),\n  }\n}\n\n#[cfg(test)]\nmod test {\n  use super::*;\n  use quickcheck::{Arbitrary, Gen};\n  use quickcheck_macros::quickcheck;\n\n  impl Arbitrary for CallbackFn {\n    fn arbitrary(g: &mut Gen) -> CallbackFn {\n      CallbackFn(u32::arbitrary(g))\n    }\n  }\n\n  #[derive(Debug, Clone)]\n  struct JsonStr(String);\n\n  impl Arbitrary for JsonStr {\n    fn arbitrary(g: &mut Gen) -> Self {\n      if bool::arbitrary(g) {\n        Self(format!(\n          \"{{ {}: {} }}\",\n          serde_json::to_string(&String::arbitrary(g)).unwrap(),\n          serde_json::to_string(&String::arbitrary(g)).unwrap()\n        ))\n      } else {\n        Self(serde_json::to_string(&String::arbitrary(g)).unwrap())\n      }\n    }\n  }\n\n  fn serialize_js<T: Serialize>(value: &T) -> crate::Result<String> {\n    serialize_js_with(serde_json::to_string(value)?, Default::default(), |v| {\n      v.into()\n    })\n  }\n\n  fn serialize_js_raw(value: impl Into<String>) -> crate::Result<String> {\n    serialize_js_with(value.into(), Default::default(), |v| v.into())\n  }\n\n  #[test]\n  fn test_serialize_js() {\n    assert_eq!(serialize_js(&()).unwrap(), \"null\");\n    assert_eq!(serialize_js(&5i32).unwrap(), \"5\");\n\n    #[derive(serde::Serialize)]\n    struct JsonObj {\n      value: String,\n    }\n\n    let raw_str = \"T\".repeat(MIN_JSON_PARSE_LEN);\n    assert_eq!(serialize_js(&raw_str).unwrap(), format!(\"\\\"{raw_str}\\\"\"));\n\n    assert_eq!(\n      serialize_js(&JsonObj {\n        value: raw_str.clone()\n      })\n      .unwrap(),\n      format!(\"JSON.parse('{{\\\"value\\\":\\\"{raw_str}\\\"}}')\")\n    );\n\n    assert_eq!(\n      serialize_js(&JsonObj {\n        value: format!(\"\\\"{raw_str}\\\"\")\n      })\n      .unwrap(),\n      format!(\"JSON.parse('{{\\\"value\\\":\\\"\\\\\\\\\\\"{raw_str}\\\\\\\\\\\"\\\"}}')\")\n    );\n\n    let dangerous_json = RawValue::from_string(\n      r#\"{\"test\":\"don\\\\🚀🐱‍👤\\\\'t forget to escape me!🚀🐱‍👤\",\"te🚀🐱‍👤st2\":\"don't forget to escape me!\",\"test3\":\"\\\\🚀🐱‍👤\\\\\\\\'''\\\\\\\\🚀🐱‍👤\\\\\\\\🚀🐱‍👤\\\\'''''\"}\"#.into()\n    ).unwrap();\n\n    let definitely_escaped_dangerous_json = format!(\n      \"JSON.parse('{}')\",\n      dangerous_json\n        .get()\n        .replace('\\\\', \"\\\\\\\\\")\n        .replace('\\'', \"\\\\'\")\n    );\n    let escape_single_quoted_json_test =\n      serialize_to_javascript::Serialized::new(&dangerous_json, &Default::default()).into_string();\n\n    let result = r#\"JSON.parse('{\"test\":\"don\\\\\\\\🚀🐱‍👤\\\\\\\\\\'t forget to escape me!🚀🐱‍👤\",\"te🚀🐱‍👤st2\":\"don\\'t forget to escape me!\",\"test3\":\"\\\\\\\\🚀🐱‍👤\\\\\\\\\\\\\\\\\\'\\'\\'\\\\\\\\\\\\\\\\🚀🐱‍👤\\\\\\\\\\\\\\\\🚀🐱‍👤\\\\\\\\\\'\\'\\'\\'\\'\"}')\"#;\n    assert_eq!(definitely_escaped_dangerous_json, result);\n    assert_eq!(escape_single_quoted_json_test, result);\n  }\n\n  // check arbitrary strings in the format callback function\n  #[quickcheck]\n  fn qc_formatting(f: CallbackFn, a: String) -> bool {\n    // call format callback\n    let fc = format(f, &a).unwrap();\n    fc.contains(&format!(\n      \"window.__TAURI_INTERNALS__.runCallback({}, JSON.parse('{}'))\",\n      f.0,\n      serde_json::Value::String(a.clone()),\n    )) || fc.contains(&format!(\n      r#\"window.__TAURI_INTERNALS__.runCallback({}, {})\"#,\n      f.0,\n      serde_json::Value::String(a),\n    ))\n  }\n\n  // check arbitrary strings in format_result\n  #[quickcheck]\n  fn qc_format_res(result: Result<String, String>, c: CallbackFn, ec: CallbackFn) -> bool {\n    let resp = format_result(result.clone(), c, ec).expect(\"failed to format callback result\");\n    let (function, value) = match result {\n      Ok(v) => (c, v),\n      Err(e) => (ec, e),\n    };\n\n    resp.contains(&format!(\n      r#\"window.__TAURI_INTERNALS__.runCallback({}, {})\"#,\n      function.0,\n      serde_json::Value::String(value),\n    ))\n  }\n\n  #[test]\n  fn test_serialize_js_raw() {\n    assert_eq!(serialize_js_raw(\"null\").unwrap(), \"null\");\n    assert_eq!(serialize_js_raw(\"5\").unwrap(), \"5\");\n    assert_eq!(\n      serialize_js_raw(\"{ \\\"x\\\": [1, 2, 3] }\").unwrap(),\n      \"{ \\\"x\\\": [1, 2, 3] }\"\n    );\n\n    #[derive(serde::Serialize)]\n    struct JsonObj {\n      value: String,\n    }\n\n    let raw_str = \"T\".repeat(MIN_JSON_PARSE_LEN);\n    assert_eq!(\n      serialize_js_raw(format!(\"\\\"{raw_str}\\\"\")).unwrap(),\n      format!(\"\\\"{raw_str}\\\"\")\n    );\n\n    assert_eq!(\n      serialize_js_raw(format!(\"{{\\\"value\\\":\\\"{raw_str}\\\"}}\")).unwrap(),\n      format!(\"JSON.parse('{{\\\"value\\\":\\\"{raw_str}\\\"}}')\")\n    );\n\n    assert_eq!(\n      serialize_js(&JsonObj {\n        value: format!(\"\\\"{raw_str}\\\"\")\n      })\n      .unwrap(),\n      format!(\"JSON.parse('{{\\\"value\\\":\\\"\\\\\\\\\\\"{raw_str}\\\\\\\\\\\"\\\"}}')\")\n    );\n\n    let dangerous_json = RawValue::from_string(\n      r#\"{\"test\":\"don\\\\🚀🐱‍👤\\\\'t forget to escape me!🚀🐱‍👤\",\"te🚀🐱‍👤st2\":\"don't forget to escape me!\",\"test3\":\"\\\\🚀🐱‍👤\\\\\\\\'''\\\\\\\\🚀🐱‍👤\\\\\\\\🚀🐱‍👤\\\\'''''\"}\"#.into()\n    ).unwrap();\n\n    let definitely_escaped_dangerous_json = format!(\n      \"JSON.parse('{}')\",\n      dangerous_json\n        .get()\n        .replace('\\\\', \"\\\\\\\\\")\n        .replace('\\'', \"\\\\'\")\n    );\n    let escape_single_quoted_json_test =\n      serialize_to_javascript::Serialized::new(&dangerous_json, &Default::default()).into_string();\n\n    let result = r#\"JSON.parse('{\"test\":\"don\\\\\\\\🚀🐱‍👤\\\\\\\\\\'t forget to escape me!🚀🐱‍👤\",\"te🚀🐱‍👤st2\":\"don\\'t forget to escape me!\",\"test3\":\"\\\\\\\\🚀🐱‍👤\\\\\\\\\\\\\\\\\\'\\'\\'\\\\\\\\\\\\\\\\🚀🐱‍👤\\\\\\\\\\\\\\\\🚀🐱‍👤\\\\\\\\\\'\\'\\'\\'\\'\"}')\"#;\n    assert_eq!(definitely_escaped_dangerous_json, result);\n    assert_eq!(escape_single_quoted_json_test, result);\n  }\n\n  // check arbitrary strings in the format callback function\n  #[quickcheck]\n  fn qc_formatting_raw(f: CallbackFn, a: JsonStr) -> bool {\n    let a = a.0;\n    // call format callback\n    let fc = format_raw(f, a.clone()).unwrap();\n    fc.contains(&format!(\n      r#\"window.__TAURI_INTERNALS__.runCallback({}, JSON.parse('{}'))\"#,\n      f.0, a\n    )) || fc.contains(&format!(\n      r#\"window.__TAURI_INTERNALS__.runCallback({}, {})\"#,\n      f.0, a\n    ))\n  }\n\n  // check arbitrary strings in format_result\n  #[quickcheck]\n  fn qc_format_raw_res(result: Result<JsonStr, JsonStr>, c: CallbackFn, ec: CallbackFn) -> bool {\n    let result = result.map(|v| v.0).map_err(|e| e.0);\n    let resp = format_result_raw(result.clone(), c, ec).expect(\"failed to format callback result\");\n    let (function, value) = match result {\n      Ok(v) => (c, v),\n      Err(e) => (ec, e),\n    };\n\n    resp.contains(&format!(\n      r#\"window.__TAURI_INTERNALS__.runCallback({}, {})\"#,\n      function.0, value\n    ))\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/ipc/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Types and functions related to Inter Procedure Call(IPC).\n//!\n//! This module includes utilities to send messages to the JS layer of the webview.\n\nuse std::{\n  future::Future,\n  sync::{Arc, Mutex},\n};\n\nuse http::HeaderMap;\nuse serde::{\n  de::{DeserializeOwned, IntoDeserializer},\n  Deserialize, Serialize,\n};\nuse serde_json::Value as JsonValue;\npub use serialize_to_javascript::Options as SerializeOptions;\nuse tauri_macros::default_runtime;\nuse tauri_utils::acl::resolved::ResolvedCommand;\n\nuse crate::{webview::Webview, Runtime, StateManager};\n\nmod authority;\n#[cfg(feature = \"dynamic-acl\")]\nmod capability_builder;\npub(crate) mod channel;\nmod command;\npub(crate) mod format_callback;\npub(crate) mod protocol;\n\npub use authority::{\n  CommandScope, GlobalScope, Origin, RuntimeAuthority, ScopeObject, ScopeObjectMatch, ScopeValue,\n};\n#[cfg(feature = \"dynamic-acl\")]\npub use capability_builder::{CapabilityBuilder, RuntimeCapability};\npub use channel::{Channel, JavaScriptChannelId};\npub use command::{private, CommandArg, CommandItem};\n\n/// A closure that is run every time Tauri receives a message it doesn't explicitly handle.\npub type InvokeHandler<R> = dyn Fn(Invoke<R>) -> bool + Send + Sync + 'static;\n\n/// A closure that is responsible for respond a JS message.\npub type InvokeResponder<R> =\n  dyn Fn(&Webview<R>, &str, &InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static;\n/// Similar to [`InvokeResponder`] but taking owned arguments.\npub type OwnedInvokeResponder<R> =\n  dyn FnOnce(Webview<R>, String, InvokeResponse, CallbackFn, CallbackFn) + Send + 'static;\n\n/// Possible values of an IPC payload.\n///\n/// ### Android\n/// On Android, [InvokeBody::Raw] is not supported. The enum will always contain [InvokeBody::Json].\n/// When targeting Android Devices, consider passing raw bytes as a base64 [[std::string::String]], which is still more efficient than passing them as a number array in [InvokeBody::Json]\n#[derive(Debug, Clone)]\n#[cfg_attr(test, derive(PartialEq))]\npub enum InvokeBody {\n  /// Json payload.\n  Json(JsonValue),\n  /// Bytes payload.\n  Raw(Vec<u8>),\n}\n\nimpl Default for InvokeBody {\n  fn default() -> Self {\n    Self::Json(Default::default())\n  }\n}\n\nimpl From<JsonValue> for InvokeBody {\n  fn from(value: JsonValue) -> Self {\n    Self::Json(value)\n  }\n}\n\nimpl From<Vec<u8>> for InvokeBody {\n  fn from(value: Vec<u8>) -> Self {\n    Self::Raw(value)\n  }\n}\n\nimpl InvokeBody {\n  #[cfg(mobile)]\n  pub(crate) fn into_json(self) -> JsonValue {\n    match self {\n      Self::Json(v) => v,\n      Self::Raw(v) => {\n        JsonValue::Array(v.into_iter().map(|n| JsonValue::Number(n.into())).collect())\n      }\n    }\n  }\n}\n\n/// Possible values of an IPC response.\n#[derive(Debug, Clone)]\n#[cfg_attr(test, derive(PartialEq))]\npub enum InvokeResponseBody {\n  /// Json payload.\n  Json(String),\n  /// Bytes payload.\n  Raw(Vec<u8>),\n}\n\nimpl From<String> for InvokeResponseBody {\n  fn from(value: String) -> Self {\n    Self::Json(value)\n  }\n}\n\nimpl From<Vec<u8>> for InvokeResponseBody {\n  fn from(value: Vec<u8>) -> Self {\n    Self::Raw(value)\n  }\n}\n\nimpl From<InvokeBody> for InvokeResponseBody {\n  fn from(value: InvokeBody) -> Self {\n    match value {\n      InvokeBody::Json(v) => Self::Json(serde_json::to_string(&v).unwrap()),\n      InvokeBody::Raw(v) => Self::Raw(v),\n    }\n  }\n}\n\nimpl IpcResponse for InvokeResponseBody {\n  fn body(self) -> crate::Result<InvokeResponseBody> {\n    Ok(self)\n  }\n}\n\nimpl InvokeResponseBody {\n  /// Attempts to deserialize the response.\n  pub fn deserialize<T: DeserializeOwned>(self) -> serde_json::Result<T> {\n    match self {\n      Self::Json(v) => serde_json::from_str(&v),\n      Self::Raw(v) => T::deserialize(v.into_deserializer()),\n    }\n  }\n}\n\n/// The IPC request.\n///\n/// Includes the `body` and `headers` parameters of a Tauri command invocation.\n/// This allows commands to accept raw bytes - on all platforms except Android.\n#[derive(Debug)]\npub struct Request<'a> {\n  body: &'a InvokeBody,\n  headers: &'a HeaderMap,\n}\n\nimpl Request<'_> {\n  /// The request body.\n  pub fn body(&self) -> &InvokeBody {\n    self.body\n  }\n\n  /// Thr request headers.\n  pub fn headers(&self) -> &HeaderMap {\n    self.headers\n  }\n}\n\nimpl<'a, R: Runtime> CommandArg<'a, R> for Request<'a> {\n  /// Returns the invoke [`Request`].\n  fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {\n    Ok(Self {\n      body: command.message.payload(),\n      headers: command.message.headers(),\n    })\n  }\n}\n\n/// Marks a type as a response to an IPC call.\npub trait IpcResponse {\n  /// Resolve the IPC response body.\n  fn body(self) -> crate::Result<InvokeResponseBody>;\n}\n\nimpl<T: Serialize> IpcResponse for T {\n  fn body(self) -> crate::Result<InvokeResponseBody> {\n    serde_json::to_string(&self)\n      .map(Into::into)\n      .map_err(Into::into)\n  }\n}\n\n/// The IPC response.\npub struct Response {\n  body: InvokeResponseBody,\n}\n\nimpl IpcResponse for Response {\n  fn body(self) -> crate::Result<InvokeResponseBody> {\n    Ok(self.body)\n  }\n}\n\nimpl Response {\n  /// Defines a response with the given body.\n  pub fn new(body: impl Into<InvokeResponseBody>) -> Self {\n    Self { body: body.into() }\n  }\n}\n\n/// The message and resolver given to a custom command.\n///\n/// This struct is used internally by macros and is explicitly **NOT** stable.\n#[default_runtime(crate::Wry, wry)]\npub struct Invoke<R: Runtime> {\n  /// The message passed.\n  pub message: InvokeMessage<R>,\n\n  /// The resolver of the message.\n  pub resolver: InvokeResolver<R>,\n\n  /// Resolved ACL for this IPC invoke.\n  pub acl: Option<Vec<ResolvedCommand>>,\n}\n\n/// Error response from an [`InvokeMessage`].\n#[derive(Debug)]\npub struct InvokeError(pub serde_json::Value);\n\nimpl InvokeError {\n  /// Create an [`InvokeError`] as a string of the [`std::error::Error`] message.\n  #[inline(always)]\n  pub fn from_error<E: std::error::Error>(error: E) -> Self {\n    Self(serde_json::Value::String(error.to_string()))\n  }\n\n  /// Create an [`InvokeError`] as a string of the [`anyhow::Error`] message.\n  #[inline(always)]\n  pub fn from_anyhow(error: anyhow::Error) -> Self {\n    Self(serde_json::Value::String(format!(\"{error:#}\")))\n  }\n}\n\nimpl<T: Serialize> From<T> for InvokeError {\n  #[inline]\n  fn from(value: T) -> Self {\n    serde_json::to_value(value)\n      .map(Self)\n      .unwrap_or_else(Self::from_error)\n  }\n}\n\nimpl From<crate::Error> for InvokeError {\n  #[inline(always)]\n  fn from(error: crate::Error) -> Self {\n    Self(serde_json::Value::String(error.to_string()))\n  }\n}\n\n/// Response from a [`InvokeMessage`] passed to the [`InvokeResolver`].\n#[derive(Debug)]\npub enum InvokeResponse {\n  /// Resolve the promise.\n  Ok(InvokeResponseBody),\n  /// Reject the promise.\n  Err(InvokeError),\n}\n\nimpl<T: IpcResponse, E: Into<InvokeError>> From<Result<T, E>> for InvokeResponse {\n  #[inline]\n  fn from(result: Result<T, E>) -> Self {\n    match result {\n      Ok(ok) => match ok.body() {\n        Ok(value) => Self::Ok(value),\n        Err(err) => Self::Err(InvokeError::from_error(err)),\n      },\n      Err(err) => Self::Err(err.into()),\n    }\n  }\n}\n\nimpl From<InvokeError> for InvokeResponse {\n  fn from(error: InvokeError) -> Self {\n    Self::Err(error)\n  }\n}\n\n/// Resolver of a invoke message.\n#[default_runtime(crate::Wry, wry)]\npub struct InvokeResolver<R: Runtime> {\n  webview: Webview<R>,\n  responder: Arc<Mutex<Option<Box<OwnedInvokeResponder<R>>>>>,\n  cmd: String,\n  pub(crate) callback: CallbackFn,\n  pub(crate) error: CallbackFn,\n}\n\nimpl<R: Runtime> Clone for InvokeResolver<R> {\n  fn clone(&self) -> Self {\n    Self {\n      webview: self.webview.clone(),\n      responder: self.responder.clone(),\n      cmd: self.cmd.clone(),\n      callback: self.callback,\n      error: self.error,\n    }\n  }\n}\n\nimpl<R: Runtime> InvokeResolver<R> {\n  pub(crate) fn new(\n    webview: Webview<R>,\n    responder: Arc<Mutex<Option<Box<OwnedInvokeResponder<R>>>>>,\n    cmd: String,\n    callback: CallbackFn,\n    error: CallbackFn,\n  ) -> Self {\n    Self {\n      webview,\n      responder,\n      cmd,\n      callback,\n      error,\n    }\n  }\n\n  /// Reply to the invoke promise with an async task.\n  pub fn respond_async<T, F>(self, task: F)\n  where\n    T: IpcResponse,\n    F: Future<Output = Result<T, InvokeError>> + Send + 'static,\n  {\n    crate::async_runtime::spawn(async move {\n      Self::return_task(\n        self.webview,\n        self.responder,\n        task,\n        self.cmd,\n        self.callback,\n        self.error,\n      )\n      .await;\n    });\n  }\n\n  /// Reply to the invoke promise with an async task which is already serialized.\n  pub fn respond_async_serialized<F>(self, task: F)\n  where\n    F: Future<Output = Result<InvokeResponseBody, InvokeError>> + Send + 'static,\n  {\n    // Dynamic dispatch the call in dev for a faster compile time\n    // TODO: Revisit this and see if we can do this for the release build as well if the performance hit is not a problem\n    #[cfg(debug_assertions)]\n    {\n      self.respond_async_serialized_dyn(Box::pin(task))\n    }\n    #[cfg(not(debug_assertions))]\n    {\n      self.respond_async_serialized_inner(task)\n    }\n  }\n\n  /// Dynamic dispatch the [`Self::respond_async_serialized`] call\n  #[cfg(debug_assertions)]\n  fn respond_async_serialized_dyn(\n    self,\n    task: std::pin::Pin<\n      Box<dyn Future<Output = Result<InvokeResponseBody, InvokeError>> + Send + 'static>,\n    >,\n  ) {\n    self.respond_async_serialized_inner(task)\n  }\n\n  /// Reply to the invoke promise with an async task which is already serialized.\n  fn respond_async_serialized_inner<F>(self, task: F)\n  where\n    F: Future<Output = Result<InvokeResponseBody, InvokeError>> + Send + 'static,\n  {\n    crate::async_runtime::spawn(async move {\n      let response = match task.await {\n        Ok(ok) => InvokeResponse::Ok(ok),\n        Err(err) => InvokeResponse::Err(err),\n      };\n      Self::return_result(\n        self.webview,\n        self.responder,\n        response,\n        self.cmd,\n        self.callback,\n        self.error,\n      )\n    });\n  }\n\n  /// Reply to the invoke promise with a serializable value.\n  pub fn respond<T: IpcResponse>(self, value: Result<T, InvokeError>) {\n    Self::return_result(\n      self.webview,\n      self.responder,\n      value.into(),\n      self.cmd,\n      self.callback,\n      self.error,\n    )\n  }\n\n  /// Resolve the invoke promise with a value.\n  pub fn resolve<T: IpcResponse>(self, value: T) {\n    self.respond(Ok(value))\n  }\n\n  /// Reject the invoke promise with a value.\n  pub fn reject<T: Serialize>(self, value: T) {\n    Self::return_result(\n      self.webview,\n      self.responder,\n      Result::<(), _>::Err(value).into(),\n      self.cmd,\n      self.callback,\n      self.error,\n    )\n  }\n\n  /// Reject the invoke promise with an [`InvokeError`].\n  pub fn invoke_error(self, error: InvokeError) {\n    Self::return_result(\n      self.webview,\n      self.responder,\n      error.into(),\n      self.cmd,\n      self.callback,\n      self.error,\n    )\n  }\n\n  /// Asynchronously executes the given task\n  /// and evaluates its Result to the JS promise described by the `success_callback` and `error_callback` function names.\n  ///\n  /// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.\n  /// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.\n  pub async fn return_task<T, F>(\n    webview: Webview<R>,\n    responder: Arc<Mutex<Option<Box<OwnedInvokeResponder<R>>>>>,\n    task: F,\n    cmd: String,\n    success_callback: CallbackFn,\n    error_callback: CallbackFn,\n  ) where\n    T: IpcResponse,\n    F: Future<Output = Result<T, InvokeError>> + Send + 'static,\n  {\n    let result = task.await;\n    Self::return_closure(\n      webview,\n      responder,\n      || result,\n      cmd,\n      success_callback,\n      error_callback,\n    )\n  }\n\n  pub(crate) fn return_closure<T: IpcResponse, F: FnOnce() -> Result<T, InvokeError>>(\n    webview: Webview<R>,\n    responder: Arc<Mutex<Option<Box<OwnedInvokeResponder<R>>>>>,\n    f: F,\n    cmd: String,\n    success_callback: CallbackFn,\n    error_callback: CallbackFn,\n  ) {\n    Self::return_result(\n      webview,\n      responder,\n      f().into(),\n      cmd,\n      success_callback,\n      error_callback,\n    )\n  }\n\n  pub(crate) fn return_result(\n    webview: Webview<R>,\n    responder: Arc<Mutex<Option<Box<OwnedInvokeResponder<R>>>>>,\n    response: InvokeResponse,\n    cmd: String,\n    success_callback: CallbackFn,\n    error_callback: CallbackFn,\n  ) {\n    (responder.lock().unwrap().take().expect(\"resolver consumed\"))(\n      webview,\n      cmd,\n      response,\n      success_callback,\n      error_callback,\n    );\n  }\n}\n\n/// An invoke message.\n#[default_runtime(crate::Wry, wry)]\n#[derive(Debug)]\npub struct InvokeMessage<R: Runtime> {\n  /// The webview that received the invoke message.\n  pub(crate) webview: Webview<R>,\n  /// Application managed state.\n  pub(crate) state: Arc<StateManager>,\n  /// The IPC command.\n  pub(crate) command: String,\n  /// The JSON argument passed on the invoke message.\n  pub(crate) payload: InvokeBody,\n  /// The request headers.\n  pub(crate) headers: HeaderMap,\n}\n\nimpl<R: Runtime> Clone for InvokeMessage<R> {\n  fn clone(&self) -> Self {\n    Self {\n      webview: self.webview.clone(),\n      state: self.state.clone(),\n      command: self.command.clone(),\n      payload: self.payload.clone(),\n      headers: self.headers.clone(),\n    }\n  }\n}\n\nimpl<R: Runtime> InvokeMessage<R> {\n  /// Create an new [`InvokeMessage`] from a payload send by a webview.\n  pub(crate) fn new(\n    webview: Webview<R>,\n    state: Arc<StateManager>,\n    command: String,\n    payload: InvokeBody,\n    headers: HeaderMap,\n  ) -> Self {\n    Self {\n      webview,\n      state,\n      command,\n      payload,\n      headers,\n    }\n  }\n\n  /// The invoke command.\n  #[inline(always)]\n  pub fn command(&self) -> &str {\n    &self.command\n  }\n\n  /// The webview that received the invoke.\n  #[inline(always)]\n  pub fn webview(&self) -> Webview<R> {\n    self.webview.clone()\n  }\n\n  /// A reference to webview that received the invoke.\n  #[inline(always)]\n  pub fn webview_ref(&self) -> &Webview<R> {\n    &self.webview\n  }\n\n  /// A reference to the payload the invoke received.\n  #[inline(always)]\n  pub fn payload(&self) -> &InvokeBody {\n    &self.payload\n  }\n\n  /// The state manager associated with the application\n  #[inline(always)]\n  pub fn state(&self) -> Arc<StateManager> {\n    self.state.clone()\n  }\n\n  /// A reference to the state manager associated with application.\n  #[inline(always)]\n  pub fn state_ref(&self) -> &StateManager {\n    &self.state\n  }\n\n  /// The request headers.\n  #[inline(always)]\n  pub fn headers(&self) -> &HeaderMap {\n    &self.headers\n  }\n}\n\n/// The `Callback` type is the return value of the `transformCallback` JavaScript function.\n#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]\npub struct CallbackFn(pub u32);\n\n#[cfg(test)]\nmod tests {\n  use super::*;\n\n  #[test]\n  fn deserialize_invoke_response_body() {\n    let json = InvokeResponseBody::Json(\"[1, 123, 1231]\".to_string());\n    assert_eq!(json.deserialize::<Vec<u16>>().unwrap(), vec![1, 123, 1231]);\n\n    let json = InvokeResponseBody::Json(\"\\\"string value\\\"\".to_string());\n    assert_eq!(json.deserialize::<String>().unwrap(), \"string value\");\n\n    let json = InvokeResponseBody::Json(\"\\\"string value\\\"\".to_string());\n    assert!(json.deserialize::<Vec<u16>>().is_err());\n\n    let values = vec![1, 2, 3, 4, 5, 6, 1];\n    let raw = InvokeResponseBody::Raw(values.clone());\n    assert_eq!(raw.deserialize::<Vec<u8>>().unwrap(), values);\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/ipc/protocol.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{borrow::Cow, sync::Arc};\n\nuse crate::{\n  ipc::InvokeResponseBody,\n  manager::AppManager,\n  webview::{InvokeRequest, UriSchemeProtocolHandler},\n  Runtime,\n};\nuse http::{\n  header::{\n    ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS,\n    CONTENT_TYPE,\n  },\n  HeaderValue, Method, Request, StatusCode,\n};\nuse url::Url;\n\nuse super::{CallbackFn, InvokeResponse};\n\nconst TAURI_CALLBACK_HEADER_NAME: &str = \"Tauri-Callback\";\nconst TAURI_ERROR_HEADER_NAME: &str = \"Tauri-Error\";\nconst TAURI_INVOKE_KEY_HEADER_NAME: &str = \"Tauri-Invoke-Key\";\n\nconst TAURI_RESPONSE_HEADER_NAME: &str = \"Tauri-Response\";\nconst TAURI_RESPONSE_HEADER_ERROR: &str = \"error\";\nconst TAURI_RESPONSE_HEADER_OK: &str = \"ok\";\n\npub fn message_handler<R: Runtime>(\n  manager: Arc<AppManager<R>>,\n) -> crate::runtime::webview::WebviewIpcHandler<crate::EventLoopMessage, R> {\n  Box::new(move |webview, request| handle_ipc_message(request, &manager, &webview.label))\n}\n\npub fn get<R: Runtime>(manager: Arc<AppManager<R>>) -> UriSchemeProtocolHandler {\n  Box::new(move |label, request, responder| {\n    #[cfg(feature = \"tracing\")]\n    let span = tracing::trace_span!(\n      \"ipc::request\",\n      kind = \"custom-protocol\",\n      request = tracing::field::Empty\n    )\n    .entered();\n\n    let respond = move |mut response: http::Response<Cow<'static, [u8]>>| {\n      response\n        .headers_mut()\n        .insert(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static(\"*\"));\n      response.headers_mut().insert(\n        ACCESS_CONTROL_EXPOSE_HEADERS,\n        HeaderValue::from_static(TAURI_RESPONSE_HEADER_NAME),\n      );\n      responder.respond(response);\n    };\n\n    match *request.method() {\n      Method::POST => {\n        if let Some(webview) = manager.get_webview(label) {\n          match parse_invoke_request(&manager, request) {\n            Ok(request) => {\n              #[cfg(feature = \"tracing\")]\n              span.record(\n                \"request\",\n                match &request.body {\n                  super::InvokeBody::Json(j) => serde_json::to_string(j).unwrap(),\n                  super::InvokeBody::Raw(b) => serde_json::to_string(b).unwrap(),\n                },\n              );\n              #[cfg(feature = \"tracing\")]\n              let request_span = tracing::trace_span!(\"ipc::request::handle\", cmd = request.cmd);\n\n              webview.on_message(\n                request,\n                Box::new(move |_webview, _cmd, response, _callback, _error| {\n                  #[cfg(feature = \"tracing\")]\n                  let _respond_span = tracing::trace_span!(\n                    parent: &request_span,\n                    \"ipc::request::respond\"\n                  )\n                  .entered();\n\n                  #[cfg(feature = \"tracing\")]\n                  let response_span = match &response {\n                    InvokeResponse::Ok(InvokeResponseBody::Json(v)) => tracing::trace_span!(\n                      \"ipc::request::response\",\n                      response = v,\n                      mime_type = tracing::field::Empty\n                    )\n                    .entered(),\n                    InvokeResponse::Ok(InvokeResponseBody::Raw(v)) => tracing::trace_span!(\n                      \"ipc::request::response\",\n                      response = format!(\"{v:?}\"),\n                      mime_type = tracing::field::Empty\n                    )\n                    .entered(),\n                    InvokeResponse::Err(e) => tracing::trace_span!(\n                      \"ipc::request::response\",\n                      error = format!(\"{e:?}\"),\n                      mime_type = tracing::field::Empty\n                    )\n                    .entered(),\n                  };\n\n                  let response_header = match &response {\n                    InvokeResponse::Ok(_) => TAURI_RESPONSE_HEADER_OK,\n                    InvokeResponse::Err(_) => TAURI_RESPONSE_HEADER_ERROR,\n                  };\n\n                  let (mut response, mime_type) = match response {\n                    InvokeResponse::Ok(InvokeResponseBody::Json(v)) => (\n                      http::Response::new(v.into_bytes().into()),\n                      mime::APPLICATION_JSON,\n                    ),\n                    InvokeResponse::Ok(InvokeResponseBody::Raw(v)) => (\n                      http::Response::new(v.into()),\n                      mime::APPLICATION_OCTET_STREAM,\n                    ),\n                    InvokeResponse::Err(e) => (\n                      http::Response::new(serde_json::to_vec(&e.0).unwrap().into()),\n                      mime::APPLICATION_JSON,\n                    ),\n                  };\n\n                  response\n                    .headers_mut()\n                    .insert(TAURI_RESPONSE_HEADER_NAME, response_header.parse().unwrap());\n\n                  #[cfg(feature = \"tracing\")]\n                  response_span.record(\"mime_type\", mime_type.essence_str());\n\n                  response.headers_mut().insert(\n                    CONTENT_TYPE,\n                    HeaderValue::from_str(mime_type.essence_str()).unwrap(),\n                  );\n\n                  respond(response);\n                }),\n              );\n            }\n            Err(e) => {\n              respond(\n                http::Response::builder()\n                  .status(StatusCode::INTERNAL_SERVER_ERROR)\n                  .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())\n                  .body(e.into_bytes().into())\n                  .unwrap(),\n              );\n            }\n          }\n        } else {\n          respond(\n            http::Response::builder()\n              .status(StatusCode::INTERNAL_SERVER_ERROR)\n              .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())\n              .body(\"failed to acquire webview reference\".as_bytes().into())\n              .unwrap(),\n          );\n        }\n      }\n\n      Method::OPTIONS => {\n        let mut r = http::Response::new(Vec::new().into());\n        r.headers_mut()\n          .insert(ACCESS_CONTROL_ALLOW_HEADERS, HeaderValue::from_static(\"*\"));\n\n        respond(r);\n      }\n\n      _ => {\n        let mut r = http::Response::new(\"only POST and OPTIONS are allowed\".as_bytes().into());\n        *r.status_mut() = StatusCode::METHOD_NOT_ALLOWED;\n        r.headers_mut().insert(\n          CONTENT_TYPE,\n          HeaderValue::from_str(mime::TEXT_PLAIN.essence_str()).unwrap(),\n        );\n        respond(r);\n      }\n    }\n  })\n}\n\nfn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager<R>, label: &str) {\n  if let Some(webview) = manager.get_webview(label) {\n    #[cfg(feature = \"tracing\")]\n    let _span = tracing::trace_span!(\n      \"ipc::request\",\n      kind = \"post-message\",\n      uri = request.uri().to_string(),\n      request = request.body()\n    )\n    .entered();\n\n    use serde::{Deserialize, Deserializer};\n\n    #[derive(Default)]\n    pub(crate) struct HeaderMap(http::HeaderMap);\n\n    impl<'de> Deserialize<'de> for HeaderMap {\n      fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n      where\n        D: Deserializer<'de>,\n      {\n        let map = std::collections::HashMap::<String, String>::deserialize(deserializer)?;\n        let mut headers = http::HeaderMap::default();\n        for (key, value) in map {\n          if let (Ok(key), Ok(value)) = (\n            http::header::HeaderName::from_bytes(key.as_bytes()),\n            http::HeaderValue::from_str(&value),\n          ) {\n            headers.insert(key, value);\n          } else {\n            return Err(serde::de::Error::custom(format!(\n              \"invalid header `{key}` `{value}`\"\n            )));\n          }\n        }\n        Ok(Self(headers))\n      }\n    }\n\n    #[derive(Deserialize, Default)]\n    #[serde(rename_all = \"camelCase\")]\n    struct RequestOptions {\n      #[serde(default)]\n      headers: HeaderMap,\n      #[serde(default)]\n      custom_protocol_ipc_blocked: bool,\n    }\n\n    #[derive(Deserialize)]\n    struct Message {\n      cmd: String,\n      callback: CallbackFn,\n      error: CallbackFn,\n      payload: serde_json::Value,\n      options: Option<RequestOptions>,\n      #[serde(rename = \"__TAURI_INVOKE_KEY__\")]\n      invoke_key: String,\n    }\n\n    #[allow(unused_mut)]\n    let mut invoke_message: Option<crate::Result<Message>> = None;\n\n    #[cfg(feature = \"isolation\")]\n    {\n      #[derive(serde::Deserialize)]\n      struct IsolationMessage<'a> {\n        cmd: String,\n        callback: CallbackFn,\n        error: CallbackFn,\n        payload: crate::utils::pattern::isolation::RawIsolationPayload<'a>,\n        options: Option<RequestOptions>,\n        #[serde(rename = \"__TAURI_INVOKE_KEY__\")]\n        invoke_key: String,\n      }\n\n      if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern {\n        #[cfg(feature = \"tracing\")]\n        let _span = tracing::trace_span!(\"ipc::request::decrypt_isolation_payload\").entered();\n\n        invoke_message.replace(\n          serde_json::from_str::<IsolationMessage<'_>>(request.body())\n            .map_err(Into::into)\n            .and_then(|message| {\n              let is_raw =\n                message.payload.content_type() == &mime::APPLICATION_OCTET_STREAM.to_string();\n              let payload = crypto_keys.decrypt(message.payload)?;\n              Ok(Message {\n                cmd: message.cmd,\n                callback: message.callback,\n                error: message.error,\n                payload: if is_raw {\n                  payload.into()\n                } else {\n                  serde_json::from_slice(&payload)?\n                },\n                options: message.options,\n                invoke_key: message.invoke_key,\n              })\n            }),\n        );\n      }\n    }\n\n    let message = invoke_message.unwrap_or_else(|| {\n      #[cfg(feature = \"tracing\")]\n      let _span = tracing::trace_span!(\"ipc::request::deserialize\").entered();\n      serde_json::from_str::<Message>(request.body()).map_err(Into::into)\n    });\n\n    match message {\n      Ok(message) => {\n        let options = message.options.unwrap_or_default();\n\n        let request = InvokeRequest {\n          cmd: message.cmd,\n          callback: message.callback,\n          error: message.error,\n          url: Url::parse(&request.uri().to_string()).expect(\"invalid IPC request URL\"),\n          body: message.payload.into(),\n          headers: options.headers.0,\n          invoke_key: message.invoke_key,\n        };\n\n        #[cfg(feature = \"tracing\")]\n        let request_span = tracing::trace_span!(\"ipc::request::handle\", cmd = request.cmd);\n\n        webview.on_message(\n          request,\n          Box::new(move |webview, cmd, response, callback, error| {\n            use crate::ipc::Channel;\n\n            #[cfg(feature = \"tracing\")]\n            let _respond_span = tracing::trace_span!(\n              parent: &request_span,\n              \"ipc::request::respond\"\n            )\n            .entered();\n\n            fn responder_eval<R: Runtime>(\n              webview: &crate::Webview<R>,\n              js: crate::Result<String>,\n              error: CallbackFn,\n            ) {\n              let eval_js = match js {\n                Ok(js) => js,\n                Err(e) => crate::ipc::format_callback::format(error, &e.to_string())\n                  .expect(\"unable to serialize response error string to json\"),\n              };\n\n              let _ = webview.eval(eval_js);\n            }\n\n            let can_use_channel_for_response = cmd\n              != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND\n              && !options.custom_protocol_ipc_blocked;\n\n            #[cfg(feature = \"tracing\")]\n            let mime_type = match &response {\n              InvokeResponse::Ok(InvokeResponseBody::Json(_)) => mime::APPLICATION_JSON,\n              InvokeResponse::Ok(InvokeResponseBody::Raw(_)) => mime::APPLICATION_OCTET_STREAM,\n              InvokeResponse::Err(_) => mime::APPLICATION_JSON,\n            };\n\n            #[cfg(feature = \"tracing\")]\n            let _response_span = match &response {\n              InvokeResponse::Ok(InvokeResponseBody::Json(v)) => tracing::trace_span!(\n                \"ipc::request::response\",\n                response = v,\n                mime_type = mime_type.essence_str()\n              )\n              .entered(),\n              InvokeResponse::Ok(InvokeResponseBody::Raw(v)) => tracing::trace_span!(\n                \"ipc::request::response\",\n                response = format!(\"{v:?}\"),\n                mime_type = mime_type.essence_str()\n              )\n              .entered(),\n              InvokeResponse::Err(e) => tracing::trace_span!(\n                \"ipc::request::response\",\n                response = format!(\"{e:?}\"),\n                mime_type = mime_type.essence_str()\n              )\n              .entered(),\n            };\n\n            match response {\n              InvokeResponse::Ok(InvokeResponseBody::Json(v)) => {\n                if !(cfg!(target_os = \"macos\") || cfg!(target_os = \"ios\"))\n                  && (v.starts_with('{') || v.starts_with('['))\n                  && can_use_channel_for_response\n                {\n                  let _ =\n                    Channel::from_callback_fn(webview, callback).send(InvokeResponseBody::Json(v));\n                } else {\n                  responder_eval(\n                    &webview,\n                    crate::ipc::format_callback::format_result_raw(\n                      Result::<_, String>::Ok(v),\n                      callback,\n                      error,\n                    ),\n                    error,\n                  )\n                }\n              }\n              InvokeResponse::Ok(InvokeResponseBody::Raw(v)) => {\n                if cfg!(target_os = \"macos\")\n                  || cfg!(target_os = \"ios\")\n                  || !can_use_channel_for_response\n                {\n                  responder_eval(\n                    &webview,\n                    crate::ipc::format_callback::format_result(\n                      Result::<_, ()>::Ok(v),\n                      callback,\n                      error,\n                    ),\n                    error,\n                  );\n                } else {\n                  let _ =\n                    Channel::from_callback_fn(webview, callback).send(InvokeResponseBody::Raw(v));\n                }\n              }\n              InvokeResponse::Err(e) => responder_eval(\n                &webview,\n                crate::ipc::format_callback::format_result(\n                  Result::<(), _>::Err(&e.0),\n                  callback,\n                  error,\n                ),\n                error,\n              ),\n            }\n          }),\n        );\n      }\n      Err(e) => {\n        #[cfg(feature = \"tracing\")]\n        tracing::trace!(\"ipc.request.error {}\", e);\n\n        let _ = webview.eval(format!(\n          r#\"console.error({})\"#,\n          serde_json::Value::String(e.to_string())\n        ));\n      }\n    }\n  }\n}\n\nfn parse_invoke_request<R: Runtime>(\n  #[allow(unused_variables)] manager: &AppManager<R>,\n  request: http::Request<Vec<u8>>,\n) -> std::result::Result<InvokeRequest, String> {\n  #[allow(unused_mut)]\n  let (parts, mut body) = request.into_parts();\n\n  // skip leading `/`\n  let cmd = percent_encoding::percent_decode(&parts.uri.path().as_bytes()[1..])\n    .decode_utf8_lossy()\n    .to_string();\n\n  // on Android we cannot read the request body\n  // so we must ignore it because some commands use the IPC for faster response\n  let has_payload = !body.is_empty();\n\n  #[allow(unused_mut)]\n  let mut content_type = parts\n    .headers\n    .get(http::header::CONTENT_TYPE)\n    .and_then(|h| h.to_str().ok())\n    .map(|mime| mime.parse())\n    .unwrap_or(Ok(mime::APPLICATION_OCTET_STREAM))\n    .map_err(|_| \"unknown content type\")?;\n\n  #[cfg(feature = \"isolation\")]\n  if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern {\n    // if the platform does not support request body, we ignore it\n    if has_payload {\n      #[cfg(feature = \"tracing\")]\n      let _span = tracing::trace_span!(\"ipc::request::decrypt_isolation_payload\").entered();\n\n      (body, content_type) = crate::utils::pattern::isolation::RawIsolationPayload::try_from(&body)\n        .and_then(|raw| {\n          let content_type = raw.content_type().clone();\n          crypto_keys.decrypt(raw).map(|decrypted| {\n            (\n              decrypted,\n              content_type\n                .parse()\n                .unwrap_or(mime::APPLICATION_OCTET_STREAM),\n            )\n          })\n        })\n        .map_err(|e| e.to_string())?;\n    }\n  }\n\n  let invoke_key = parts\n    .headers\n    .get(TAURI_INVOKE_KEY_HEADER_NAME)\n    .ok_or(\"missing Tauri-Invoke-Key header\")?\n    .to_str()\n    .map_err(|_| \"Tauri invoke key header value must be a string\")?\n    .to_owned();\n\n  let url = Url::parse(\n    parts\n      .headers\n      .get(\"Origin\")\n      .ok_or(\"missing Origin header\")?\n      .to_str()\n      .map_err(|_| \"Origin header value must be a string\")?,\n  )\n  .map_err(|_| \"Origin header is not a valid URL\")?;\n\n  let callback = CallbackFn(\n    parts\n      .headers\n      .get(TAURI_CALLBACK_HEADER_NAME)\n      .ok_or(\"missing Tauri-Callback header\")?\n      .to_str()\n      .map_err(|_| \"Tauri callback header value must be a string\")?\n      .parse()\n      .map_err(|_| \"Tauri callback header value must be a numeric string\")?,\n  );\n  let error = CallbackFn(\n    parts\n      .headers\n      .get(TAURI_ERROR_HEADER_NAME)\n      .ok_or(\"missing Tauri-Error header\")?\n      .to_str()\n      .map_err(|_| \"Tauri error header value must be a string\")?\n      .parse()\n      .map_err(|_| \"Tauri error header value must be a numeric string\")?,\n  );\n\n  #[cfg(feature = \"tracing\")]\n  let span = tracing::trace_span!(\"ipc::request::deserialize\").entered();\n\n  let body = if content_type == mime::APPLICATION_OCTET_STREAM {\n    body.into()\n  } else if content_type == mime::APPLICATION_JSON {\n    // if the platform does not support request body, we ignore it\n    if has_payload {\n      serde_json::from_slice::<serde_json::Value>(&body)\n        .map_err(|e| e.to_string())?\n        .into()\n    } else {\n      serde_json::Value::Object(Default::default()).into()\n    }\n  } else {\n    return Err(format!(\"content type {content_type} is not implemented\"));\n  };\n\n  #[cfg(feature = \"tracing\")]\n  drop(span);\n\n  let payload = InvokeRequest {\n    cmd,\n    callback,\n    error,\n    url,\n    body,\n    headers: parts.headers,\n    invoke_key,\n  };\n\n  Ok(payload)\n}\n\n#[cfg(test)]\nmod tests {\n  use std::str::FromStr;\n\n  use super::*;\n  use crate::{ipc::InvokeBody, manager::AppManager, plugin::PluginStore, StateManager, Wry};\n  use http::header::*;\n  use serde_json::json;\n  use tauri_macros::generate_context;\n\n  #[test]\n  fn parse_invoke_request() {\n    let context = generate_context!(\"test/fixture/src-tauri/tauri.conf.json\", crate, test = true);\n    let manager: AppManager<Wry> = AppManager::with_handlers(\n      context,\n      PluginStore::default(),\n      Box::new(|_| false),\n      None,\n      Default::default(),\n      StateManager::new(),\n      Default::default(),\n      #[cfg(all(desktop, feature = \"tray-icon\"))]\n      Default::default(),\n      Default::default(),\n      Default::default(),\n      Default::default(),\n      \"\".into(),\n      None,\n      crate::generate_invoke_key().unwrap(),\n    );\n\n    let cmd = \"write_something\";\n    let url = \"tauri://localhost\";\n    let invoke_key = \"1234ahdsjkl123\";\n    let callback = 12378123;\n    let error = 6243;\n    let mut headers = HeaderMap::from_iter(vec![\n      (\n        CONTENT_TYPE,\n        HeaderValue::from_str(mime::APPLICATION_OCTET_STREAM.as_ref()).unwrap(),\n      ),\n      (\n        HeaderName::from_str(TAURI_INVOKE_KEY_HEADER_NAME).unwrap(),\n        HeaderValue::from_str(invoke_key).unwrap(),\n      ),\n      (\n        HeaderName::from_str(TAURI_CALLBACK_HEADER_NAME).unwrap(),\n        HeaderValue::from_str(&callback.to_string()).unwrap(),\n      ),\n      (\n        HeaderName::from_str(TAURI_ERROR_HEADER_NAME).unwrap(),\n        HeaderValue::from_str(&error.to_string()).unwrap(),\n      ),\n      (ORIGIN, HeaderValue::from_str(\"tauri://localhost\").unwrap()),\n    ]);\n\n    let mut request = Request::builder().uri(format!(\"ipc://localhost/{cmd}\"));\n    *request.headers_mut().unwrap() = headers.clone();\n\n    let body = vec![123, 31, 45];\n    let request = request.body(body.clone()).unwrap();\n    let invoke_request = super::parse_invoke_request(&manager, request).unwrap();\n\n    assert_eq!(invoke_request.cmd, cmd);\n    assert_eq!(invoke_request.callback.0, callback);\n    assert_eq!(invoke_request.error.0, error);\n    assert_eq!(invoke_request.invoke_key, invoke_key);\n    assert_eq!(invoke_request.url, url.parse().unwrap());\n    assert_eq!(invoke_request.headers, headers);\n    assert_eq!(invoke_request.body, InvokeBody::Raw(body));\n\n    let body = json!({\n      \"key\": 1,\n      \"anotherKey\": \"asda\",\n    });\n\n    headers.insert(\n      CONTENT_TYPE,\n      HeaderValue::from_str(mime::APPLICATION_JSON.as_ref()).unwrap(),\n    );\n\n    let mut request = Request::builder().uri(format!(\"ipc://localhost/{cmd}\"));\n    *request.headers_mut().unwrap() = headers.clone();\n\n    let request = request.body(serde_json::to_vec(&body).unwrap()).unwrap();\n    let invoke_request = super::parse_invoke_request(&manager, request).unwrap();\n\n    assert_eq!(invoke_request.headers, headers);\n    assert_eq!(invoke_request.body, InvokeBody::Json(body));\n  }\n\n  #[test]\n  #[cfg(feature = \"isolation\")]\n  fn parse_invoke_request_isolation() {\n    let context = generate_context!(\n      \"test/fixture/isolation/src-tauri/tauri.conf.json\",\n      crate,\n      test = false\n    );\n\n    let crate::pattern::Pattern::Isolation { crypto_keys, .. } = &context.pattern else {\n      unreachable!()\n    };\n\n    let mut nonce = [0u8; 12];\n    getrandom::fill(&mut nonce).unwrap();\n\n    let body_raw = vec![1, 41, 65, 12, 78];\n    let body_bytes = crypto_keys.aes_gcm().encrypt(&nonce, &body_raw).unwrap();\n    let isolation_payload_raw = json!({\n      \"nonce\": nonce,\n      \"payload\": body_bytes,\n      \"contentType\":  mime::APPLICATION_OCTET_STREAM.to_string(),\n    });\n\n    let body_json = json!({\n      \"key\": 1,\n      \"anotherKey\": \"string\"\n    });\n    let body_bytes = crypto_keys\n      .aes_gcm()\n      .encrypt(&nonce, &serde_json::to_vec(&body_json).unwrap())\n      .unwrap();\n    let isolation_payload_json = json!({\n      \"nonce\": nonce,\n      \"payload\": body_bytes,\n      \"contentType\":  mime::APPLICATION_JSON.to_string(),\n    });\n\n    let manager: AppManager<Wry> = AppManager::with_handlers(\n      context,\n      PluginStore::default(),\n      Box::new(|_| false),\n      None,\n      Default::default(),\n      StateManager::new(),\n      Default::default(),\n      #[cfg(all(desktop, feature = \"tray-icon\"))]\n      Default::default(),\n      Default::default(),\n      Default::default(),\n      Default::default(),\n      \"\".into(),\n      None,\n      crate::generate_invoke_key().unwrap(),\n    );\n\n    let cmd = \"write_something\";\n    let url = \"tauri://localhost\";\n    let invoke_key = \"1234ahdsjkl123\";\n    let callback = 12378123;\n    let error = 6243;\n\n    let headers = HeaderMap::from_iter(vec![\n      (\n        CONTENT_TYPE,\n        HeaderValue::from_str(mime::APPLICATION_JSON.as_ref()).unwrap(),\n      ),\n      (\n        HeaderName::from_str(TAURI_INVOKE_KEY_HEADER_NAME).unwrap(),\n        HeaderValue::from_str(invoke_key).unwrap(),\n      ),\n      (\n        HeaderName::from_str(TAURI_CALLBACK_HEADER_NAME).unwrap(),\n        HeaderValue::from_str(&callback.to_string()).unwrap(),\n      ),\n      (\n        HeaderName::from_str(TAURI_ERROR_HEADER_NAME).unwrap(),\n        HeaderValue::from_str(&error.to_string()).unwrap(),\n      ),\n      (ORIGIN, HeaderValue::from_str(\"tauri://localhost\").unwrap()),\n    ]);\n\n    let mut request = Request::builder().uri(format!(\"ipc://localhost/{cmd}\"));\n    *request.headers_mut().unwrap() = headers.clone();\n    let body = serde_json::to_vec(&isolation_payload_raw).unwrap();\n    let request = request.body(body).unwrap();\n    let invoke_request = super::parse_invoke_request(&manager, request).unwrap();\n\n    assert_eq!(invoke_request.cmd, cmd);\n    assert_eq!(invoke_request.callback.0, callback);\n    assert_eq!(invoke_request.error.0, error);\n    assert_eq!(invoke_request.invoke_key, invoke_key);\n    assert_eq!(invoke_request.url, url.parse().unwrap());\n    assert_eq!(invoke_request.headers, headers);\n    assert_eq!(invoke_request.body, InvokeBody::Raw(body_raw));\n\n    let mut request = Request::builder().uri(format!(\"ipc://localhost/{cmd}\"));\n    *request.headers_mut().unwrap() = headers.clone();\n    let body = serde_json::to_vec(&isolation_payload_json).unwrap();\n    let request = request.body(body).unwrap();\n    let invoke_request = super::parse_invoke_request(&manager, request).unwrap();\n\n    assert_eq!(invoke_request.headers, headers);\n    assert_eq!(invoke_request.body, InvokeBody::Json(body_json));\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Tauri is a framework for building tiny, blazing fast binaries for all major desktop platforms.\n//! Developers can integrate any front-end framework that compiles to HTML, JS and CSS for building their user interface.\n//! The backend of the application is a rust-sourced binary with an API that the front-end can interact with.\n//!\n//! # Cargo features\n//!\n//! The following are a list of [Cargo features](https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section) that can be enabled or disabled:\n//!\n//! - **wry** *(enabled by default)*: Enables the [wry](https://github.com/tauri-apps/wry) runtime. Only disable it if you want a custom runtime.\n//! - **common-controls-v6** *(enabled by default)*: Enables [Common Controls v6](https://learn.microsoft.com/en-us/windows/win32/controls/common-control-versions) support on Windows, mainly for the predefined `about` menu item.\n//! - **x11** *(enabled by default)*: Enables X11 support. Disable this if you only target Wayland.\n//! - **unstable**: Enables unstable features. Be careful, it might introduce breaking changes in future minor releases.\n//! - **tracing**: Enables [`tracing`](https://docs.rs/tracing/latest/tracing) for window startup, plugins, `Window::eval`, events, IPC, updater and custom protocol request handlers.\n//! - **test**: Enables the [`mod@test`] module exposing unit test helpers.\n//! - **objc-exception**: This feature flag is no-op since 2.3.0.\n//! - **linux-libxdo**: Enables linking to libxdo which enables Cut, Copy, Paste and SelectAll menu items to work on Linux.\n//! - **isolation**: Enables the isolation pattern. Enabled by default if the `app > security > pattern > use` config option is set to `isolation` on the `tauri.conf.json` file.\n//! - **custom-protocol**: Feature managed by the Tauri CLI. When enabled, Tauri assumes a production environment instead of a development one.\n//! - **devtools**: Enables the developer tools (Web inspector) and [`window::Window#method.open_devtools`]. Enabled by default on debug builds.\n//!   On macOS it uses private APIs, so you can't enable it if your app will be published to the App Store.\n//! - **native-tls**: Provides TLS support to connect over HTTPS.\n//! - **native-tls-vendored**: Compile and statically link to a vendored copy of OpenSSL.\n//! - **rustls-tls**: Provides TLS support to connect over HTTPS using rustls.\n//! - **process-relaunch-dangerous-allow-symlink-macos**: Allows the [`process::current_binary`] function to allow symlinks on macOS (this is dangerous, see the Security section in the documentation website).\n//! - **tray-icon**: Enables application tray icon APIs. Enabled by default if the `trayIcon` config is defined on the `tauri.conf.json` file.\n//! - **macos-private-api**: Enables features only available in **macOS**'s private APIs, currently the `transparent` window functionality and the `fullScreenEnabled` preference setting to `true`. Enabled by default if the `tauri > macosPrivateApi` config flag is set to `true` on the `tauri.conf.json` file.\n//! - **webview-data-url**: Enables usage of data URLs on the webview.\n//! - **compression** *(enabled by default): Enables asset compression. You should only disable this if you want faster compile times in release builds - it produces larger binaries.\n//! - **config-json5**: Adds support to JSON5 format for `tauri.conf.json`.\n//! - **config-toml**: Adds support to TOML format for the configuration `Tauri.toml`.\n//! - **image-ico**: Adds support to parse `.ico` image, see [`Image`].\n//! - **image-png**: Adds support to parse `.png` image, see [`Image`].\n//! - **macos-proxy**: Adds support for [`WebviewBuilder::proxy_url`] on macOS. Requires macOS 14+.\n//! - **specta**: Add support for [`specta::specta`](https://docs.rs/specta/%5E2.0.0-rc.9/specta/attr.specta.html) with Tauri arguments such as [`State`](crate::State), [`Window`](crate::Window) and [`AppHandle`](crate::AppHandle)\n//! - **dynamic-acl** *(enabled by default)*: Enables you to add ACLs at runtime, notably it enables the [`Manager::add_capability`] function.\n//!\n//! ## Cargo allowlist features\n//!\n//! The following are a list of [Cargo features](https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section) that enables commands for Tauri's API package.\n//! These features are automatically enabled by the Tauri CLI based on the `allowlist` configuration under `tauri.conf.json`.\n//!\n//! ### Protocol allowlist\n//!\n//! - **protocol-asset**: Enables the `asset` custom protocol.\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n#![warn(missing_docs, rust_2018_idioms)]\n#![cfg_attr(docsrs, feature(doc_cfg))]\n\n/// Setups the binding that initializes an iOS plugin.\n#[cfg(target_os = \"ios\")]\n#[macro_export]\nmacro_rules! ios_plugin_binding {\n  ($fn_name: ident) => {\n    tauri::swift_rs::swift!(fn $fn_name() -> *const ::std::ffi::c_void);\n  }\n}\n#[cfg(target_os = \"macos\")]\n#[doc(hidden)]\npub use embed_plist;\npub use error::{Error, Result};\nuse ipc::RuntimeAuthority;\n#[cfg(feature = \"dynamic-acl\")]\nuse ipc::RuntimeCapability;\npub use resources::{Resource, ResourceId, ResourceTable};\n#[cfg(target_os = \"ios\")]\n#[doc(hidden)]\npub use swift_rs;\npub use tauri_macros::include_image;\n#[cfg(mobile)]\npub use tauri_macros::mobile_entry_point;\npub use tauri_macros::{command, generate_handler};\n\nuse tauri_utils::assets::AssetsIter;\npub use url::Url;\n\npub(crate) mod app;\npub mod async_runtime;\nmod error;\nmod event;\npub mod ipc;\nmod manager;\nmod pattern;\npub mod plugin;\npub(crate) mod protocol;\nmod resources;\nmod vibrancy;\npub mod webview;\npub mod window;\nuse tauri_runtime as runtime;\npub mod image;\n#[cfg(target_os = \"ios\")]\nmod ios;\n#[cfg(desktop)]\n#[cfg_attr(docsrs, doc(cfg(desktop)))]\npub mod menu;\n/// Path APIs.\npub mod path;\npub mod process;\n/// The allowlist scopes.\npub mod scope;\nmod state;\n\n#[cfg(all(desktop, feature = \"tray-icon\"))]\n#[cfg_attr(docsrs, doc(cfg(all(desktop, feature = \"tray-icon\"))))]\npub mod tray;\npub use tauri_utils as utils;\n\npub use http;\n\n/// A Tauri [`Runtime`] wrapper around wry.\n#[cfg(feature = \"wry\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"wry\")))]\npub type Wry = tauri_runtime_wry::Wry<EventLoopMessage>;\n/// A Tauri [`RuntimeHandle`] wrapper around wry.\n#[cfg(feature = \"wry\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"wry\")))]\npub type WryHandle = tauri_runtime_wry::WryHandle<EventLoopMessage>;\n\n#[cfg(all(feature = \"wry\", target_os = \"android\"))]\n#[cfg_attr(docsrs, doc(cfg(all(feature = \"wry\", target_os = \"android\"))))]\n#[doc(hidden)]\n#[macro_export]\nmacro_rules! android_binding {\n  ($domain:ident, $app_name:ident, $main:ident, $wry:path) => {\n    use $wry::{\n      android_setup,\n      prelude::{JClass, JNIEnv, JString},\n    };\n\n    ::tauri::wry::android_binding!($domain, $app_name, $wry);\n\n    ::tauri::tao::android_binding!(\n      $domain,\n      $app_name,\n      WryActivity,\n      android_setup,\n      $main,\n      ::tauri::tao\n    );\n\n    // be careful when renaming this, the `Java_app_tauri_plugin_PluginManager_handlePluginResponse` symbol is checked by the CLI\n    ::tauri::tao::platform::android::prelude::android_fn!(\n      app_tauri,\n      plugin,\n      PluginManager,\n      handlePluginResponse,\n      [i32, JString, JString],\n    );\n    ::tauri::tao::platform::android::prelude::android_fn!(\n      app_tauri,\n      plugin,\n      PluginManager,\n      sendChannelData,\n      [i64, JString],\n    );\n\n    // this function is a glue between PluginManager.kt > handlePluginResponse and Rust\n    #[allow(non_snake_case)]\n    pub fn handlePluginResponse(\n      mut env: JNIEnv,\n      _: JClass,\n      id: i32,\n      success: JString,\n      error: JString,\n    ) {\n      ::tauri::handle_android_plugin_response(&mut env, id, success, error);\n    }\n\n    // this function is a glue between PluginManager.kt > sendChannelData and Rust\n    #[allow(non_snake_case)]\n    pub fn sendChannelData(mut env: JNIEnv, _: JClass, id: i64, data: JString) {\n      ::tauri::send_channel_data(&mut env, id, data);\n    }\n  };\n}\n\n#[cfg(all(feature = \"wry\", target_os = \"android\"))]\n#[doc(hidden)]\npub use plugin::mobile::{handle_android_plugin_response, send_channel_data};\n#[cfg(all(feature = \"wry\", target_os = \"android\"))]\n#[doc(hidden)]\npub use tauri_runtime_wry::{tao, wry};\n\n/// A task to run on the main thread.\npub type SyncTask = Box<dyn FnOnce() + Send>;\n\nuse serde::Serialize;\nuse std::{\n  borrow::Cow,\n  collections::HashMap,\n  fmt::{self, Debug},\n  sync::MutexGuard,\n};\nuse utils::assets::{AssetKey, CspHash, EmbeddedAssets};\n\n#[cfg(feature = \"wry\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"wry\")))]\npub use tauri_runtime_wry::webview_version;\n\n#[cfg(target_os = \"macos\")]\n#[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\npub use runtime::ActivationPolicy;\n\npub use self::utils::TitleBarStyle;\n\nuse self::event::EventName;\npub use self::event::{Event, EventId, EventTarget};\nuse self::manager::EmitPayload;\npub use {\n  self::app::{\n    App, AppHandle, AssetResolver, Builder, CloseRequestApi, ExitRequestApi, RunEvent,\n    UriSchemeContext, UriSchemeResponder, WebviewEvent, WindowEvent, RESTART_EXIT_CODE,\n  },\n  self::manager::Asset,\n  self::runtime::{\n    dpi::{\n      LogicalPosition, LogicalRect, LogicalSize, LogicalUnit, PhysicalPosition, PhysicalRect,\n      PhysicalSize, PhysicalUnit, Pixel, PixelUnit, Position, Rect, Size,\n    },\n    window::{CursorIcon, DragDropEvent, WindowSizeConstraints},\n    DeviceEventFilter, UserAttentionType,\n  },\n  self::state::{State, StateManager},\n  self::utils::{\n    config::{Config, WebviewUrl},\n    Env, PackageInfo, Theme,\n  },\n  self::webview::{Webview, WebviewWindow, WebviewWindowBuilder},\n  self::window::{Monitor, Window},\n  scope::*,\n};\n\n#[cfg(feature = \"unstable\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"unstable\")))]\npub use {self::webview::WebviewBuilder, self::window::WindowBuilder};\n\n/// The Tauri version.\npub const VERSION: &str = env!(\"CARGO_PKG_VERSION\");\n\n#[cfg(target_os = \"ios\")]\n#[doc(hidden)]\npub fn log_stdout() {\n  #[cfg(target_os = \"ios\")]\n  unsafe {\n    crate::ios::log_stdout();\n  }\n}\n\n/// The user event type.\n#[derive(Debug, Clone)]\npub enum EventLoopMessage {\n  /// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu.\n  #[cfg(desktop)]\n  MenuEvent(menu::MenuEvent),\n  /// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu.\n  #[cfg(all(desktop, feature = \"tray-icon\"))]\n  #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = \"tray-icon\"))))]\n  TrayIconEvent(tray::TrayIconEvent),\n}\n\n/// The webview runtime interface. A wrapper around [`runtime::Runtime`] with the proper user event type associated.\npub trait Runtime: runtime::Runtime<EventLoopMessage> {}\n/// The webview runtime handle. A wrapper around [`runtime::RuntimeHandle`] with the proper user event type associated.\npub trait RuntimeHandle: runtime::RuntimeHandle<EventLoopMessage> {}\n\nimpl<W: runtime::Runtime<EventLoopMessage>> Runtime for W {}\nimpl<R: runtime::RuntimeHandle<EventLoopMessage>> RuntimeHandle for R {}\n\n/// Reads the config file at compile time and generates a [`Context`] based on its content.\n///\n/// The default config file path is a `tauri.conf.json` file inside the Cargo manifest directory of\n/// the crate being built.\n///\n/// # Custom Config Path\n///\n/// You may pass a string literal to this macro to specify a custom path for the Tauri config file.\n/// If the path is relative, it will be search for relative to the Cargo manifest of the compiling\n/// crate.\n///\n/// # Note\n///\n/// This macro should not be called if you are using [`tauri-build`] to generate the context from\n/// inside your build script as it will just cause excess computations that will be discarded. Use\n/// either the [`tauri-build`] method or this macro - not both.\n///\n/// [`tauri-build`]: https://docs.rs/tauri-build\npub use tauri_macros::generate_context;\n\n/// Include a [`Context`] that was generated by [`tauri-build`] inside your build script.\n///\n/// You should either use [`tauri-build`] and this macro to include the compile time generated code,\n/// or [`generate_context!`]. Do not use both at the same time, as they generate the same code and\n/// will cause excess computations that will be discarded.\n///\n/// [`tauri-build`]: https://docs.rs/tauri-build\n#[macro_export]\nmacro_rules! tauri_build_context {\n  () => {\n    include!(concat!(env!(\"OUT_DIR\"), \"/tauri-build-context.rs\"))\n  };\n}\n\npub use pattern::Pattern;\n\n/// Whether we are running in development mode or not.\npub const fn is_dev() -> bool {\n  !cfg!(feature = \"custom-protocol\")\n}\n\n/// Represents a container of file assets that are retrievable during runtime.\npub trait Assets<R: Runtime>: Send + Sync + 'static {\n  /// Initialize the asset provider.\n  fn setup(&self, app: &App<R>) {\n    let _ = app;\n  }\n\n  /// Get the content of the passed [`AssetKey`].\n  fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>>;\n\n  /// Iterator for the assets.\n  fn iter(&self) -> Box<tauri_utils::assets::AssetsIter<'_>>;\n\n  /// Gets the hashes for the CSP tag of the HTML on the given path.\n  fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_>;\n}\n\nimpl<R: Runtime> Assets<R> for EmbeddedAssets {\n  fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {\n    EmbeddedAssets::get(self, key)\n  }\n\n  fn iter(&self) -> Box<AssetsIter<'_>> {\n    EmbeddedAssets::iter(self)\n  }\n\n  fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {\n    EmbeddedAssets::csp_hashes(self, html_path)\n  }\n}\n\n/// User supplied data required inside of a Tauri application.\n///\n/// # Stability\n/// This is the output of the [`generate_context`] macro, and is not considered part of the stable API.\n/// Unless you know what you are doing and are prepared for this type to have breaking changes, do not create it yourself.\n#[tauri_macros::default_runtime(Wry, wry)]\npub struct Context<R: Runtime> {\n  pub(crate) config: Config,\n  #[cfg(dev)]\n  pub(crate) config_parent: Option<std::path::PathBuf>,\n  /// Asset provider.\n  pub assets: Box<dyn Assets<R>>,\n  pub(crate) default_window_icon: Option<image::Image<'static>>,\n  pub(crate) app_icon: Option<Vec<u8>>,\n  #[cfg(all(desktop, feature = \"tray-icon\"))]\n  pub(crate) tray_icon: Option<image::Image<'static>>,\n  pub(crate) package_info: PackageInfo,\n  pub(crate) pattern: Pattern,\n  pub(crate) runtime_authority: RuntimeAuthority,\n  pub(crate) plugin_global_api_scripts: Option<&'static [&'static str]>,\n}\n\n/// Temporary struct that overrides the Debug formatting for the `app_icon` field.\n///\n/// It reduces the output size compared to the default, as that would format the binary\n/// data as a slice of numbers `[65, 66, 67]`. This instead shows the length of the Vec.\n///\n/// For example: `Some([u8; 493])`\npub(crate) struct DebugAppIcon<'a>(&'a Option<Vec<u8>>);\n\nimpl std::fmt::Debug for DebugAppIcon<'_> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    match self.0 {\n      Option::None => f.write_str(\"None\"),\n      Option::Some(icon) => f\n        .debug_tuple(\"Some\")\n        .field(&format_args!(\"[u8; {}]\", icon.len()))\n        .finish(),\n    }\n  }\n}\n\nimpl<R: Runtime> fmt::Debug for Context<R> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    let mut d = f.debug_struct(\"Context\");\n    d.field(\"config\", &self.config)\n      .field(\"default_window_icon\", &self.default_window_icon)\n      .field(\"app_icon\", &DebugAppIcon(&self.app_icon))\n      .field(\"package_info\", &self.package_info)\n      .field(\"pattern\", &self.pattern)\n      .field(\"plugin_global_api_scripts\", &self.plugin_global_api_scripts);\n\n    #[cfg(all(desktop, feature = \"tray-icon\"))]\n    d.field(\"tray_icon\", &self.tray_icon);\n\n    d.finish()\n  }\n}\n\nimpl<R: Runtime> Context<R> {\n  /// The config the application was prepared with.\n  #[inline(always)]\n  pub fn config(&self) -> &Config {\n    &self.config\n  }\n\n  /// A mutable reference to the config the application was prepared with.\n  #[inline(always)]\n  pub fn config_mut(&mut self) -> &mut Config {\n    &mut self.config\n  }\n\n  /// The assets to be served directly by Tauri.\n  #[inline(always)]\n  pub fn assets(&self) -> &dyn Assets<R> {\n    self.assets.as_ref()\n  }\n\n  /// Replace the [`Assets`] implementation and returns the previous value so you can use it as a fallback if desired.\n  #[inline(always)]\n  pub fn set_assets(&mut self, assets: Box<dyn Assets<R>>) -> Box<dyn Assets<R>> {\n    std::mem::replace(&mut self.assets, assets)\n  }\n\n  /// The default window icon Tauri should use when creating windows.\n  #[inline(always)]\n  pub fn default_window_icon(&self) -> Option<&image::Image<'_>> {\n    self.default_window_icon.as_ref()\n  }\n\n  /// Set the default window icon Tauri should use when creating windows.\n  #[inline(always)]\n  pub fn set_default_window_icon(&mut self, icon: Option<image::Image<'static>>) {\n    self.default_window_icon = icon;\n  }\n\n  /// The icon to use on the tray icon.\n  #[cfg(all(desktop, feature = \"tray-icon\"))]\n  #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = \"tray-icon\"))))]\n  #[inline(always)]\n  pub fn tray_icon(&self) -> Option<&image::Image<'_>> {\n    self.tray_icon.as_ref()\n  }\n\n  /// Set the icon to use on the tray icon.\n  #[cfg(all(desktop, feature = \"tray-icon\"))]\n  #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = \"tray-icon\"))))]\n  #[inline(always)]\n  pub fn set_tray_icon(&mut self, icon: Option<image::Image<'static>>) {\n    self.tray_icon = icon;\n  }\n\n  /// Package information.\n  #[inline(always)]\n  pub fn package_info(&self) -> &PackageInfo {\n    &self.package_info\n  }\n\n  /// A mutable reference to the package information.\n  #[inline(always)]\n  pub fn package_info_mut(&mut self) -> &mut PackageInfo {\n    &mut self.package_info\n  }\n\n  /// The application pattern.\n  #[inline(always)]\n  pub fn pattern(&self) -> &Pattern {\n    &self.pattern\n  }\n\n  /// A mutable reference to the resolved ACL.\n  ///\n  /// # Stability\n  ///\n  /// This API is unstable.\n  #[doc(hidden)]\n  #[inline(always)]\n  pub fn runtime_authority_mut(&mut self) -> &mut RuntimeAuthority {\n    &mut self.runtime_authority\n  }\n\n  /// Create a new [`Context`] from the minimal required items.\n  #[inline(always)]\n  #[allow(clippy::too_many_arguments)]\n  pub fn new(\n    config: Config,\n    assets: Box<dyn Assets<R>>,\n    default_window_icon: Option<image::Image<'static>>,\n    app_icon: Option<Vec<u8>>,\n    package_info: PackageInfo,\n    pattern: Pattern,\n    runtime_authority: RuntimeAuthority,\n    plugin_global_api_scripts: Option<&'static [&'static str]>,\n  ) -> Self {\n    Self {\n      config,\n      #[cfg(dev)]\n      config_parent: None,\n      assets,\n      default_window_icon,\n      app_icon,\n      #[cfg(all(desktop, feature = \"tray-icon\"))]\n      tray_icon: None,\n      package_info,\n      pattern,\n      runtime_authority,\n      plugin_global_api_scripts,\n    }\n  }\n\n  #[cfg(dev)]\n  #[doc(hidden)]\n  pub fn with_config_parent(&mut self, config_parent: impl AsRef<std::path::Path>) {\n    self\n      .config_parent\n      .replace(config_parent.as_ref().to_owned());\n  }\n}\n\n// TODO: expand these docs\n/// Manages a running application.\npub trait Manager<R: Runtime>: sealed::ManagerBase<R> {\n  /// The application handle associated with this manager.\n  fn app_handle(&self) -> &AppHandle<R> {\n    self.managed_app_handle()\n  }\n\n  /// The [`Config`] the manager was created with.\n  fn config(&self) -> &Config {\n    self.manager().config()\n  }\n\n  /// The [`PackageInfo`] the manager was created with.\n  fn package_info(&self) -> &PackageInfo {\n    self.manager().package_info()\n  }\n\n  /// Fetch a single window from the manager.\n  #[cfg(feature = \"unstable\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"unstable\")))]\n  fn get_window(&self, label: &str) -> Option<Window<R>> {\n    self.manager().get_window(label)\n  }\n\n  /// Fetch the focused window. Returns `None` if there is not any focused window.\n  #[cfg(feature = \"unstable\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"unstable\")))]\n  fn get_focused_window(&self) -> Option<Window<R>> {\n    self.manager().get_focused_window()\n  }\n\n  /// Fetch all managed windows.\n  #[cfg(feature = \"unstable\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"unstable\")))]\n  fn windows(&self) -> HashMap<String, Window<R>> {\n    self.manager().windows()\n  }\n\n  /// Fetch a single webview from the manager.\n  #[cfg(feature = \"unstable\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"unstable\")))]\n  fn get_webview(&self, label: &str) -> Option<Webview<R>> {\n    self.manager().get_webview(label)\n  }\n\n  /// Fetch all managed webviews.\n  #[cfg(feature = \"unstable\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"unstable\")))]\n  fn webviews(&self) -> HashMap<String, Webview<R>> {\n    self.manager().webviews()\n  }\n\n  /// Fetch a single webview window from the manager.\n  fn get_webview_window(&self, label: &str) -> Option<WebviewWindow<R>> {\n    self.manager().get_webview(label).and_then(|webview| {\n      let window = webview.window();\n      if window.is_webview_window() {\n        Some(WebviewWindow { window, webview })\n      } else {\n        None\n      }\n    })\n  }\n\n  /// Fetch all managed webview windows.\n  fn webview_windows(&self) -> HashMap<String, WebviewWindow<R>> {\n    self\n      .manager()\n      .webviews()\n      .into_iter()\n      .filter_map(|(label, webview)| {\n        let window = webview.window();\n        if window.is_webview_window() {\n          Some((label, WebviewWindow { window, webview }))\n        } else {\n          None\n        }\n      })\n      .collect::<HashMap<_, _>>()\n  }\n\n  /// Add `state` to the state managed by the application.\n  ///\n  /// If the state for the `T` type has previously been set, the state is unchanged and false is returned. Otherwise true is returned.\n  ///\n  /// Managed state can be retrieved by any command handler via the\n  /// [`State`] guard. In particular, if a value of type `T`\n  /// is managed by Tauri, adding `State<T>` to the list of arguments in a\n  /// command handler instructs Tauri to retrieve the managed value.\n  /// Additionally, [`state`](Self#method.state) can be used to retrieve the value manually.\n  ///\n  /// # Mutability\n  ///\n  /// Since the managed state is global and must be [`Send`] + [`Sync`], mutations can only happen through interior mutability:\n  ///\n  /// ```rust,no_run\n  /// use std::{collections::HashMap, sync::Mutex};\n  /// use tauri::State;\n  /// // here we use Mutex to achieve interior mutability\n  /// struct Storage {\n  ///   store: Mutex<HashMap<u64, String>>,\n  /// }\n  /// struct Connection;\n  /// struct DbConnection {\n  ///   db: Mutex<Option<Connection>>,\n  /// }\n  ///\n  /// #[tauri::command]\n  /// fn connect(connection: State<DbConnection>) {\n  ///   // initialize the connection, mutating the state with interior mutability\n  ///   *connection.db.lock().unwrap() = Some(Connection {});\n  /// }\n  ///\n  /// #[tauri::command]\n  /// fn storage_insert(key: u64, value: String, storage: State<Storage>) {\n  ///   // mutate the storage behind the Mutex\n  ///   storage.store.lock().unwrap().insert(key, value);\n  /// }\n  ///\n  /// tauri::Builder::default()\n  ///   .manage(Storage { store: Default::default() })\n  ///   .manage(DbConnection { db: Default::default() })\n  ///   .invoke_handler(tauri::generate_handler![connect, storage_insert])\n  ///   // on an actual app, remove the string argument\n  ///   .run(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n  ///   .expect(\"error while running tauri application\");\n  /// ```\n  ///\n  /// # Examples\n  ///\n  /// ```rust,no_run\n  /// use tauri::{Manager, State};\n  ///\n  /// struct MyInt(isize);\n  /// struct MyString(String);\n  ///\n  /// #[tauri::command]\n  /// fn int_command(state: State<MyInt>) -> String {\n  ///     format!(\"The stateful int is: {}\", state.0)\n  /// }\n  ///\n  /// #[tauri::command]\n  /// fn string_command<'r>(state: State<'r, MyString>) {\n  ///     println!(\"state: {}\", state.inner().0);\n  /// }\n  ///\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     app.manage(MyInt(0));\n  ///     app.manage(MyString(\"tauri\".into()));\n  ///     // `MyInt` is already managed, so `manage()` returns false\n  ///     assert!(!app.manage(MyInt(1)));\n  ///     // read the `MyInt` managed state with the turbofish syntax\n  ///     let int = app.state::<MyInt>();\n  ///     assert_eq!(int.0, 0);\n  ///     // read the `MyString` managed state with the `State` guard\n  ///     let val: State<MyString> = app.state();\n  ///     assert_eq!(val.0, \"tauri\");\n  ///     Ok(())\n  ///   })\n  ///   .invoke_handler(tauri::generate_handler![int_command, string_command])\n  ///   // on an actual app, remove the string argument\n  ///   .run(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n  ///   .expect(\"error while running tauri application\");\n  /// ```\n  fn manage<T>(&self, state: T) -> bool\n  where\n    T: Send + Sync + 'static,\n  {\n    self.manager().state().set(state)\n  }\n\n  /// Removes the state managed by the application for T. Returns the state if it was actually removed.\n  ///\n  /// <div class=\"warning\">\n  ///\n  /// This method is *UNSAFE* and calling it will cause previously obtained references through\n  /// [Manager::state] and [State::inner] to become dangling references.\n  ///\n  /// It is currently deprecated and may be removed in the future.\n  ///\n  /// If you really want to unmanage a state, use [std::sync::Mutex] and [Option::take] to wrap the state instead.\n  ///\n  /// See [tauri-apps/tauri#12721] for more information.\n  ///\n  /// [tauri-apps/tauri#12721]: https://github.com/tauri-apps/tauri/issues/12721\n  ///\n  /// </div>\n  #[deprecated(\n    since = \"2.3.0\",\n    note = \"This method is unsafe, since it can cause dangling references.\"\n  )]\n  fn unmanage<T>(&self) -> Option<T>\n  where\n    T: Send + Sync + 'static,\n  {\n    // The caller decides to break the safety here, then OK, just let it go.\n    unsafe { self.manager().state().unmanage() }\n  }\n\n  /// Retrieves the managed state for the type `T`.\n  ///\n  /// # Panics\n  ///\n  /// Panics if the state for the type `T` has not been previously [managed](Self::manage).\n  /// Use [try_state](Self::try_state) for a non-panicking version.\n  fn state<T>(&self) -> State<'_, T>\n  where\n    T: Send + Sync + 'static,\n  {\n    self.manager().state.try_get().unwrap_or_else(|| {\n      panic!(\n        \"state() called before manage() for {}\",\n        std::any::type_name::<T>()\n      )\n    })\n  }\n\n  /// Attempts to retrieve the managed state for the type `T`.\n  ///\n  /// Returns `Some` if the state has previously been [managed](Self::manage). Otherwise returns `None`.\n  fn try_state<T>(&self) -> Option<State<'_, T>>\n  where\n    T: Send + Sync + 'static,\n  {\n    self.manager().state.try_get()\n  }\n\n  /// Get a reference to the resources table of this manager.\n  fn resources_table(&self) -> MutexGuard<'_, ResourceTable>;\n\n  /// Gets the managed [`Env`].\n  fn env(&self) -> Env {\n    self.state::<Env>().inner().clone()\n  }\n\n  /// Gets the scope for the asset protocol.\n  #[cfg(feature = \"protocol-asset\")]\n  fn asset_protocol_scope(&self) -> scope::fs::Scope {\n    self.state::<Scopes>().inner().asset_protocol.clone()\n  }\n\n  /// The path resolver.\n  fn path(&self) -> &crate::path::PathResolver<R> {\n    self.state::<crate::path::PathResolver<R>>().inner()\n  }\n\n  /// Adds a capability to the app.\n  ///\n  /// Note that by default every capability file in the `src-tauri/capabilities` folder\n  /// are automatically enabled unless specific capabilities are configured in [`tauri.conf.json > app > security > capabilities`],\n  /// so you should use a different director for the runtime-added capabilities or use [tauri_build::Attributes::capabilities_path_pattern].\n  ///\n  /// # Examples\n  /// ```\n  /// use tauri::Manager;\n  ///\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     #[cfg(feature = \"beta\")]\n  ///     app.add_capability(include_str!(\"../capabilities/beta/cap.json\"));\n  ///\n  ///     #[cfg(feature = \"stable\")]\n  ///     app.add_capability(include_str!(\"../capabilities/stable/cap.json\"));\n  ///     Ok(())\n  ///   });\n  /// ```\n  ///\n  /// The above example assumes the following directory layout:\n  /// ```md\n  /// ├── capabilities\n  /// │   ├── app (default capabilities used by any app flavor)\n  /// |   |   |-- cap.json\n  /// │   ├── beta (capabilities only added to a `beta` flavor)\n  /// |   |   |-- cap.json\n  /// │   ├── stable (capabilities only added to a `stable` flavor)\n  /// |       |-- cap.json\n  /// ```\n  ///\n  /// For this layout to be properly parsed by Tauri, we need to change the build script to\n  ///\n  /// ```skip\n  /// // only pick up capabilities in the capabilities/app folder by default\n  /// let attributes = tauri_build::Attributes::new().capabilities_path_pattern(\"./capabilities/app/*.json\");\n  /// tauri_build::try_build(attributes).unwrap();\n  /// ```\n  ///\n  /// [`tauri.conf.json > app > security > capabilities`]: https://tauri.app/reference/config/#capabilities\n  /// [tauri_build::Attributes::capabilities_path_pattern]: https://docs.rs/tauri-build/2/tauri_build/struct.Attributes.html#method.capabilities_path_pattern\n  #[cfg(feature = \"dynamic-acl\")]\n  fn add_capability(&self, capability: impl RuntimeCapability) -> Result<()> {\n    self\n      .manager()\n      .runtime_authority\n      .lock()\n      .unwrap()\n      .add_capability(capability)\n  }\n}\n\n/// Listen to events.\npub trait Listener<R: Runtime>: sealed::ManagerBase<R> {\n  /// Listen to an emitted event on this manager.\n  ///\n  /// # Examples\n  /// ```\n  /// use tauri::{Manager, Listener, Emitter};\n  ///\n  /// #[tauri::command]\n  /// fn synchronize(window: tauri::Window) {\n  ///   // emits the synchronized event to all windows\n  ///   window.emit(\"synchronized\", ());\n  /// }\n  ///\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     app.listen(\"synchronized\", |event| {\n  ///       println!(\"app is in sync\");\n  ///     });\n  ///     Ok(())\n  ///   })\n  ///   .invoke_handler(tauri::generate_handler![synchronize]);\n  /// ```\n  /// # Panics\n  /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_`\n  fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId\n  where\n    F: Fn(Event) + Send + 'static;\n\n  /// Listen to an event on this manager only once.\n  ///\n  /// See [`Self::listen`] for more information.\n  /// # Panics\n  /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_`\n  fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId\n  where\n    F: FnOnce(Event) + Send + 'static;\n\n  /// Remove an event listener.\n  ///\n  /// # Examples\n  /// ```\n  /// use tauri::{Manager, Listener};\n  ///\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let handle = app.handle().clone();\n  ///     let handler = app.listen_any(\"ready\", move |event| {\n  ///       println!(\"app is ready\");\n  ///\n  ///       // we no longer need to listen to the event\n  ///       // we also could have used `app.once_global` instead\n  ///       handle.unlisten(event.id());\n  ///     });\n  ///\n  ///     // stop listening to the event when you do not need it anymore\n  ///     app.unlisten(handler);\n  ///\n  ///\n  ///     Ok(())\n  ///   });\n  /// ```\n  fn unlisten(&self, id: EventId);\n\n  /// Listen to an emitted event to any [target](EventTarget).\n  ///\n  /// # Examples\n  /// ```\n  /// use tauri::{Manager, Emitter, Listener};\n  ///\n  /// #[tauri::command]\n  /// fn synchronize(window: tauri::Window) {\n  ///   // emits the synchronized event to all windows\n  ///   window.emit(\"synchronized\", ());\n  /// }\n  ///\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     app.listen_any(\"synchronized\", |event| {\n  ///       println!(\"app is in sync\");\n  ///     });\n  ///     Ok(())\n  ///   })\n  ///   .invoke_handler(tauri::generate_handler![synchronize]);\n  /// ```\n  /// # Panics\n  /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_`\n  fn listen_any<F>(&self, event: impl Into<String>, handler: F) -> EventId\n  where\n    F: Fn(Event) + Send + 'static,\n  {\n    let event = EventName::new(event.into()).unwrap();\n    self.manager().listen(event, EventTarget::Any, handler)\n  }\n\n  /// Listens once to an emitted event to any [target](EventTarget) .\n  ///\n  /// See [`Self::listen_any`] for more information.\n  /// # Panics\n  /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_`\n  fn once_any<F>(&self, event: impl Into<String>, handler: F) -> EventId\n  where\n    F: FnOnce(Event) + Send + 'static,\n  {\n    let event = EventName::new(event.into()).unwrap();\n    self.manager().once(event, EventTarget::Any, handler)\n  }\n}\n\n/// Emit events.\npub trait Emitter<R: Runtime>: sealed::ManagerBase<R> {\n  /// Emits an event to all [targets](EventTarget).\n  ///\n  /// # Examples\n  /// ```\n  /// use tauri::Emitter;\n  ///\n  /// #[tauri::command]\n  /// fn synchronize(app: tauri::AppHandle) {\n  ///   // emits the synchronized event to all webviews\n  ///   app.emit(\"synchronized\", ());\n  /// }\n  /// ```\n  fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {\n    let event = EventName::new(event)?;\n    let payload = EmitPayload::Serialize(&payload);\n    self.manager().emit(event, payload)\n  }\n\n  /// Similar to [`Emitter::emit`] but the payload is json serialized.\n  fn emit_str(&self, event: &str, payload: String) -> Result<()> {\n    let event = EventName::new(event)?;\n    let payload = EmitPayload::<()>::Str(payload);\n    self.manager().emit(event, payload)\n  }\n\n  /// Emits an event to all [targets](EventTarget) matching the given target.\n  ///\n  /// # Examples\n  /// ```\n  /// use tauri::{Emitter, EventTarget};\n  ///\n  /// #[tauri::command]\n  /// fn download(app: tauri::AppHandle) {\n  ///   for i in 1..100 {\n  ///     std::thread::sleep(std::time::Duration::from_millis(150));\n  ///     // emit a download progress event to all listeners\n  ///     app.emit_to(EventTarget::any(), \"download-progress\", i);\n  ///     // emit an event to listeners that used App::listen or AppHandle::listen\n  ///     app.emit_to(EventTarget::app(), \"download-progress\", i);\n  ///     // emit an event to any webview/window/webviewWindow matching the given label\n  ///     app.emit_to(\"updater\", \"download-progress\", i); // similar to using EventTarget::labeled\n  ///     app.emit_to(EventTarget::labeled(\"updater\"), \"download-progress\", i);\n  ///     // emit an event to listeners that used WebviewWindow::listen\n  ///     app.emit_to(EventTarget::webview_window(\"updater\"), \"download-progress\", i);\n  ///   }\n  /// }\n  /// ```\n  fn emit_to<I, S>(&self, target: I, event: &str, payload: S) -> Result<()>\n  where\n    I: Into<EventTarget>,\n    S: Serialize + Clone,\n  {\n    let event = EventName::new(event)?;\n    let payload = EmitPayload::Serialize(&payload);\n    self.manager().emit_to(target.into(), event, payload)\n  }\n\n  /// Similar to [`Emitter::emit_to`] but the payload is json serialized.\n  fn emit_str_to<I>(&self, target: I, event: &str, payload: String) -> Result<()>\n  where\n    I: Into<EventTarget>,\n  {\n    let event = EventName::new(event)?;\n    let payload = EmitPayload::<()>::Str(payload);\n    self.manager().emit_to(target.into(), event, payload)\n  }\n\n  /// Emits an event to all [targets](EventTarget) based on the given filter.\n  ///\n  /// # Examples\n  /// ```\n  /// use tauri::{Emitter, EventTarget};\n  ///\n  /// #[tauri::command]\n  /// fn download(app: tauri::AppHandle) {\n  ///   for i in 1..100 {\n  ///     std::thread::sleep(std::time::Duration::from_millis(150));\n  ///     // emit a download progress event to the updater window\n  ///     app.emit_filter(\"download-progress\", i, |t| match t {\n  ///       EventTarget::WebviewWindow { label } => label == \"main\",\n  ///       _ => false,\n  ///     });\n  ///   }\n  /// }\n  /// ```\n  fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> Result<()>\n  where\n    S: Serialize + Clone,\n    F: Fn(&EventTarget) -> bool,\n  {\n    let event = EventName::new(event)?;\n    let payload = EmitPayload::Serialize(&payload);\n    self.manager().emit_filter(event, payload, filter)\n  }\n\n  /// Similar to [`Emitter::emit_filter`] but the payload is json serialized.\n  fn emit_str_filter<F>(&self, event: &str, payload: String, filter: F) -> Result<()>\n  where\n    F: Fn(&EventTarget) -> bool,\n  {\n    let event = EventName::new(event)?;\n    let payload = EmitPayload::<()>::Str(payload);\n    self.manager().emit_filter(event, payload, filter)\n  }\n}\n\n/// Prevent implementation details from leaking out of the [`Manager`] trait.\npub(crate) mod sealed {\n  use super::Runtime;\n  use crate::{app::AppHandle, manager::AppManager};\n  use std::sync::Arc;\n\n  /// A running [`Runtime`] or a dispatcher to it.\n  pub enum RuntimeOrDispatch<'r, R: Runtime> {\n    /// Reference to the running [`Runtime`].\n    Runtime(&'r R),\n\n    /// Handle to the running [`Runtime`].\n    RuntimeHandle(R::Handle),\n\n    /// A dispatcher to the running [`Runtime`].\n    Dispatch(R::WindowDispatcher),\n  }\n\n  /// Managed handle to the application runtime.\n  pub trait ManagerBase<R: Runtime> {\n    fn manager(&self) -> &AppManager<R>;\n    fn manager_owned(&self) -> Arc<AppManager<R>>;\n    fn runtime(&self) -> RuntimeOrDispatch<'_, R>;\n    fn managed_app_handle(&self) -> &AppHandle<R>;\n  }\n}\n\nstruct UnsafeSend<T>(T);\nunsafe impl<T> Send for UnsafeSend<T> {}\n\nimpl<T> UnsafeSend<T> {\n  fn take(self) -> T {\n    self.0\n  }\n}\n\n#[allow(unused)]\nmacro_rules! run_main_thread {\n  ($handle:ident, $ex:expr) => {{\n    use std::sync::mpsc::channel;\n    let (tx, rx) = channel();\n    let task = move || {\n      let f = $ex;\n      let _ = tx.send(f());\n    };\n    $handle\n      .run_on_main_thread(task)\n      .and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))\n  }};\n}\n\n#[allow(unused)]\npub(crate) use run_main_thread;\n\n#[cfg(any(test, feature = \"test\"))]\n#[cfg_attr(docsrs, doc(cfg(feature = \"test\")))]\npub mod test;\n\n#[cfg(feature = \"specta\")]\nconst _: () = {\n  use specta::{datatype::DataType, function::FunctionArg, TypeMap};\n\n  impl<T: Send + Sync + 'static> FunctionArg for crate::State<'_, T> {\n    fn to_datatype(_: &mut TypeMap) -> Option<DataType> {\n      None\n    }\n  }\n\n  impl<R: crate::Runtime> FunctionArg for crate::AppHandle<R> {\n    fn to_datatype(_: &mut TypeMap) -> Option<DataType> {\n      None\n    }\n  }\n\n  impl<R: crate::Runtime> FunctionArg for crate::Window<R> {\n    fn to_datatype(_: &mut TypeMap) -> Option<DataType> {\n      None\n    }\n  }\n\n  impl<R: crate::Runtime> FunctionArg for crate::Webview<R> {\n    fn to_datatype(_: &mut TypeMap) -> Option<DataType> {\n      None\n    }\n  }\n\n  impl<R: crate::Runtime> FunctionArg for crate::WebviewWindow<R> {\n    fn to_datatype(_: &mut TypeMap) -> Option<DataType> {\n      None\n    }\n  }\n};\n\n#[cfg(test)]\nmod tests {\n  use cargo_toml::Manifest;\n  use std::{env::var, fs::read_to_string, path::PathBuf, sync::OnceLock};\n\n  static MANIFEST: OnceLock<Manifest> = OnceLock::new();\n  const CHECKED_FEATURES: &str = include_str!(concat!(env!(\"OUT_DIR\"), \"/checked_features\"));\n\n  fn get_manifest() -> &'static Manifest {\n    MANIFEST.get_or_init(|| {\n      let manifest_dir = PathBuf::from(var(\"CARGO_MANIFEST_DIR\").unwrap());\n      Manifest::from_path(manifest_dir.join(\"Cargo.toml\")).expect(\"failed to parse Cargo manifest\")\n    })\n  }\n\n  #[test]\n  fn features_are_documented() {\n    let manifest_dir = PathBuf::from(var(\"CARGO_MANIFEST_DIR\").unwrap());\n    let lib_code = read_to_string(manifest_dir.join(\"src/lib.rs\")).expect(\"failed to read lib.rs\");\n\n    for f in get_manifest().features.keys() {\n      if !(f.starts_with(\"__\") || f == \"default\" || lib_code.contains(&format!(\"*{f}**\"))) {\n        panic!(\"Feature {f} is not documented\");\n      }\n    }\n  }\n\n  #[test]\n  fn aliased_features_exist() {\n    let checked_features = CHECKED_FEATURES.split(',');\n    let manifest = get_manifest();\n    for checked_feature in checked_features {\n      if !manifest.features.iter().any(|(f, _)| f == checked_feature) {\n        panic!(\n          \"Feature {checked_feature} was checked in the alias build step but it does not exist in crates/tauri/Cargo.toml\"\n        );\n      }\n    }\n  }\n}\n\n#[cfg(test)]\nmod test_utils {\n  use proptest::prelude::*;\n\n  pub fn assert_send<T: Send>() {}\n  pub fn assert_sync<T: Sync>() {}\n\n  #[allow(dead_code)]\n  pub fn assert_not_allowlist_error<T>(res: anyhow::Result<T>) {\n    if let Err(e) = res {\n      assert!(!e.to_string().contains(\"not on the allowlist\"));\n    }\n  }\n\n  proptest! {\n    #![proptest_config(ProptestConfig::with_cases(10000))]\n    #[test]\n    // check to see if spawn executes a function.\n    fn check_spawn_task(task in \"[a-z]+\") {\n      // create dummy task function\n      let dummy_task = async move {\n        let _ = format!(\"{task}-run-dummy-task\");\n      };\n      // call spawn\n      crate::async_runtime::spawn(dummy_task);\n    }\n  }\n}\n\n/// Simple dependency-free string encoder using [Z85].\nmod z85 {\n  const TABLE: &[u8; 85] =\n    b\"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#\";\n\n  /// Encode bytes with [Z85].\n  ///\n  /// # Panics\n  ///\n  /// Will panic if the input bytes are not a multiple of 4.\n  pub fn encode(bytes: &[u8]) -> String {\n    assert_eq!(bytes.len() % 4, 0);\n\n    let mut buf = String::with_capacity(bytes.len() * 5 / 4);\n    for chunk in bytes.chunks_exact(4) {\n      let mut chars = [0u8; 5];\n      let mut chunk = u32::from_be_bytes(chunk.try_into().unwrap()) as usize;\n      for byte in chars.iter_mut().rev() {\n        *byte = TABLE[chunk % 85];\n        chunk /= 85;\n      }\n\n      buf.push_str(std::str::from_utf8(&chars).unwrap());\n    }\n\n    buf\n  }\n\n  #[cfg(test)]\n  mod tests {\n    #[test]\n    fn encode() {\n      assert_eq!(\n        super::encode(&[0x86, 0x4F, 0xD2, 0x6F, 0xB5, 0x59, 0xF7, 0x5B]),\n        \"HelloWorld\"\n      );\n    }\n  }\n}\n\n/// Generate a random 128-bit [Z85] encoded [`String`].\n///\n/// [Z85]: https://rfc.zeromq.org/spec/32/\npub(crate) fn generate_invoke_key() -> Result<String> {\n  let mut bytes = [0u8; 16];\n  getrandom::fill(&mut bytes)?;\n  Ok(z85::encode(&bytes))\n}\n"
  },
  {
    "path": "crates/tauri/src/manager/menu.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  collections::HashMap,\n  sync::{Arc, Mutex, MutexGuard},\n};\n\nuse crate::{\n  menu::{Menu, MenuEvent, MenuId},\n  AppHandle, Runtime, Window,\n};\n\npub struct MenuManager<R: Runtime> {\n  /// A set containing a reference to the active menus, including\n  /// the app-wide menu and the window-specific menus\n  ///\n  /// This should be mainly used to access [`Menu::haccel`]\n  /// to setup the accelerator handling in the event loop\n  pub menus: Arc<Mutex<HashMap<MenuId, Menu<R>>>>,\n  /// The menu set to all windows.\n  pub menu: Mutex<Option<Menu<R>>>,\n  /// Menu event listeners to all windows.\n  pub global_event_listeners: Mutex<Vec<crate::app::GlobalMenuEventListener<AppHandle<R>>>>,\n  /// Menu event listeners to specific windows.\n  pub event_listeners: Mutex<HashMap<String, crate::app::GlobalMenuEventListener<Window<R>>>>,\n}\n\nimpl<R: Runtime> MenuManager<R> {\n  /// App-wide menu.\n  pub fn menu_lock(&self) -> MutexGuard<'_, Option<Menu<R>>> {\n    self.menu.lock().expect(\"poisoned menu mutex\")\n  }\n\n  pub fn menus_stash_lock(&self) -> MutexGuard<'_, HashMap<MenuId, Menu<R>>> {\n    self.menus.lock().expect(\"poisoned menu mutex\")\n  }\n\n  pub fn is_menu_in_use<I: PartialEq<MenuId>>(&self, id: &I) -> bool {\n    self\n      .menu_lock()\n      .as_ref()\n      .map(|m| id.eq(m.id()))\n      .unwrap_or(false)\n  }\n\n  pub fn insert_menu_into_stash(&self, menu: &Menu<R>) {\n    self\n      .menus_stash_lock()\n      .insert(menu.id().clone(), menu.clone());\n  }\n\n  pub(crate) fn prepare_window_menu_creation_handler(\n    &self,\n    window_menu: Option<&crate::window::WindowMenu<R>>,\n    #[allow(unused)] theme: Option<tauri_utils::Theme>,\n  ) -> Option<impl Fn(tauri_runtime::window::RawWindow<'_>)> {\n    {\n      if let Some(menu) = window_menu {\n        self\n          .menus_stash_lock()\n          .insert(menu.menu.id().clone(), menu.menu.clone());\n      }\n    }\n\n    #[cfg(target_os = \"macos\")]\n    return None;\n\n    #[cfg_attr(target_os = \"macos\", allow(unused_variables, unreachable_code))]\n    if let Some(menu) = &window_menu {\n      let menu = menu.menu.clone();\n      Some(move |raw: tauri_runtime::window::RawWindow<'_>| {\n        #[cfg(target_os = \"windows\")]\n        {\n          let theme = theme\n            .map(crate::menu::map_to_menu_theme)\n            .unwrap_or(muda::MenuTheme::Auto);\n          let _ = unsafe { menu.inner().init_for_hwnd_with_theme(raw.hwnd as _, theme) };\n        }\n        #[cfg(any(\n          target_os = \"linux\",\n          target_os = \"dragonfly\",\n          target_os = \"freebsd\",\n          target_os = \"netbsd\",\n          target_os = \"openbsd\"\n        ))]\n        let _ = menu\n          .inner()\n          .init_for_gtk_window(raw.gtk_window, raw.default_vbox);\n      })\n    } else {\n      None\n    }\n  }\n\n  pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Send + Sync + 'static>(&self, handler: F) {\n    self\n      .global_event_listeners\n      .lock()\n      .unwrap()\n      .push(Box::new(handler));\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/manager/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  borrow::Cow,\n  collections::HashMap,\n  fmt,\n  sync::{atomic::AtomicBool, Arc, Mutex, MutexGuard},\n};\n\nuse serde::Serialize;\nuse url::Url;\n\nuse tauri_macros::default_runtime;\nuse tauri_utils::{\n  assets::{AssetKey, CspHash, SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},\n  config::{Csp, CspDirectiveSources},\n};\n\nuse crate::{\n  app::{\n    AppHandle, ChannelInterceptor, GlobalWebviewEventListener, GlobalWindowEventListener,\n    OnPageLoad,\n  },\n  event::{EmitArgs, Event, EventId, EventTarget, Listeners},\n  ipc::{Invoke, InvokeHandler, RuntimeAuthority},\n  plugin::PluginStore,\n  resources::ResourceTable,\n  utils::{config::Config, PackageInfo},\n  Assets, Context, DebugAppIcon, EventName, Pattern, Runtime, StateManager, Webview, Window,\n};\n\n#[cfg(desktop)]\nmod menu;\n#[cfg(all(desktop, feature = \"tray-icon\"))]\nmod tray;\npub mod webview;\npub mod window;\n\n#[derive(Default)]\n/// Spaced and quoted Content-Security-Policy hash values.\nstruct CspHashStrings {\n  script: Vec<String>,\n  style: Vec<String>,\n}\n\n/// Sets the CSP value to the asset HTML if needed (on Linux).\n/// Returns the CSP string for access on the response header (on Windows and macOS).\npub(crate) fn set_csp<R: Runtime>(\n  asset: &mut String,\n  assets: &impl std::borrow::Borrow<dyn Assets<R>>,\n  asset_path: &AssetKey,\n  manager: &AppManager<R>,\n  csp: Csp,\n) -> HashMap<String, CspDirectiveSources> {\n  let mut csp = csp.into();\n  let hash_strings =\n    assets\n      .borrow()\n      .csp_hashes(asset_path)\n      .fold(CspHashStrings::default(), |mut acc, hash| {\n        match hash {\n          CspHash::Script(hash) => {\n            acc.script.push(hash.into());\n          }\n          CspHash::Style(hash) => {\n            acc.style.push(hash.into());\n          }\n          _csp_hash => {\n            log::debug!(\"Unknown CspHash variant encountered: {:?}\", _csp_hash);\n          }\n        }\n\n        acc\n      });\n\n  let dangerous_disable_asset_csp_modification = &manager\n    .config()\n    .app\n    .security\n    .dangerous_disable_asset_csp_modification;\n  if dangerous_disable_asset_csp_modification.can_modify(\"script-src\") {\n    replace_csp_nonce(\n      asset,\n      SCRIPT_NONCE_TOKEN,\n      &mut csp,\n      \"script-src\",\n      hash_strings.script,\n    );\n  }\n\n  if dangerous_disable_asset_csp_modification.can_modify(\"style-src\") {\n    replace_csp_nonce(\n      asset,\n      STYLE_NONCE_TOKEN,\n      &mut csp,\n      \"style-src\",\n      hash_strings.style,\n    );\n  }\n\n  csp\n}\n\n// inspired by <https://github.com/rust-lang/rust/blob/1be5c8f90912c446ecbdc405cbc4a89f9acd20fd/library/alloc/src/str.rs#L260-L297>\nfn replace_with_callback<F: FnMut() -> String>(\n  original: &str,\n  pattern: &str,\n  mut replacement: F,\n) -> String {\n  let mut result = String::new();\n  let mut last_end = 0;\n  for (start, part) in original.match_indices(pattern) {\n    result.push_str(unsafe { original.get_unchecked(last_end..start) });\n    result.push_str(&replacement());\n    last_end = start + part.len();\n  }\n  result.push_str(unsafe { original.get_unchecked(last_end..original.len()) });\n  result\n}\n\nfn replace_csp_nonce(\n  asset: &mut String,\n  token: &str,\n  csp: &mut HashMap<String, CspDirectiveSources>,\n  directive: &str,\n  hashes: Vec<String>,\n) {\n  let mut nonces = Vec::new();\n  *asset = replace_with_callback(asset, token, || {\n    let nonce = getrandom::u64().expect(\"failed to get random bytes\");\n    nonces.push(nonce);\n    nonce.to_string()\n  });\n\n  if !(nonces.is_empty() && hashes.is_empty()) {\n    let nonce_sources = nonces\n      .into_iter()\n      .map(|n| format!(\"'nonce-{n}'\"))\n      .collect::<Vec<String>>();\n    let sources = csp.entry(directive.into()).or_default();\n    let self_source = \"'self'\".to_string();\n    if !sources.contains(&self_source) {\n      sources.push(self_source);\n    }\n    sources.extend(nonce_sources);\n    sources.extend(hashes);\n  }\n}\n\n/// A resolved asset.\n#[non_exhaustive]\npub struct Asset {\n  /// The asset bytes.\n  pub bytes: Vec<u8>,\n  /// The asset's mime type.\n  pub mime_type: String,\n  /// The `Content-Security-Policy` header value.\n  pub csp_header: Option<String>,\n}\n\nimpl Asset {\n  /// The asset bytes.\n  pub fn bytes(&self) -> &[u8] {\n    &self.bytes\n  }\n\n  /// The asset's mime type.\n  pub fn mime_type(&self) -> &str {\n    &self.mime_type\n  }\n\n  /// The `Content-Security-Policy` header value.\n  pub fn csp_header(&self) -> Option<&str> {\n    self.csp_header.as_deref()\n  }\n}\n\n#[default_runtime(crate::Wry, wry)]\npub struct AppManager<R: Runtime> {\n  pub runtime_authority: Mutex<RuntimeAuthority>,\n  pub window: window::WindowManager<R>,\n  pub webview: webview::WebviewManager<R>,\n  #[cfg(all(desktop, feature = \"tray-icon\"))]\n  pub tray: tray::TrayManager<R>,\n  #[cfg(desktop)]\n  pub menu: menu::MenuManager<R>,\n\n  pub(crate) plugins: Mutex<PluginStore<R>>,\n  pub listeners: Listeners,\n  pub state: Arc<StateManager>,\n  pub config: Config,\n  #[cfg(dev)]\n  pub config_parent: Option<std::path::PathBuf>,\n  pub assets: Box<dyn Assets<R>>,\n\n  pub app_icon: Option<Vec<u8>>,\n\n  pub package_info: PackageInfo,\n\n  /// Application pattern.\n  pub pattern: Arc<Pattern>,\n\n  /// Global API scripts collected from plugins.\n  pub plugin_global_api_scripts: Arc<Option<&'static [&'static str]>>,\n\n  /// Application Resources Table\n  pub(crate) resources_table: Arc<Mutex<ResourceTable>>,\n\n  /// Runtime-generated invoke key.\n  pub(crate) invoke_key: String,\n\n  pub(crate) channel_interceptor: Option<ChannelInterceptor<R>>,\n\n  /// Sets to true in [`AppHandle::request_restart`] and [`AppHandle::restart`]\n  /// and we will call `restart` on the next `RuntimeRunEvent::Exit` event\n  pub(crate) restart_on_exit: AtomicBool,\n}\n\nimpl<R: Runtime> fmt::Debug for AppManager<R> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    let mut d = f.debug_struct(\"AppManager\");\n\n    d.field(\"window\", &self.window)\n      .field(\"plugins\", &self.plugins)\n      .field(\"state\", &self.state)\n      .field(\"config\", &self.config)\n      .field(\"app_icon\", &DebugAppIcon(&self.app_icon))\n      .field(\"package_info\", &self.package_info)\n      .field(\"pattern\", &self.pattern);\n\n    #[cfg(all(desktop, feature = \"tray-icon\"))]\n    {\n      d.field(\"tray\", &self.tray);\n    }\n\n    d.finish()\n  }\n}\n\npub(crate) enum EmitPayload<'a, S: Serialize> {\n  Serialize(&'a S),\n  Str(String),\n}\n\nimpl<R: Runtime> AppManager<R> {\n  #[allow(clippy::too_many_arguments, clippy::type_complexity)]\n  pub(crate) fn with_handlers(\n    #[allow(unused_mut)] mut context: Context<R>,\n    plugins: PluginStore<R>,\n    invoke_handler: Box<InvokeHandler<R>>,\n    on_page_load: Option<Arc<OnPageLoad<R>>>,\n    uri_scheme_protocols: HashMap<String, Arc<webview::UriSchemeProtocol<R>>>,\n    state: StateManager,\n    #[cfg(desktop)] menu_event_listener: Vec<crate::app::GlobalMenuEventListener<AppHandle<R>>>,\n    #[cfg(all(desktop, feature = \"tray-icon\"))] tray_icon_event_listeners: Vec<\n      crate::app::GlobalTrayIconEventListener<AppHandle<R>>,\n    >,\n    window_event_listeners: Vec<GlobalWindowEventListener<R>>,\n    webview_event_listeners: Vec<GlobalWebviewEventListener<R>>,\n    #[cfg(desktop)] window_menu_event_listeners: HashMap<\n      String,\n      crate::app::GlobalMenuEventListener<Window<R>>,\n    >,\n    invoke_initialization_script: String,\n    channel_interceptor: Option<ChannelInterceptor<R>>,\n    invoke_key: String,\n  ) -> Self {\n    // generate a random isolation key at runtime\n    #[cfg(feature = \"isolation\")]\n    if let Pattern::Isolation { key, .. } = &mut context.pattern {\n      *key = uuid::Uuid::new_v4().to_string();\n    }\n\n    Self {\n      runtime_authority: Mutex::new(context.runtime_authority),\n      window: window::WindowManager {\n        windows: Mutex::default(),\n        default_icon: context.default_window_icon,\n        event_listeners: Arc::new(window_event_listeners),\n      },\n      webview: webview::WebviewManager {\n        webviews: Mutex::default(),\n        invoke_handler,\n        on_page_load,\n        uri_scheme_protocols: Mutex::new(uri_scheme_protocols),\n        event_listeners: Arc::new(webview_event_listeners),\n        invoke_initialization_script,\n        invoke_key: invoke_key.clone(),\n      },\n      #[cfg(all(desktop, feature = \"tray-icon\"))]\n      tray: tray::TrayManager {\n        icon: context.tray_icon,\n        icons: Default::default(),\n        global_event_listeners: Mutex::new(tray_icon_event_listeners),\n        event_listeners: Default::default(),\n      },\n      #[cfg(desktop)]\n      menu: menu::MenuManager {\n        menus: Default::default(),\n        menu: Default::default(),\n        global_event_listeners: Mutex::new(menu_event_listener),\n        event_listeners: Mutex::new(window_menu_event_listeners),\n      },\n      plugins: Mutex::new(plugins),\n      listeners: Listeners::default(),\n      state: Arc::new(state),\n      config: context.config,\n      #[cfg(dev)]\n      config_parent: context.config_parent,\n      assets: context.assets,\n      app_icon: context.app_icon,\n      package_info: context.package_info,\n      pattern: Arc::new(context.pattern),\n      plugin_global_api_scripts: Arc::new(context.plugin_global_api_scripts),\n      resources_table: Arc::default(),\n      invoke_key,\n      channel_interceptor,\n      restart_on_exit: AtomicBool::new(false),\n    }\n  }\n\n  /// State managed by the application.\n  pub(crate) fn state(&self) -> Arc<StateManager> {\n    self.state.clone()\n  }\n\n  /// The `tauri` custom protocol URL we use to serve the embedded assets.\n  /// Returns `tauri://localhost` or its `wry` workaround URL `http://tauri.localhost`/`https://tauri.localhost`\n  pub(crate) fn tauri_protocol_url(&self, https: bool) -> Cow<'_, Url> {\n    if cfg!(windows) || cfg!(target_os = \"android\") {\n      let scheme = if https { \"https\" } else { \"http\" };\n      Cow::Owned(Url::parse(&format!(\"{scheme}://tauri.localhost\")).unwrap())\n    } else {\n      Cow::Owned(Url::parse(\"tauri://localhost\").unwrap())\n    }\n  }\n\n  /// Get the base app URL for [`WebviewUrl::App`](tauri_utils::config::WebviewUrl::App).\n  ///\n  /// * In dev mode, this is the [`devUrl`](tauri_utils::config::BuildConfig::dev_url) configuration value if it exsits.\n  /// * In production mode, this is the [`frontendDist`](tauri_utils::config::BuildConfig::frontend_dist) configuration value if it's a [`FrontendDist::Url`](tauri_utils::config::FrontendDist::Url).\n  /// * Returns [`Self::tauri_protocol_url`] (e.g. `tauri://localhost`) otherwise.\n  pub(crate) fn get_app_url(&self, https: bool) -> Cow<'_, Url> {\n    #[cfg(dev)]\n    let url = self.config.build.dev_url.as_ref();\n    #[cfg(not(dev))]\n    let url = match self.config.build.frontend_dist.as_ref() {\n      Some(crate::utils::config::FrontendDist::Url(url)) => Some(url),\n      _ => None,\n    };\n\n    if let Some(url) = url {\n      Cow::Borrowed(url)\n    } else {\n      self.tauri_protocol_url(https)\n    }\n  }\n\n  fn csp(&self) -> Option<Csp> {\n    if !crate::is_dev() {\n      self.config.app.security.csp.clone()\n    } else {\n      self\n        .config\n        .app\n        .security\n        .dev_csp\n        .clone()\n        .or_else(|| self.config.app.security.csp.clone())\n    }\n  }\n\n  // TODO: Change to return `crate::Result` here in v3\n  pub fn get_asset(\n    &self,\n    mut path: String,\n    _use_https_schema: bool,\n  ) -> Result<Asset, Box<dyn std::error::Error>> {\n    let assets = &self.assets;\n    if path.ends_with('/') {\n      path.pop();\n    }\n    path = percent_encoding::percent_decode(path.as_bytes())\n      .decode_utf8_lossy()\n      .to_string();\n    let path = if path.is_empty() {\n      // if the url is `tauri://localhost`, we should load `index.html`\n      \"index.html\".to_string()\n    } else {\n      // skip the leading `/`, if it starts with one.\n      path.strip_prefix('/').unwrap_or(path.as_str()).to_string()\n    };\n\n    let mut asset_path = AssetKey::from(path.as_str());\n\n    let asset_response = assets\n      .get(&asset_path)\n      .or_else(|| {\n        log::debug!(\"Asset `{path}` not found; fallback to {path}.html\");\n        let fallback = format!(\"{path}.html\").into();\n        let asset = assets.get(&fallback);\n        asset_path = fallback;\n        asset\n      })\n      .or_else(|| {\n        log::debug!(\"Asset `{path}` not found; fallback to {path}/index.html\",);\n        let fallback = format!(\"{path}/index.html\").into();\n        let asset = assets.get(&fallback);\n        asset_path = fallback;\n        asset\n      })\n      .or_else(|| {\n        log::debug!(\"Asset `{path}` not found; fallback to index.html\");\n        let fallback = AssetKey::from(\"index.html\");\n        let asset = assets.get(&fallback);\n        asset_path = fallback;\n        asset\n      })\n      .ok_or_else(|| {\n        let error = crate::Error::AssetNotFound(path.clone());\n        log::error!(\"{error}\");\n        Box::new(error)\n      })?;\n\n    let mut csp_header = None;\n    let is_html = asset_path.as_ref().ends_with(\".html\");\n\n    let final_data = if is_html {\n      let mut asset = String::from_utf8_lossy(&asset_response).into_owned();\n      if let Some(csp) = self.csp() {\n        #[allow(unused_mut)]\n        let mut csp_map = set_csp(&mut asset, &self.assets, &asset_path, self, csp);\n        #[cfg(feature = \"isolation\")]\n        if let Pattern::Isolation { schema, .. } = &*self.pattern {\n          let default_src = csp_map.entry(\"default-src\".to_owned()).or_default();\n          default_src.push(crate::pattern::format_real_schema(\n            schema,\n            _use_https_schema,\n          ));\n        }\n\n        csp_header.replace(Csp::DirectiveMap(csp_map).to_string());\n      }\n\n      asset.into_bytes()\n    } else {\n      asset_response.into_owned()\n    };\n    let mime_type = tauri_utils::mime_type::MimeType::parse(&final_data, &path);\n    Ok(Asset {\n      bytes: final_data,\n      mime_type,\n      csp_header,\n    })\n  }\n\n  pub(crate) fn listeners(&self) -> &Listeners {\n    &self.listeners\n  }\n\n  pub fn run_invoke_handler(&self, invoke: Invoke<R>) -> bool {\n    (self.webview.invoke_handler)(invoke)\n  }\n\n  pub fn extend_api(&self, plugin: &str, invoke: Invoke<R>) -> bool {\n    self\n      .plugins\n      .lock()\n      .expect(\"poisoned plugin store\")\n      .extend_api(plugin, invoke)\n  }\n\n  pub fn initialize_plugins(&self, app: &AppHandle<R>) -> crate::Result<()> {\n    self\n      .plugins\n      .lock()\n      .expect(\"poisoned plugin store\")\n      .initialize_all(app, &self.config.plugins)\n  }\n\n  pub fn config(&self) -> &Config {\n    &self.config\n  }\n\n  #[cfg(dev)]\n  pub fn config_parent(&self) -> Option<&std::path::PathBuf> {\n    self.config_parent.as_ref()\n  }\n\n  pub fn package_info(&self) -> &PackageInfo {\n    &self.package_info\n  }\n\n  /// # Panics\n  /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_`\n  pub(crate) fn listen<F: Fn(Event) + Send + 'static>(\n    &self,\n    event: EventName,\n    target: EventTarget,\n    handler: F,\n  ) -> EventId {\n    self.listeners().listen(event, target, handler)\n  }\n\n  /// # Panics\n  /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_`\n  pub(crate) fn once<F: FnOnce(Event) + Send + 'static>(\n    &self,\n    event: EventName,\n    target: EventTarget,\n    handler: F,\n  ) -> EventId {\n    self.listeners().once(event, target, handler)\n  }\n\n  pub fn unlisten(&self, id: EventId) {\n    self.listeners().unlisten(id)\n  }\n\n  #[cfg_attr(\n    feature = \"tracing\",\n    tracing::instrument(\"app::emit\", skip(self, payload))\n  )]\n  pub(crate) fn emit<S: Serialize>(\n    &self,\n    event: EventName<&str>,\n    payload: EmitPayload<'_, S>,\n  ) -> crate::Result<()> {\n    #[cfg(feature = \"tracing\")]\n    let _span = tracing::debug_span!(\"emit::run\").entered();\n    let emit_args = match payload {\n      EmitPayload::Serialize(payload) => EmitArgs::new(event, payload)?,\n      EmitPayload::Str(payload) => EmitArgs::new_str(event, payload)?,\n    };\n\n    let listeners = self.listeners();\n\n    listeners.emit_js(self.webview.webviews_lock().values(), &emit_args)?;\n    listeners.emit(emit_args)?;\n\n    Ok(())\n  }\n\n  #[cfg_attr(\n    feature = \"tracing\",\n    tracing::instrument(\"app::emit::filter\", skip(self, payload, filter))\n  )]\n  pub(crate) fn emit_filter<S, F>(\n    &self,\n    event: EventName<&str>,\n    payload: EmitPayload<'_, S>,\n    filter: F,\n  ) -> crate::Result<()>\n  where\n    S: Serialize,\n    F: Fn(&EventTarget) -> bool,\n  {\n    #[cfg(feature = \"tracing\")]\n    let _span = tracing::debug_span!(\"emit::run\").entered();\n    let emit_args = match payload {\n      EmitPayload::Serialize(payload) => EmitArgs::new(event, payload)?,\n      EmitPayload::Str(payload) => EmitArgs::new_str(event, payload)?,\n    };\n\n    let listeners = self.listeners();\n\n    listeners.emit_js_filter(\n      self.webview.webviews_lock().values(),\n      &emit_args,\n      Some(&filter),\n    )?;\n\n    listeners.emit_filter(emit_args, Some(filter))?;\n\n    Ok(())\n  }\n\n  #[cfg_attr(\n    feature = \"tracing\",\n    tracing::instrument(\"app::emit::to\", skip(self, target, payload), fields(target))\n  )]\n  pub(crate) fn emit_to<S>(\n    &self,\n    target: EventTarget,\n    event: EventName<&str>,\n    payload: EmitPayload<'_, S>,\n  ) -> crate::Result<()>\n  where\n    S: Serialize,\n  {\n    #[cfg(feature = \"tracing\")]\n    tracing::Span::current().record(\"target\", format!(\"{target:?}\"));\n\n    fn filter_target(target: &EventTarget, candidate: &EventTarget) -> bool {\n      match target {\n        // if targeting any label, filter matching labels\n        EventTarget::AnyLabel { label } => match candidate {\n          EventTarget::Window { label: l }\n          | EventTarget::Webview { label: l }\n          | EventTarget::WebviewWindow { label: l }\n          | EventTarget::AnyLabel { label: l } => l == label,\n          _ => false,\n        },\n        EventTarget::Window { label } => match candidate {\n          EventTarget::AnyLabel { label: l } | EventTarget::Window { label: l } => l == label,\n          _ => false,\n        },\n        EventTarget::Webview { label } => match candidate {\n          EventTarget::AnyLabel { label: l } | EventTarget::Webview { label: l } => l == label,\n          _ => false,\n        },\n        EventTarget::WebviewWindow { label } => match candidate {\n          EventTarget::AnyLabel { label: l } | EventTarget::WebviewWindow { label: l } => {\n            l == label\n          }\n          _ => false,\n        },\n        // otherwise match same target\n        _ => target == candidate,\n      }\n    }\n\n    match target {\n      // if targeting all, emit to all using emit without filter\n      EventTarget::Any => self.emit(event, payload),\n      target => self.emit_filter(event, payload, |t| filter_target(&target, t)),\n    }\n  }\n\n  pub fn get_window(&self, label: &str) -> Option<Window<R>> {\n    self.window.windows_lock().get(label).cloned()\n  }\n\n  pub fn get_focused_window(&self) -> Option<Window<R>> {\n    self\n      .window\n      .windows_lock()\n      .iter()\n      .find(|w| w.1.is_focused().unwrap_or(false))\n      .map(|w| w.1.clone())\n  }\n\n  pub(crate) fn on_window_close(&self, label: &str) {\n    let window = self.window.windows_lock().remove(label);\n    if let Some(window) = window {\n      for webview in window.webviews() {\n        self.webview.webviews_lock().remove(webview.label());\n      }\n    }\n  }\n\n  #[cfg(desktop)]\n  pub(crate) fn on_webview_close(&self, label: &str) {\n    self.webview.webviews_lock().remove(label);\n  }\n\n  pub fn windows(&self) -> HashMap<String, Window<R>> {\n    self.window.windows_lock().clone()\n  }\n\n  pub fn get_webview(&self, label: &str) -> Option<Webview<R>> {\n    self.webview.webviews_lock().get(label).cloned()\n  }\n\n  pub fn webviews(&self) -> HashMap<String, Webview<R>> {\n    self.webview.webviews_lock().clone()\n  }\n\n  pub(crate) fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {\n    self\n      .resources_table\n      .lock()\n      .expect(\"poisoned window manager\")\n  }\n\n  pub(crate) fn invoke_key(&self) -> &str {\n    &self.invoke_key\n  }\n}\n\n#[cfg(desktop)]\nimpl<R: Runtime> AppManager<R> {\n  pub fn remove_menu_from_stash_by_id(&self, id: Option<&crate::menu::MenuId>) {\n    if let Some(id) = id {\n      let is_used_by_a_window = self\n        .window\n        .windows_lock()\n        .values()\n        .any(|w| w.is_menu_in_use(id));\n      if !(self.menu.is_menu_in_use(id) || is_used_by_a_window) {\n        self.menu.menus_stash_lock().remove(id);\n      }\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use super::replace_with_callback;\n\n  #[test]\n  fn string_replace_with_callback() {\n    let mut tauri_index = 0;\n    #[allow(clippy::single_element_loop)]\n    for (src, pattern, replacement, result) in [(\n      \"tauri is awesome, tauri is amazing\",\n      \"tauri\",\n      || {\n        tauri_index += 1;\n        tauri_index.to_string()\n      },\n      \"1 is awesome, 2 is amazing\",\n    )] {\n      assert_eq!(replace_with_callback(src, pattern, replacement), result);\n    }\n  }\n}\n\n#[cfg(test)]\nmod test {\n  use std::{\n    sync::mpsc::{channel, Receiver, Sender},\n    time::Duration,\n  };\n\n  use crate::{\n    event::EventTarget,\n    generate_context,\n    plugin::PluginStore,\n    test::{mock_app, MockRuntime},\n    webview::WebviewBuilder,\n    window::WindowBuilder,\n    App, Emitter, Listener, Manager, StateManager, Webview, WebviewWindow, WebviewWindowBuilder,\n    Window, Wry,\n  };\n\n  use super::AppManager;\n\n  const APP_LISTEN_ID: &str = \"App::listen\";\n  const APP_LISTEN_ANY_ID: &str = \"App::listen_any\";\n  const WINDOW_LISTEN_ID: &str = \"Window::listen\";\n  const WINDOW_LISTEN_ANY_ID: &str = \"Window::listen_any\";\n  const WEBVIEW_LISTEN_ID: &str = \"Webview::listen\";\n  const WEBVIEW_LISTEN_ANY_ID: &str = \"Webview::listen_any\";\n  const WEBVIEW_WINDOW_LISTEN_ID: &str = \"WebviewWindow::listen\";\n  const WEBVIEW_WINDOW_LISTEN_ANY_ID: &str = \"WebviewWindow::listen_any\";\n  const TEST_EVENT_NAME: &str = \"event\";\n\n  #[test]\n  fn check_get_url() {\n    let context = generate_context!(\"test/fixture/src-tauri/tauri.conf.json\", crate, test = true);\n    let manager: AppManager<Wry> = AppManager::with_handlers(\n      context,\n      PluginStore::default(),\n      Box::new(|_| false),\n      None,\n      Default::default(),\n      StateManager::new(),\n      Default::default(),\n      #[cfg(all(desktop, feature = \"tray-icon\"))]\n      Default::default(),\n      Default::default(),\n      Default::default(),\n      Default::default(),\n      \"\".into(),\n      None,\n      crate::generate_invoke_key().unwrap(),\n    );\n\n    #[cfg(custom_protocol)]\n    {\n      assert_eq!(\n        manager.get_app_url(false).to_string(),\n        if cfg!(windows) || cfg!(target_os = \"android\") {\n          \"http://tauri.localhost/\"\n        } else {\n          \"tauri://localhost\"\n        }\n      );\n      assert_eq!(\n        manager.get_app_url(true).to_string(),\n        if cfg!(windows) || cfg!(target_os = \"android\") {\n          \"https://tauri.localhost/\"\n        } else {\n          \"tauri://localhost\"\n        }\n      );\n    }\n\n    #[cfg(dev)]\n    assert_eq!(\n      manager.get_app_url(false).to_string(),\n      \"http://localhost:4000/\"\n    );\n  }\n\n  struct EventSetup {\n    app: App<MockRuntime>,\n    window: Window<MockRuntime>,\n    webview: Webview<MockRuntime>,\n    webview_window: WebviewWindow<MockRuntime>,\n    tx: Sender<(&'static str, String)>,\n    rx: Receiver<(&'static str, String)>,\n  }\n\n  fn setup_events(setup_any: bool) -> EventSetup {\n    let app = mock_app();\n\n    let window = WindowBuilder::new(&app, \"main-window\").build().unwrap();\n\n    let webview = window\n      .add_child(\n        WebviewBuilder::new(\"main-webview\", Default::default()),\n        crate::LogicalPosition::new(0, 0),\n        window.inner_size().unwrap(),\n      )\n      .unwrap();\n\n    let webview_window = WebviewWindowBuilder::new(&app, \"main-webview-window\", Default::default())\n      .build()\n      .unwrap();\n\n    let (tx, rx) = channel();\n\n    macro_rules! setup_listener {\n      ($type:ident, $id:ident, $any_id:ident) => {\n        let tx_ = tx.clone();\n        $type.listen(TEST_EVENT_NAME, move |evt| {\n          tx_\n            .send(($id, serde_json::from_str::<String>(evt.payload()).unwrap()))\n            .unwrap();\n        });\n\n        if setup_any {\n          let tx_ = tx.clone();\n          $type.listen_any(TEST_EVENT_NAME, move |evt| {\n            tx_\n              .send((\n                $any_id,\n                serde_json::from_str::<String>(evt.payload()).unwrap(),\n              ))\n              .unwrap();\n          });\n        }\n      };\n    }\n\n    setup_listener!(app, APP_LISTEN_ID, APP_LISTEN_ANY_ID);\n    setup_listener!(window, WINDOW_LISTEN_ID, WINDOW_LISTEN_ANY_ID);\n    setup_listener!(webview, WEBVIEW_LISTEN_ID, WEBVIEW_LISTEN_ANY_ID);\n    setup_listener!(\n      webview_window,\n      WEBVIEW_WINDOW_LISTEN_ID,\n      WEBVIEW_WINDOW_LISTEN_ANY_ID\n    );\n\n    EventSetup {\n      app,\n      window,\n      webview,\n      webview_window,\n      tx,\n      rx,\n    }\n  }\n\n  fn assert_events(kind: &str, received: &[&str], expected: &[&str]) {\n    for e in expected {\n      assert!(received.contains(e), \"{e} did not receive `{kind}` event\");\n    }\n    assert_eq!(\n      received.len(),\n      expected.len(),\n      \"received {received:?} `{kind}` events but expected {expected:?}\"\n    );\n  }\n\n  #[test]\n  fn emit() {\n    let EventSetup {\n      app,\n      window,\n      webview,\n      webview_window,\n      tx: _,\n      rx,\n    } = setup_events(true);\n\n    run_emit_test(\"emit (app)\", app, &rx);\n    run_emit_test(\"emit (window)\", window, &rx);\n    run_emit_test(\"emit (webview)\", webview, &rx);\n    run_emit_test(\"emit (webview_window)\", webview_window, &rx);\n  }\n\n  fn run_emit_test<M: Manager<MockRuntime> + Emitter<MockRuntime>>(\n    kind: &str,\n    m: M,\n    rx: &Receiver<(&str, String)>,\n  ) {\n    let mut received = Vec::new();\n    let payload = \"global-payload\";\n    m.emit(TEST_EVENT_NAME, payload).unwrap();\n    while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {\n      assert_eq!(p, payload);\n      received.push(source);\n    }\n    assert_events(\n      kind,\n      &received,\n      &[\n        APP_LISTEN_ID,\n        APP_LISTEN_ANY_ID,\n        WINDOW_LISTEN_ID,\n        WINDOW_LISTEN_ANY_ID,\n        WEBVIEW_LISTEN_ID,\n        WEBVIEW_LISTEN_ANY_ID,\n        WEBVIEW_WINDOW_LISTEN_ID,\n        WEBVIEW_WINDOW_LISTEN_ANY_ID,\n      ],\n    );\n  }\n\n  #[test]\n  fn emit_to() {\n    let EventSetup {\n      app,\n      window,\n      webview,\n      webview_window,\n      tx,\n      rx,\n    } = setup_events(false);\n\n    run_emit_to_test(\n      \"emit_to (App)\",\n      &app,\n      &window,\n      &webview,\n      &webview_window,\n      tx.clone(),\n      &rx,\n    );\n    run_emit_to_test(\n      \"emit_to (window)\",\n      &window,\n      &window,\n      &webview,\n      &webview_window,\n      tx.clone(),\n      &rx,\n    );\n    run_emit_to_test(\n      \"emit_to (webview)\",\n      &webview,\n      &window,\n      &webview,\n      &webview_window,\n      tx.clone(),\n      &rx,\n    );\n    run_emit_to_test(\n      \"emit_to (webview_window)\",\n      &webview_window,\n      &window,\n      &webview,\n      &webview_window,\n      tx.clone(),\n      &rx,\n    );\n  }\n\n  fn run_emit_to_test<M: Manager<MockRuntime> + Emitter<MockRuntime>>(\n    kind: &str,\n    m: &M,\n    window: &Window<MockRuntime>,\n    webview: &Webview<MockRuntime>,\n    webview_window: &WebviewWindow<MockRuntime>,\n    tx: Sender<(&'static str, String)>,\n    rx: &Receiver<(&'static str, String)>,\n  ) {\n    let mut received = Vec::new();\n    let payload = \"global-payload\";\n\n    macro_rules! test_target {\n      ($target:expr, $id:ident) => {\n        m.emit_to($target, TEST_EVENT_NAME, payload).unwrap();\n        while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {\n          assert_eq!(p, payload);\n          received.push(source);\n        }\n        assert_events(kind, &received, &[$id]);\n\n        received.clear();\n      };\n    }\n\n    test_target!(EventTarget::App, APP_LISTEN_ID);\n    test_target!(window.label(), WINDOW_LISTEN_ID);\n    test_target!(webview.label(), WEBVIEW_LISTEN_ID);\n    test_target!(webview_window.label(), WEBVIEW_WINDOW_LISTEN_ID);\n\n    let other_webview_listen_id = \"OtherWebview::listen\";\n    let other_webview = WebviewWindowBuilder::new(\n      window,\n      kind.replace(['(', ')', ' '], \"\"),\n      Default::default(),\n    )\n    .build()\n    .unwrap();\n\n    other_webview.listen(TEST_EVENT_NAME, move |evt| {\n      tx.send((\n        other_webview_listen_id,\n        serde_json::from_str::<String>(evt.payload()).unwrap(),\n      ))\n      .unwrap();\n    });\n    m.emit_to(other_webview.label(), TEST_EVENT_NAME, payload)\n      .unwrap();\n    while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {\n      assert_eq!(p, payload);\n      received.push(source);\n    }\n    assert_events(\"emit_to\", &received, &[other_webview_listen_id]);\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/manager/tray.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  collections::HashMap,\n  fmt,\n  sync::{Arc, Mutex},\n};\n\nuse crate::{\n  app::GlobalTrayIconEventListener,\n  image::Image,\n  tray::{TrayIcon, TrayIconEvent, TrayIconId},\n  AppHandle, Manager, Resource, ResourceId, Runtime,\n};\n\npub struct TrayManager<R: Runtime> {\n  pub(crate) icon: Option<Image<'static>>,\n  /// Tray icons\n  pub(crate) icons: Mutex<Vec<(TrayIconId, ResourceId)>>,\n  /// Global Tray icon event listeners.\n  pub(crate) global_event_listeners: Mutex<Vec<GlobalTrayIconEventListener<AppHandle<R>>>>,\n  /// Tray icon event listeners.\n  pub(crate) event_listeners: Mutex<HashMap<TrayIconId, GlobalTrayIconEventListener<TrayIcon<R>>>>,\n}\n\nimpl<R: Runtime> fmt::Debug for TrayManager<R> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"TrayManager\")\n      .field(\"icon\", &self.icon)\n      .finish()\n  }\n}\n\nimpl<R: Runtime> TrayManager<R> {\n  pub fn on_tray_icon_event<F: Fn(&AppHandle<R>, TrayIconEvent) + Send + Sync + 'static>(\n    &self,\n    handler: F,\n  ) {\n    self\n      .global_event_listeners\n      .lock()\n      .unwrap()\n      .push(Box::new(handler));\n  }\n\n  pub fn tray_by_id<'a, I>(&self, app: &AppHandle<R>, id: &'a I) -> Option<TrayIcon<R>>\n  where\n    I: ?Sized,\n    TrayIconId: PartialEq<&'a I>,\n  {\n    let icons = self.icons.lock().unwrap();\n    icons.iter().find_map(|(tray_icon_id, rid)| {\n      if tray_icon_id == &id {\n        let icon = app.resources_table().get::<TrayIcon<R>>(*rid).ok()?;\n        Some(Arc::unwrap_or_clone(icon))\n      } else {\n        None\n      }\n    })\n  }\n\n  pub fn tray_resource_by_id<'a, I>(&self, id: &'a I) -> Option<ResourceId>\n  where\n    I: ?Sized,\n    TrayIconId: PartialEq<&'a I>,\n  {\n    let icons = self.icons.lock().unwrap();\n    icons.iter().find_map(|(tray_icon_id, rid)| {\n      if tray_icon_id == &id {\n        Some(*rid)\n      } else {\n        None\n      }\n    })\n  }\n\n  pub fn remove_tray_by_id<'a, I>(&self, app: &AppHandle<R>, id: &'a I) -> Option<TrayIcon<R>>\n  where\n    I: ?Sized,\n    TrayIconId: PartialEq<&'a I>,\n  {\n    let rid = self.tray_resource_by_id(id)?;\n    let icon = app.resources_table().take::<TrayIcon<R>>(rid).ok()?;\n    let icon_to_return = icon.clone();\n    icon.close();\n    Some(Arc::unwrap_or_clone(icon_to_return))\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/manager/webview.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  borrow::Cow,\n  collections::{HashMap, HashSet},\n  fmt,\n  fs::create_dir_all,\n  sync::{Arc, Mutex, MutexGuard},\n};\n\nuse serde::Serialize;\nuse serialize_to_javascript::{default_template, DefaultTemplate, Template};\nuse tauri_runtime::{\n  webview::{DetachedWebview, InitializationScript, PendingWebview},\n  window::DragDropEvent,\n};\nuse tauri_utils::config::WebviewUrl;\nuse url::Url;\n\nuse crate::{\n  app::{GlobalWebviewEventListener, OnPageLoad, UriSchemeResponder, WebviewEvent},\n  ipc::InvokeHandler,\n  pattern::PatternJavascript,\n  sealed::ManagerBase,\n  webview::PageLoadPayload,\n  EventLoopMessage, EventTarget, Manager, Runtime, Scopes, UriSchemeContext, Webview, Window,\n};\n\nuse super::{\n  window::{DragDropPayload, DRAG_DROP_EVENT, DRAG_ENTER_EVENT, DRAG_LEAVE_EVENT, DRAG_OVER_EVENT},\n  {AppManager, EmitPayload},\n};\n\n// we need to proxy the dev server on mobile because we can't use `localhost`, so we use the local IP address\n// and we do not get a secure context without the custom protocol that proxies to the dev server\n// additionally, we need the custom protocol to inject the initialization scripts on Android\n// must also keep in sync with the `let mut response` assignment in prepare_uri_scheme_protocol\npub(crate) const PROXY_DEV_SERVER: bool = cfg!(all(dev, mobile));\n\npub(crate) const PROCESS_IPC_MESSAGE_FN: &str =\n  include_str!(\"../../scripts/process-ipc-message-fn.js\");\n\n#[cfg(feature = \"isolation\")]\n#[derive(Template)]\n#[default_template(\"../../scripts/isolation.js\")]\npub(crate) struct IsolationJavascript<'a> {\n  pub(crate) isolation_src: &'a str,\n  pub(crate) style: &'a str,\n}\n\n#[derive(Template)]\n#[default_template(\"../../scripts/ipc.js\")]\npub(crate) struct IpcJavascript<'a> {\n  pub(crate) isolation_origin: &'a str,\n}\n\n/// Uses a custom URI scheme handler to resolve file requests\npub struct UriSchemeProtocol<R: Runtime> {\n  /// Handler for protocol\n  #[allow(clippy::type_complexity)]\n  pub handler:\n    Box<dyn Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync>,\n}\n\npub struct WebviewManager<R: Runtime> {\n  pub webviews: Mutex<HashMap<String, Webview<R>>>,\n  /// The JS message handler.\n  pub invoke_handler: Box<InvokeHandler<R>>,\n  /// The page load hook, invoked when the webview performs a navigation.\n  pub on_page_load: Option<Arc<OnPageLoad<R>>>,\n  /// The webview protocols available to all webviews.\n  pub uri_scheme_protocols: Mutex<HashMap<String, Arc<UriSchemeProtocol<R>>>>,\n  /// Webview event listeners to all webviews.\n  pub event_listeners: Arc<Vec<GlobalWebviewEventListener<R>>>,\n\n  /// The script that initializes the invoke system.\n  pub invoke_initialization_script: String,\n\n  /// A runtime generated invoke key.\n  pub(crate) invoke_key: String,\n}\n\nimpl<R: Runtime> fmt::Debug for WebviewManager<R> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"WebviewManager\")\n      .field(\n        \"invoke_initialization_script\",\n        &self.invoke_initialization_script,\n      )\n      .field(\"invoke_key\", &self.invoke_key)\n      .finish()\n  }\n}\n\nimpl<R: Runtime> WebviewManager<R> {\n  pub(crate) fn register_uri_scheme_protocol<N: Into<String>>(\n    &self,\n    uri_scheme: N,\n    protocol: Arc<UriSchemeProtocol<R>>,\n  ) {\n    let uri_scheme = uri_scheme.into();\n    self\n      .uri_scheme_protocols\n      .lock()\n      .unwrap()\n      .insert(uri_scheme, protocol);\n  }\n\n  /// Get a locked handle to the webviews.\n  pub(crate) fn webviews_lock(&self) -> MutexGuard<'_, HashMap<String, Webview<R>>> {\n    self.webviews.lock().expect(\"poisoned webview manager\")\n  }\n\n  fn prepare_pending_webview<M: Manager<R>>(\n    &self,\n    mut pending: PendingWebview<EventLoopMessage, R>,\n    label: &str,\n    window_label: &str,\n    manager: &M,\n  ) -> crate::Result<PendingWebview<EventLoopMessage, R>> {\n    let app_manager = manager.manager();\n\n    let plugin_init_scripts = app_manager\n      .plugins\n      .lock()\n      .expect(\"poisoned plugin store\")\n      .initialization_script();\n\n    let pattern_init = PatternJavascript {\n      pattern: (&*app_manager.pattern).into(),\n    }\n    .render_default(&Default::default())?;\n\n    let mut webview_attributes = pending.webview_attributes;\n\n    let use_https_scheme = webview_attributes.use_https_scheme;\n\n    let ipc_init = IpcJavascript {\n      isolation_origin: &match &*app_manager.pattern {\n        #[cfg(feature = \"isolation\")]\n        crate::Pattern::Isolation { schema, .. } => {\n          crate::pattern::format_real_schema(schema, use_https_scheme)\n        }\n        _ => \"\".to_owned(),\n      },\n    }\n    .render_default(&Default::default())?;\n\n    let mut all_initialization_scripts: Vec<InitializationScript> = vec![];\n\n    fn main_frame_script(script: String) -> InitializationScript {\n      InitializationScript {\n        script,\n        for_main_frame_only: true,\n      }\n    }\n\n    all_initialization_scripts.push(main_frame_script(\n      r\"\n        Object.defineProperty(window, 'isTauri', {\n          value: true,\n        });\n\n        if (!window.__TAURI_INTERNALS__) {\n          Object.defineProperty(window, '__TAURI_INTERNALS__', {\n            value: {\n              plugins: {}\n            }\n          })\n        }\n      \"\n      .to_owned(),\n    ));\n    all_initialization_scripts.push(main_frame_script(self.invoke_initialization_script.clone()));\n    all_initialization_scripts.push(main_frame_script(format!(\n      r#\"\n          Object.defineProperty(window.__TAURI_INTERNALS__, 'metadata', {{\n            value: {{\n              currentWindow: {{ label: {current_window_label} }},\n              currentWebview: {{ label: {current_webview_label} }}\n            }}\n          }})\n        \"#,\n      current_window_label = serde_json::to_string(window_label)?,\n      current_webview_label = serde_json::to_string(&label)?,\n    )));\n    all_initialization_scripts.push(main_frame_script(self.initialization_script(\n      app_manager,\n      &ipc_init.into_string(),\n      &pattern_init.into_string(),\n      use_https_scheme,\n    )?));\n\n    all_initialization_scripts.extend(plugin_init_scripts);\n\n    #[cfg(feature = \"isolation\")]\n    if let crate::Pattern::Isolation { schema, .. } = &*app_manager.pattern {\n      all_initialization_scripts.push(main_frame_script(\n        IsolationJavascript {\n          isolation_src: &crate::pattern::format_real_schema(schema, use_https_scheme),\n          style: tauri_utils::pattern::isolation::IFRAME_STYLE,\n        }\n        .render_default(&Default::default())?\n        .into_string(),\n      ));\n    }\n\n    if let Some(plugin_global_api_scripts) = &*app_manager.plugin_global_api_scripts {\n      for &script in plugin_global_api_scripts.iter() {\n        all_initialization_scripts.push(main_frame_script(script.to_owned()));\n      }\n    }\n\n    // Prepend `all_initialization_scripts` to `webview_attributes.initialization_scripts`\n    all_initialization_scripts.extend(webview_attributes.initialization_scripts);\n    webview_attributes.initialization_scripts = all_initialization_scripts;\n\n    pending.webview_attributes = webview_attributes;\n\n    let mut registered_scheme_protocols = Vec::new();\n\n    for (uri_scheme, protocol) in &*self.uri_scheme_protocols.lock().unwrap() {\n      registered_scheme_protocols.push(uri_scheme.clone());\n      let protocol = protocol.clone();\n      let app_handle = manager.app_handle().clone();\n\n      pending.register_uri_scheme_protocol(uri_scheme, move |webview_id, request, responder| {\n        let context = UriSchemeContext {\n          app_handle: &app_handle,\n          webview_label: webview_id,\n        };\n        (protocol.handler)(context, request, UriSchemeResponder(responder))\n      });\n    }\n\n    let window_url = Url::parse(&pending.url).unwrap();\n    let window_origin = if window_url.scheme() == \"data\" {\n      \"null\".into()\n    } else if (cfg!(windows) || cfg!(target_os = \"android\"))\n      && window_url.scheme() != \"http\"\n      && window_url.scheme() != \"https\"\n    {\n      let https = if use_https_scheme { \"https\" } else { \"http\" };\n      format!(\"{https}://{}.localhost\", window_url.scheme())\n    } else if let Some(host) = window_url.host() {\n      format!(\n        \"{}://{}{}\",\n        window_url.scheme(),\n        host,\n        window_url\n          .port()\n          .map(|p| format!(\":{p}\"))\n          .unwrap_or_default()\n      )\n    } else {\n      \"null\".into()\n    };\n\n    if !registered_scheme_protocols.contains(&\"tauri\".into()) {\n      let web_resource_request_handler = pending.web_resource_request_handler.take();\n      let protocol = crate::protocol::tauri::get(\n        manager.manager_owned(),\n        &window_origin,\n        web_resource_request_handler,\n      );\n      pending.register_uri_scheme_protocol(\"tauri\", move |webview_id, request, responder| {\n        protocol(webview_id, request, UriSchemeResponder(responder))\n      });\n      registered_scheme_protocols.push(\"tauri\".into());\n    }\n\n    if !registered_scheme_protocols.contains(&\"ipc\".into()) {\n      let protocol = crate::ipc::protocol::get(manager.manager_owned());\n      pending.register_uri_scheme_protocol(\"ipc\", move |webview_id, request, responder| {\n        protocol(webview_id, request, UriSchemeResponder(responder))\n      });\n      registered_scheme_protocols.push(\"ipc\".into());\n    }\n\n    let label = pending.label.clone();\n    let app_manager_ = manager.manager_owned();\n    let on_page_load_handler = pending.on_page_load_handler.take();\n    pending\n      .on_page_load_handler\n      .replace(Box::new(move |url, event| {\n        let payload = PageLoadPayload { url: &url, event };\n\n        if let Some(w) = app_manager_.get_webview(&label) {\n          if let Some(on_page_load) = &app_manager_.webview.on_page_load {\n            on_page_load(&w, &payload);\n          }\n\n          app_manager_\n            .plugins\n            .lock()\n            .unwrap()\n            .on_page_load(&w, &payload);\n        }\n\n        if let Some(handler) = &on_page_load_handler {\n          handler(url, event);\n        }\n      }));\n\n    #[cfg(feature = \"protocol-asset\")]\n    if !registered_scheme_protocols.contains(&\"asset\".into()) {\n      let asset_scope = app_manager\n        .state()\n        .get::<crate::Scopes>()\n        .asset_protocol\n        .clone();\n      let protocol = crate::protocol::asset::get(asset_scope, window_origin.clone());\n      pending.register_uri_scheme_protocol(\"asset\", move |webview_id, request, responder| {\n        protocol(webview_id, request, UriSchemeResponder(responder))\n      });\n    }\n\n    #[cfg(feature = \"isolation\")]\n    if let crate::Pattern::Isolation {\n      assets,\n      schema,\n      key: _,\n      crypto_keys,\n    } = &*app_manager.pattern\n    {\n      let protocol = crate::protocol::isolation::get(\n        manager.manager_owned(),\n        schema,\n        assets.clone(),\n        *crypto_keys.aes_gcm().raw(),\n        window_origin,\n        use_https_scheme,\n      );\n      pending.register_uri_scheme_protocol(schema, move |webview_id, request, responder| {\n        protocol(webview_id, request, UriSchemeResponder(responder))\n      });\n    }\n\n    Ok(pending)\n  }\n\n  fn initialization_script(\n    &self,\n    app_manager: &AppManager<R>,\n    ipc_script: &str,\n    pattern_script: &str,\n    use_https_scheme: bool,\n  ) -> crate::Result<String> {\n    #[derive(Template)]\n    #[default_template(\"../../scripts/init.js\")]\n    struct InitJavascript<'a> {\n      #[raw]\n      pattern_script: &'a str,\n      #[raw]\n      ipc_script: &'a str,\n      #[raw]\n      core_script: &'a str,\n      #[raw]\n      event_initialization_script: &'a str,\n      #[raw]\n      freeze_prototype: &'a str,\n    }\n\n    #[derive(Template)]\n    #[default_template(\"../../scripts/core.js\")]\n    struct CoreJavascript<'a> {\n      os_name: &'a str,\n      protocol_scheme: &'a str,\n      invoke_key: &'a str,\n    }\n\n    let freeze_prototype = if app_manager.config.app.security.freeze_prototype {\n      include_str!(\"../../scripts/freeze_prototype.js\")\n    } else {\n      \"\"\n    };\n\n    InitJavascript {\n      pattern_script,\n      ipc_script,\n      core_script: &CoreJavascript {\n        os_name: std::env::consts::OS,\n        protocol_scheme: if use_https_scheme { \"https\" } else { \"http\" },\n        invoke_key: self.invoke_key(),\n      }\n      .render_default(&Default::default())?\n      .into_string(),\n      event_initialization_script: &crate::event::event_initialization_script(\n        app_manager.listeners().function_name(),\n        app_manager.listeners().listeners_object_name(),\n      ),\n      freeze_prototype,\n    }\n    .render_default(&Default::default())\n    .map(|s| s.into_string())\n    .map_err(Into::into)\n  }\n\n  pub fn prepare_webview<M: Manager<R>>(\n    &self,\n    manager: &M,\n    mut pending: PendingWebview<EventLoopMessage, R>,\n    window_label: &str,\n  ) -> crate::Result<PendingWebview<EventLoopMessage, R>> {\n    if self.webviews_lock().contains_key(&pending.label) {\n      return Err(crate::Error::WebviewLabelAlreadyExists(pending.label));\n    }\n\n    let app_manager = manager.manager();\n\n    #[allow(unused_mut)] // mut url only for the data-url parsing\n    let mut url = match &pending.webview_attributes.url {\n      WebviewUrl::App(path) => {\n        let app_url = app_manager.get_app_url(pending.webview_attributes.use_https_scheme);\n        let url = if PROXY_DEV_SERVER && is_local_network_url(&app_url) {\n          Cow::Owned(Url::parse(\"tauri://localhost\").unwrap())\n        } else {\n          app_url\n        };\n        // ignore \"index.html\" just to simplify the url\n        if path.to_str() != Some(\"index.html\") {\n          url\n            .join(&path.to_string_lossy())\n            .map_err(crate::Error::InvalidUrl)\n            // this will never fail\n            .unwrap()\n        } else {\n          url.into_owned()\n        }\n      }\n      WebviewUrl::External(url) => {\n        let config_url = app_manager.get_app_url(pending.webview_attributes.use_https_scheme);\n        let is_app_url = config_url.make_relative(url).is_some();\n        let mut url = url.clone();\n        if is_app_url && PROXY_DEV_SERVER && is_local_network_url(&url) {\n          Url::parse(\"tauri://localhost\").unwrap()\n        } else {\n          url\n        }\n      }\n\n      WebviewUrl::CustomProtocol(url) => url.clone(),\n      _ => unimplemented!(),\n    };\n\n    #[cfg(not(feature = \"webview-data-url\"))]\n    if url.scheme() == \"data\" {\n      return Err(crate::Error::InvalidWebviewUrl(\n        \"data URLs are not supported without the `webview-data-url` feature.\",\n      ));\n    }\n\n    #[cfg(feature = \"webview-data-url\")]\n    if let Some(csp) = app_manager.csp() {\n      if url.scheme() == \"data\" {\n        if let Ok(data_url) = data_url::DataUrl::process(url.as_str()) {\n          let (body, _) = data_url.decode_to_vec().unwrap();\n          let html = String::from_utf8_lossy(&body).into_owned();\n          // naive way to check if it's an html\n          if html.contains('<') && html.contains('>') {\n            let document = tauri_utils::html::parse(html);\n            tauri_utils::html::inject_csp(&document, &csp.to_string());\n            url.set_path(&format!(\"{},{document}\", mime::TEXT_HTML));\n          }\n        }\n      }\n    }\n\n    pending.url = url.to_string();\n\n    #[cfg(target_os = \"android\")]\n    {\n      pending = pending.on_webview_created(move |ctx| {\n        let plugin_manager = ctx\n          .env\n          .call_method(\n            ctx.activity,\n            \"getPluginManager\",\n            \"()Lapp/tauri/plugin/PluginManager;\",\n            &[],\n          )?\n          .l()?;\n\n        // tell the manager the webview is ready\n        ctx.env.call_method(\n          plugin_manager,\n          \"onWebViewCreated\",\n          \"(Landroid/webkit/WebView;)V\",\n          &[ctx.webview.into()],\n        )?;\n\n        Ok(())\n      });\n    }\n\n    let label = pending.label.clone();\n    pending = self.prepare_pending_webview(pending, &label, window_label, manager)?;\n\n    pending.ipc_handler = Some(crate::ipc::protocol::message_handler(\n      manager.manager_owned(),\n    ));\n\n    // in `windows`, we need to force a data_directory\n    // but we do respect user-specification\n    #[cfg(any(target_os = \"linux\", target_os = \"windows\"))]\n    if pending.webview_attributes.data_directory.is_none() {\n      let local_app_data = manager.path().resolve(\n        &app_manager.config.identifier,\n        crate::path::BaseDirectory::LocalData,\n      );\n      if let Ok(user_data_dir) = local_app_data {\n        pending.webview_attributes.data_directory = Some(user_data_dir);\n      }\n    }\n\n    // make sure the directory is created and available to prevent a panic\n    if let Some(user_data_dir) = &pending.webview_attributes.data_directory {\n      if !user_data_dir.exists() {\n        create_dir_all(user_data_dir)?;\n      }\n    }\n\n    #[cfg(all(desktop, not(target_os = \"windows\")))]\n    if pending.webview_attributes.zoom_hotkeys_enabled {\n      #[derive(Template)]\n      #[default_template(\"../webview/scripts/zoom-hotkey.js\")]\n      struct HotkeyZoom<'a> {\n        os_name: &'a str,\n      }\n\n      pending\n        .webview_attributes\n        .initialization_scripts\n        .push(InitializationScript {\n          script: HotkeyZoom {\n            os_name: std::env::consts::OS,\n          }\n          .render_default(&Default::default())?\n          .into_string(),\n          for_main_frame_only: true,\n        })\n    }\n\n    #[cfg(feature = \"isolation\")]\n    let pattern = app_manager.pattern.clone();\n    let navigation_handler = pending.navigation_handler.take();\n    let app_manager = manager.manager_owned();\n    let label = pending.label.clone();\n    pending.navigation_handler = Some(Box::new(move |url| {\n      // always allow navigation events for the isolation iframe and do not emit them for consumers\n      #[cfg(feature = \"isolation\")]\n      if let crate::Pattern::Isolation { schema, .. } = &*pattern {\n        if url.scheme() == schema\n          && url.domain() == Some(crate::pattern::ISOLATION_IFRAME_SRC_DOMAIN)\n        {\n          return true;\n        }\n      }\n      if let Some(handler) = &navigation_handler {\n        if !handler(url) {\n          return false;\n        }\n      }\n      let webview = app_manager.webview.webviews_lock().get(&label).cloned();\n      if let Some(w) = webview {\n        app_manager\n          .plugins\n          .lock()\n          .expect(\"poisoned plugin store\")\n          .on_navigation(&w, url)\n      } else {\n        true\n      }\n    }));\n\n    Ok(pending)\n  }\n\n  pub(crate) fn attach_webview(\n    &self,\n    window: Window<R>,\n    webview: DetachedWebview<EventLoopMessage, R>,\n    use_https_scheme: bool,\n  ) -> Webview<R> {\n    let webview = Webview::new(window, webview, use_https_scheme);\n\n    let webview_event_listeners = self.event_listeners.clone();\n    let webview_ = webview.clone();\n    webview.on_webview_event(move |event| {\n      let _ = on_webview_event(&webview_, event);\n      for handler in webview_event_listeners.iter() {\n        handler(&webview_, event);\n      }\n    });\n\n    // insert the webview into our manager\n    {\n      self\n        .webviews_lock()\n        .insert(webview.label().to_string(), webview.clone());\n    }\n\n    // let plugins know that a new webview has been added to the manager\n    let manager = webview.manager_owned();\n    let webview_ = webview.clone();\n    // run on main thread so the plugin store doesn't dead lock with the event loop handler in App\n    let _ = webview.run_on_main_thread(move || {\n      manager\n        .plugins\n        .lock()\n        .expect(\"poisoned plugin store\")\n        .webview_created(webview_);\n    });\n\n    #[cfg(all(target_os = \"ios\", feature = \"wry\"))]\n    {\n      webview\n        .with_webview(|w| {\n          unsafe { crate::ios::on_webview_created(w.inner() as _, w.view_controller() as _) };\n        })\n        .expect(\"failed to run on_webview_created hook\");\n    }\n\n    let event = crate::EventName::from_str(\"tauri://webview-created\");\n    let payload = Some(crate::webview::CreatedEvent {\n      label: webview.label().into(),\n    });\n\n    let _ = webview\n      .manager\n      .emit(event, EmitPayload::Serialize(&payload));\n\n    webview\n  }\n\n  pub fn eval_script_all<S: Into<String>>(&self, script: S) -> crate::Result<()> {\n    let script = script.into();\n    let webviews = self.webviews_lock().values().cloned().collect::<Vec<_>>();\n    webviews\n      .iter()\n      .try_for_each(|webview| webview.eval(&script))\n  }\n\n  pub fn labels(&self) -> HashSet<String> {\n    self.webviews_lock().keys().cloned().collect()\n  }\n\n  pub(crate) fn invoke_key(&self) -> &str {\n    &self.invoke_key\n  }\n}\n\nimpl<R: Runtime> Webview<R> {\n  /// Emits event to [`EventTarget::Window`] and [`EventTarget::WebviewWindow`]\n  fn emit_to_webview<S: Serialize>(\n    &self,\n    event: crate::EventName<&str>,\n    payload: &S,\n  ) -> crate::Result<()> {\n    let window_label = self.label();\n    let payload = EmitPayload::Serialize(payload);\n    self\n      .manager()\n      .emit_filter(event, payload, |target| match target {\n        EventTarget::Webview { label } | EventTarget::WebviewWindow { label } => {\n          label == window_label\n        }\n        _ => false,\n      })\n  }\n}\n\nfn on_webview_event<R: Runtime>(webview: &Webview<R>, event: &WebviewEvent) -> crate::Result<()> {\n  match event {\n    WebviewEvent::DragDrop(event) => match event {\n      DragDropEvent::Enter { paths, position } => {\n        let payload = DragDropPayload {\n          paths: Some(paths),\n          position,\n        };\n        webview.emit_to_webview(DRAG_ENTER_EVENT, &payload)?\n      }\n      DragDropEvent::Over { position } => {\n        let payload = DragDropPayload {\n          position,\n          paths: None,\n        };\n        webview.emit_to_webview(DRAG_OVER_EVENT, &payload)?\n      }\n      DragDropEvent::Drop { paths, position } => {\n        let scopes = webview.state::<Scopes>();\n        for path in paths {\n          if path.is_file() {\n            let _ = scopes.allow_file(path);\n          } else {\n            let _ = scopes.allow_directory(path, false);\n          }\n        }\n        let payload = DragDropPayload {\n          paths: Some(paths),\n          position,\n        };\n        webview.emit_to_webview(DRAG_DROP_EVENT, &payload)?\n      }\n      DragDropEvent::Leave => webview.emit_to_webview(DRAG_LEAVE_EVENT, &())?,\n      _ => unimplemented!(),\n    },\n  }\n\n  Ok(())\n}\n\nfn is_local_network_url(url: &url::Url) -> bool {\n  match url.host() {\n    Some(url::Host::Domain(s)) => s == \"localhost\",\n    Some(url::Host::Ipv4(_)) | Some(url::Host::Ipv6(_)) => true,\n    None => false,\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use super::*;\n\n  #[test]\n  fn local_network_url() {\n    assert!(is_local_network_url(&\"http://localhost\".parse().unwrap()));\n    assert!(is_local_network_url(\n      &\"http://127.0.0.1:8080\".parse().unwrap()\n    ));\n    assert!(is_local_network_url(\n      &\"https://192.168.3.17\".parse().unwrap()\n    ));\n\n    assert!(!is_local_network_url(&\"https://tauri.app\".parse().unwrap()));\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/manager/window.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  collections::{HashMap, HashSet},\n  fmt,\n  path::PathBuf,\n  sync::{Arc, Mutex, MutexGuard},\n};\n\nuse serde::Serialize;\nuse tauri_runtime::{\n  dpi::{PhysicalPosition, PhysicalSize},\n  window::WindowBuilder,\n  window::{DetachedWindow, DragDropEvent, PendingWindow},\n};\n\nuse crate::{\n  app::GlobalWindowEventListener, event::EventName, image::Image, sealed::ManagerBase, AppHandle,\n  EventLoopMessage, EventTarget, Manager, Runtime, Scopes, Window, WindowEvent,\n};\n\nuse super::EmitPayload;\n\nconst WINDOW_RESIZED_EVENT: EventName<&str> = EventName::from_str(\"tauri://resize\");\nconst WINDOW_MOVED_EVENT: EventName<&str> = EventName::from_str(\"tauri://move\");\nconst WINDOW_CLOSE_REQUESTED_EVENT: EventName<&str> =\n  EventName::from_str(\"tauri://close-requested\");\nconst WINDOW_DESTROYED_EVENT: EventName<&str> = EventName::from_str(\"tauri://destroyed\");\nconst WINDOW_FOCUS_EVENT: EventName<&str> = EventName::from_str(\"tauri://focus\");\nconst WINDOW_BLUR_EVENT: EventName<&str> = EventName::from_str(\"tauri://blur\");\nconst WINDOW_SCALE_FACTOR_CHANGED_EVENT: EventName<&str> =\n  EventName::from_str(\"tauri://scale-change\");\nconst WINDOW_THEME_CHANGED: EventName<&str> = EventName::from_str(\"tauri://theme-changed\");\npub(crate) const DRAG_ENTER_EVENT: EventName<&str> = EventName::from_str(\"tauri://drag-enter\");\npub(crate) const DRAG_OVER_EVENT: EventName<&str> = EventName::from_str(\"tauri://drag-over\");\npub(crate) const DRAG_DROP_EVENT: EventName<&str> = EventName::from_str(\"tauri://drag-drop\");\npub(crate) const DRAG_LEAVE_EVENT: EventName<&str> = EventName::from_str(\"tauri://drag-leave\");\n\npub struct WindowManager<R: Runtime> {\n  pub windows: Mutex<HashMap<String, Window<R>>>,\n  pub default_icon: Option<Image<'static>>,\n  /// Window event listeners to all windows.\n  pub event_listeners: Arc<Vec<GlobalWindowEventListener<R>>>,\n}\n\nimpl<R: Runtime> fmt::Debug for WindowManager<R> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"WindowManager\")\n      .field(\"default_window_icon\", &self.default_icon)\n      .finish()\n  }\n}\n\nimpl<R: Runtime> WindowManager<R> {\n  /// Get a locked handle to the windows.\n  pub(crate) fn windows_lock(&self) -> MutexGuard<'_, HashMap<String, Window<R>>> {\n    self.windows.lock().expect(\"poisoned window manager\")\n  }\n\n  pub fn prepare_window(\n    &self,\n    mut pending: PendingWindow<EventLoopMessage, R>,\n  ) -> crate::Result<PendingWindow<EventLoopMessage, R>> {\n    if self.windows_lock().contains_key(&pending.label) {\n      return Err(crate::Error::WindowLabelAlreadyExists(pending.label));\n    }\n\n    if !pending.window_builder.has_icon() {\n      if let Some(default_window_icon) = self.default_icon.clone() {\n        pending.window_builder = pending.window_builder.icon(default_window_icon.into())?;\n      }\n    }\n\n    Ok(pending)\n  }\n\n  pub(crate) fn attach_window(\n    &self,\n    app_handle: AppHandle<R>,\n    window: DetachedWindow<EventLoopMessage, R>,\n    #[cfg(desktop)] menu: Option<crate::window::WindowMenu<R>>,\n  ) -> Window<R> {\n    let window = Window::new(\n      app_handle.manager.clone(),\n      window,\n      app_handle,\n      #[cfg(desktop)]\n      menu,\n    );\n\n    let window_ = window.clone();\n    let window_event_listeners = self.event_listeners.clone();\n    window.on_window_event(move |event| {\n      let _ = on_window_event(&window_, event);\n      for handler in window_event_listeners.iter() {\n        handler(&window_, event);\n      }\n    });\n\n    // insert the window into our manager\n    {\n      self\n        .windows_lock()\n        .insert(window.label().to_string(), window.clone());\n    }\n\n    // let plugins know that a new window has been added to the manager\n    let manager = window.manager.clone();\n    let window_ = window.clone();\n    // run on main thread so the plugin store doesn't dead lock with the event loop handler in App\n    let _ = window.run_on_main_thread(move || {\n      manager\n        .plugins\n        .lock()\n        .expect(\"poisoned plugin store\")\n        .window_created(window_);\n    });\n\n    window\n  }\n\n  pub fn labels(&self) -> HashSet<String> {\n    self.windows_lock().keys().cloned().collect()\n  }\n}\n\nimpl<R: Runtime> Window<R> {\n  /// Emits event to [`EventTarget::Window`] and [`EventTarget::WebviewWindow`]\n  fn emit_to_window<S: Serialize>(&self, event: EventName<&str>, payload: &S) -> crate::Result<()> {\n    let window_label = self.label();\n    let payload = EmitPayload::Serialize(payload);\n    self\n      .manager()\n      .emit_filter(event, payload, |target| match target {\n        EventTarget::Window { label } | EventTarget::WebviewWindow { label } => {\n          label == window_label\n        }\n        _ => false,\n      })\n  }\n\n  /// Checks whether has js listener for [`EventTarget::Window`] or [`EventTarget::WebviewWindow`]\n  fn has_js_listener(&self, event: EventName<&str>) -> bool {\n    let window_label = self.label();\n    let listeners = self.manager().listeners();\n    listeners.has_js_listener(event, |target| match target {\n      EventTarget::Window { label } | EventTarget::WebviewWindow { label } => label == window_label,\n      _ => false,\n    })\n  }\n}\n\n#[derive(Serialize, Clone)]\npub(crate) struct DragDropPayload<'a> {\n  #[serde(skip_serializing_if = \"Option::is_none\")]\n  pub paths: Option<&'a Vec<PathBuf>>,\n  pub position: &'a PhysicalPosition<f64>,\n}\n\nfn on_window_event<R: Runtime>(window: &Window<R>, event: &WindowEvent) -> crate::Result<()> {\n  match event {\n    WindowEvent::Resized(size) => window.emit_to_window(WINDOW_RESIZED_EVENT, size)?,\n    WindowEvent::Moved(position) => window.emit_to_window(WINDOW_MOVED_EVENT, position)?,\n    WindowEvent::CloseRequested { api } => {\n      if window.has_js_listener(WINDOW_CLOSE_REQUESTED_EVENT) {\n        api.prevent_close();\n      }\n      window.emit_to_window(WINDOW_CLOSE_REQUESTED_EVENT, &())?;\n    }\n    WindowEvent::Destroyed => {\n      window.emit_to_window(WINDOW_DESTROYED_EVENT, &())?;\n    }\n    WindowEvent::Focused(focused) => window.emit_to_window(\n      if *focused {\n        WINDOW_FOCUS_EVENT\n      } else {\n        WINDOW_BLUR_EVENT\n      },\n      &(),\n    )?,\n    WindowEvent::ScaleFactorChanged {\n      scale_factor,\n      new_inner_size,\n      ..\n    } => window.emit_to_window(\n      WINDOW_SCALE_FACTOR_CHANGED_EVENT,\n      &ScaleFactorChanged {\n        scale_factor: *scale_factor,\n        size: *new_inner_size,\n      },\n    )?,\n    WindowEvent::DragDrop(event) => match event {\n      DragDropEvent::Enter { paths, position } => {\n        let payload = DragDropPayload {\n          paths: Some(paths),\n          position,\n        };\n\n        if window.is_webview_window() {\n          // use underlying manager, otherwise have to recheck EventName\n          window.manager().emit_to(\n            EventTarget::labeled(window.label()),\n            DRAG_ENTER_EVENT,\n            EmitPayload::Serialize(&payload),\n          )?\n        } else {\n          window.emit_to_window(DRAG_ENTER_EVENT, &payload)?\n        }\n      }\n      DragDropEvent::Over { position } => {\n        let payload = DragDropPayload {\n          position,\n          paths: None,\n        };\n        if window.is_webview_window() {\n          // use underlying manager, otherwise have to recheck EventName\n          window.manager().emit_to(\n            EventTarget::labeled(window.label()),\n            DRAG_OVER_EVENT,\n            EmitPayload::Serialize(&payload),\n          )?\n        } else {\n          window.emit_to_window(DRAG_OVER_EVENT, &payload)?\n        }\n      }\n      DragDropEvent::Drop { paths, position } => {\n        let scopes = window.state::<Scopes>();\n        for path in paths {\n          if path.is_file() {\n            let _ = scopes.allow_file(path);\n          } else {\n            let _ = scopes.allow_directory(path, true);\n          }\n        }\n        let payload = DragDropPayload {\n          paths: Some(paths),\n          position,\n        };\n\n        if window.is_webview_window() {\n          // use underlying manager, otherwise have to recheck EventName\n          window.manager().emit_to(\n            EventTarget::labeled(window.label()),\n            DRAG_DROP_EVENT,\n            EmitPayload::Serialize(&payload),\n          )?\n        } else {\n          window.emit_to_window(DRAG_DROP_EVENT, &payload)?\n        }\n      }\n      DragDropEvent::Leave => {\n        if window.is_webview_window() {\n          // use underlying manager, otherwise have to recheck EventName\n          window.manager().emit_to(\n            EventTarget::labeled(window.label()),\n            DRAG_LEAVE_EVENT,\n            EmitPayload::Serialize(&()),\n          )?\n        } else {\n          window.emit_to_window(DRAG_LEAVE_EVENT, &())?\n        }\n      }\n      _ => unimplemented!(),\n    },\n    WindowEvent::ThemeChanged(theme) => window.emit_to_window(WINDOW_THEME_CHANGED, &theme)?,\n  }\n  Ok(())\n}\n\n#[derive(Clone, Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct ScaleFactorChanged {\n  scale_factor: f64,\n  size: PhysicalSize<u32>,\n}\n"
  },
  {
    "path": "crates/tauri/src/menu/builders/check.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{menu::CheckMenuItem, menu::MenuId, Manager, Runtime};\n\n/// A builder type for [`CheckMenuItem`]\npub struct CheckMenuItemBuilder {\n  id: Option<MenuId>,\n  text: String,\n  enabled: bool,\n  checked: bool,\n  accelerator: Option<String>,\n}\n\nimpl CheckMenuItemBuilder {\n  /// Create a new menu item builder.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn new<S: AsRef<str>>(text: S) -> Self {\n    Self {\n      id: None,\n      text: text.as_ref().to_string(),\n      enabled: true,\n      checked: true,\n      accelerator: None,\n    }\n  }\n\n  /// Create a new menu item builder with the specified id.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn with_id<I: Into<MenuId>, S: AsRef<str>>(id: I, text: S) -> Self {\n    Self {\n      id: Some(id.into()),\n      text: text.as_ref().to_string(),\n      enabled: true,\n      checked: true,\n      accelerator: None,\n    }\n  }\n\n  /// Set the id for this menu item.\n  pub fn id<I: Into<MenuId>>(mut self, id: I) -> Self {\n    self.id.replace(id.into());\n    self\n  }\n\n  /// Set the enabled state for this menu item.\n  pub fn enabled(mut self, enabled: bool) -> Self {\n    self.enabled = enabled;\n    self\n  }\n\n  /// Set the checked state for this menu item.\n  pub fn checked(mut self, checked: bool) -> Self {\n    self.checked = checked;\n    self\n  }\n\n  /// Set the accelerator for this menu item.\n  pub fn accelerator<S: AsRef<str>>(mut self, accelerator: S) -> Self {\n    self.accelerator.replace(accelerator.as_ref().to_string());\n    self\n  }\n\n  /// Build the menu item\n  pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> crate::Result<CheckMenuItem<R>> {\n    if let Some(id) = self.id {\n      CheckMenuItem::with_id(\n        manager,\n        id,\n        self.text,\n        self.enabled,\n        self.checked,\n        self.accelerator,\n      )\n    } else {\n      CheckMenuItem::new(\n        manager,\n        self.text,\n        self.enabled,\n        self.checked,\n        self.accelerator,\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/menu/builders/icon.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  image::Image,\n  menu::{IconMenuItem, MenuId, NativeIcon},\n  Manager, Runtime,\n};\n\n/// A builder type for [`IconMenuItem`]\npub struct IconMenuItemBuilder<'a> {\n  id: Option<MenuId>,\n  text: String,\n  enabled: bool,\n  icon: Option<Image<'a>>,\n  native_icon: Option<NativeIcon>,\n  accelerator: Option<String>,\n}\n\nimpl<'a> IconMenuItemBuilder<'a> {\n  /// Create a new menu item builder.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn new<S: AsRef<str>>(text: S) -> Self {\n    Self {\n      id: None,\n      text: text.as_ref().to_string(),\n      enabled: true,\n      icon: None,\n      native_icon: None,\n      accelerator: None,\n    }\n  }\n\n  /// Create a new menu item builder with the specified id.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn with_id<I: Into<MenuId>, S: AsRef<str>>(id: I, text: S) -> Self {\n    Self {\n      id: Some(id.into()),\n      text: text.as_ref().to_string(),\n      enabled: true,\n      icon: None,\n      native_icon: None,\n      accelerator: None,\n    }\n  }\n\n  /// Set the id for this menu item.\n  pub fn id<I: Into<MenuId>>(mut self, id: I) -> Self {\n    self.id.replace(id.into());\n    self\n  }\n\n  /// Set the enabled state for this menu item.\n  pub fn enabled(mut self, enabled: bool) -> Self {\n    self.enabled = enabled;\n    self\n  }\n\n  /// Set the accelerator for this menu item.\n  pub fn accelerator<S: AsRef<str>>(mut self, accelerator: S) -> Self {\n    self.accelerator.replace(accelerator.as_ref().to_string());\n    self\n  }\n\n  /// Set the icon for this menu item.\n  ///\n  /// **Note:** This method conflicts with [`Self::native_icon`]\n  /// so calling one of them, will reset the other.\n  pub fn icon(mut self, icon: Image<'a>) -> Self {\n    self.icon.replace(icon);\n    self.native_icon = None;\n    self\n  }\n\n  /// Set the icon for this menu item.\n  ///\n  /// **Note:** This method conflicts with [`Self::icon`]\n  /// so calling one of them, will reset the other.\n  pub fn native_icon(mut self, icon: NativeIcon) -> Self {\n    self.native_icon.replace(icon);\n    self.icon = None;\n    self\n  }\n\n  /// Build the menu item\n  pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> crate::Result<IconMenuItem<R>> {\n    if self.icon.is_some() {\n      if let Some(id) = self.id {\n        IconMenuItem::with_id(\n          manager,\n          id,\n          self.text,\n          self.enabled,\n          self.icon,\n          self.accelerator,\n        )\n      } else {\n        IconMenuItem::new(\n          manager,\n          self.text,\n          self.enabled,\n          self.icon,\n          self.accelerator,\n        )\n      }\n    } else if let Some(id) = self.id {\n      IconMenuItem::with_id_and_native_icon(\n        manager,\n        id,\n        self.text,\n        self.enabled,\n        self.native_icon,\n        self.accelerator,\n      )\n    } else {\n      IconMenuItem::with_native_icon(\n        manager,\n        self.text,\n        self.enabled,\n        self.native_icon,\n        self.accelerator,\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/menu/builders/menu.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{image::Image, menu::*, Manager, Runtime};\n\n/// A builder type for [`Menu`]\n///\n/// ## Platform-specific:\n///\n/// - **macOS**: if using [`MenuBuilder`] for the global menubar, it can only contain [`Submenu`]s\n///\n/// # Example\n///\n/// ```no_run\n/// use tauri::menu::*;\n/// tauri::Builder::default()\n///   .setup(move |app| {\n///     let handle = app.handle();\n///     # let icon1 = tauri::image::Image::new(&[], 0, 0);\n///     let menu = MenuBuilder::new(handle)\n///       .item(&MenuItem::new(handle, \"MenuItem 1\", true, None::<&str>)?)\n///       .items(&[\n///         &CheckMenuItem::new(handle, \"CheckMenuItem 1\", true, true, None::<&str>)?,\n///         &IconMenuItem::new(handle, \"IconMenuItem 1\", true, Some(icon1), None::<&str>)?,\n///       ])\n///       .separator()\n///       .cut()\n///       .copy()\n///       .paste()\n///       .separator()\n///       .text(\"item2\", \"MenuItem 2\")\n///       .check(\"checkitem2\", \"CheckMenuItem 2\")\n///       .icon(\"iconitem2\", \"IconMenuItem 2\", app.default_window_icon().cloned().unwrap())\n///       .build()?;\n///     app.set_menu(menu);\n///     Ok(())\n///   });\n/// ```\npub struct MenuBuilder<'m, R: Runtime, M: Manager<R>> {\n  pub(crate) id: Option<MenuId>,\n  pub(crate) manager: &'m M,\n  pub(crate) items: Vec<crate::Result<MenuItemKind<R>>>,\n}\n\nimpl<'m, R: Runtime, M: Manager<R>> MenuBuilder<'m, R, M> {\n  /// Create a new menu builder.\n  pub fn new(manager: &'m M) -> Self {\n    Self {\n      id: None,\n      items: Vec::new(),\n      manager,\n    }\n  }\n\n  /// Create a new menu builder with the specified id.\n  pub fn with_id<I: Into<MenuId>>(manager: &'m M, id: I) -> Self {\n    Self {\n      id: Some(id.into()),\n      items: Vec::new(),\n      manager,\n    }\n  }\n\n  /// Builds this menu\n  pub fn build(self) -> crate::Result<Menu<R>> {\n    let menu = if let Some(id) = self.id {\n      Menu::with_id(self.manager, id)?\n    } else {\n      Menu::new(self.manager)?\n    };\n\n    for item in self.items {\n      let item = item?;\n      menu.append(&item)?;\n    }\n\n    Ok(menu)\n  }\n}\n\n/// A builder type for [`Submenu`]\n///\n/// # Example\n///\n/// ```no_run\n/// use tauri::menu::*;\n/// tauri::Builder::default()\n///   .setup(move |app| {\n///     let handle = app.handle();\n///     # let icon1 = tauri::image::Image::new(&[], 0, 0);\n///     # let icon2 = icon1.clone();\n///     let menu = Menu::new(handle)?;\n///     let submenu = SubmenuBuilder::new(handle, \"File\")\n///       .item(&MenuItem::new(handle, \"MenuItem 1\", true, None::<&str>)?)\n///       .items(&[\n///         &CheckMenuItem::new(handle, \"CheckMenuItem 1\", true, true, None::<&str>)?,\n///         &IconMenuItem::new(handle, \"IconMenuItem 1\", true, Some(icon1), None::<&str>)?,\n///       ])\n///       .separator()\n///       .cut()\n///       .copy()\n///       .paste()\n///       .separator()\n///       .text(\"item2\", \"MenuItem 2\")\n///       .check(\"checkitem2\", \"CheckMenuItem 2\")\n///       .icon(\"iconitem2\", \"IconMenuItem 2\", app.default_window_icon().cloned().unwrap())\n///       .build()?;\n///     menu.append(&submenu)?;\n///     app.set_menu(menu);\n///     Ok(())\n///   });\n/// ```\npub struct SubmenuBuilder<'m, R: Runtime, M: Manager<R>> {\n  pub(crate) id: Option<MenuId>,\n  pub(crate) manager: &'m M,\n  pub(crate) text: String,\n  pub(crate) enabled: bool,\n  pub(crate) items: Vec<crate::Result<MenuItemKind<R>>>,\n  pub(crate) icon: Option<crate::image::Image<'m>>,\n  pub(crate) native_icon: Option<NativeIcon>,\n}\n\nimpl<'m, R: Runtime, M: Manager<R>> SubmenuBuilder<'m, R, M> {\n  /// Create a new submenu builder.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn new<S: AsRef<str>>(manager: &'m M, text: S) -> Self {\n    Self {\n      id: None,\n      items: Vec::new(),\n      text: text.as_ref().to_string(),\n      enabled: true,\n      manager,\n      icon: None,\n      native_icon: None,\n    }\n  }\n\n  /// Create a new submenu builder with the specified id.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn with_id<I: Into<MenuId>, S: AsRef<str>>(manager: &'m M, id: I, text: S) -> Self {\n    Self {\n      id: Some(id.into()),\n      text: text.as_ref().to_string(),\n      enabled: true,\n      items: Vec::new(),\n      manager,\n      icon: None,\n      native_icon: None,\n    }\n  }\n\n  /// Set an icon for the submenu.\n  /// Calling this method resets the native_icon.\n  pub fn submenu_icon(mut self, icon: crate::image::Image<'m>) -> Self {\n    self.icon = Some(icon);\n    self.native_icon = None;\n    self\n  }\n\n  /// Set a native icon for the submenu.\n  /// Calling this method resets the icon.\n  pub fn submenu_native_icon(mut self, icon: NativeIcon) -> Self {\n    self.native_icon = Some(icon);\n    self.icon = None;\n    self\n  }\n\n  /// Set the enabled state for the submenu.\n  pub fn enabled(mut self, enabled: bool) -> Self {\n    self.enabled = enabled;\n    self\n  }\n\n  /// Builds this submenu\n  pub fn build(self) -> crate::Result<Submenu<R>> {\n    let submenu = if let Some(id) = self.id {\n      if let Some(icon) = self.icon {\n        Submenu::with_id_and_icon(self.manager, id, self.text, self.enabled, Some(icon))?\n      } else if let Some(native_icon) = self.native_icon {\n        Submenu::with_id_and_native_icon(\n          self.manager,\n          id,\n          self.text,\n          self.enabled,\n          Some(native_icon),\n        )?\n      } else {\n        Submenu::with_id(self.manager, id, self.text, self.enabled)?\n      }\n    } else if let Some(icon) = self.icon {\n      Submenu::new_with_icon(self.manager, self.text, self.enabled, Some(icon))?\n    } else if let Some(native_icon) = self.native_icon {\n      Submenu::new_with_native_icon(self.manager, self.text, self.enabled, Some(native_icon))?\n    } else {\n      Submenu::new(self.manager, self.text, self.enabled)?\n    };\n\n    for item in self.items {\n      let item = item?;\n      submenu.append(&item)?;\n    }\n\n    Ok(submenu)\n  }\n}\n\nmacro_rules! shared_menu_builder {\n  ($menu: ty) => {\n    impl<'m, R: Runtime, M: Manager<R>> $menu {\n      /// Set the id for this menu.\n      pub fn id<I: Into<MenuId>>(mut self, id: I) -> Self {\n        self.id.replace(id.into());\n        self\n      }\n\n      /// Add this item to the menu.\n      pub fn item(mut self, item: &dyn IsMenuItem<R>) -> Self {\n        self.items.push(Ok(item.kind()));\n        self\n      }\n\n      /// Add these items to the menu.\n      pub fn items(mut self, items: &[&dyn IsMenuItem<R>]) -> Self {\n        for item in items {\n          self = self.item(*item);\n        }\n        self\n      }\n\n      /// Add a [MenuItem] to the menu.\n      pub fn text<I: Into<MenuId>, S: AsRef<str>>(mut self, id: I, text: S) -> Self {\n        self\n          .items\n          .push(MenuItem::with_id(self.manager, id, text, true, None::<&str>).map(|i| i.kind()));\n        self\n      }\n\n      /// Add a [CheckMenuItem] to the menu.\n      pub fn check<I: Into<MenuId>, S: AsRef<str>>(mut self, id: I, text: S) -> Self {\n        self.items.push(\n          CheckMenuItem::with_id(self.manager, id, text, true, true, None::<&str>)\n            .map(|i| i.kind()),\n        );\n        self\n      }\n\n      /// Add an [IconMenuItem] to the menu.\n      pub fn icon<I: Into<MenuId>, S: AsRef<str>>(\n        mut self,\n        id: I,\n        text: S,\n        icon: Image<'_>,\n      ) -> Self {\n        self.items.push(\n          IconMenuItem::with_id(self.manager, id, text, true, Some(icon), None::<&str>)\n            .map(|i| i.kind()),\n        );\n        self\n      }\n\n      /// Add an [IconMenuItem] with a native icon to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Windows / Linux**: Unsupported.\n      pub fn native_icon<I: Into<MenuId>, S: AsRef<str>>(\n        mut self,\n        id: I,\n        text: S,\n        icon: NativeIcon,\n      ) -> Self {\n        self.items.push(\n          IconMenuItem::with_id_and_native_icon(\n            self.manager,\n            id,\n            text,\n            true,\n            Some(icon),\n            None::<&str>,\n          )\n          .map(|i| i.kind()),\n        );\n        self\n      }\n\n      /// Add Separator menu item to the menu.\n      pub fn separator(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::separator(self.manager).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Copy menu item to the menu.\n      pub fn copy(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::copy(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Copy menu item with specified text to the menu.\n      pub fn copy_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::copy(self.manager, Some(text.as_ref())).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Cut menu item to the menu.\n      pub fn cut(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::cut(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Cut menu item with specified text to the menu.\n      pub fn cut_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::cut(self.manager, Some(text.as_ref())).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Paste menu item to the menu.\n      pub fn paste(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::paste(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Paste menu item with specified text to the menu.\n      pub fn paste_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::paste(self.manager, Some(text.as_ref())).map(|i| i.kind()));\n        self\n      }\n\n      /// Add SelectAll menu item to the menu.\n      pub fn select_all(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::select_all(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add SelectAll menu item with specified text to the menu.\n      pub fn select_all_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self.items.push(\n          PredefinedMenuItem::select_all(self.manager, Some(text.as_ref())).map(|i| i.kind()),\n        );\n        self\n      }\n\n      /// Add Undo menu item to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Windows / Linux:** Unsupported.\n      pub fn undo(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::undo(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Undo menu item with specified text to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Windows / Linux:** Unsupported.\n      pub fn undo_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::undo(self.manager, Some(text.as_ref())).map(|i| i.kind()));\n        self\n      }\n      /// Add Redo menu item to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Windows / Linux:** Unsupported.\n      pub fn redo(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::redo(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Redo menu item with specified text to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Windows / Linux:** Unsupported.\n      pub fn redo_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::redo(self.manager, Some(text.as_ref())).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Minimize window menu item to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Linux:** Unsupported.\n      pub fn minimize(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::minimize(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Minimize window menu item with specified text to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Linux:** Unsupported.\n      pub fn minimize_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::minimize(self.manager, Some(text.as_ref())).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Maximize window menu item to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Linux:** Unsupported.\n      pub fn maximize(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::maximize(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Maximize window menu item with specified text to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Linux:** Unsupported.\n      pub fn maximize_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::maximize(self.manager, Some(text.as_ref())).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Fullscreen menu item to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Windows / Linux:** Unsupported.\n      pub fn fullscreen(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::fullscreen(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Fullscreen menu item with specified text to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Windows / Linux:** Unsupported.\n      pub fn fullscreen_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self.items.push(\n          PredefinedMenuItem::fullscreen(self.manager, Some(text.as_ref())).map(|i| i.kind()),\n        );\n        self\n      }\n\n      /// Add Hide window menu item to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Linux:** Unsupported.\n      pub fn hide(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::hide(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Hide window menu item with specified text to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Linux:** Unsupported.\n      pub fn hide_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::hide(self.manager, Some(text.as_ref())).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Hide other windows menu item to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Linux:** Unsupported.\n      pub fn hide_others(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::hide_others(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Hide other windows menu item with specified text to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Linux:** Unsupported.\n      pub fn hide_others_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self.items.push(\n          PredefinedMenuItem::hide_others(self.manager, Some(text.as_ref())).map(|i| i.kind()),\n        );\n        self\n      }\n\n      /// Add Show all app windows menu item to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Windows / Linux:** Unsupported.\n      pub fn show_all(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::show_all(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Show all app windows menu item with specified text to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Windows / Linux:** Unsupported.\n      pub fn show_all_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::show_all(self.manager, Some(text.as_ref())).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Close window menu item to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Linux:** Unsupported.\n      pub fn close_window(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::close_window(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Close window menu item with specified text to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Linux:** Unsupported.\n      pub fn close_window_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self.items.push(\n          PredefinedMenuItem::close_window(self.manager, Some(text.as_ref())).map(|i| i.kind()),\n        );\n        self\n      }\n\n      /// Add Quit app menu item to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Linux:** Unsupported.\n      pub fn quit(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::quit(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Quit app menu item with specified text to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Linux:** Unsupported.\n      pub fn quit_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::quit(self.manager, Some(text.as_ref())).map(|i| i.kind()));\n        self\n      }\n\n      /// Add About app menu item to the menu.\n      pub fn about(mut self, metadata: Option<AboutMetadata<'_>>) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::about(self.manager, None, metadata).map(|i| i.kind()));\n        self\n      }\n\n      /// Add About app menu item with specified text to the menu.\n      pub fn about_with_text<S: AsRef<str>>(\n        mut self,\n        text: S,\n        metadata: Option<AboutMetadata<'_>>,\n      ) -> Self {\n        self.items.push(\n          PredefinedMenuItem::about(self.manager, Some(text.as_ref()), metadata).map(|i| i.kind()),\n        );\n        self\n      }\n\n      /// Add Services menu item to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Windows / Linux:** Unsupported.\n      pub fn services(mut self) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::services(self.manager, None).map(|i| i.kind()));\n        self\n      }\n\n      /// Add Services menu item with specified text to the menu.\n      ///\n      /// ## Platform-specific:\n      ///\n      /// - **Windows / Linux:** Unsupported.\n      pub fn services_with_text<S: AsRef<str>>(mut self, text: S) -> Self {\n        self\n          .items\n          .push(PredefinedMenuItem::services(self.manager, Some(text.as_ref())).map(|i| i.kind()));\n        self\n      }\n    }\n  };\n}\n\nshared_menu_builder!(MenuBuilder<'m, R, M>);\nshared_menu_builder!(SubmenuBuilder<'m, R, M>);\n"
  },
  {
    "path": "crates/tauri/src/menu/builders/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg(desktop)]\n\n//! A module containing menu builder types\n\nmod menu;\npub use menu::MenuBuilder;\npub use menu::SubmenuBuilder;\nmod normal;\npub use normal::MenuItemBuilder;\nmod check;\npub use check::CheckMenuItemBuilder;\nmod icon;\npub use icon::IconMenuItemBuilder;\n"
  },
  {
    "path": "crates/tauri/src/menu/builders/normal.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{menu::MenuId, menu::MenuItem, Manager, Runtime};\n\n/// A builder type for [`MenuItem`]\npub struct MenuItemBuilder {\n  id: Option<MenuId>,\n  text: String,\n  enabled: bool,\n  accelerator: Option<String>,\n}\n\nimpl MenuItemBuilder {\n  /// Create a new menu item builder.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn new<S: AsRef<str>>(text: S) -> Self {\n    Self {\n      id: None,\n      text: text.as_ref().to_string(),\n      enabled: true,\n      accelerator: None,\n    }\n  }\n\n  /// Create a new menu item builder with the specified id.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn with_id<I: Into<MenuId>, S: AsRef<str>>(id: I, text: S) -> Self {\n    Self {\n      id: Some(id.into()),\n      text: text.as_ref().to_string(),\n      enabled: true,\n      accelerator: None,\n    }\n  }\n\n  /// Set the id for this menu item.\n  pub fn id<I: Into<MenuId>>(mut self, id: I) -> Self {\n    self.id.replace(id.into());\n    self\n  }\n\n  /// Set the enabled state for this menu item.\n  pub fn enabled(mut self, enabled: bool) -> Self {\n    self.enabled = enabled;\n    self\n  }\n\n  /// Set the accelerator for this menu item.\n  pub fn accelerator<S: AsRef<str>>(mut self, accelerator: S) -> Self {\n    self.accelerator.replace(accelerator.as_ref().to_string());\n    self\n  }\n\n  /// Build the menu item\n  pub fn build<R: Runtime, M: Manager<R>>(self, manager: &M) -> crate::Result<MenuItem<R>> {\n    if let Some(id) = self.id {\n      MenuItem::with_id(manager, id, self.text, self.enabled, self.accelerator)\n    } else {\n      MenuItem::new(manager, self.text, self.enabled, self.accelerator)\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/menu/check.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::sync::Arc;\n\nuse super::run_item_main_thread;\nuse crate::menu::CheckMenuItemInner;\nuse crate::run_main_thread;\nuse crate::{menu::MenuId, AppHandle, Manager, Runtime};\n\nuse super::CheckMenuItem;\n\nimpl<R: Runtime> CheckMenuItem<R> {\n  /// Create a new menu item.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn new<M, T, A>(\n    manager: &M,\n    text: T,\n    enabled: bool,\n    checked: bool,\n    accelerator: Option<A>,\n  ) -> crate::Result<Self>\n  where\n    M: Manager<R>,\n    T: AsRef<str>,\n    A: AsRef<str>,\n  {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.as_ref().to_owned();\n    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::CheckMenuItem::new(text, enabled, checked, accelerator);\n      CheckMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Create a new menu item with the specified id.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn with_id<M, I, T, A>(\n    manager: &M,\n    id: I,\n    text: T,\n    enabled: bool,\n    checked: bool,\n    accelerator: Option<A>,\n  ) -> crate::Result<Self>\n  where\n    M: Manager<R>,\n    I: Into<MenuId>,\n    T: AsRef<str>,\n    A: AsRef<str>,\n  {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let id = id.into();\n    let text = text.as_ref().to_owned();\n    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::CheckMenuItem::with_id(id.clone(), text, enabled, checked, accelerator);\n      CheckMenuItemInner {\n        id,\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// The application handle associated with this type.\n  pub fn app_handle(&self) -> &AppHandle<R> {\n    &self.0.app_handle\n  }\n\n  /// Returns a unique identifier associated with this menu item.\n  pub fn id(&self) -> &MenuId {\n    &self.0.id\n  }\n\n  /// Get the text for this menu item.\n  pub fn text(&self) -> crate::Result<String> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().text())\n  }\n\n  /// Set the text for this menu item. `text` could optionally contain\n  /// an `&` before a character to assign this character as the mnemonic\n  /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {\n    let text = text.as_ref().to_string();\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_text(text))\n  }\n\n  /// Get whether this menu item is enabled or not.\n  pub fn is_enabled(&self) -> crate::Result<bool> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().is_enabled())\n  }\n\n  /// Enable or disable this menu item.\n  pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_enabled(enabled))\n  }\n\n  /// Set this menu item accelerator.\n  pub fn set_accelerator<S: AsRef<str>>(&self, accelerator: Option<S>) -> crate::Result<()> {\n    let accel = accelerator.and_then(|s| s.as_ref().parse().ok());\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().set_accelerator(accel)\n    })?\n    .map_err(Into::into)\n  }\n\n  /// Get whether this check menu item is checked or not.\n  pub fn is_checked(&self) -> crate::Result<bool> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().is_checked())\n  }\n\n  /// Check or Uncheck this check menu item.\n  pub fn set_checked(&self, checked: bool) -> crate::Result<()> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_checked(checked))\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/menu/icon.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::sync::Arc;\n\nuse super::run_item_main_thread;\nuse super::{IconMenuItem, NativeIcon};\nuse crate::menu::IconMenuItemInner;\nuse crate::run_main_thread;\nuse crate::{image::Image, menu::MenuId, AppHandle, Manager, Runtime};\n\nimpl<R: Runtime> IconMenuItem<R> {\n  /// Create a new menu item.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn new<M, T, A>(\n    manager: &M,\n    text: T,\n    enabled: bool,\n    icon: Option<Image<'_>>,\n    accelerator: Option<A>,\n  ) -> crate::Result<Self>\n  where\n    M: Manager<R>,\n    T: AsRef<str>,\n    A: AsRef<str>,\n  {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.as_ref().to_owned();\n    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());\n    let icon = match icon {\n      Some(i) => Some(i.try_into()?),\n      None => None,\n    };\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::IconMenuItem::new(text, enabled, icon, accelerator);\n      IconMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Create a new menu item with the specified id.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn with_id<M, I, T, A>(\n    manager: &M,\n    id: I,\n    text: T,\n    enabled: bool,\n    icon: Option<Image<'_>>,\n    accelerator: Option<A>,\n  ) -> crate::Result<Self>\n  where\n    M: Manager<R>,\n    I: Into<MenuId>,\n    T: AsRef<str>,\n    A: AsRef<str>,\n  {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let id = id.into();\n    let text = text.as_ref().to_owned();\n    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());\n    let icon = match icon {\n      Some(i) => Some(i.try_into()?),\n      None => None,\n    };\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::IconMenuItem::with_id(id.clone(), text, enabled, icon, accelerator);\n      IconMenuItemInner {\n        id,\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Create a new icon menu item but with a native icon.\n  ///\n  /// See [`IconMenuItem::new`] for more info.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows / Linux**: Unsupported.\n  pub fn with_native_icon<M, T, A>(\n    manager: &M,\n    text: T,\n    enabled: bool,\n    native_icon: Option<NativeIcon>,\n    accelerator: Option<A>,\n  ) -> crate::Result<Self>\n  where\n    M: Manager<R>,\n    T: AsRef<str>,\n    A: AsRef<str>,\n  {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.as_ref().to_owned();\n    let icon = native_icon.map(Into::into);\n    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::IconMenuItem::with_native_icon(text, enabled, icon, accelerator);\n      IconMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Create a new icon menu item with the specified id but with a native icon.\n  ///\n  /// See [`IconMenuItem::new`] for more info.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows / Linux**: Unsupported.\n  pub fn with_id_and_native_icon<M, I, T, A>(\n    manager: &M,\n    id: I,\n    text: T,\n    enabled: bool,\n    native_icon: Option<NativeIcon>,\n    accelerator: Option<A>,\n  ) -> crate::Result<Self>\n  where\n    M: Manager<R>,\n    I: Into<MenuId>,\n    T: AsRef<str>,\n    A: AsRef<str>,\n  {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let id = id.into();\n    let text = text.as_ref().to_owned();\n    let icon = native_icon.map(Into::into);\n    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());\n\n    let item = run_main_thread!(handle, || {\n      let item =\n        muda::IconMenuItem::with_id_and_native_icon(id.clone(), text, enabled, icon, accelerator);\n      IconMenuItemInner {\n        id,\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// The application handle associated with this type.\n  pub fn app_handle(&self) -> &AppHandle<R> {\n    &self.0.app_handle\n  }\n\n  /// Returns a unique identifier associated with this menu item.\n  pub fn id(&self) -> &MenuId {\n    &self.0.id\n  }\n\n  /// Get the text for this menu item.\n  pub fn text(&self) -> crate::Result<String> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().text())\n  }\n\n  /// Set the text for this menu item. `text` could optionally contain\n  /// an `&` before a character to assign this character as the mnemonic\n  /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {\n    let text = text.as_ref().to_string();\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_text(text))\n  }\n\n  /// Get whether this menu item is enabled or not.\n  pub fn is_enabled(&self) -> crate::Result<bool> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().is_enabled())\n  }\n\n  /// Enable or disable this menu item.\n  pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_enabled(enabled))\n  }\n\n  /// Set this menu item accelerator.\n  pub fn set_accelerator<S: AsRef<str>>(&self, accelerator: Option<S>) -> crate::Result<()> {\n    let accel = accelerator.and_then(|s| s.as_ref().parse().ok());\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().set_accelerator(accel)\n    })?\n    .map_err(Into::into)\n  }\n\n  /// Change this menu item icon or remove it.\n  pub fn set_icon(&self, icon: Option<Image<'_>>) -> crate::Result<()> {\n    let icon = match icon {\n      Some(i) => Some(i.try_into()?),\n      None => None,\n    };\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_icon(icon))\n  }\n\n  /// Change this menu item icon to a native image or remove it.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows / Linux**: Unsupported.\n  pub fn set_native_icon(&self, _icon: Option<NativeIcon>) -> crate::Result<()> {\n    #[cfg(target_os = \"macos\")]\n    return run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().set_native_icon(_icon.map(Into::into))\n    });\n    #[allow(unreachable_code)]\n    Ok(())\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/menu/menu.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::sync::Arc;\n\nuse super::run_item_main_thread;\nuse super::sealed::ContextMenuBase;\nuse super::{\n  AboutMetadata, IsMenuItem, Menu, MenuInner, MenuItemKind, PredefinedMenuItem, Submenu,\n};\nuse crate::run_main_thread;\nuse crate::Window;\nuse crate::{AppHandle, Manager, Position, Runtime};\nuse muda::ContextMenu;\nuse muda::MenuId;\n\n/// Expected submenu id of the Window menu for macOS.\npub const WINDOW_SUBMENU_ID: &str = \"__tauri_window_menu__\";\n/// Expected submenu id of the Help menu for macOS.\npub const HELP_SUBMENU_ID: &str = \"__tauri_help_menu__\";\n\nimpl<R: Runtime> super::ContextMenu for Menu<R> {\n  #[cfg(target_os = \"windows\")]\n  fn hpopupmenu(&self) -> crate::Result<isize> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().hpopupmenu())\n  }\n\n  fn popup<T: Runtime>(&self, window: Window<T>) -> crate::Result<()> {\n    self.popup_inner(window, None::<Position>)\n  }\n\n  fn popup_at<T: Runtime, P: Into<Position>>(\n    &self,\n    window: Window<T>,\n    position: P,\n  ) -> crate::Result<()> {\n    self.popup_inner(window, Some(position))\n  }\n}\n\nimpl<R: Runtime> ContextMenuBase for Menu<R> {\n  fn popup_inner<T: Runtime, P: Into<crate::Position>>(\n    &self,\n    window: crate::Window<T>,\n    position: Option<P>,\n  ) -> crate::Result<()> {\n    let position = position.map(Into::into);\n    run_item_main_thread!(self, move |self_: Self| {\n      #[cfg(target_os = \"macos\")]\n      if let Ok(view) = window.ns_view() {\n        unsafe {\n          self_\n            .inner()\n            .show_context_menu_for_nsview(view as _, position);\n        }\n      }\n\n      #[cfg(any(\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n      ))]\n      if let Ok(w) = window.gtk_window() {\n        self_\n          .inner()\n          .show_context_menu_for_gtk_window(w.as_ref(), position);\n      }\n\n      #[cfg(windows)]\n      if let Ok(hwnd) = window.hwnd() {\n        unsafe {\n          self_\n            .inner()\n            .show_context_menu_for_hwnd(hwnd.0 as _, position);\n        }\n      }\n    })\n  }\n  fn inner_context(&self) -> &dyn muda::ContextMenu {\n    (*self.0).as_ref()\n  }\n\n  fn inner_context_owned(&self) -> Box<dyn muda::ContextMenu> {\n    Box::new((*self.0).as_ref().clone())\n  }\n}\n\nimpl<R: Runtime> Menu<R> {\n  /// Creates a new menu.\n  pub fn new<M: Manager<R>>(manager: &M) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let menu = run_main_thread!(handle, || {\n      let menu = muda::Menu::new();\n      MenuInner {\n        id: menu.id().clone(),\n        inner: Some(menu),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(menu)))\n  }\n\n  /// Creates a new menu with the specified id.\n  pub fn with_id<M: Manager<R>, I: Into<MenuId>>(manager: &M, id: I) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let id = id.into();\n    let menu = run_main_thread!(handle, || {\n      let menu = muda::Menu::with_id(id.clone());\n      MenuInner {\n        id,\n        inner: Some(menu),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(menu)))\n  }\n\n  /// Creates a new menu with given `items`. It calls [`Menu::new`] and [`Menu::append_items`] internally.\n  pub fn with_items<M: Manager<R>>(\n    manager: &M,\n    items: &[&dyn IsMenuItem<R>],\n  ) -> crate::Result<Self> {\n    let menu = Self::new(manager)?;\n    menu.append_items(items)?;\n    Ok(menu)\n  }\n\n  /// Creates a new menu with the specified id and given `items`.\n  /// It calls [`Menu::new`] and [`Menu::append_items`] internally.\n  pub fn with_id_and_items<M: Manager<R>, I: Into<MenuId>>(\n    manager: &M,\n    id: I,\n    items: &[&dyn IsMenuItem<R>],\n  ) -> crate::Result<Self> {\n    let menu = Self::with_id(manager, id)?;\n    menu.append_items(items)?;\n    Ok(menu)\n  }\n\n  /// Creates a menu filled with default menu items and submenus.\n  pub fn default(app_handle: &AppHandle<R>) -> crate::Result<Self> {\n    let pkg_info = app_handle.package_info();\n    let config = app_handle.config();\n    let about_metadata = AboutMetadata {\n      name: Some(pkg_info.name.clone()),\n      version: Some(pkg_info.version.to_string()),\n      copyright: config.bundle.copyright.clone(),\n      authors: config.bundle.publisher.clone().map(|p| vec![p]),\n      ..Default::default()\n    };\n\n    let window_menu = Submenu::with_id_and_items(\n      app_handle,\n      WINDOW_SUBMENU_ID,\n      \"Window\",\n      true,\n      &[\n        &PredefinedMenuItem::minimize(app_handle, None)?,\n        &PredefinedMenuItem::maximize(app_handle, None)?,\n        #[cfg(target_os = \"macos\")]\n        &PredefinedMenuItem::separator(app_handle)?,\n        &PredefinedMenuItem::close_window(app_handle, None)?,\n      ],\n    )?;\n\n    let help_menu = Submenu::with_id_and_items(\n      app_handle,\n      HELP_SUBMENU_ID,\n      \"Help\",\n      true,\n      &[\n        #[cfg(not(target_os = \"macos\"))]\n        &PredefinedMenuItem::about(app_handle, None, Some(about_metadata))?,\n      ],\n    )?;\n\n    let menu = Menu::with_items(\n      app_handle,\n      &[\n        #[cfg(target_os = \"macos\")]\n        &Submenu::with_items(\n          app_handle,\n          pkg_info.name.clone(),\n          true,\n          &[\n            &PredefinedMenuItem::about(app_handle, None, Some(about_metadata))?,\n            &PredefinedMenuItem::separator(app_handle)?,\n            &PredefinedMenuItem::services(app_handle, None)?,\n            &PredefinedMenuItem::separator(app_handle)?,\n            &PredefinedMenuItem::hide(app_handle, None)?,\n            &PredefinedMenuItem::hide_others(app_handle, None)?,\n            &PredefinedMenuItem::separator(app_handle)?,\n            &PredefinedMenuItem::quit(app_handle, None)?,\n          ],\n        )?,\n        #[cfg(not(any(\n          target_os = \"linux\",\n          target_os = \"dragonfly\",\n          target_os = \"freebsd\",\n          target_os = \"netbsd\",\n          target_os = \"openbsd\"\n        )))]\n        &Submenu::with_items(\n          app_handle,\n          \"File\",\n          true,\n          &[\n            &PredefinedMenuItem::close_window(app_handle, None)?,\n            #[cfg(not(target_os = \"macos\"))]\n            &PredefinedMenuItem::quit(app_handle, None)?,\n          ],\n        )?,\n        &Submenu::with_items(\n          app_handle,\n          \"Edit\",\n          true,\n          &[\n            &PredefinedMenuItem::undo(app_handle, None)?,\n            &PredefinedMenuItem::redo(app_handle, None)?,\n            &PredefinedMenuItem::separator(app_handle)?,\n            &PredefinedMenuItem::cut(app_handle, None)?,\n            &PredefinedMenuItem::copy(app_handle, None)?,\n            &PredefinedMenuItem::paste(app_handle, None)?,\n            &PredefinedMenuItem::select_all(app_handle, None)?,\n          ],\n        )?,\n        #[cfg(target_os = \"macos\")]\n        &Submenu::with_items(\n          app_handle,\n          \"View\",\n          true,\n          &[&PredefinedMenuItem::fullscreen(app_handle, None)?],\n        )?,\n        &window_menu,\n        &help_menu,\n      ],\n    )?;\n\n    Ok(menu)\n  }\n\n  pub(crate) fn inner(&self) -> &muda::Menu {\n    (*self.0).as_ref()\n  }\n\n  /// The application handle associated with this type.\n  pub fn app_handle(&self) -> &AppHandle<R> {\n    &self.0.app_handle\n  }\n\n  /// Returns a unique identifier associated with this menu.\n  pub fn id(&self) -> &MenuId {\n    &self.0.id\n  }\n\n  /// Add a menu item to the end of this menu.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS:** Only [`Submenu`] can be added to the menu.\n  ///\n  /// [`Submenu`]: super::Submenu\n  pub fn append(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {\n    let kind = item.kind();\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().append(kind.inner().inner_muda())\n    })?\n    .map_err(Into::into)\n  }\n\n  /// Add menu items to the end of this menu. It calls [`Menu::append`] in a loop internally.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS:** Only [`Submenu`] can be added to the menu\n  ///\n  /// [`Submenu`]: super::Submenu\n  pub fn append_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {\n    for item in items {\n      self.append(*item)?\n    }\n\n    Ok(())\n  }\n\n  /// Add a menu item to the beginning of this menu.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS:** Only [`Submenu`] can be added to the menu\n  ///\n  /// [`Submenu`]: super::Submenu\n  pub fn prepend(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {\n    let kind = item.kind();\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().prepend(kind.inner().inner_muda())\n    })?\n    .map_err(Into::into)\n  }\n\n  /// Add menu items to the beginning of this menu. It calls [`Menu::insert_items`] with position of `0` internally.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS:** Only [`Submenu`] can be added to the menu\n  ///\n  /// [`Submenu`]: super::Submenu\n  pub fn prepend_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {\n    self.insert_items(items, 0)\n  }\n\n  /// Insert a menu item at the specified `position` in the menu.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS:** Only [`Submenu`] can be added to the menu\n  ///\n  /// [`Submenu`]: super::Submenu\n  pub fn insert(&self, item: &dyn IsMenuItem<R>, position: usize) -> crate::Result<()> {\n    let kind = item.kind();\n    run_item_main_thread!(self, |self_: Self| (*self_.0)\n      .as_ref()\n      .insert(kind.inner().inner_muda(), position))?\n    .map_err(Into::into)\n  }\n\n  /// Insert menu items at the specified `position` in the menu.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS:** Only [`Submenu`] can be added to the menu\n  ///\n  /// [`Submenu`]: super::Submenu\n  pub fn insert_items(&self, items: &[&dyn IsMenuItem<R>], position: usize) -> crate::Result<()> {\n    for (i, item) in items.iter().enumerate() {\n      self.insert(*item, position + i)?\n    }\n\n    Ok(())\n  }\n\n  /// Remove a menu item from this menu.\n  pub fn remove(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {\n    let kind = item.kind();\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().remove(kind.inner().inner_muda())\n    })?\n    .map_err(Into::into)\n  }\n\n  /// Remove the menu item at the specified position from this menu and returns it.\n  pub fn remove_at(&self, position: usize) -> crate::Result<Option<MenuItemKind<R>>> {\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0)\n        .as_ref()\n        .remove_at(position)\n        .map(|i| MenuItemKind::from_muda(self_.0.app_handle.clone(), i))\n    })\n  }\n\n  /// Retrieves the menu item matching the given identifier.\n  pub fn get<'a, I>(&self, id: &'a I) -> Option<MenuItemKind<R>>\n  where\n    I: ?Sized,\n    MenuId: PartialEq<&'a I>,\n  {\n    self\n      .items()\n      .unwrap_or_default()\n      .into_iter()\n      .find(|i| i.id() == &id)\n  }\n\n  /// Returns a list of menu items that has been added to this menu.\n  pub fn items(&self) -> crate::Result<Vec<MenuItemKind<R>>> {\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0)\n        .as_ref()\n        .items()\n        .into_iter()\n        .map(|i| MenuItemKind::from_muda(self_.0.app_handle.clone(), i))\n        .collect::<Vec<_>>()\n    })\n  }\n\n  /// Set this menu as the application menu.\n  ///\n  /// This is an alias for [`AppHandle::set_menu`].\n  pub fn set_as_app_menu(&self) -> crate::Result<Option<Menu<R>>> {\n    self.0.app_handle.set_menu(self.clone())\n  }\n\n  /// Set this menu as the window menu.\n  ///\n  /// This is an alias for [`Window::set_menu`].\n  pub fn set_as_window_menu(&self, window: &Window<R>) -> crate::Result<Option<Menu<R>>> {\n    window.set_menu(self.clone())\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/menu/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Menu types and utilities.\n\nmod builders;\nmod check;\nmod icon;\n#[allow(clippy::module_inception)]\nmod menu;\nmod normal;\npub(crate) mod plugin;\nmod predefined;\nmod submenu;\nuse std::sync::Arc;\n\npub use builders::*;\npub use menu::{HELP_SUBMENU_ID, WINDOW_SUBMENU_ID};\nuse serde::{Deserialize, Serialize};\n\nuse crate::{image::Image, AppHandle, Runtime};\npub use muda::MenuId;\n\nmacro_rules! run_item_main_thread {\n  ($self:ident, $ex:expr) => {{\n    use std::sync::mpsc::channel;\n    let (tx, rx) = channel();\n    let self_ = $self.clone();\n    let task = move || {\n      let f = $ex;\n      let _ = tx.send(f(self_));\n    };\n    $self\n      .app_handle()\n      .run_on_main_thread(task)\n      .and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))\n  }};\n}\n\npub(crate) use run_item_main_thread;\n\n/// Describes a menu event emitted when a menu item is activated\n#[derive(Debug, Clone, Serialize)]\npub struct MenuEvent {\n  /// Id of the menu item which triggered this event\n  pub id: MenuId,\n}\n\nimpl MenuEvent {\n  /// Returns the id of the menu item which triggered this event\n  pub fn id(&self) -> &MenuId {\n    &self.id\n  }\n}\n\nimpl From<muda::MenuEvent> for MenuEvent {\n  fn from(value: muda::MenuEvent) -> Self {\n    Self { id: value.id }\n  }\n}\n\nmacro_rules! gen_wrappers {\n  (\n    $(\n      $(#[$attr:meta])*\n      $type:ident($inner:ident$(, $kind:ident)?)\n    ),*\n  ) => {\n    $(\n      #[tauri_macros::default_runtime(crate::Wry, wry)]\n      pub(crate) struct $inner<R: $crate::Runtime> {\n        id: $crate::menu::MenuId,\n        inner: ::std::option::Option<::muda::$type>,\n        app_handle: $crate::AppHandle<R>,\n      }\n\n      /// # Safety\n      ///\n      /// We make sure it always runs on the main thread.\n      unsafe impl<R: $crate::Runtime> Sync for $inner<R> {}\n      unsafe impl<R: $crate::Runtime> Send for $inner<R> {}\n\n      impl<R: Runtime> $crate::Resource for $type<R> {}\n\n      impl<R: $crate::Runtime> Clone for $inner<R> {\n        fn clone(&self) -> Self {\n          Self {\n            id: self.id.clone(),\n            inner: self.inner.clone(),\n            app_handle: self.app_handle.clone(),\n          }\n        }\n      }\n\n      impl<R: Runtime> Drop for $inner<R> {\n        fn drop(&mut self) {\n          let inner = self.inner.take();\n          // SAFETY: inner was created on main thread and is being dropped on main thread\n          let inner = $crate::UnsafeSend(inner);\n          let _ = self.app_handle.run_on_main_thread(move || {\n            drop(inner.take());\n          });\n        }\n      }\n\n      impl<R: Runtime> AsRef<::muda::$type> for $inner<R> {\n        fn as_ref(&self) -> &::muda::$type {\n          self.inner.as_ref().unwrap()\n        }\n      }\n\n\n      $(#[$attr])*\n      pub struct $type<R: $crate::Runtime>(::std::sync::Arc<$inner<R>>);\n\n      impl<R: $crate::Runtime> Clone for $type<R> {\n        fn clone(&self) -> Self {\n          Self(self.0.clone())\n        }\n      }\n\n      $(\n        impl<R: $crate::Runtime> $crate::menu::sealed::IsMenuItemBase for $type<R> {\n          fn inner_muda(&self) -> &dyn muda::IsMenuItem {\n            (*self.0).as_ref()\n          }\n        }\n\n        impl<R: $crate::Runtime> $crate::menu::IsMenuItem<R> for $type<R> {\n          fn kind(&self) -> MenuItemKind<R> {\n            MenuItemKind::$kind(self.clone())\n          }\n\n          fn id(&self) -> &MenuId {\n            &self.0.id\n          }\n        }\n      )*\n    )*\n  };\n}\n\ngen_wrappers!(\n  /// A type that is either a menu bar on the window\n  /// on Windows and Linux or as a global menu in the menubar on macOS.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS**: if using [`Menu`] for the global menubar, it can only contain [`Submenu`]s\n  Menu(MenuInner),\n  /// A menu item inside a [`Menu`] or [`Submenu`] and contains only text.\n  MenuItem(MenuItemInner, MenuItem),\n  /// A type that is a submenu inside a [`Menu`] or [`Submenu`]\n  Submenu(SubmenuInner, Submenu),\n  /// A predefined (native) menu item which has a predefined behavior by the OS or by this crate.\n  PredefinedMenuItem(PredefinedMenuItemInner, Predefined),\n  /// A menu item inside a [`Menu`] or [`Submenu`]\n  /// and usually contains a text and a check mark or a similar toggle\n  /// that corresponds to a checked and unchecked states.\n  CheckMenuItem(CheckMenuItemInner, Check),\n  /// A menu item inside a [`Menu`] or [`Submenu`]\n  /// and usually contains an icon and a text.\n  IconMenuItem(IconMenuItemInner, Icon)\n);\n\n/// Application metadata for the [`PredefinedMenuItem::about`].\n#[derive(Debug, Clone, Default)]\npub struct AboutMetadata<'a> {\n  /// Sets the application name.\n  pub name: Option<String>,\n  /// The application version.\n  pub version: Option<String>,\n  /// The short version, e.g. \"1.0\".\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows / Linux:** Appended to the end of `version` in parentheses.\n  pub short_version: Option<String>,\n  /// The authors of the application.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Unsupported.\n  pub authors: Option<Vec<String>>,\n  /// Application comments.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Unsupported.\n  pub comments: Option<String>,\n  /// The copyright of the application.\n  pub copyright: Option<String>,\n  /// The license of the application.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Unsupported.\n  pub license: Option<String>,\n  /// The application website.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Unsupported.\n  pub website: Option<String>,\n  /// The website label.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Unsupported.\n  pub website_label: Option<String>,\n  /// The credits.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows / Linux:** Unsupported.\n  pub credits: Option<String>,\n  /// The application icon.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:** Unsupported.\n  pub icon: Option<Image<'a>>,\n}\n\n/// A builder type for [`AboutMetadata`].\n#[derive(Clone, Debug, Default)]\npub struct AboutMetadataBuilder<'a>(AboutMetadata<'a>);\n\nimpl<'a> AboutMetadataBuilder<'a> {\n  /// Create a new about metadata builder.\n  pub fn new() -> Self {\n    Default::default()\n  }\n\n  /// Sets the application name.\n  pub fn name<S: Into<String>>(mut self, name: Option<S>) -> Self {\n    self.0.name = name.map(|s| s.into());\n    self\n  }\n  /// Sets the application version.\n  pub fn version<S: Into<String>>(mut self, version: Option<S>) -> Self {\n    self.0.version = version.map(|s| s.into());\n    self\n  }\n  /// Sets the short version, e.g. \"1.0\".\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows / Linux:** Appended to the end of `version` in parentheses.\n  pub fn short_version<S: Into<String>>(mut self, short_version: Option<S>) -> Self {\n    self.0.short_version = short_version.map(|s| s.into());\n    self\n  }\n  /// Sets the authors of the application.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Unsupported.\n  pub fn authors(mut self, authors: Option<Vec<String>>) -> Self {\n    self.0.authors = authors;\n    self\n  }\n  /// Application comments.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Unsupported.\n  pub fn comments<S: Into<String>>(mut self, comments: Option<S>) -> Self {\n    self.0.comments = comments.map(|s| s.into());\n    self\n  }\n  /// Sets the copyright of the application.\n  pub fn copyright<S: Into<String>>(mut self, copyright: Option<S>) -> Self {\n    self.0.copyright = copyright.map(|s| s.into());\n    self\n  }\n  /// Sets the license of the application.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Unsupported.\n  pub fn license<S: Into<String>>(mut self, license: Option<S>) -> Self {\n    self.0.license = license.map(|s| s.into());\n    self\n  }\n  /// Sets the application website.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Unsupported.\n  pub fn website<S: Into<String>>(mut self, website: Option<S>) -> Self {\n    self.0.website = website.map(|s| s.into());\n    self\n  }\n  /// Sets the website label.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Unsupported.\n  pub fn website_label<S: Into<String>>(mut self, website_label: Option<S>) -> Self {\n    self.0.website_label = website_label.map(|s| s.into());\n    self\n  }\n  /// Sets the credits.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows / Linux:** Unsupported.\n  pub fn credits<S: Into<String>>(mut self, credits: Option<S>) -> Self {\n    self.0.credits = credits.map(|s| s.into());\n    self\n  }\n  /// Sets the application icon.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:** Unsupported.\n  pub fn icon(mut self, icon: Option<Image<'a>>) -> Self {\n    self.0.icon = icon;\n    self\n  }\n\n  /// Construct the final [`AboutMetadata`]\n  pub fn build(self) -> AboutMetadata<'a> {\n    self.0\n  }\n}\n\nimpl TryFrom<AboutMetadata<'_>> for muda::AboutMetadata {\n  type Error = crate::Error;\n\n  fn try_from(value: AboutMetadata<'_>) -> Result<Self, Self::Error> {\n    let icon = match value.icon {\n      Some(i) => Some(i.try_into()?),\n      None => None,\n    };\n\n    Ok(Self {\n      authors: value.authors,\n      name: value.name,\n      version: value.version,\n      short_version: value.short_version,\n      comments: value.comments,\n      copyright: value.copyright,\n      license: value.license,\n      website: value.website,\n      website_label: value.website_label,\n      credits: value.credits,\n      icon,\n    })\n  }\n}\n\n/// A native Icon to be used for the menu item\n///\n/// ## Platform-specific:\n///\n/// - **Windows / Linux**: Unsupported.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]\npub enum NativeIcon {\n  /// An add item template image.\n  Add,\n  /// Advanced preferences toolbar icon for the preferences window.\n  Advanced,\n  /// A Bluetooth template image.\n  Bluetooth,\n  /// Bookmarks image suitable for a template.\n  Bookmarks,\n  /// A caution image.\n  Caution,\n  /// A color panel toolbar icon.\n  ColorPanel,\n  /// A column view mode template image.\n  ColumnView,\n  /// A computer icon.\n  Computer,\n  /// An enter full-screen mode template image.\n  EnterFullScreen,\n  /// Permissions for all users.\n  Everyone,\n  /// An exit full-screen mode template image.\n  ExitFullScreen,\n  /// A cover flow view mode template image.\n  FlowView,\n  /// A folder image.\n  Folder,\n  /// A burnable folder icon.\n  FolderBurnable,\n  /// A smart folder icon.\n  FolderSmart,\n  /// A link template image.\n  FollowLinkFreestanding,\n  /// A font panel toolbar icon.\n  FontPanel,\n  /// A `go back` template image.\n  GoLeft,\n  /// A `go forward` template image.\n  GoRight,\n  /// Home image suitable for a template.\n  Home,\n  /// An iChat Theater template image.\n  IChatTheater,\n  /// An icon view mode template image.\n  IconView,\n  /// An information toolbar icon.\n  Info,\n  /// A template image used to denote invalid data.\n  InvalidDataFreestanding,\n  /// A generic left-facing triangle template image.\n  LeftFacingTriangle,\n  /// A list view mode template image.\n  ListView,\n  /// A locked padlock template image.\n  LockLocked,\n  /// An unlocked padlock template image.\n  LockUnlocked,\n  /// A horizontal dash, for use in menus.\n  MenuMixedState,\n  /// A check mark template image, for use in menus.\n  MenuOnState,\n  /// A MobileMe icon.\n  MobileMe,\n  /// A drag image for multiple items.\n  MultipleDocuments,\n  /// A network icon.\n  Network,\n  /// A path button template image.\n  Path,\n  /// General preferences toolbar icon for the preferences window.\n  PreferencesGeneral,\n  /// A Quick Look template image.\n  QuickLook,\n  /// A refresh template image.\n  RefreshFreestanding,\n  /// A refresh template image.\n  Refresh,\n  /// A remove item template image.\n  Remove,\n  /// A reveal contents template image.\n  RevealFreestanding,\n  /// A generic right-facing triangle template image.\n  RightFacingTriangle,\n  /// A share view template image.\n  Share,\n  /// A slideshow template image.\n  Slideshow,\n  /// A badge for a `smart` item.\n  SmartBadge,\n  /// Small green indicator, similar to iChat's available image.\n  StatusAvailable,\n  /// Small clear indicator.\n  StatusNone,\n  /// Small yellow indicator, similar to iChat's idle image.\n  StatusPartiallyAvailable,\n  /// Small red indicator, similar to iChat's unavailable image.\n  StatusUnavailable,\n  /// A stop progress template image.\n  StopProgressFreestanding,\n  /// A stop progress button template image.\n  StopProgress,\n  /// An image of the empty trash can.\n  TrashEmpty,\n  /// An image of the full trash can.\n  TrashFull,\n  /// Permissions for a single user.\n  User,\n  /// User account toolbar icon for the preferences window.\n  UserAccounts,\n  /// Permissions for a group of users.\n  UserGroup,\n  /// Permissions for guests.\n  UserGuest,\n}\n\nimpl From<NativeIcon> for muda::NativeIcon {\n  fn from(value: NativeIcon) -> Self {\n    match value {\n      NativeIcon::Add => muda::NativeIcon::Add,\n      NativeIcon::Advanced => muda::NativeIcon::Advanced,\n      NativeIcon::Bluetooth => muda::NativeIcon::Bluetooth,\n      NativeIcon::Bookmarks => muda::NativeIcon::Bookmarks,\n      NativeIcon::Caution => muda::NativeIcon::Caution,\n      NativeIcon::ColorPanel => muda::NativeIcon::ColorPanel,\n      NativeIcon::ColumnView => muda::NativeIcon::ColumnView,\n      NativeIcon::Computer => muda::NativeIcon::Computer,\n      NativeIcon::EnterFullScreen => muda::NativeIcon::EnterFullScreen,\n      NativeIcon::Everyone => muda::NativeIcon::Everyone,\n      NativeIcon::ExitFullScreen => muda::NativeIcon::ExitFullScreen,\n      NativeIcon::FlowView => muda::NativeIcon::FlowView,\n      NativeIcon::Folder => muda::NativeIcon::Folder,\n      NativeIcon::FolderBurnable => muda::NativeIcon::FolderBurnable,\n      NativeIcon::FolderSmart => muda::NativeIcon::FolderSmart,\n      NativeIcon::FollowLinkFreestanding => muda::NativeIcon::FollowLinkFreestanding,\n      NativeIcon::FontPanel => muda::NativeIcon::FontPanel,\n      NativeIcon::GoLeft => muda::NativeIcon::GoLeft,\n      NativeIcon::GoRight => muda::NativeIcon::GoRight,\n      NativeIcon::Home => muda::NativeIcon::Home,\n      NativeIcon::IChatTheater => muda::NativeIcon::IChatTheater,\n      NativeIcon::IconView => muda::NativeIcon::IconView,\n      NativeIcon::Info => muda::NativeIcon::Info,\n      NativeIcon::InvalidDataFreestanding => muda::NativeIcon::InvalidDataFreestanding,\n      NativeIcon::LeftFacingTriangle => muda::NativeIcon::LeftFacingTriangle,\n      NativeIcon::ListView => muda::NativeIcon::ListView,\n      NativeIcon::LockLocked => muda::NativeIcon::LockLocked,\n      NativeIcon::LockUnlocked => muda::NativeIcon::LockUnlocked,\n      NativeIcon::MenuMixedState => muda::NativeIcon::MenuMixedState,\n      NativeIcon::MenuOnState => muda::NativeIcon::MenuOnState,\n      NativeIcon::MobileMe => muda::NativeIcon::MobileMe,\n      NativeIcon::MultipleDocuments => muda::NativeIcon::MultipleDocuments,\n      NativeIcon::Network => muda::NativeIcon::Network,\n      NativeIcon::Path => muda::NativeIcon::Path,\n      NativeIcon::PreferencesGeneral => muda::NativeIcon::PreferencesGeneral,\n      NativeIcon::QuickLook => muda::NativeIcon::QuickLook,\n      NativeIcon::RefreshFreestanding => muda::NativeIcon::RefreshFreestanding,\n      NativeIcon::Refresh => muda::NativeIcon::Refresh,\n      NativeIcon::Remove => muda::NativeIcon::Remove,\n      NativeIcon::RevealFreestanding => muda::NativeIcon::RevealFreestanding,\n      NativeIcon::RightFacingTriangle => muda::NativeIcon::RightFacingTriangle,\n      NativeIcon::Share => muda::NativeIcon::Share,\n      NativeIcon::Slideshow => muda::NativeIcon::Slideshow,\n      NativeIcon::SmartBadge => muda::NativeIcon::SmartBadge,\n      NativeIcon::StatusAvailable => muda::NativeIcon::StatusAvailable,\n      NativeIcon::StatusNone => muda::NativeIcon::StatusNone,\n      NativeIcon::StatusPartiallyAvailable => muda::NativeIcon::StatusPartiallyAvailable,\n      NativeIcon::StatusUnavailable => muda::NativeIcon::StatusUnavailable,\n      NativeIcon::StopProgressFreestanding => muda::NativeIcon::StopProgressFreestanding,\n      NativeIcon::StopProgress => muda::NativeIcon::StopProgress,\n      NativeIcon::TrashEmpty => muda::NativeIcon::TrashEmpty,\n      NativeIcon::TrashFull => muda::NativeIcon::TrashFull,\n      NativeIcon::User => muda::NativeIcon::User,\n      NativeIcon::UserAccounts => muda::NativeIcon::UserAccounts,\n      NativeIcon::UserGroup => muda::NativeIcon::UserGroup,\n      NativeIcon::UserGuest => muda::NativeIcon::UserGuest,\n    }\n  }\n}\n\n/// An enumeration of all menu item kinds that could be added to\n/// a [`Menu`] or [`Submenu`]\npub enum MenuItemKind<R: Runtime> {\n  /// Normal menu item\n  MenuItem(MenuItem<R>),\n  /// Submenu menu item\n  Submenu(Submenu<R>),\n  /// Predefined menu item\n  Predefined(PredefinedMenuItem<R>),\n  /// Check menu item\n  Check(CheckMenuItem<R>),\n  /// Icon menu item\n  Icon(IconMenuItem<R>),\n}\n\nimpl<R: Runtime> MenuItemKind<R> {\n  /// Returns a unique identifier associated with this menu item.\n  pub fn id(&self) -> &MenuId {\n    match self {\n      MenuItemKind::MenuItem(i) => i.id(),\n      MenuItemKind::Submenu(i) => i.id(),\n      MenuItemKind::Predefined(i) => i.id(),\n      MenuItemKind::Check(i) => i.id(),\n      MenuItemKind::Icon(i) => i.id(),\n    }\n  }\n\n  pub(crate) fn inner(&self) -> &dyn IsMenuItem<R> {\n    match self {\n      MenuItemKind::MenuItem(i) => i,\n      MenuItemKind::Submenu(i) => i,\n      MenuItemKind::Predefined(i) => i,\n      MenuItemKind::Check(i) => i,\n      MenuItemKind::Icon(i) => i,\n    }\n  }\n\n  pub(crate) fn from_muda(app_handle: AppHandle<R>, i: muda::MenuItemKind) -> Self {\n    match i {\n      muda::MenuItemKind::MenuItem(i) => Self::MenuItem(MenuItem(Arc::new(MenuItemInner {\n        id: i.id().clone(),\n        inner: i.into(),\n        app_handle,\n      }))),\n      muda::MenuItemKind::Submenu(i) => Self::Submenu(Submenu(Arc::new(SubmenuInner {\n        id: i.id().clone(),\n        inner: i.into(),\n        app_handle,\n      }))),\n      muda::MenuItemKind::Predefined(i) => {\n        Self::Predefined(PredefinedMenuItem(Arc::new(PredefinedMenuItemInner {\n          id: i.id().clone(),\n          inner: i.into(),\n          app_handle,\n        })))\n      }\n      muda::MenuItemKind::Check(i) => Self::Check(CheckMenuItem(Arc::new(CheckMenuItemInner {\n        id: i.id().clone(),\n        inner: i.into(),\n        app_handle,\n      }))),\n      muda::MenuItemKind::Icon(i) => Self::Icon(IconMenuItem(Arc::new(IconMenuItemInner {\n        id: i.id().clone(),\n        inner: i.into(),\n        app_handle,\n      }))),\n    }\n  }\n\n  /// Casts this item to a [`MenuItem`], and returns `None` if it wasn't.\n  pub fn as_menuitem(&self) -> Option<&MenuItem<R>> {\n    match self {\n      MenuItemKind::MenuItem(i) => Some(i),\n      _ => None,\n    }\n  }\n\n  /// Casts this item to a [`MenuItem`], and panics if it wasn't.\n  pub fn as_menuitem_unchecked(&self) -> &MenuItem<R> {\n    match self {\n      MenuItemKind::MenuItem(i) => i,\n      _ => panic!(\"Not a MenuItem\"),\n    }\n  }\n\n  /// Casts this item to a [`Submenu`], and returns `None` if it wasn't.\n  pub fn as_submenu(&self) -> Option<&Submenu<R>> {\n    match self {\n      MenuItemKind::Submenu(i) => Some(i),\n      _ => None,\n    }\n  }\n\n  /// Casts this item to a [`Submenu`], and panics if it wasn't.\n  pub fn as_submenu_unchecked(&self) -> &Submenu<R> {\n    match self {\n      MenuItemKind::Submenu(i) => i,\n      _ => panic!(\"Not a Submenu\"),\n    }\n  }\n\n  /// Casts this item to a [`PredefinedMenuItem`], and returns `None` if it wasn't.\n  pub fn as_predefined_menuitem(&self) -> Option<&PredefinedMenuItem<R>> {\n    match self {\n      MenuItemKind::Predefined(i) => Some(i),\n      _ => None,\n    }\n  }\n\n  /// Casts this item to a [`PredefinedMenuItem`], and panics if it wasn't.\n  pub fn as_predefined_menuitem_unchecked(&self) -> &PredefinedMenuItem<R> {\n    match self {\n      MenuItemKind::Predefined(i) => i,\n      _ => panic!(\"Not a PredefinedMenuItem\"),\n    }\n  }\n\n  /// Casts this item to a [`CheckMenuItem`], and returns `None` if it wasn't.\n  pub fn as_check_menuitem(&self) -> Option<&CheckMenuItem<R>> {\n    match self {\n      MenuItemKind::Check(i) => Some(i),\n      _ => None,\n    }\n  }\n\n  /// Casts this item to a [`CheckMenuItem`], and panics if it wasn't.\n  pub fn as_check_menuitem_unchecked(&self) -> &CheckMenuItem<R> {\n    match self {\n      MenuItemKind::Check(i) => i,\n      _ => panic!(\"Not a CheckMenuItem\"),\n    }\n  }\n\n  /// Casts this item to a [`IconMenuItem`], and returns `None` if it wasn't.\n  pub fn as_icon_menuitem(&self) -> Option<&IconMenuItem<R>> {\n    match self {\n      MenuItemKind::Icon(i) => Some(i),\n      _ => None,\n    }\n  }\n\n  /// Casts this item to a [`IconMenuItem`], and panics if it wasn't.\n  pub fn as_icon_menuitem_unchecked(&self) -> &IconMenuItem<R> {\n    match self {\n      MenuItemKind::Icon(i) => i,\n      _ => panic!(\"Not an IconMenuItem\"),\n    }\n  }\n}\n\nimpl<R: Runtime> Clone for MenuItemKind<R> {\n  fn clone(&self) -> Self {\n    match self {\n      Self::MenuItem(i) => Self::MenuItem(i.clone()),\n      Self::Submenu(i) => Self::Submenu(i.clone()),\n      Self::Predefined(i) => Self::Predefined(i.clone()),\n      Self::Check(i) => Self::Check(i.clone()),\n      Self::Icon(i) => Self::Icon(i.clone()),\n    }\n  }\n}\n\nimpl<R: Runtime> sealed::IsMenuItemBase for MenuItemKind<R> {\n  fn inner_muda(&self) -> &dyn muda::IsMenuItem {\n    self.inner().inner_muda()\n  }\n}\n\nimpl<R: Runtime> IsMenuItem<R> for MenuItemKind<R> {\n  fn kind(&self) -> MenuItemKind<R> {\n    self.clone()\n  }\n\n  fn id(&self) -> &MenuId {\n    self.id()\n  }\n}\n\n/// A trait that defines a generic item in a menu, which may be one of [`MenuItemKind`]\n///\n/// # Safety\n///\n/// This trait is ONLY meant to be implemented internally by the crate.\npub trait IsMenuItem<R: Runtime>: sealed::IsMenuItemBase {\n  /// Returns the kind of this menu item.\n  fn kind(&self) -> MenuItemKind<R>;\n\n  /// Returns a unique identifier associated with this menu.\n  fn id(&self) -> &MenuId;\n}\n\n/// A helper trait with methods to help creating a context menu.\n///\n/// # Safety\n///\n/// This trait is ONLY meant to be implemented internally by the crate.\npub trait ContextMenu: sealed::ContextMenuBase + Send + Sync {\n  /// Get the popup [`HMENU`] for this menu.\n  ///\n  /// The returned [`HMENU`] is valid as long as the [`ContextMenu`] is.\n  ///\n  /// [`HMENU`]: https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types#HMENU\n  #[cfg(windows)]\n  #[cfg_attr(docsrs, doc(cfg(windows)))]\n  fn hpopupmenu(&self) -> crate::Result<isize>;\n\n  /// Popup this menu as a context menu on the specified window at the cursor position.\n  fn popup<R: crate::Runtime>(&self, window: crate::Window<R>) -> crate::Result<()>;\n\n  /// Popup this menu as a context menu on the specified window at the specified position.\n  ///\n  /// The position is relative to the window's top-left corner.\n  fn popup_at<R: crate::Runtime, P: Into<crate::Position>>(\n    &self,\n    window: crate::Window<R>,\n    position: P,\n  ) -> crate::Result<()>;\n}\n\npub(crate) mod sealed {\n\n  pub trait IsMenuItemBase {\n    fn inner_muda(&self) -> &dyn muda::IsMenuItem;\n  }\n\n  pub trait ContextMenuBase {\n    fn inner_context(&self) -> &dyn muda::ContextMenu;\n    fn inner_context_owned(&self) -> Box<dyn muda::ContextMenu>;\n    fn popup_inner<R: crate::Runtime, P: Into<crate::Position>>(\n      &self,\n      window: crate::Window<R>,\n      position: Option<P>,\n    ) -> crate::Result<()>;\n  }\n}\n\n#[cfg(windows)]\npub(crate) fn map_to_menu_theme(theme: tauri_utils::Theme) -> muda::MenuTheme {\n  match theme {\n    tauri_utils::Theme::Light => muda::MenuTheme::Light,\n    tauri_utils::Theme::Dark => muda::MenuTheme::Dark,\n    _ => muda::MenuTheme::Auto,\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/menu/normal.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::sync::Arc;\n\nuse super::run_item_main_thread;\nuse crate::menu::MenuItemInner;\nuse crate::run_main_thread;\nuse crate::{menu::MenuId, AppHandle, Manager, Runtime};\n\nuse super::MenuItem;\n\nimpl<R: Runtime> MenuItem<R> {\n  /// Create a new menu item.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn new<M, T, A>(\n    manager: &M,\n    text: T,\n    enabled: bool,\n    accelerator: Option<A>,\n  ) -> crate::Result<Self>\n  where\n    M: Manager<R>,\n    T: AsRef<str>,\n    A: AsRef<str>,\n  {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.as_ref().to_owned();\n    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::MenuItem::new(text, enabled, accelerator);\n      MenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Create a new menu item with the specified id.\n  ///\n  /// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic\n  ///   for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn with_id<M, I, T, A>(\n    manager: &M,\n    id: I,\n    text: T,\n    enabled: bool,\n    accelerator: Option<A>,\n  ) -> crate::Result<Self>\n  where\n    M: Manager<R>,\n    I: Into<MenuId>,\n    T: AsRef<str>,\n    A: AsRef<str>,\n  {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let id = id.into();\n    let accelerator = accelerator.and_then(|s| s.as_ref().parse().ok());\n    let text = text.as_ref().to_owned();\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::MenuItem::with_id(id.clone(), text, enabled, accelerator);\n      MenuItemInner {\n        id,\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// The application handle associated with this type.\n  pub fn app_handle(&self) -> &AppHandle<R> {\n    &self.0.app_handle\n  }\n\n  /// Returns a unique identifier associated with this menu item.\n  pub fn id(&self) -> &MenuId {\n    &self.0.id\n  }\n\n  /// Get the text for this menu item.\n  pub fn text(&self) -> crate::Result<String> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().text())\n  }\n\n  /// Set the text for this menu item. `text` could optionally contain\n  /// an `&` before a character to assign this character as the mnemonic\n  /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {\n    let text = text.as_ref().to_string();\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_text(text))\n  }\n\n  /// Get whether this menu item is enabled or not.\n  pub fn is_enabled(&self) -> crate::Result<bool> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().is_enabled())\n  }\n\n  /// Enable or disable this menu item.\n  pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_enabled(enabled))\n  }\n\n  /// Set this menu item accelerator.\n  pub fn set_accelerator<S: AsRef<str>>(&self, accelerator: Option<S>) -> crate::Result<()> {\n    let accel = accelerator.and_then(|s| s.as_ref().parse().ok());\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().set_accelerator(accel)\n    })?\n    .map_err(Into::into)\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/menu/plugin.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{collections::HashMap, sync::Mutex};\n\nuse serde::{Deserialize, Serialize};\nuse tauri_runtime::dpi::Position;\n\nuse super::{sealed::ContextMenuBase, *};\nuse crate::{\n  command,\n  image::JsImage,\n  ipc::{channel::JavaScriptChannelId, Channel},\n  plugin::{Builder, TauriPlugin},\n  resources::ResourceId,\n  sealed::ManagerBase,\n  Manager, ResourceTable, RunEvent, Runtime, State, Webview, Window,\n};\nuse tauri_macros::do_menu_item;\n\n#[derive(Deserialize, Serialize)]\npub(crate) enum ItemKind {\n  Menu,\n  MenuItem,\n  Predefined,\n  Submenu,\n  Check,\n  Icon,\n}\n\n#[derive(Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct AboutMetadata {\n  pub name: Option<String>,\n  pub version: Option<String>,\n  pub short_version: Option<String>,\n  pub authors: Option<Vec<String>>,\n  pub comments: Option<String>,\n  pub copyright: Option<String>,\n  pub license: Option<String>,\n  pub website: Option<String>,\n  pub website_label: Option<String>,\n  pub credits: Option<String>,\n  pub icon: Option<JsImage>,\n}\n\nimpl AboutMetadata {\n  pub fn into_metadata(\n    self,\n    resources_table: &ResourceTable,\n  ) -> crate::Result<super::AboutMetadata<'_>> {\n    let icon = match self.icon {\n      Some(i) => Some(i.into_img(resources_table)?.as_ref().clone()),\n      None => None,\n    };\n\n    Ok(super::AboutMetadata {\n      name: self.name,\n      version: self.version,\n      short_version: self.short_version,\n      authors: self.authors,\n      comments: self.comments,\n      copyright: self.copyright,\n      license: self.license,\n      website: self.website,\n      website_label: self.website_label,\n      credits: self.credits,\n      icon,\n    })\n  }\n}\n\n#[allow(clippy::large_enum_variant)]\n#[derive(Deserialize)]\nenum Predefined {\n  Separator,\n  Copy,\n  Cut,\n  Paste,\n  SelectAll,\n  Undo,\n  Redo,\n  Minimize,\n  Maximize,\n  Fullscreen,\n  Hide,\n  HideOthers,\n  ShowAll,\n  CloseWindow,\n  Quit,\n  About(Option<AboutMetadata>),\n  Services,\n}\n\n#[derive(Deserialize)]\nstruct SubmenuPayload {\n  id: Option<MenuId>,\n  text: String,\n  enabled: Option<bool>,\n  items: Vec<MenuItemPayloadKind>,\n  icon: Option<Icon>,\n}\n\nimpl SubmenuPayload {\n  pub fn create_item<R: Runtime>(\n    self,\n    webview: &Webview<R>,\n    resources_table: &ResourceTable,\n  ) -> crate::Result<Submenu<R>> {\n    let mut builder = if let Some(id) = self.id {\n      SubmenuBuilder::with_id(webview, id, self.text)\n    } else {\n      SubmenuBuilder::new(webview, self.text)\n    };\n    if let Some(enabled) = self.enabled {\n      builder = builder.enabled(enabled);\n    }\n    if let Some(icon) = self.icon {\n      builder = match icon {\n        Icon::Native(native_icon) => builder.submenu_native_icon(native_icon),\n        Icon::Icon(js_icon) => {\n          builder.submenu_icon(js_icon.into_img(resources_table)?.as_ref().clone())\n        }\n      };\n    }\n    for item in self.items {\n      builder = item.with_item(webview, resources_table, |i| Ok(builder.item(i)))?;\n    }\n\n    builder.build()\n  }\n}\n\n#[derive(Deserialize)]\nstruct CheckMenuItemPayload {\n  handler: Option<JavaScriptChannelId>,\n  id: Option<MenuId>,\n  text: String,\n  checked: bool,\n  enabled: Option<bool>,\n  accelerator: Option<String>,\n}\n\nimpl CheckMenuItemPayload {\n  pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> crate::Result<CheckMenuItem<R>> {\n    let mut builder = if let Some(id) = self.id {\n      CheckMenuItemBuilder::with_id(id, self.text)\n    } else {\n      CheckMenuItemBuilder::new(self.text)\n    };\n    if let Some(accelerator) = self.accelerator {\n      builder = builder.accelerator(accelerator);\n    }\n    if let Some(enabled) = self.enabled {\n      builder = builder.enabled(enabled);\n    }\n\n    let item = builder.checked(self.checked).build(webview)?;\n\n    if let Some(handler) = self.handler {\n      let handler = handler.channel_on(webview.clone());\n      webview\n        .state::<MenuChannels>()\n        .0\n        .lock()\n        .unwrap()\n        .insert(item.id().clone(), handler);\n    }\n\n    Ok(item)\n  }\n}\n\n#[derive(Deserialize)]\n#[serde(untagged)]\nenum Icon {\n  Native(NativeIcon),\n  Icon(JsImage),\n}\n\n#[derive(Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct IconMenuItemPayload {\n  handler: Option<JavaScriptChannelId>,\n  id: Option<MenuId>,\n  text: String,\n  icon: Icon,\n  enabled: Option<bool>,\n  accelerator: Option<String>,\n}\n\nimpl IconMenuItemPayload {\n  pub fn create_item<R: Runtime>(\n    self,\n    webview: &Webview<R>,\n    resources_table: &ResourceTable,\n  ) -> crate::Result<IconMenuItem<R>> {\n    let mut builder = if let Some(id) = self.id {\n      IconMenuItemBuilder::with_id(id, self.text)\n    } else {\n      IconMenuItemBuilder::new(self.text)\n    };\n    if let Some(accelerator) = self.accelerator {\n      builder = builder.accelerator(accelerator);\n    }\n    if let Some(enabled) = self.enabled {\n      builder = builder.enabled(enabled);\n    }\n    builder = match self.icon {\n      Icon::Native(native_icon) => builder.native_icon(native_icon),\n      Icon::Icon(icon) => builder.icon(icon.into_img(resources_table)?.as_ref().clone()),\n    };\n\n    let item = builder.build(webview)?;\n\n    if let Some(handler) = self.handler {\n      let handler = handler.channel_on(webview.clone());\n      webview\n        .state::<MenuChannels>()\n        .0\n        .lock()\n        .unwrap()\n        .insert(item.id().clone(), handler);\n    }\n\n    Ok(item)\n  }\n}\n\n#[derive(Deserialize)]\nstruct MenuItemPayload {\n  handler: Option<JavaScriptChannelId>,\n  id: Option<MenuId>,\n  text: String,\n  enabled: Option<bool>,\n  accelerator: Option<String>,\n}\n\nimpl MenuItemPayload {\n  pub fn create_item<R: Runtime>(self, webview: &Webview<R>) -> crate::Result<MenuItem<R>> {\n    let mut builder = if let Some(id) = self.id {\n      MenuItemBuilder::with_id(id, self.text)\n    } else {\n      MenuItemBuilder::new(self.text)\n    };\n    if let Some(accelerator) = self.accelerator {\n      builder = builder.accelerator(accelerator);\n    }\n    if let Some(enabled) = self.enabled {\n      builder = builder.enabled(enabled);\n    }\n\n    let item = builder.build(webview)?;\n\n    if let Some(handler) = self.handler {\n      let handler = handler.channel_on(webview.clone());\n      webview\n        .state::<MenuChannels>()\n        .0\n        .lock()\n        .unwrap()\n        .insert(item.id().clone(), handler);\n    }\n\n    Ok(item)\n  }\n}\n\n#[derive(Deserialize)]\nstruct PredefinedMenuItemPayload {\n  item: Predefined,\n  text: Option<String>,\n}\n\nimpl PredefinedMenuItemPayload {\n  pub fn create_item<R: Runtime>(\n    self,\n    webview: &Webview<R>,\n    resources_table: &ResourceTable,\n  ) -> crate::Result<PredefinedMenuItem<R>> {\n    match self.item {\n      Predefined::Separator => PredefinedMenuItem::separator(webview),\n      Predefined::Copy => PredefinedMenuItem::copy(webview, self.text.as_deref()),\n      Predefined::Cut => PredefinedMenuItem::cut(webview, self.text.as_deref()),\n      Predefined::Paste => PredefinedMenuItem::paste(webview, self.text.as_deref()),\n      Predefined::SelectAll => PredefinedMenuItem::select_all(webview, self.text.as_deref()),\n      Predefined::Undo => PredefinedMenuItem::undo(webview, self.text.as_deref()),\n      Predefined::Redo => PredefinedMenuItem::redo(webview, self.text.as_deref()),\n      Predefined::Minimize => PredefinedMenuItem::minimize(webview, self.text.as_deref()),\n      Predefined::Maximize => PredefinedMenuItem::maximize(webview, self.text.as_deref()),\n      Predefined::Fullscreen => PredefinedMenuItem::fullscreen(webview, self.text.as_deref()),\n      Predefined::Hide => PredefinedMenuItem::hide(webview, self.text.as_deref()),\n      Predefined::HideOthers => PredefinedMenuItem::hide_others(webview, self.text.as_deref()),\n      Predefined::ShowAll => PredefinedMenuItem::show_all(webview, self.text.as_deref()),\n      Predefined::CloseWindow => PredefinedMenuItem::close_window(webview, self.text.as_deref()),\n      Predefined::Quit => PredefinedMenuItem::quit(webview, self.text.as_deref()),\n      Predefined::About(metadata) => {\n        let metadata = match metadata {\n          Some(m) => Some(m.into_metadata(resources_table)?),\n          None => None,\n        };\n        PredefinedMenuItem::about(webview, self.text.as_deref(), metadata)\n      }\n      Predefined::Services => PredefinedMenuItem::services(webview, self.text.as_deref()),\n    }\n  }\n}\n\n#[derive(Deserialize)]\n#[serde(untagged)]\n// Note, order matters for untagged enum deserialization\nenum MenuItemPayloadKind {\n  ExistingItem((ResourceId, ItemKind)),\n  Predefined(PredefinedMenuItemPayload),\n  Check(CheckMenuItemPayload),\n  Icon(IconMenuItemPayload),\n  Submenu(SubmenuPayload),\n  MenuItem(MenuItemPayload),\n}\n\nimpl MenuItemPayloadKind {\n  pub fn with_item<T, R: Runtime, F: FnOnce(&dyn IsMenuItem<R>) -> crate::Result<T>>(\n    self,\n    webview: &Webview<R>,\n    resources_table: &ResourceTable,\n    f: F,\n  ) -> crate::Result<T> {\n    match self {\n      Self::ExistingItem((rid, kind)) => {\n        do_menu_item!(resources_table, rid, kind, |i| f(&*i))\n      }\n      Self::Submenu(i) => f(&i.create_item(webview, resources_table)?),\n      Self::Predefined(i) => f(&i.create_item(webview, resources_table)?),\n      Self::Check(i) => f(&i.create_item(webview)?),\n      Self::Icon(i) => f(&i.create_item(webview, resources_table)?),\n      Self::MenuItem(i) => f(&i.create_item(webview)?),\n    }\n  }\n}\n\n#[derive(Deserialize, Default)]\n#[serde(rename_all = \"camelCase\")]\nstruct NewOptions {\n  id: Option<MenuId>,\n  text: Option<String>,\n  enabled: Option<bool>,\n  checked: Option<bool>,\n  accelerator: Option<String>,\n  #[serde(rename = \"item\")]\n  predefined_item: Option<Predefined>,\n  icon: Option<Icon>,\n  items: Option<Vec<MenuItemPayloadKind>>,\n}\n\n#[command(root = \"crate\")]\nfn new<R: Runtime>(\n  app: Webview<R>,\n  webview: Webview<R>,\n  kind: ItemKind,\n  options: Option<NewOptions>,\n  channels: State<'_, MenuChannels>,\n  handler: Channel<MenuId>,\n) -> crate::Result<(ResourceId, MenuId)> {\n  let options = options.unwrap_or_default();\n  let mut resources_table = app.resources_table();\n\n  let (rid, id) = match kind {\n    ItemKind::Menu => {\n      let mut builder = MenuBuilder::new(&app);\n      if let Some(id) = options.id {\n        builder = builder.id(id);\n      }\n      if let Some(items) = options.items {\n        for item in items {\n          builder = item.with_item(&webview, &resources_table, |i| Ok(builder.item(i)))?;\n        }\n      }\n      let menu = builder.build()?;\n      let id = menu.id().clone();\n      let rid = resources_table.add(menu);\n\n      (rid, id)\n    }\n\n    ItemKind::Submenu => {\n      let submenu = SubmenuPayload {\n        id: options.id,\n        text: options.text.unwrap_or_default(),\n        enabled: options.enabled,\n        items: options.items.unwrap_or_default(),\n        icon: options.icon,\n      }\n      .create_item(&webview, &resources_table)?;\n      let id = submenu.id().clone();\n      let rid = resources_table.add(submenu);\n\n      (rid, id)\n    }\n\n    ItemKind::MenuItem => {\n      let item = MenuItemPayload {\n        // handler managed in this function instead\n        handler: None,\n        id: options.id,\n        text: options.text.unwrap_or_default(),\n        enabled: options.enabled,\n        accelerator: options.accelerator,\n      }\n      .create_item(&webview)?;\n      let id = item.id().clone();\n      let rid = resources_table.add(item);\n      (rid, id)\n    }\n\n    ItemKind::Predefined => {\n      let item = PredefinedMenuItemPayload {\n        item: options.predefined_item.unwrap(),\n        text: options.text,\n      }\n      .create_item(&webview, &resources_table)?;\n      let id = item.id().clone();\n      let rid = resources_table.add(item);\n      (rid, id)\n    }\n\n    ItemKind::Check => {\n      let item = CheckMenuItemPayload {\n        // handler managed in this function instead\n        handler: None,\n        id: options.id,\n        text: options.text.unwrap_or_default(),\n        checked: options.checked.unwrap_or_default(),\n        enabled: options.enabled,\n        accelerator: options.accelerator,\n      }\n      .create_item(&webview)?;\n      let id = item.id().clone();\n      let rid = resources_table.add(item);\n      (rid, id)\n    }\n\n    ItemKind::Icon => {\n      let item = IconMenuItemPayload {\n        // handler managed in this function instead\n        handler: None,\n        id: options.id,\n        text: options.text.unwrap_or_default(),\n        icon: options.icon.unwrap_or(Icon::Native(NativeIcon::User)),\n        enabled: options.enabled,\n        accelerator: options.accelerator,\n      }\n      .create_item(&webview, &resources_table)?;\n      let id = item.id().clone();\n      let rid = resources_table.add(item);\n      (rid, id)\n    }\n  };\n\n  channels.0.lock().unwrap().insert(id.clone(), handler);\n\n  Ok((rid, id))\n}\n\n#[command(root = \"crate\")]\nfn append<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n  items: Vec<MenuItemPayloadKind>,\n) -> crate::Result<()> {\n  let resources_table = webview.resources_table();\n  match kind {\n    ItemKind::Menu => {\n      let menu = resources_table.get::<Menu<R>>(rid)?;\n      for item in items {\n        item.with_item(&webview, &resources_table, |i| menu.append(i))?;\n      }\n    }\n    ItemKind::Submenu => {\n      let submenu = resources_table.get::<Submenu<R>>(rid)?;\n      for item in items {\n        item.with_item(&webview, &resources_table, |i| submenu.append(i))?;\n      }\n    }\n    _ => return Err(anyhow::anyhow!(\"unexpected menu item kind\").into()),\n  };\n\n  Ok(())\n}\n\n#[command(root = \"crate\")]\nfn prepend<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n  items: Vec<MenuItemPayloadKind>,\n) -> crate::Result<()> {\n  let resources_table = webview.resources_table();\n  match kind {\n    ItemKind::Menu => {\n      let menu = resources_table.get::<Menu<R>>(rid)?;\n      for item in items {\n        item.with_item(&webview, &resources_table, |i| menu.prepend(i))?;\n      }\n    }\n    ItemKind::Submenu => {\n      let submenu = resources_table.get::<Submenu<R>>(rid)?;\n      for item in items {\n        item.with_item(&webview, &resources_table, |i| submenu.prepend(i))?;\n      }\n    }\n    _ => return Err(anyhow::anyhow!(\"unexpected menu item kind\").into()),\n  };\n\n  Ok(())\n}\n\n#[command(root = \"crate\")]\nfn insert<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n  items: Vec<MenuItemPayloadKind>,\n  mut position: usize,\n) -> crate::Result<()> {\n  let resources_table = webview.resources_table();\n  match kind {\n    ItemKind::Menu => {\n      let menu = resources_table.get::<Menu<R>>(rid)?;\n      for item in items {\n        item.with_item(&webview, &resources_table, |i| menu.insert(i, position))?;\n        position += 1\n      }\n    }\n    ItemKind::Submenu => {\n      let submenu = resources_table.get::<Submenu<R>>(rid)?;\n      for item in items {\n        item.with_item(&webview, &resources_table, |i| submenu.insert(i, position))?;\n        position += 1\n      }\n    }\n    _ => return Err(anyhow::anyhow!(\"unexpected menu item kind\").into()),\n  };\n\n  Ok(())\n}\n\n#[command(root = \"crate\")]\nfn remove<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n  item: (ResourceId, ItemKind),\n) -> crate::Result<()> {\n  let resources_table = webview.resources_table();\n  let (item_rid, item_kind) = item;\n  match kind {\n    ItemKind::Menu => {\n      let menu = resources_table.get::<Menu<R>>(rid)?;\n      do_menu_item!(resources_table, item_rid, item_kind, |i| menu.remove(&*i))?;\n    }\n    ItemKind::Submenu => {\n      let submenu = resources_table.get::<Submenu<R>>(rid)?;\n      do_menu_item!(resources_table, item_rid, item_kind, |i| submenu\n        .remove(&*i))?;\n    }\n    _ => return Err(anyhow::anyhow!(\"unexpected menu item kind\").into()),\n  };\n\n  Ok(())\n}\n\nmacro_rules! make_item_resource {\n  ($resources_table:ident, $item:ident) => {{\n    let id = $item.id().clone();\n    let (rid, kind) = match $item {\n      MenuItemKind::MenuItem(i) => ($resources_table.add(i), ItemKind::MenuItem),\n      MenuItemKind::Submenu(i) => ($resources_table.add(i), ItemKind::Submenu),\n      MenuItemKind::Predefined(i) => ($resources_table.add(i), ItemKind::Predefined),\n      MenuItemKind::Check(i) => ($resources_table.add(i), ItemKind::Check),\n      MenuItemKind::Icon(i) => ($resources_table.add(i), ItemKind::Icon),\n    };\n    (rid, id, kind)\n  }};\n}\n\n#[command(root = \"crate\")]\nfn remove_at<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n  position: usize,\n) -> crate::Result<Option<(ResourceId, MenuId, ItemKind)>> {\n  let mut resources_table = webview.resources_table();\n  match kind {\n    ItemKind::Menu => {\n      let menu = resources_table.get::<Menu<R>>(rid)?;\n      if let Some(item) = menu.remove_at(position)? {\n        return Ok(Some(make_item_resource!(resources_table, item)));\n      }\n    }\n    ItemKind::Submenu => {\n      let submenu = resources_table.get::<Submenu<R>>(rid)?;\n      if let Some(item) = submenu.remove_at(position)? {\n        return Ok(Some(make_item_resource!(resources_table, item)));\n      }\n    }\n    _ => return Err(anyhow::anyhow!(\"unexpected menu item kind\").into()),\n  };\n\n  Ok(None)\n}\n\n#[command(root = \"crate\")]\nfn items<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n) -> crate::Result<Vec<(ResourceId, MenuId, ItemKind)>> {\n  let mut resources_table = webview.resources_table();\n  let items = match kind {\n    ItemKind::Menu => resources_table.get::<Menu<R>>(rid)?.items()?,\n    ItemKind::Submenu => resources_table.get::<Submenu<R>>(rid)?.items()?,\n    _ => return Err(anyhow::anyhow!(\"unexpected menu item kind\").into()),\n  };\n\n  Ok(\n    items\n      .into_iter()\n      .map(|i| make_item_resource!(resources_table, i))\n      .collect::<Vec<_>>(),\n  )\n}\n\n#[command(root = \"crate\")]\nfn get<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n  id: MenuId,\n) -> crate::Result<Option<(ResourceId, MenuId, ItemKind)>> {\n  let mut resources_table = webview.resources_table();\n  match kind {\n    ItemKind::Menu => {\n      let menu = resources_table.get::<Menu<R>>(rid)?;\n      if let Some(item) = menu.get(&id) {\n        return Ok(Some(make_item_resource!(resources_table, item)));\n      }\n    }\n    ItemKind::Submenu => {\n      let submenu = resources_table.get::<Submenu<R>>(rid)?;\n      if let Some(item) = submenu.get(&id) {\n        return Ok(Some(make_item_resource!(resources_table, item)));\n      }\n    }\n    _ => return Err(anyhow::anyhow!(\"unexpected menu item kind\").into()),\n  };\n\n  Ok(None)\n}\n\n#[command(root = \"crate\")]\nasync fn popup<R: Runtime>(\n  webview: Webview<R>,\n  current_window: Window<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n  window: Option<String>,\n  at: Option<Position>,\n) -> crate::Result<()> {\n  let window = window\n    .map(|w| webview.manager().get_window(&w))\n    .unwrap_or(Some(current_window));\n\n  if let Some(window) = window {\n    let resources_table = webview.resources_table();\n    match kind {\n      ItemKind::Menu => {\n        let menu = resources_table.get::<Menu<R>>(rid)?;\n        menu.popup_inner(window, at)?;\n      }\n      ItemKind::Submenu => {\n        let submenu = resources_table.get::<Submenu<R>>(rid)?;\n        submenu.popup_inner(window, at)?;\n      }\n      _ => return Err(anyhow::anyhow!(\"unexpected menu item kind\").into()),\n    };\n  }\n\n  Ok(())\n}\n\n#[command(root = \"crate\")]\nfn create_default<R: Runtime>(\n  app: AppHandle<R>,\n  webview: Webview<R>,\n) -> crate::Result<(ResourceId, MenuId)> {\n  let mut resources_table = webview.resources_table();\n  let menu = Menu::default(&app)?;\n  let id = menu.id().clone();\n  let rid = resources_table.add(menu);\n  Ok((rid, id))\n}\n\n#[command(root = \"crate\")]\nasync fn set_as_app_menu<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n) -> crate::Result<Option<(ResourceId, MenuId)>> {\n  let mut resources_table = webview.resources_table();\n  let menu = resources_table.get::<Menu<R>>(rid)?;\n  if let Some(menu) = menu.set_as_app_menu()? {\n    let id = menu.id().clone();\n    let rid = resources_table.add(menu);\n    return Ok(Some((rid, id)));\n  }\n  Ok(None)\n}\n\n#[command(root = \"crate\")]\nasync fn set_as_window_menu<R: Runtime>(\n  webview: Webview<R>,\n  current_window: Window<R>,\n  rid: ResourceId,\n  window: Option<String>,\n) -> crate::Result<Option<(ResourceId, MenuId)>> {\n  let window = window\n    .map(|w| webview.manager().get_window(&w))\n    .unwrap_or(Some(current_window));\n\n  if let Some(window) = window {\n    let mut resources_table = webview.resources_table();\n    let menu = resources_table.get::<Menu<R>>(rid)?;\n    if let Some(menu) = menu.set_as_window_menu(&window)? {\n      let id = menu.id().clone();\n      let rid = resources_table.add(menu);\n      return Ok(Some((rid, id)));\n    }\n  }\n  Ok(None)\n}\n\n#[command(root = \"crate\")]\nfn text<R: Runtime>(webview: Webview<R>, rid: ResourceId, kind: ItemKind) -> crate::Result<String> {\n  let resources_table = webview.resources_table();\n  do_menu_item!(resources_table, rid, kind, |i| i.text())\n}\n\n#[command(root = \"crate\")]\nfn set_text<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n  text: String,\n) -> crate::Result<()> {\n  let resources_table = webview.resources_table();\n  do_menu_item!(resources_table, rid, kind, |i| i.set_text(text))\n}\n\n#[command(root = \"crate\")]\nfn is_enabled<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n) -> crate::Result<bool> {\n  let resources_table = webview.resources_table();\n  do_menu_item!(resources_table, rid, kind, |i| i.is_enabled(), !Predefined)\n}\n\n#[command(root = \"crate\")]\nfn set_enabled<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n  enabled: bool,\n) -> crate::Result<()> {\n  let resources_table = webview.resources_table();\n  do_menu_item!(\n    resources_table,\n    rid,\n    kind,\n    |i| i.set_enabled(enabled),\n    !Predefined\n  )\n}\n\n#[command(root = \"crate\")]\nfn set_accelerator<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n  accelerator: Option<String>,\n) -> crate::Result<()> {\n  let resources_table = webview.resources_table();\n  do_menu_item!(\n    resources_table,\n    rid,\n    kind,\n    |i| i.set_accelerator(accelerator),\n    !Predefined | !Submenu\n  )\n}\n\n#[command(root = \"crate\")]\nfn set_as_windows_menu_for_nsapp<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n) -> crate::Result<()> {\n  #[cfg(target_os = \"macos\")]\n  {\n    let resources_table = webview.resources_table();\n    let submenu = resources_table.get::<Submenu<R>>(rid)?;\n    submenu.set_as_help_menu_for_nsapp()?;\n  }\n\n  let _ = rid;\n  let _ = webview;\n  Ok(())\n}\n\n#[command(root = \"crate\")]\nfn set_as_help_menu_for_nsapp<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n) -> crate::Result<()> {\n  #[cfg(target_os = \"macos\")]\n  {\n    let resources_table = webview.resources_table();\n    let submenu = resources_table.get::<Submenu<R>>(rid)?;\n    submenu.set_as_help_menu_for_nsapp()?;\n  }\n\n  let _ = rid;\n  let _ = webview;\n\n  Ok(())\n}\n\n#[command(root = \"crate\")]\nfn is_checked<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> crate::Result<bool> {\n  let resources_table = webview.resources_table();\n  let check_item = resources_table.get::<CheckMenuItem<R>>(rid)?;\n  check_item.is_checked()\n}\n\n#[command(root = \"crate\")]\nfn set_checked<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  checked: bool,\n) -> crate::Result<()> {\n  let resources_table = webview.resources_table();\n  let check_item = resources_table.get::<CheckMenuItem<R>>(rid)?;\n  check_item.set_checked(checked)\n}\n\n#[command(root = \"crate\")]\nfn set_icon<R: Runtime>(\n  webview: Webview<R>,\n  rid: ResourceId,\n  kind: ItemKind,\n  icon: Option<Icon>,\n) -> crate::Result<()> {\n  let resources_table = webview.resources_table();\n  do_menu_item!(\n    resources_table,\n    rid,\n    kind,\n    |icon_item| match icon {\n      Some(Icon::Native(icon)) => icon_item.set_native_icon(Some(icon)),\n      Some(Icon::Icon(icon)) => {\n        icon_item.set_icon(Some(icon.into_img(&resources_table)?.as_ref().clone()))\n      }\n      None => {\n        icon_item.set_icon(None)?;\n        icon_item.set_native_icon(None)?;\n        Ok(())\n      }\n    },\n    Icon | Submenu\n  )\n}\n\nstruct MenuChannels(Mutex<HashMap<MenuId, Channel<MenuId>>>);\n\npub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {\n  Builder::new(\"menu\")\n    .setup(|app, _api| {\n      app.manage(MenuChannels(Mutex::default()));\n      Ok(())\n    })\n    .on_event(|app, e| {\n      if let RunEvent::MenuEvent(e) = e {\n        if let Some(channel) = app.state::<MenuChannels>().0.lock().unwrap().get(&e.id) {\n          let _ = channel.send(e.id.clone());\n        }\n      }\n    })\n    .invoke_handler(crate::generate_handler![\n      #![plugin(menu)]\n      new,\n      append,\n      prepend,\n      insert,\n      remove,\n      remove_at,\n      items,\n      get,\n      popup,\n      create_default,\n      set_as_app_menu,\n      set_as_window_menu,\n      text,\n      set_text,\n      is_enabled,\n      set_enabled,\n      set_accelerator,\n      set_as_windows_menu_for_nsapp,\n      set_as_help_menu_for_nsapp,\n      is_checked,\n      set_checked,\n      set_icon,\n    ])\n    .build()\n}\n"
  },
  {
    "path": "crates/tauri/src/menu/predefined.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::sync::Arc;\n\nuse super::run_item_main_thread;\nuse super::{AboutMetadata, PredefinedMenuItem};\nuse crate::menu::PredefinedMenuItemInner;\nuse crate::run_main_thread;\nuse crate::{menu::MenuId, AppHandle, Manager, Runtime};\n\nimpl<R: Runtime> PredefinedMenuItem<R> {\n  /// Separator menu item\n  pub fn separator<M: Manager<R>>(manager: &M) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::separator();\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Copy menu item\n  pub fn copy<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::copy(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Cut menu item\n  pub fn cut<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::cut(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Paste menu item\n  pub fn paste<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::paste(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// SelectAll menu item\n  pub fn select_all<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::select_all(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Undo menu item\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows / Linux:** Unsupported.\n  pub fn undo<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::undo(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n  /// Redo menu item\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows / Linux:** Unsupported.\n  pub fn redo<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::redo(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Minimize window menu item\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Unsupported.\n  pub fn minimize<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::minimize(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Maximize window menu item\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Unsupported.\n  pub fn maximize<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::maximize(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Fullscreen menu item\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows / Linux:** Unsupported.\n  pub fn fullscreen<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::fullscreen(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Hide window menu item\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Unsupported.\n  pub fn hide<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::hide(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Hide other windows menu item\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Unsupported.\n  pub fn hide_others<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::hide_others(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Show all app windows menu item\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows / Linux:** Unsupported.\n  pub fn show_all<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::show_all(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Close window menu item\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Unsupported.\n  pub fn close_window<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::close_window(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Quit app menu item\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Unsupported.\n  pub fn quit<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::quit(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// About app menu item\n  pub fn about<M: Manager<R>>(\n    manager: &M,\n    text: Option<&str>,\n    metadata: Option<AboutMetadata<'_>>,\n  ) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let metadata = match metadata {\n      Some(m) => Some(m.try_into()?),\n      None => None,\n    };\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::about(text.as_deref(), metadata);\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Services menu item\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows / Linux:** Unsupported.\n  pub fn services<M: Manager<R>>(manager: &M, text: Option<&str>) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.map(|t| t.to_owned());\n\n    let item = run_main_thread!(handle, || {\n      let item = muda::PredefinedMenuItem::services(text.as_deref());\n      PredefinedMenuItemInner {\n        id: item.id().clone(),\n        inner: Some(item),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(item)))\n  }\n\n  /// Returns a unique identifier associated with this menu item.\n  pub fn id(&self) -> &MenuId {\n    &self.0.id\n  }\n\n  /// Get the text for this menu item.\n  pub fn text(&self) -> crate::Result<String> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().text())\n  }\n\n  /// Set the text for this menu item. `text` could optionally contain\n  /// an `&` before a character to assign this character as the mnemonic\n  /// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.\n  pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {\n    let text = text.as_ref().to_string();\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_text(text))\n  }\n\n  /// The application handle associated with this type.\n  pub fn app_handle(&self) -> &AppHandle<R> {\n    &self.0.app_handle\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/menu/submenu.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::sync::Arc;\n\nuse super::run_item_main_thread;\nuse super::Submenu;\nuse super::{sealed::ContextMenuBase, IsMenuItem, MenuItemKind};\nuse crate::menu::NativeIcon;\nuse crate::menu::SubmenuInner;\nuse crate::run_main_thread;\nuse crate::{AppHandle, Manager, Position, Runtime, Window};\nuse muda::{ContextMenu, Icon as MudaIcon, MenuId};\n\nimpl<R: Runtime> super::ContextMenu for Submenu<R> {\n  #[cfg(target_os = \"windows\")]\n  fn hpopupmenu(&self) -> crate::Result<isize> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().hpopupmenu())\n  }\n\n  fn popup<T: Runtime>(&self, window: Window<T>) -> crate::Result<()> {\n    self.popup_inner(window, None::<Position>)\n  }\n\n  fn popup_at<T: Runtime, P: Into<Position>>(\n    &self,\n    window: Window<T>,\n    position: P,\n  ) -> crate::Result<()> {\n    self.popup_inner(window, Some(position))\n  }\n}\n\nimpl<R: Runtime> ContextMenuBase for Submenu<R> {\n  fn popup_inner<T: Runtime, P: Into<crate::Position>>(\n    &self,\n    window: crate::Window<T>,\n    position: Option<P>,\n  ) -> crate::Result<()> {\n    let position = position.map(Into::into);\n    run_item_main_thread!(self, move |self_: Self| {\n      #[cfg(target_os = \"macos\")]\n      if let Ok(view) = window.ns_view() {\n        unsafe {\n          self_\n            .inner()\n            .show_context_menu_for_nsview(view as _, position);\n        }\n      }\n\n      #[cfg(any(\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n      ))]\n      if let Ok(w) = window.gtk_window() {\n        self_\n          .inner()\n          .show_context_menu_for_gtk_window(w.as_ref(), position);\n      }\n\n      #[cfg(windows)]\n      if let Ok(hwnd) = window.hwnd() {\n        unsafe {\n          self_\n            .inner()\n            .show_context_menu_for_hwnd(hwnd.0 as _, position);\n        }\n      }\n    })\n  }\n\n  fn inner_context(&self) -> &dyn muda::ContextMenu {\n    (*self.0).as_ref()\n  }\n\n  fn inner_context_owned(&self) -> Box<dyn muda::ContextMenu> {\n    Box::new((*self.0).as_ref().clone())\n  }\n}\n\nimpl<R: Runtime> Submenu<R> {\n  /// Creates a new submenu.\n  pub fn new<M: Manager<R>, S: AsRef<str>>(\n    manager: &M,\n    text: S,\n    enabled: bool,\n  ) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let text = text.as_ref().to_owned();\n\n    let submenu = run_main_thread!(handle, || {\n      let submenu = muda::Submenu::new(text, enabled);\n      SubmenuInner {\n        id: submenu.id().clone(),\n        inner: Some(submenu),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(submenu)))\n  }\n\n  /// Create a new submenu with an icon.\n  pub fn new_with_icon<M: Manager<R>, S: AsRef<str>>(\n    manager: &M,\n    text: S,\n    enabled: bool,\n    icon: Option<crate::image::Image<'_>>,\n  ) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n    let text = text.as_ref().to_owned();\n    let icon_data = icon.map(|i| (i.rgba().to_vec(), i.width(), i.height()));\n    let submenu = run_main_thread!(handle, || {\n      let submenu = muda::Submenu::new(text, enabled);\n      if let Some((rgba, width, height)) = icon_data.clone() {\n        submenu.set_icon(Some(MudaIcon::from_rgba(rgba, width, height).unwrap()));\n      }\n      SubmenuInner {\n        id: submenu.id().clone(),\n        inner: Some(submenu),\n        app_handle,\n      }\n    })?;\n    Ok(Self(Arc::new(submenu)))\n  }\n\n  /// Create a new submenu with a native icon.\n  pub fn new_with_native_icon<M: Manager<R>, S: AsRef<str>>(\n    manager: &M,\n    text: S,\n    enabled: bool,\n    icon: Option<NativeIcon>,\n  ) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n    let text = text.as_ref().to_owned();\n    let submenu = run_main_thread!(handle, || {\n      let submenu = muda::Submenu::new(text, enabled);\n      if let Some(icon) = icon {\n        submenu.set_native_icon(Some(icon.into()));\n      }\n      SubmenuInner {\n        id: submenu.id().clone(),\n        inner: Some(submenu),\n        app_handle,\n      }\n    })?;\n    Ok(Self(Arc::new(submenu)))\n  }\n\n  /// Creates a new submenu with the specified id.\n  pub fn with_id<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(\n    manager: &M,\n    id: I,\n    text: S,\n    enabled: bool,\n  ) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n\n    let id = id.into();\n    let text = text.as_ref().to_owned();\n\n    let submenu = run_main_thread!(handle, || {\n      let submenu = muda::Submenu::with_id(id.clone(), text, enabled);\n      SubmenuInner {\n        id,\n        inner: Some(submenu),\n        app_handle,\n      }\n    })?;\n\n    Ok(Self(Arc::new(submenu)))\n  }\n\n  /// Create a new submenu with an id and an icon.\n  pub fn with_id_and_icon<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(\n    manager: &M,\n    id: I,\n    text: S,\n    enabled: bool,\n    icon: Option<crate::image::Image<'_>>,\n  ) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n    let id = id.into();\n    let text = text.as_ref().to_owned();\n    let icon_data = icon.map(|i| (i.rgba().to_vec(), i.width(), i.height()));\n    let submenu = run_main_thread!(handle, || {\n      let submenu = muda::Submenu::with_id(id.clone(), text, enabled);\n      if let Some((rgba, width, height)) = icon_data.clone() {\n        submenu.set_icon(Some(MudaIcon::from_rgba(rgba, width, height).unwrap()));\n      }\n      SubmenuInner {\n        id,\n        inner: Some(submenu),\n        app_handle,\n      }\n    })?;\n    Ok(Self(Arc::new(submenu)))\n  }\n\n  /// Create a new submenu with an id and a native icon.\n  pub fn with_id_and_native_icon<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(\n    manager: &M,\n    id: I,\n    text: S,\n    enabled: bool,\n    icon: Option<NativeIcon>,\n  ) -> crate::Result<Self> {\n    let handle = manager.app_handle();\n    let app_handle = handle.clone();\n    let id = id.into();\n    let text = text.as_ref().to_owned();\n    let submenu = run_main_thread!(handle, || {\n      let submenu = muda::Submenu::with_id(id.clone(), text, enabled);\n      if let Some(icon) = icon {\n        submenu.set_native_icon(Some(icon.into()));\n      }\n      SubmenuInner {\n        id,\n        inner: Some(submenu),\n        app_handle,\n      }\n    })?;\n    Ok(Self(Arc::new(submenu)))\n  }\n\n  /// Creates a new menu with given `items`. It calls [`Submenu::new`] and [`Submenu::append_items`] internally.\n  pub fn with_items<M: Manager<R>, S: AsRef<str>>(\n    manager: &M,\n    text: S,\n    enabled: bool,\n    items: &[&dyn IsMenuItem<R>],\n  ) -> crate::Result<Self> {\n    let menu = Self::new(manager, text, enabled)?;\n    menu.append_items(items)?;\n    Ok(menu)\n  }\n\n  /// Creates a new menu with the specified id and given `items`.\n  /// It calls [`Submenu::new`] and [`Submenu::append_items`] internally.\n  pub fn with_id_and_items<M: Manager<R>, I: Into<MenuId>, S: AsRef<str>>(\n    manager: &M,\n    id: I,\n    text: S,\n    enabled: bool,\n    items: &[&dyn IsMenuItem<R>],\n  ) -> crate::Result<Self> {\n    let menu = Self::with_id(manager, id, text, enabled)?;\n    menu.append_items(items)?;\n    Ok(menu)\n  }\n\n  pub(crate) fn inner(&self) -> &muda::Submenu {\n    (*self.0).as_ref()\n  }\n\n  /// The application handle associated with this type.\n  pub fn app_handle(&self) -> &AppHandle<R> {\n    &self.0.app_handle\n  }\n\n  /// Returns a unique identifier associated with this submenu.\n  pub fn id(&self) -> &MenuId {\n    &self.0.id\n  }\n\n  /// Add a menu item to the end of this submenu.\n  pub fn append(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {\n    let kind = item.kind();\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().append(kind.inner().inner_muda())\n    })?\n    .map_err(Into::into)\n  }\n\n  /// Add menu items to the end of this submenu. It calls [`Submenu::append`] in a loop internally.\n  pub fn append_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {\n    for item in items {\n      self.append(*item)?\n    }\n\n    Ok(())\n  }\n\n  /// Add a menu item to the beginning of this submenu.\n  pub fn prepend(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {\n    let kind = item.kind();\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().prepend(kind.inner().inner_muda())\n    })?\n    .map_err(Into::into)\n  }\n\n  /// Add menu items to the beginning of this submenu. It calls [`Submenu::insert_items`] with position of `0` internally.\n  pub fn prepend_items(&self, items: &[&dyn IsMenuItem<R>]) -> crate::Result<()> {\n    self.insert_items(items, 0)\n  }\n\n  /// Insert a menu item at the specified `position` in this submenu.\n  pub fn insert(&self, item: &dyn IsMenuItem<R>, position: usize) -> crate::Result<()> {\n    let kind = item.kind();\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0)\n        .as_ref()\n        .insert(kind.inner().inner_muda(), position)\n    })?\n    .map_err(Into::into)\n  }\n\n  /// Insert menu items at the specified `position` in this submenu.\n  pub fn insert_items(&self, items: &[&dyn IsMenuItem<R>], position: usize) -> crate::Result<()> {\n    for (i, item) in items.iter().enumerate() {\n      self.insert(*item, position + i)?\n    }\n\n    Ok(())\n  }\n\n  /// Remove a menu item from this submenu.\n  pub fn remove(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {\n    let kind = item.kind();\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().remove(kind.inner().inner_muda())\n    })?\n    .map_err(Into::into)\n  }\n\n  /// Remove the menu item at the specified position from this submenu and returns it.\n  pub fn remove_at(&self, position: usize) -> crate::Result<Option<MenuItemKind<R>>> {\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0)\n        .as_ref()\n        .remove_at(position)\n        .map(|i| MenuItemKind::from_muda(self_.0.app_handle.clone(), i))\n    })\n  }\n\n  /// Retrieves the menu item matching the given identifier.\n  pub fn get<'a, I>(&self, id: &'a I) -> Option<MenuItemKind<R>>\n  where\n    I: ?Sized,\n    MenuId: PartialEq<&'a I>,\n  {\n    self\n      .items()\n      .unwrap_or_default()\n      .into_iter()\n      .find(|i| i.id() == &id)\n  }\n\n  /// Returns a list of menu items that has been added to this submenu.\n  pub fn items(&self) -> crate::Result<Vec<MenuItemKind<R>>> {\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0)\n        .as_ref()\n        .items()\n        .into_iter()\n        .map(|i| MenuItemKind::from_muda(self_.0.app_handle.clone(), i))\n        .collect::<Vec<_>>()\n    })\n  }\n\n  /// Get the text for this submenu.\n  pub fn text(&self) -> crate::Result<String> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().text())\n  }\n\n  /// Set the text for this submenu. `text` could optionally contain\n  /// an `&` before a character to assign this character as the mnemonic\n  /// for this submenu. To display a `&` without assigning a mnemonic, use `&&`.\n  pub fn set_text<S: AsRef<str>>(&self, text: S) -> crate::Result<()> {\n    let text = text.as_ref().to_string();\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_text(text))\n  }\n\n  /// Get whether this submenu is enabled or not.\n  pub fn is_enabled(&self) -> crate::Result<bool> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().is_enabled())\n  }\n\n  /// Enable or disable this submenu.\n  pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_enabled(enabled))\n  }\n\n  /// Set this submenu as the Window menu for the application on macOS.\n  ///\n  /// This will cause macOS to automatically add window-switching items and\n  /// certain other items to the menu.\n  #[cfg(target_os = \"macos\")]\n  pub fn set_as_windows_menu_for_nsapp(&self) -> crate::Result<()> {\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().set_as_windows_menu_for_nsapp()\n    })?;\n    Ok(())\n  }\n\n  /// Set this submenu as the Help menu for the application on macOS.\n  ///\n  /// This will cause macOS to automatically add a search box to the menu.\n  ///\n  /// If no menu is set as the Help menu, macOS will automatically use any menu\n  /// which has a title matching the localized word \"Help\".\n  #[cfg(target_os = \"macos\")]\n  pub fn set_as_help_menu_for_nsapp(&self) -> crate::Result<()> {\n    run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().set_as_help_menu_for_nsapp()\n    })?;\n    Ok(())\n  }\n\n  /// Change this submenu icon or remove it.\n  pub fn set_icon(&self, icon: Option<crate::image::Image<'_>>) -> crate::Result<()> {\n    let icon = match icon {\n      Some(i) => Some(i.try_into()?),\n      None => None,\n    };\n    run_item_main_thread!(self, |self_: Self| (*self_.0).as_ref().set_icon(icon))\n  }\n\n  /// Change this submenu icon to a native image or remove it.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows / Linux**: Unsupported.\n  pub fn set_native_icon(&self, _icon: Option<NativeIcon>) -> crate::Result<()> {\n    #[cfg(target_os = \"macos\")]\n    return run_item_main_thread!(self, |self_: Self| {\n      (*self_.0).as_ref().set_native_icon(_icon.map(Into::into))\n    });\n    #[allow(unreachable_code)]\n    Ok(())\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/path/android.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::Result;\nuse crate::{plugin::PluginHandle, Runtime};\nuse std::path::{Path, PathBuf};\n\n/// A helper class to access the mobile path APIs.\npub struct PathResolver<R: Runtime>(pub(crate) PluginHandle<R>);\n\nimpl<R: Runtime> Clone for PathResolver<R> {\n  fn clone(&self) -> Self {\n    Self(self.0.clone())\n  }\n}\n\n#[derive(serde::Deserialize)]\nstruct PathResponse {\n  path: PathBuf,\n}\n\n#[derive(serde::Serialize)]\nstruct GetFileNameFromUriRequest<'a> {\n  uri: &'a str,\n}\n\n#[derive(serde::Deserialize)]\nstruct GetFileNameFromUriResponse {\n  name: Option<String>,\n}\n\nimpl<R: Runtime> PathResolver<R> {\n  /// Returns the final component of the `Path`, if there is one.\n  ///\n  /// If the path is a normal file, this is the file name. If it's the path of a directory, this\n  /// is the directory name.\n  ///\n  /// Returns [`None`] if the path terminates in `..`.\n  ///\n  /// On Android this also supports checking the file name of content URIs, such as the values returned by the dialog plugin.\n  ///\n  /// If you are dealing with plain file system paths or not worried about Android content URIs, prefer [`Path::file_name`].\n  pub fn file_name(&self, path: &str) -> Option<String> {\n    if path.starts_with(\"content://\") || path.starts_with(\"file://\") {\n      self\n        .0\n        .run_mobile_plugin::<GetFileNameFromUriResponse>(\n          \"getFileNameFromUri\",\n          GetFileNameFromUriRequest { uri: path },\n        )\n        .map(|r| r.name)\n        .unwrap_or_else(|e| {\n          log::error!(\"failed to get file name from URI: {e}\");\n          None\n        })\n    } else {\n      Path::new(path)\n        .file_name()\n        .map(|name| name.to_string_lossy().into_owned())\n    }\n  }\n\n  fn call_resolve(&self, dir: &str) -> Result<PathBuf> {\n    self\n      .0\n      .run_mobile_plugin::<PathResponse>(dir, ())\n      .map(|r| r.path)\n      .map_err(Into::into)\n  }\n\n  /// Returns the path to the user's audio directory.\n  pub fn audio_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getAudioDir\")\n  }\n\n  /// Returns the path to the user's cache directory.\n  pub fn cache_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getExternalCacheDir\")\n  }\n\n  /// Returns the path to the user's config directory.\n  pub fn config_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getConfigDir\")\n  }\n\n  /// Returns the path to the user's data directory.\n  pub fn data_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getDataDir\")\n  }\n\n  /// Returns the path to the user's local data directory.\n  pub fn local_data_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getDataDir\")\n  }\n\n  /// Returns the path to the user's document directory.\n  pub fn document_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getDocumentDir\")\n  }\n\n  /// Returns the path to the user's download directory.\n  pub fn download_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getDownloadDir\")\n  }\n\n  /// Returns the path to the user's picture directory.\n  pub fn picture_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getPictureDir\")\n  }\n\n  /// Returns the path to the user's public directory.\n  pub fn public_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getPublicDir\")\n  }\n\n  /// Returns the path to the user's video dir\n  pub fn video_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getVideoDir\")\n  }\n\n  /// Returns the path to the resource directory of this app.\n  pub fn resource_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getResourcesDir\")\n  }\n\n  /// Returns the path to the suggested directory for your app's config files.\n  ///\n  /// Resolves to [`config_dir`]`/${bundle_identifier}`.\n  pub fn app_config_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getConfigDir\")\n  }\n\n  /// Returns the path to the suggested directory for your app's data files.\n  ///\n  /// Resolves to [`data_dir`]`/${bundle_identifier}`.\n  pub fn app_data_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getDataDir\")\n  }\n\n  /// Returns the path to the suggested directory for your app's local data files.\n  ///\n  /// Resolves to [`local_data_dir`]`/${bundle_identifier}`.\n  pub fn app_local_data_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getDataDir\")\n  }\n\n  /// Returns the path to the suggested directory for your app's cache files.\n  ///\n  /// Resolves to [`cache_dir`]`/${bundle_identifier}`.\n  pub fn app_cache_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getCacheDir\")\n  }\n\n  /// Returns the path to the suggested directory for your app's log files.\n  pub fn app_log_dir(&self) -> Result<PathBuf> {\n    self\n      .call_resolve(\"getConfigDir\")\n      .map(|dir| dir.join(\"logs\"))\n  }\n\n  /// A temporary directory. Resolves to [`std::env::temp_dir`].\n  pub fn temp_dir(&self) -> Result<PathBuf> {\n    Ok(std::env::temp_dir())\n  }\n\n  /// Returns the path to the user's home directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to `$HOME`.\n  /// - **macOS:** Resolves to `$HOME`.\n  /// - **Windows:** Resolves to `{FOLDERID_Profile}`.\n  /// - **iOS**: Cannot be written to directly, use one of the app paths instead.\n  pub fn home_dir(&self) -> Result<PathBuf> {\n    self.call_resolve(\"getHomeDir\")\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/path/desktop.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{Error, Result};\nuse crate::{AppHandle, Manager, Runtime};\nuse std::path::{Path, PathBuf};\n\n/// The path resolver is a helper class for general and application-specific path APIs.\npub struct PathResolver<R: Runtime>(pub(crate) AppHandle<R>);\n\nimpl<R: Runtime> Clone for PathResolver<R> {\n  fn clone(&self) -> Self {\n    Self(self.0.clone())\n  }\n}\n\nimpl<R: Runtime> PathResolver<R> {\n  /// Returns the final component of the `Path`, if there is one.\n  ///\n  /// If the path is a normal file, this is the file name. If it's the path of a directory, this\n  /// is the directory name.\n  ///\n  /// Returns [`None`] if the path terminates in `..`.\n  ///\n  /// On Android this also supports checking the file name of content URIs, such as the values returned by the dialog plugin.\n  ///\n  /// If you are dealing with plain file system paths or not worried about Android content URIs, prefer [`Path::file_name`].\n  pub fn file_name(&self, path: &str) -> Option<String> {\n    Path::new(path)\n      .file_name()\n      .map(|name| name.to_string_lossy().into_owned())\n  }\n\n  /// Returns the path to the user's audio directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_MUSIC_DIR`.\n  /// - **macOS:** Resolves to `$HOME/Music`.\n  /// - **Windows:** Resolves to `{FOLDERID_Music}`.\n  pub fn audio_dir(&self) -> Result<PathBuf> {\n    dirs::audio_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's cache directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to `$XDG_CACHE_HOME` or `$HOME/.cache`.\n  /// - **macOS:** Resolves to `$HOME/Library/Caches`.\n  /// - **Windows:** Resolves to `{FOLDERID_LocalAppData}`.\n  pub fn cache_dir(&self) -> Result<PathBuf> {\n    dirs::cache_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's config directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to `$XDG_CONFIG_HOME` or `$HOME/.config`.\n  /// - **macOS:** Resolves to `$HOME/Library/Application Support`.\n  /// - **Windows:** Resolves to `{FOLDERID_RoamingAppData}`.\n  pub fn config_dir(&self) -> Result<PathBuf> {\n    dirs::config_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's data directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to `$XDG_DATA_HOME` or `$HOME/.local/share`.\n  /// - **macOS:** Resolves to `$HOME/Library/Application Support`.\n  /// - **Windows:** Resolves to `{FOLDERID_RoamingAppData}`.\n  pub fn data_dir(&self) -> Result<PathBuf> {\n    dirs::data_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's local data directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to `$XDG_DATA_HOME` or `$HOME/.local/share`.\n  /// - **macOS:** Resolves to `$HOME/Library/Application Support`.\n  /// - **Windows:** Resolves to `{FOLDERID_LocalAppData}`.\n  pub fn local_data_dir(&self) -> Result<PathBuf> {\n    dirs::data_local_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's desktop directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DESKTOP_DIR`.\n  /// - **macOS:** Resolves to `$HOME/Desktop`.\n  /// - **Windows:** Resolves to `{FOLDERID_Desktop}`.\n  pub fn desktop_dir(&self) -> Result<PathBuf> {\n    dirs::desktop_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's document directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DOCUMENTS_DIR`.\n  /// - **macOS:** Resolves to `$HOME/Documents`.\n  /// - **Windows:** Resolves to `{FOLDERID_Documents}`.\n  pub fn document_dir(&self) -> Result<PathBuf> {\n    dirs::document_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's download directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DOWNLOAD_DIR`.\n  /// - **macOS:** Resolves to `$HOME/Downloads`.\n  /// - **Windows:** Resolves to `{FOLDERID_Downloads}`.\n  pub fn download_dir(&self) -> Result<PathBuf> {\n    dirs::download_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's executable directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to `$XDG_BIN_HOME/../bin` or `$XDG_DATA_HOME/../bin` or `$HOME/.local/bin`.\n  /// - **macOS:** Not supported.\n  /// - **Windows:** Not supported.\n  pub fn executable_dir(&self) -> Result<PathBuf> {\n    dirs::executable_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's font directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to `$XDG_DATA_HOME/fonts` or `$HOME/.local/share/fonts`.\n  /// - **macOS:** Resolves to `$HOME/Library/Fonts`.\n  /// - **Windows:** Not supported.\n  pub fn font_dir(&self) -> Result<PathBuf> {\n    dirs::font_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's home directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to `$HOME`.\n  /// - **macOS:** Resolves to `$HOME`.\n  /// - **Windows:** Resolves to `{FOLDERID_Profile}`.\n  /// - **iOS**: Cannot be written to directly, use one of the app paths instead.\n  pub fn home_dir(&self) -> Result<PathBuf> {\n    dirs::home_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's picture directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_PICTURES_DIR`.\n  /// - **macOS:** Resolves to `$HOME/Pictures`.\n  /// - **Windows:** Resolves to `{FOLDERID_Pictures}`.\n  pub fn picture_dir(&self) -> Result<PathBuf> {\n    dirs::picture_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's public directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_PUBLICSHARE_DIR`.\n  /// - **macOS:** Resolves to `$HOME/Public`.\n  /// - **Windows:** Resolves to `{FOLDERID_Public}`.\n  pub fn public_dir(&self) -> Result<PathBuf> {\n    dirs::public_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's runtime directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to `$XDG_RUNTIME_DIR`.\n  /// - **macOS:** Not supported.\n  /// - **Windows:** Not supported.\n  pub fn runtime_dir(&self) -> Result<PathBuf> {\n    dirs::runtime_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's template directory.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_TEMPLATES_DIR`.\n  /// - **macOS:** Not supported.\n  /// - **Windows:** Resolves to `{FOLDERID_Templates}`.\n  pub fn template_dir(&self) -> Result<PathBuf> {\n    dirs::template_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the user's video dir\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_VIDEOS_DIR`.\n  /// - **macOS:** Resolves to `$HOME/Movies`.\n  /// - **Windows:** Resolves to `{FOLDERID_Videos}`.\n  pub fn video_dir(&self) -> Result<PathBuf> {\n    dirs::video_dir().ok_or(Error::UnknownPath)\n  }\n\n  /// Returns the path to the resource directory of this app.\n  ///\n  /// ## Platform-specific\n  ///\n  /// Although we provide the exact path where this function resolves to,\n  /// this is not a contract and things might change in the future\n  ///\n  /// - **Windows:** Resolves to the directory that contains the main executable.\n  /// - **Linux:** When running in an AppImage, the `APPDIR` variable will be set to\n  ///   the mounted location of the app, and the resource dir will be `${APPDIR}/usr/lib/${exe_name}`.\n  ///   If not running in an AppImage, the path is `/usr/lib/${exe_name}`.\n  ///   When running the app from `src-tauri/target/(debug|release)/`, the path is `${exe_dir}/../lib/${exe_name}`.\n  /// - **macOS:** Resolves to `${exe_dir}/../Resources` (inside .app).\n  /// - **iOS:** Resolves to `${exe_dir}/assets`.\n  /// - **Android:** Currently the resources are stored in the APK as assets so it's not a normal file system path,\n  ///   we return a special URI prefix `asset://localhost/` here that can be used with the [file system plugin](https://tauri.app/plugin/file-system/),\n  ///   with that, you can read the files through [`FsExt::fs`](https://docs.rs/tauri-plugin-fs/latest/tauri_plugin_fs/trait.FsExt.html#tymethod.fs)\n  ///   like this: `app.fs().read_to_string(app.path().resource_dir().unwrap().join(\"resource\"));`\n  pub fn resource_dir(&self) -> Result<PathBuf> {\n    crate::utils::platform::resource_dir(self.0.package_info(), &self.0.env())\n      .map_err(|_| Error::UnknownPath)\n  }\n\n  /// Returns the path to the suggested directory for your app's config files.\n  ///\n  /// Resolves to [`config_dir`](Self::config_dir)`/${bundle_identifier}`.\n  pub fn app_config_dir(&self) -> Result<PathBuf> {\n    dirs::config_dir()\n      .ok_or(Error::UnknownPath)\n      .map(|dir| dir.join(&self.0.config().identifier))\n  }\n\n  /// Returns the path to the suggested directory for your app's data files.\n  ///\n  /// Resolves to [`data_dir`](Self::data_dir)`/${bundle_identifier}`.\n  pub fn app_data_dir(&self) -> Result<PathBuf> {\n    dirs::data_dir()\n      .ok_or(Error::UnknownPath)\n      .map(|dir| dir.join(&self.0.config().identifier))\n  }\n\n  /// Returns the path to the suggested directory for your app's local data files.\n  ///\n  /// Resolves to [`local_data_dir`](Self::local_data_dir)`/${bundle_identifier}`.\n  pub fn app_local_data_dir(&self) -> Result<PathBuf> {\n    dirs::data_local_dir()\n      .ok_or(Error::UnknownPath)\n      .map(|dir| dir.join(&self.0.config().identifier))\n  }\n\n  /// Returns the path to the suggested directory for your app's cache files.\n  ///\n  /// Resolves to [`cache_dir`](Self::cache_dir)`/${bundle_identifier}`.\n  pub fn app_cache_dir(&self) -> Result<PathBuf> {\n    dirs::cache_dir()\n      .ok_or(Error::UnknownPath)\n      .map(|dir| dir.join(&self.0.config().identifier))\n  }\n\n  /// Returns the path to the suggested directory for your app's log files.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Resolves to [`local_data_dir`](Self::local_data_dir)`/${bundle_identifier}/logs`.\n  /// - **macOS:** Resolves to [`home_dir`](Self::home_dir)`/Library/Logs/${bundle_identifier}`\n  /// - **Windows:** Resolves to [`local_data_dir`](Self::local_data_dir)`/${bundle_identifier}/logs`.\n  pub fn app_log_dir(&self) -> Result<PathBuf> {\n    #[cfg(target_os = \"macos\")]\n    let path = dirs::home_dir()\n      .ok_or(Error::UnknownPath)\n      .map(|dir| dir.join(\"Library/Logs\").join(&self.0.config().identifier));\n\n    #[cfg(not(target_os = \"macos\"))]\n    let path = dirs::data_local_dir()\n      .ok_or(Error::UnknownPath)\n      .map(|dir| dir.join(&self.0.config().identifier).join(\"logs\"));\n\n    path\n  }\n\n  /// A temporary directory. Resolves to [`std::env::temp_dir`].\n  pub fn temp_dir(&self) -> Result<PathBuf> {\n    Ok(std::env::temp_dir())\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/path/init.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nObject.defineProperty(window.__TAURI_INTERNALS__.plugins, 'path', {\n  value: {\n    sep: __TEMPLATE_sep__,\n    delimiter: __TEMPLATE_delimiter__\n  }\n})\n"
  },
  {
    "path": "crates/tauri/src/path/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  path::{Component, Display, Path, PathBuf},\n  str::FromStr,\n};\n\nuse crate::Runtime;\n\nuse serde::{de::Error as DeError, Deserialize, Deserializer, Serialize};\nuse serde_repr::{Deserialize_repr, Serialize_repr};\n\npub(crate) mod plugin;\n\nuse crate::error::*;\n\n#[cfg(target_os = \"android\")]\nmod android;\n#[cfg(not(target_os = \"android\"))]\nmod desktop;\n\n#[cfg(target_os = \"android\")]\npub use android::PathResolver;\n#[cfg(not(target_os = \"android\"))]\npub use desktop::PathResolver;\n\n/// A wrapper for [`PathBuf`] that prevents path traversal.\n///\n/// # Examples\n///\n/// ```\n/// # use tauri::path::SafePathBuf;\n/// assert!(SafePathBuf::new(\"../secret.txt\".into()).is_err());\n/// assert!(SafePathBuf::new(\"/home/user/stuff/../secret.txt\".into()).is_err());\n///\n/// assert!(SafePathBuf::new(\"./file.txt\".into()).is_ok());\n/// assert!(SafePathBuf::new(\"/home/user/secret.txt\".into()).is_ok());\n/// ```\n#[derive(Clone, Debug, Serialize)]\npub struct SafePathBuf(PathBuf);\n\nimpl SafePathBuf {\n  /// Validates the path for directory traversal vulnerabilities and returns a new [`SafePathBuf`] instance if it is safe.\n  pub fn new(path: PathBuf) -> std::result::Result<Self, &'static str> {\n    if path.components().any(|x| matches!(x, Component::ParentDir)) {\n      Err(\"cannot traverse directory, rewrite the path without the use of `../`\")\n    } else {\n      Ok(Self(path))\n    }\n  }\n\n  /// Returns an object that implements [`std::fmt::Display`] for safely printing paths.\n  ///\n  /// See [`PathBuf#method.display`] for more information.\n  pub fn display(&self) -> Display<'_> {\n    self.0.display()\n  }\n}\n\nimpl AsRef<Path> for SafePathBuf {\n  fn as_ref(&self) -> &Path {\n    self.0.as_ref()\n  }\n}\n\nimpl FromStr for SafePathBuf {\n  type Err = &'static str;\n\n  fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n    Self::new(s.into())\n  }\n}\n\nimpl<'de> Deserialize<'de> for SafePathBuf {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let path = PathBuf::deserialize(deserializer)?;\n    SafePathBuf::new(path).map_err(DeError::custom)\n  }\n}\n\n/// A base directory for a path.\n///\n/// The base directory is the optional root of a file system operation.\n/// If informed by the API call, all paths will be relative to the path of the given directory.\n///\n/// For more information, check the [`dirs` documentation](https://docs.rs/dirs/).\n#[derive(Serialize_repr, Deserialize_repr, Clone, Copy, Debug)]\n#[repr(u16)]\n#[non_exhaustive]\npub enum BaseDirectory {\n  /// The Audio directory.\n  /// Resolves to [`crate::path::PathResolver::audio_dir`].\n  Audio = 1,\n  /// The Cache directory.\n  /// Resolves to [`crate::path::PathResolver::cache_dir`].\n  Cache = 2,\n  /// The Config directory.\n  /// Resolves to [`crate::path::PathResolver::config_dir`].\n  Config = 3,\n  /// The Data directory.\n  /// Resolves to [`crate::path::PathResolver::data_dir`].\n  Data = 4,\n  /// The LocalData directory.\n  /// Resolves to [`crate::path::PathResolver::local_data_dir`].\n  LocalData = 5,\n  /// The Document directory.\n  /// Resolves to [`crate::path::PathResolver::document_dir`].\n  Document = 6,\n  /// The Download directory.\n  /// Resolves to [`crate::path::PathResolver::download_dir`].\n  Download = 7,\n  /// The Picture directory.\n  /// Resolves to [`crate::path::PathResolver::picture_dir`].\n  Picture = 8,\n  /// The Public directory.\n  /// Resolves to [`crate::path::PathResolver::public_dir`].\n  Public = 9,\n  /// The Video directory.\n  /// Resolves to [`crate::path::PathResolver::video_dir`].\n  Video = 10,\n  /// The Resource directory.\n  /// Resolves to [`crate::path::PathResolver::resource_dir`].\n  Resource = 11,\n  /// A temporary directory.\n  /// Resolves to [`std::env::temp_dir`].\n  Temp = 12,\n  /// The default app config directory.\n  /// Resolves to [`BaseDirectory::Config`]`/{bundle_identifier}`.\n  AppConfig = 13,\n  /// The default app data directory.\n  /// Resolves to [`BaseDirectory::Data`]`/{bundle_identifier}`.\n  AppData = 14,\n  /// The default app local data directory.\n  /// Resolves to [`BaseDirectory::LocalData`]`/{bundle_identifier}`.\n  AppLocalData = 15,\n  /// The default app cache directory.\n  /// Resolves to [`BaseDirectory::Cache`]`/{bundle_identifier}`.\n  AppCache = 16,\n  /// The default app log directory.\n  /// Resolves to [`BaseDirectory::Home`]`/Library/Logs/{bundle_identifier}` on macOS\n  /// and [`BaseDirectory::Config`]`/{bundle_identifier}/logs` on linux and Windows.\n  AppLog = 17,\n  /// The Desktop directory.\n  /// Resolves to [`crate::path::PathResolver::desktop_dir`].\n  #[cfg(not(target_os = \"android\"))]\n  Desktop = 18,\n  /// The Executable directory.\n  /// Resolves to [`crate::path::PathResolver::executable_dir`].\n  #[cfg(not(target_os = \"android\"))]\n  Executable = 19,\n  /// The Font directory.\n  /// Resolves to [`crate::path::PathResolver::font_dir`].\n  #[cfg(not(target_os = \"android\"))]\n  Font = 20,\n  /// The Home directory.\n  /// Resolves to [`crate::path::PathResolver::home_dir`].\n  Home = 21,\n  /// The Runtime directory.\n  /// Resolves to [`crate::path::PathResolver::runtime_dir`].\n  #[cfg(not(target_os = \"android\"))]\n  Runtime = 22,\n  /// The Template directory.\n  /// Resolves to [`crate::path::PathResolver::template_dir`].\n  #[cfg(not(target_os = \"android\"))]\n  Template = 23,\n}\n\nimpl BaseDirectory {\n  /// Gets the variable that represents this [`BaseDirectory`] for string paths.\n  pub fn variable(self) -> &'static str {\n    match self {\n      Self::Audio => \"$AUDIO\",\n      Self::Cache => \"$CACHE\",\n      Self::Config => \"$CONFIG\",\n      Self::Data => \"$DATA\",\n      Self::LocalData => \"$LOCALDATA\",\n      Self::Document => \"$DOCUMENT\",\n      Self::Download => \"$DOWNLOAD\",\n      Self::Picture => \"$PICTURE\",\n      Self::Public => \"$PUBLIC\",\n      Self::Video => \"$VIDEO\",\n      Self::Resource => \"$RESOURCE\",\n      Self::Temp => \"$TEMP\",\n      Self::AppConfig => \"$APPCONFIG\",\n      Self::AppData => \"$APPDATA\",\n      Self::AppLocalData => \"$APPLOCALDATA\",\n      Self::AppCache => \"$APPCACHE\",\n      Self::AppLog => \"$APPLOG\",\n      Self::Home => \"$HOME\",\n\n      #[cfg(not(target_os = \"android\"))]\n      Self::Desktop => \"$DESKTOP\",\n      #[cfg(not(target_os = \"android\"))]\n      Self::Executable => \"$EXE\",\n      #[cfg(not(target_os = \"android\"))]\n      Self::Font => \"$FONT\",\n      #[cfg(not(target_os = \"android\"))]\n      Self::Runtime => \"$RUNTIME\",\n      #[cfg(not(target_os = \"android\"))]\n      Self::Template => \"$TEMPLATE\",\n    }\n  }\n\n  /// Gets the [`BaseDirectory`] associated with the given variable, or [`None`] if the variable doesn't match any.\n  pub fn from_variable(variable: &str) -> Option<Self> {\n    let res = match variable {\n      \"$AUDIO\" => Self::Audio,\n      \"$CACHE\" => Self::Cache,\n      \"$CONFIG\" => Self::Config,\n      \"$DATA\" => Self::Data,\n      \"$LOCALDATA\" => Self::LocalData,\n      \"$DOCUMENT\" => Self::Document,\n      \"$DOWNLOAD\" => Self::Download,\n\n      \"$PICTURE\" => Self::Picture,\n      \"$PUBLIC\" => Self::Public,\n      \"$VIDEO\" => Self::Video,\n      \"$RESOURCE\" => Self::Resource,\n      \"$TEMP\" => Self::Temp,\n      \"$APPCONFIG\" => Self::AppConfig,\n      \"$APPDATA\" => Self::AppData,\n      \"$APPLOCALDATA\" => Self::AppLocalData,\n      \"$APPCACHE\" => Self::AppCache,\n      \"$APPLOG\" => Self::AppLog,\n      \"$HOME\" => Self::Home,\n\n      #[cfg(not(target_os = \"android\"))]\n      \"$DESKTOP\" => Self::Desktop,\n      #[cfg(not(target_os = \"android\"))]\n      \"$EXE\" => Self::Executable,\n      #[cfg(not(target_os = \"android\"))]\n      \"$FONT\" => Self::Font,\n      #[cfg(not(target_os = \"android\"))]\n      \"$RUNTIME\" => Self::Runtime,\n      #[cfg(not(target_os = \"android\"))]\n      \"$TEMPLATE\" => Self::Template,\n\n      _ => return None,\n    };\n    Some(res)\n  }\n}\n\nimpl<R: Runtime> PathResolver<R> {\n  /// Resolves the path with the base directory.\n  ///\n  /// # Examples\n  ///\n  /// ```rust,no_run\n  /// use tauri::{path::BaseDirectory, Manager};\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let path = app.path().resolve(\"path/to/something\", BaseDirectory::Config)?;\n  ///     assert_eq!(path.to_str().unwrap(), \"/home/${whoami}/.config/path/to/something\");\n  ///     Ok(())\n  ///   });\n  /// ```\n  pub fn resolve<P: AsRef<Path>>(&self, path: P, base_directory: BaseDirectory) -> Result<PathBuf> {\n    resolve_path::<R>(self, base_directory, Some(path.as_ref().to_path_buf()))\n  }\n\n  /// Parse the given path, resolving a [`BaseDirectory`] variable if the path starts with one.\n  ///\n  /// # Examples\n  ///\n  /// ```rust,no_run\n  /// use tauri::Manager;\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let path = app.path().parse(\"$HOME/.bashrc\")?;\n  ///     assert_eq!(path.to_str().unwrap(), \"/home/${whoami}/.bashrc\");\n  ///     Ok(())\n  ///   });\n  /// ```\n  pub fn parse<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf> {\n    let mut p = PathBuf::new();\n    let mut components = path.as_ref().components();\n    match components.next() {\n      Some(Component::Normal(str)) => {\n        if let Some(base_directory) = BaseDirectory::from_variable(&str.to_string_lossy()) {\n          p.push(resolve_path::<R>(self, base_directory, None)?);\n        } else {\n          p.push(str);\n        }\n      }\n      Some(component) => p.push(component),\n      None => (),\n    }\n\n    for component in components {\n      if let Component::ParentDir = component {\n        continue;\n      }\n      p.push(component);\n    }\n\n    Ok(p)\n  }\n}\n\nfn resolve_path<R: Runtime>(\n  resolver: &PathResolver<R>,\n  directory: BaseDirectory,\n  path: Option<PathBuf>,\n) -> Result<PathBuf> {\n  let resolve_resource = matches!(directory, BaseDirectory::Resource);\n  let mut base_dir_path = match directory {\n    BaseDirectory::Audio => resolver.audio_dir(),\n    BaseDirectory::Cache => resolver.cache_dir(),\n    BaseDirectory::Config => resolver.config_dir(),\n    BaseDirectory::Data => resolver.data_dir(),\n    BaseDirectory::LocalData => resolver.local_data_dir(),\n    BaseDirectory::Document => resolver.document_dir(),\n    BaseDirectory::Download => resolver.download_dir(),\n    BaseDirectory::Picture => resolver.picture_dir(),\n    BaseDirectory::Public => resolver.public_dir(),\n    BaseDirectory::Video => resolver.video_dir(),\n    BaseDirectory::Resource => resolver.resource_dir(),\n    BaseDirectory::Temp => resolver.temp_dir(),\n    BaseDirectory::AppConfig => resolver.app_config_dir(),\n    BaseDirectory::AppData => resolver.app_data_dir(),\n    BaseDirectory::AppLocalData => resolver.app_local_data_dir(),\n    BaseDirectory::AppCache => resolver.app_cache_dir(),\n    BaseDirectory::AppLog => resolver.app_log_dir(),\n    BaseDirectory::Home => resolver.home_dir(),\n    #[cfg(not(target_os = \"android\"))]\n    BaseDirectory::Desktop => resolver.desktop_dir(),\n    #[cfg(not(target_os = \"android\"))]\n    BaseDirectory::Executable => resolver.executable_dir(),\n    #[cfg(not(target_os = \"android\"))]\n    BaseDirectory::Font => resolver.font_dir(),\n    #[cfg(not(target_os = \"android\"))]\n    BaseDirectory::Runtime => resolver.runtime_dir(),\n    #[cfg(not(target_os = \"android\"))]\n    BaseDirectory::Template => resolver.template_dir(),\n  }?;\n\n  if let Some(path) = path {\n    // use the same path resolution mechanism as the bundler's resource injection algorithm\n    if resolve_resource {\n      let mut resource_path = PathBuf::new();\n      for component in path.components() {\n        match component {\n          Component::Prefix(_) => {}\n          Component::RootDir => resource_path.push(\"_root_\"),\n          Component::CurDir => {}\n          Component::ParentDir => resource_path.push(\"_up_\"),\n          Component::Normal(p) => resource_path.push(p),\n        }\n      }\n      base_dir_path.push(resource_path);\n    } else {\n      base_dir_path.push(path);\n    }\n  }\n\n  Ok(base_dir_path)\n}\n\n#[cfg(test)]\nmod test {\n  use super::SafePathBuf;\n  use quickcheck::{Arbitrary, Gen};\n\n  use std::path::PathBuf;\n\n  impl Arbitrary for SafePathBuf {\n    fn arbitrary(g: &mut Gen) -> Self {\n      Self(PathBuf::arbitrary(g))\n    }\n\n    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {\n      Box::new(self.0.shrink().map(SafePathBuf))\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/path/plugin.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::{Component, Path, PathBuf, MAIN_SEPARATOR};\n\nuse serialize_to_javascript::{default_template, DefaultTemplate, Template};\n\nuse super::{BaseDirectory, Error, PathResolver, Result};\nuse crate::{\n  command,\n  plugin::{Builder, TauriPlugin},\n  AppHandle, Manager, Runtime, State,\n};\n\n/// Normalize a path, removing things like `.` and `..`, this snippet is taken from cargo's paths util.\n/// <https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106>\nfn normalize_path(path: &Path) -> PathBuf {\n  let mut components = path.components().peekable();\n  let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() {\n    components.next();\n    PathBuf::from(c.as_os_str())\n  } else {\n    PathBuf::new()\n  };\n\n  for component in components {\n    match component {\n      Component::Prefix(..) => unreachable!(),\n      Component::RootDir => {\n        ret.push(component.as_os_str());\n      }\n      Component::CurDir => {}\n      Component::ParentDir => {\n        ret.pop();\n      }\n      Component::Normal(c) => {\n        ret.push(c);\n      }\n    }\n  }\n  ret\n}\n\n/// Normalize a path, removing things like `.` and `..`, this snippet is taken from cargo's paths util but\n/// slightly modified to not resolve absolute paths.\n/// <https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106>\nfn normalize_path_no_absolute(path: &Path) -> PathBuf {\n  let mut components = path.components().peekable();\n  let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() {\n    components.next();\n    PathBuf::from(c.as_os_str())\n  } else {\n    PathBuf::new()\n  };\n\n  for component in components {\n    match component {\n      Component::Prefix(..) => unreachable!(),\n      Component::RootDir => {\n        ret.push(component.as_os_str());\n      }\n      Component::CurDir => {}\n      Component::ParentDir => {\n        ret.pop();\n      }\n      Component::Normal(c) => {\n        // Using PathBuf::push here will replace the whole path if an absolute path is encountered\n        // which is not the intended behavior, so instead of that, convert the current resolved path\n        // to a string and do simple string concatenation with the current component then convert it\n        // back to a PathBuf\n        let mut p = ret.to_string_lossy().to_string();\n        // Only add a separator if it doesn't have one already or if current normalized path is empty,\n        // this ensures it won't have an unwanted leading separator\n        if !p.is_empty() && !p.ends_with('/') && !p.ends_with('\\\\') {\n          p.push(MAIN_SEPARATOR);\n        }\n        if let Some(c) = c.to_str() {\n          p.push_str(c);\n        }\n        ret = PathBuf::from(p);\n      }\n    }\n  }\n  ret\n}\n\n#[command(root = \"crate\")]\npub fn resolve_directory<R: Runtime>(\n  _app: AppHandle<R>,\n  resolver: State<'_, PathResolver<R>>,\n  directory: BaseDirectory,\n  path: Option<PathBuf>,\n) -> Result<PathBuf> {\n  super::resolve_path(&resolver, directory, path).map(|p| dunce::simplified(&p).to_path_buf())\n}\n\n#[command(root = \"crate\")]\npub fn resolve(paths: Vec<String>) -> Result<PathBuf> {\n  // Start with current directory then start adding paths from the vector one by one using `PathBuf.push()` which\n  // will ensure that if an absolute path is encountered in the iteration, it will be used as the current full path.\n  //\n  // examples:\n  // 1. `vec![\".\"]` or `vec![]` will be equal to `std::env::current_dir()`\n  // 2. `vec![\"/foo/bar\", \"/tmp/file\", \"baz\"]` will be equal to `PathBuf::from(\"/tmp/file/baz\")`\n  let mut path = std::env::current_dir().map_err(Error::CurrentDir)?;\n  for p in paths {\n    path.push(p);\n  }\n  Ok(dunce::simplified(&normalize_path(&path)).to_path_buf())\n}\n\n#[command(root = \"crate\")]\npub fn normalize(path: String) -> String {\n  let mut p = dunce::simplified(&normalize_path_no_absolute(Path::new(&path)))\n    .to_string_lossy()\n    .to_string();\n\n  // Node.js behavior is to return `\"..\"` for `normalize(\"..\")`\n  // and `\".\"` for `normalize(\"\")` or `normalize(\".\")`\n  if p.is_empty() && path == \"..\" {\n    \"..\".into()\n  } else if p.is_empty() && path == \".\" {\n    \".\".into()\n  } else {\n    // Add a trailing separator if the path passed to this functions had a trailing separator. That's how Node.js behaves.\n    if (path.ends_with('/') || path.ends_with('\\\\')) && (!p.ends_with('/') || !p.ends_with('\\\\')) {\n      p.push(MAIN_SEPARATOR);\n    }\n    p\n  }\n}\n\n#[command(root = \"crate\")]\npub fn join(paths: Vec<String>) -> String {\n  let path = PathBuf::from(\n    paths\n      .into_iter()\n      .map(|mut p| {\n        // Add a `MAIN_SEPARATOR` if it doesn't already have one and is not an empty string.\n        // Doing this to ensure that the vector elements are separated in\n        // the resulting string so path.components() can work correctly when called\n        // in `normalize_path_no_absolute()` later on.\n        if !p.is_empty() && !p.ends_with('/') && !p.ends_with('\\\\') {\n          p.push(MAIN_SEPARATOR);\n        }\n        p\n      })\n      .collect::<String>(),\n  );\n\n  let p = dunce::simplified(&normalize_path_no_absolute(&path))\n    .to_string_lossy()\n    .to_string();\n\n  if p.is_empty() {\n    \".\".into()\n  } else {\n    p\n  }\n}\n\n#[command(root = \"crate\")]\npub fn dirname(path: String) -> Result<PathBuf> {\n  match Path::new(&path).parent() {\n    Some(p) => Ok(dunce::simplified(p).to_path_buf()),\n    None => Err(Error::NoParent),\n  }\n}\n\n#[command(root = \"crate\")]\npub fn extname<R: Runtime>(app: AppHandle<R>, path: String) -> Result<String> {\n  let file_name = app.path().file_name(&path).ok_or(Error::NoExtension)?;\n  match Path::new(&file_name)\n    .extension()\n    .and_then(std::ffi::OsStr::to_str)\n  {\n    Some(p) => Ok(p.to_string()),\n    None => Err(Error::NoExtension),\n  }\n}\n\n#[command(root = \"crate\")]\npub fn basename<R: Runtime>(app: AppHandle<R>, path: &str, ext: Option<&str>) -> Result<String> {\n  let file_name = app.path().file_name(path);\n  match file_name {\n    Some(p) => {\n      let maybe_stripped = if let Some(ext) = ext {\n        p.strip_suffix(ext).unwrap_or(&p).to_string()\n      } else {\n        p\n      };\n      Ok(maybe_stripped)\n    }\n    None => Err(Error::NoBasename),\n  }\n}\n\n#[command(root = \"crate\")]\npub fn is_absolute(path: String) -> bool {\n  Path::new(&path).is_absolute()\n}\n\n#[derive(Template)]\n#[default_template(\"./init.js\")]\nstruct InitJavascript {\n  sep: &'static str,\n  delimiter: &'static str,\n}\n\n/// Initializes the plugin.\npub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {\n  #[cfg(windows)]\n  let (sep, delimiter) = (\"\\\\\", \";\");\n  #[cfg(not(windows))]\n  let (sep, delimiter) = (\"/\", \":\");\n\n  let init_js = InitJavascript { sep, delimiter }\n    .render_default(&Default::default())\n    // this will never fail with the above sep and delimiter values\n    .unwrap();\n\n  Builder::new(\"path\")\n    .invoke_handler(crate::generate_handler![\n      #![plugin(path)]\n      resolve_directory,\n      resolve,\n      normalize,\n      join,\n      dirname,\n      extname,\n      basename,\n      is_absolute\n    ])\n    .js_init_script(init_js.to_string())\n    .setup(|app, _api| {\n      #[cfg(target_os = \"android\")]\n      {\n        let handle = _api.register_android_plugin(\"app.tauri\", \"PathPlugin\")?;\n        app.manage(PathResolver(handle));\n      }\n\n      #[cfg(not(target_os = \"android\"))]\n      {\n        app.manage(PathResolver(app.clone()));\n      }\n\n      Ok(())\n    })\n    .build()\n}\n\n#[cfg(test)]\nmod tests {\n  use crate::test::mock_app;\n\n  #[test]\n  fn basename() {\n    let app = mock_app();\n    let path = \"/path/to/some-json-file.json\";\n    assert_eq!(\n      super::basename(app.handle().clone(), path, Some(\".json\")).unwrap(),\n      \"some-json-file\"\n    );\n\n    let path = \"/path/to/some-json-file.json\";\n    assert_eq!(\n      super::basename(app.handle().clone(), path, Some(\"json\")).unwrap(),\n      \"some-json-file.\"\n    );\n\n    let path = \"/path/to/some-json-file.html.json\";\n    assert_eq!(\n      super::basename(app.handle().clone(), path, Some(\".json\")).unwrap(),\n      \"some-json-file.html\"\n    );\n\n    let path = \"/path/to/some-json-file.json.json\";\n    assert_eq!(\n      super::basename(app.handle().clone(), path, Some(\".json\")).unwrap(),\n      \"some-json-file.json\"\n    );\n\n    let path = \"/path/to/some-json-file.json.html\";\n    assert_eq!(\n      super::basename(app.handle().clone(), path, Some(\".json\")).unwrap(),\n      \"some-json-file.json.html\"\n    );\n  }\n\n  #[test]\n  fn join() {\n    fn check(paths: Vec<&str>, expected_unix: &str, expected_windows: &str) {\n      let expected = if cfg!(windows) {\n        expected_windows\n      } else {\n        expected_unix\n      };\n      let paths = paths.into_iter().map(String::from).collect();\n      assert_eq!(super::join(paths), expected);\n    }\n\n    check(vec![\"\"], \".\", \".\");\n    check(vec![\"\", \"\"], \".\", \".\");\n    check(vec![\"a\"], \"a\", \"a\");\n    check(vec![\"\", \"a\"], \"a\", \"a\");\n    check(vec![\"a\", \"b\"], \"a/b\", r\"a\\b\");\n    check(vec![\"a\", \"\", \"b\"], \"a/b\", r\"a\\b\");\n    check(vec![\"a\", \"/b\", \"c\"], \"a/b/c\", r\"a\\b\\c\");\n    check(vec![\"a\", \"b/c\", \"d\"], \"a/b/c/d\", r\"a\\b\\c\\d\");\n    check(vec![\"a/\", \"b\"], \"a/b\", r\"a\\b\");\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/pattern.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#[cfg(feature = \"isolation\")]\nuse std::sync::Arc;\n\nuse serde::Serialize;\nuse serialize_to_javascript::{default_template, Template};\n\n/// The domain of the isolation iframe source.\n#[cfg(feature = \"isolation\")]\npub const ISOLATION_IFRAME_SRC_DOMAIN: &str = \"localhost\";\n\n/// An application pattern.\n#[derive(Debug)]\npub enum Pattern {\n  /// The brownfield pattern.\n  Brownfield,\n  /// Isolation pattern. Recommended for security purposes.\n  #[cfg(feature = \"isolation\")]\n  Isolation {\n    /// The HTML served on `isolation://index.html`.\n    assets: Arc<tauri_utils::assets::EmbeddedAssets>,\n\n    /// The schema used for the isolation frames.\n    schema: String,\n\n    /// A random string used to ensure that the message went through the isolation frame.\n    ///\n    /// This should be regenerated at runtime.\n    key: String,\n\n    /// Cryptographically secure keys\n    crypto_keys: Box<tauri_utils::pattern::isolation::Keys>,\n  },\n}\n\n/// The shape of the JavaScript Pattern config\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"lowercase\", tag = \"pattern\")]\npub(crate) enum PatternObject {\n  /// Brownfield pattern.\n  Brownfield,\n  /// Isolation pattern. Recommended for security purposes.\n  #[cfg(feature = \"isolation\")]\n  Isolation {\n    /// Which `IsolationSide` this `PatternObject` is getting injected into\n    side: IsolationSide,\n  },\n}\n\nimpl From<&Pattern> for PatternObject {\n  fn from(pattern: &Pattern) -> Self {\n    match pattern {\n      Pattern::Brownfield => Self::Brownfield,\n      #[cfg(feature = \"isolation\")]\n      Pattern::Isolation { .. } => Self::Isolation {\n        side: IsolationSide::default(),\n      },\n    }\n  }\n}\n\n/// Where the JavaScript is injected to\n#[cfg(feature = \"isolation\")]\n#[derive(Default, Debug, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub(crate) enum IsolationSide {\n  /// Original frame, the Brownfield application\n  #[default]\n  Original,\n  /// Secure frame, the isolation security application\n  #[allow(dead_code)]\n  Secure,\n}\n\n#[derive(Template)]\n#[default_template(\"../scripts/pattern.js\")]\npub(crate) struct PatternJavascript {\n  pub(crate) pattern: PatternObject,\n}\n\n#[cfg(feature = \"isolation\")]\npub(crate) fn format_real_schema(schema: &str, https: bool) -> String {\n  if cfg!(windows) || cfg!(target_os = \"android\") {\n    let scheme = if https { \"https\" } else { \"http\" };\n    format!(\"{scheme}://{schema}.{ISOLATION_IFRAME_SRC_DOMAIN}/\")\n  } else {\n    format!(\"{schema}://{ISOLATION_IFRAME_SRC_DOMAIN}/\")\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/plugin/mobile.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{PluginApi, PluginHandle};\n\nuse crate::{ipc::Channel, AppHandle, Runtime};\n#[cfg(target_os = \"android\")]\nuse crate::{\n  runtime::RuntimeHandle,\n  sealed::{ManagerBase, RuntimeOrDispatch},\n};\n\n#[cfg(mobile)]\nuse std::sync::atomic::{AtomicI32, Ordering};\n#[cfg(mobile)]\nuse tokio::sync::oneshot;\n\nuse serde::{de::DeserializeOwned, Serialize};\n\nuse std::{\n  collections::HashMap,\n  fmt,\n  sync::{mpsc::channel, Mutex, OnceLock},\n};\n\ntype PluginResponse = Result<serde_json::Value, serde_json::Value>;\n\ntype PendingPluginCallHandler = Box<dyn FnOnce(PluginResponse) + Send + 'static>;\n\n#[cfg(mobile)]\nstatic PENDING_PLUGIN_CALLS_ID: AtomicI32 = AtomicI32::new(0);\nstatic PENDING_PLUGIN_CALLS: OnceLock<Mutex<HashMap<i32, PendingPluginCallHandler>>> =\n  OnceLock::new();\nstatic CHANNELS: OnceLock<Mutex<HashMap<u32, Channel<serde_json::Value>>>> = OnceLock::new();\n\n/// Possible errors when invoking a plugin.\n#[derive(Debug, thiserror::Error)]\npub enum PluginInvokeError {\n  /// Failed to reach platform webview handle.\n  #[error(\"the webview is unreachable\")]\n  UnreachableWebview,\n  /// JNI error.\n  #[cfg(target_os = \"android\")]\n  #[error(\"jni error: {0}\")]\n  Jni(#[from] jni::errors::Error),\n  /// Error returned from direct mobile plugin invoke.\n  #[error(transparent)]\n  InvokeRejected(#[from] ErrorResponse),\n  /// Failed to deserialize response.\n  #[error(\"failed to deserialize response: {0}\")]\n  CannotDeserializeResponse(serde_json::Error),\n  /// Failed to serialize request payload.\n  #[error(\"failed to serialize payload: {0}\")]\n  CannotSerializePayload(serde_json::Error),\n}\n\npub(crate) fn register_channel(channel: Channel<serde_json::Value>) {\n  CHANNELS\n    .get_or_init(Default::default)\n    .lock()\n    .unwrap()\n    .insert(channel.id(), channel);\n}\n\n/// Glue between Rust and the Kotlin code that sends the plugin response back.\n#[cfg(target_os = \"android\")]\npub fn handle_android_plugin_response(\n  env: &mut jni::JNIEnv<'_>,\n  id: i32,\n  success: jni::objects::JString<'_>,\n  error: jni::objects::JString<'_>,\n) {\n  let (payload, is_ok): (serde_json::Value, bool) = match (\n    env\n      .is_same_object(&success, jni::objects::JObject::default())\n      .unwrap_or_default(),\n    env\n      .is_same_object(&error, jni::objects::JObject::default())\n      .unwrap_or_default(),\n  ) {\n    // both null\n    (true, true) => (serde_json::Value::Null, true),\n    // error null\n    (false, true) => (\n      serde_json::from_str(env.get_string(&success).unwrap().to_str().unwrap()).unwrap(),\n      true,\n    ),\n    // success null\n    (true, false) => (\n      serde_json::from_str(env.get_string(&error).unwrap().to_str().unwrap()).unwrap(),\n      false,\n    ),\n    // both are set - impossible in the Kotlin code\n    (false, false) => unreachable!(),\n  };\n\n  if let Some(handler) = PENDING_PLUGIN_CALLS\n    .get_or_init(Default::default)\n    .lock()\n    .unwrap()\n    .remove(&id)\n  {\n    handler(if is_ok { Ok(payload) } else { Err(payload) });\n  }\n}\n\n/// Glue between Rust and the Kotlin code that sends the channel data.\n#[cfg(target_os = \"android\")]\npub fn send_channel_data(\n  env: &mut jni::JNIEnv<'_>,\n  channel_id: i64,\n  data_str: jni::objects::JString<'_>,\n) {\n  let data: serde_json::Value =\n    serde_json::from_str(env.get_string(&data_str).unwrap().to_str().unwrap()).unwrap();\n\n  if let Some(channel) = CHANNELS\n    .get_or_init(Default::default)\n    .lock()\n    .unwrap()\n    .get(&(channel_id as u32))\n  {\n    let _ = channel.send(data);\n  }\n}\n\n/// Error response from the Kotlin and Swift backends.\n#[derive(Debug, thiserror::Error, Clone, serde::Deserialize)]\npub struct ErrorResponse<T = ()> {\n  /// Error code.\n  pub code: Option<String>,\n  /// Error message.\n  pub message: Option<String>,\n  /// Optional error data.\n  #[serde(flatten)]\n  pub data: T,\n}\n\nimpl<T> fmt::Display for ErrorResponse<T> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    if let Some(code) = &self.code {\n      write!(f, \"[{code}]\")?;\n      if self.message.is_some() {\n        write!(f, \" - \")?;\n      }\n    }\n    if let Some(message) = &self.message {\n      write!(f, \"{message}\")?;\n    }\n    Ok(())\n  }\n}\n\nimpl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {\n  /// Registers an iOS plugin.\n  #[cfg(all(target_os = \"ios\", feature = \"wry\"))]\n  pub fn register_ios_plugin(\n    &self,\n    init_fn: unsafe fn() -> *const std::ffi::c_void,\n  ) -> Result<PluginHandle<R>, PluginInvokeError> {\n    if let Some(webview) = self.handle.manager.webviews().values().next() {\n      let (tx, rx) = channel();\n      let name = self.name;\n      let config = self.raw_config.clone();\n      webview\n        .with_webview(move |w| {\n          unsafe {\n            crate::ios::register_plugin(\n              &name.into(),\n              init_fn(),\n              &serde_json::to_string(&config).unwrap().as_str().into(),\n              w.inner() as _,\n            )\n          };\n          tx.send(()).unwrap();\n        })\n        .map_err(|_| PluginInvokeError::UnreachableWebview)?;\n      rx.recv().unwrap();\n    } else {\n      unsafe {\n        crate::ios::register_plugin(\n          &self.name.into(),\n          init_fn(),\n          &serde_json::to_string(&self.raw_config)\n            .unwrap()\n            .as_str()\n            .into(),\n          std::ptr::null(),\n        )\n      };\n    }\n    Ok(PluginHandle {\n      name: self.name,\n      handle: self.handle.clone(),\n    })\n  }\n\n  /// Registers an Android plugin.\n  #[cfg(target_os = \"android\")]\n  pub fn register_android_plugin(\n    &self,\n    plugin_identifier: &str,\n    class_name: &str,\n  ) -> Result<PluginHandle<R>, PluginInvokeError> {\n    use jni::{errors::Error as JniError, objects::JObject, JNIEnv};\n\n    fn initialize_plugin<R: Runtime>(\n      env: &mut JNIEnv<'_>,\n      activity: &JObject<'_>,\n      webview: &JObject<'_>,\n      runtime_handle: &R::Handle,\n      plugin_name: &'static str,\n      plugin_class: String,\n      plugin_config: &serde_json::Value,\n    ) -> Result<(), JniError> {\n      // instantiate plugin\n      let plugin_class = runtime_handle.find_class(env, activity, plugin_class)?;\n      let plugin = env.new_object(\n        plugin_class,\n        \"(Landroid/app/Activity;)V\",\n        &[activity.into()],\n      )?;\n\n      // load plugin\n\n      let plugin_manager = env\n        .call_method(\n          activity,\n          \"getPluginManager\",\n          \"()Lapp/tauri/plugin/PluginManager;\",\n          &[],\n        )?\n        .l()?;\n\n      let plugin_name = env.new_string(plugin_name)?;\n      let config = env.new_string(&serde_json::to_string(plugin_config).unwrap())?;\n      env.call_method(\n        plugin_manager,\n        \"load\",\n        \"(Landroid/webkit/WebView;Ljava/lang/String;Lapp/tauri/plugin/Plugin;Ljava/lang/String;)V\",\n        &[\n          webview.into(),\n          (&plugin_name).into(),\n          (&plugin).into(),\n          (&config).into(),\n        ],\n      )?;\n\n      Ok(())\n    }\n\n    let plugin_class = format!(\"{}/{}\", plugin_identifier.replace('.', \"/\"), class_name);\n    let plugin_name = self.name;\n    let plugin_config = self.raw_config.clone();\n    let runtime_handle = self.handle.runtime_handle.clone();\n    let (tx, rx) = channel();\n    self\n      .handle\n      .runtime_handle\n      .run_on_android_context(move |env, activity, webview| {\n        let result = initialize_plugin::<R>(\n          env,\n          activity,\n          webview,\n          &runtime_handle,\n          plugin_name,\n          plugin_class,\n          &plugin_config,\n        );\n        tx.send(result).unwrap();\n      });\n\n    rx.recv().unwrap()?;\n\n    Ok(PluginHandle {\n      name: self.name,\n      handle: self.handle.clone(),\n    })\n  }\n}\n\nimpl<R: Runtime> PluginHandle<R> {\n  /// Executes the given mobile command.\n  /// This is an async optimized variant of run_mobile_plugin\n  pub async fn run_mobile_plugin_async<T: DeserializeOwned>(\n    &self,\n    command: impl AsRef<str>,\n    payload: impl Serialize,\n  ) -> Result<T, PluginInvokeError> {\n    let (tx, rx) = oneshot::channel();\n    // the closure is an FnOnce but on Android we need to clone it (error handling)\n    let tx = std::sync::Arc::new(std::sync::Mutex::new(Some(tx)));\n    run_command(\n      self.name,\n      &self.handle,\n      command,\n      serde_json::to_value(payload).map_err(PluginInvokeError::CannotSerializePayload)?,\n      move |response| {\n        tx.lock().unwrap().take().unwrap().send(response).unwrap();\n      },\n    )?;\n\n    let response = rx.await.unwrap();\n\n    match response {\n      Ok(r) => serde_json::from_value(r).map_err(PluginInvokeError::CannotDeserializeResponse),\n      Err(r) => Err(\n        serde_json::from_value::<ErrorResponse>(r)\n          .map(Into::into)\n          .map_err(PluginInvokeError::CannotDeserializeResponse)?,\n      ),\n    }\n  }\n\n  /// Executes the given mobile command.\n  pub fn run_mobile_plugin<T: DeserializeOwned>(\n    &self,\n    command: impl AsRef<str>,\n    payload: impl Serialize,\n  ) -> Result<T, PluginInvokeError> {\n    let (tx, rx) = channel();\n    run_command(\n      self.name,\n      &self.handle,\n      command,\n      serde_json::to_value(payload).map_err(PluginInvokeError::CannotSerializePayload)?,\n      move |response| {\n        tx.send(response).unwrap();\n      },\n    )?;\n\n    let response = rx.recv().unwrap();\n    match response {\n      Ok(r) => serde_json::from_value(r).map_err(PluginInvokeError::CannotDeserializeResponse),\n      Err(r) => Err(\n        serde_json::from_value::<ErrorResponse>(r)\n          .map(Into::into)\n          .map_err(PluginInvokeError::CannotDeserializeResponse)?,\n      ),\n    }\n  }\n}\n\n#[cfg(target_os = \"ios\")]\npub(crate) fn run_command<R: Runtime, C: AsRef<str>, F: FnOnce(PluginResponse) + Send + 'static>(\n  name: &str,\n  _handle: &AppHandle<R>,\n  command: C,\n  payload: serde_json::Value,\n  handler: F,\n) -> Result<(), PluginInvokeError> {\n  use std::{\n    ffi::CStr,\n    os::raw::{c_char, c_int, c_ulonglong},\n  };\n\n  let id: i32 = PENDING_PLUGIN_CALLS_ID.fetch_add(1, Ordering::Relaxed);\n  PENDING_PLUGIN_CALLS\n    .get_or_init(Default::default)\n    .lock()\n    .unwrap()\n    .insert(id, Box::new(handler));\n\n  unsafe {\n    extern \"C\" fn plugin_command_response_handler(\n      id: c_int,\n      success: c_int,\n      payload: *const c_char,\n    ) {\n      let payload = unsafe {\n        assert!(!payload.is_null());\n        CStr::from_ptr(payload)\n      };\n\n      let handler = PENDING_PLUGIN_CALLS\n        .get_or_init(Default::default)\n        .lock()\n        .unwrap()\n        .remove(&id);\n      if let Some(handler) = handler {\n        let json = payload.to_str().unwrap();\n        match serde_json::from_str(json) {\n          Ok(payload) => {\n            handler(if success == 1 {\n              Ok(payload)\n            } else {\n              Err(payload)\n            });\n          }\n          Err(err) => {\n            handler(Err(format!(\"{err}, data: {}\", json).into()));\n          }\n        }\n      }\n    }\n\n    extern \"C\" fn send_channel_data_handler(id: c_ulonglong, payload: *const c_char) {\n      let payload = unsafe {\n        assert!(!payload.is_null());\n        CStr::from_ptr(payload)\n      };\n\n      if let Some(channel) = CHANNELS\n        .get_or_init(Default::default)\n        .lock()\n        .unwrap()\n        .get(&(id as u32))\n      {\n        let payload: serde_json::Value = serde_json::from_str(payload.to_str().unwrap()).unwrap();\n        let _ = channel.send(payload);\n      }\n    }\n\n    crate::ios::run_plugin_command(\n      id,\n      &name.into(),\n      &command.as_ref().into(),\n      &serde_json::to_string(&payload).unwrap().as_str().into(),\n      crate::ios::PluginMessageCallback(plugin_command_response_handler),\n      crate::ios::ChannelSendDataCallback(send_channel_data_handler),\n    );\n  }\n\n  Ok(())\n}\n\n#[cfg(target_os = \"android\")]\npub(crate) fn run_command<\n  R: Runtime,\n  C: AsRef<str>,\n  F: FnOnce(PluginResponse) + Send + Clone + 'static,\n>(\n  name: &str,\n  handle: &AppHandle<R>,\n  command: C,\n  payload: serde_json::Value,\n  handler: F,\n) -> Result<(), PluginInvokeError> {\n  use jni::{errors::Error as JniError, objects::JObject, JNIEnv};\n\n  fn run<R: Runtime>(\n    id: i32,\n    plugin: &str,\n    command: String,\n    payload: &serde_json::Value,\n    env: &mut JNIEnv<'_>,\n    activity: &JObject<'_>,\n  ) -> Result<(), JniError> {\n    let plugin = env.new_string(plugin)?;\n    let command = env.new_string(&command)?;\n    let data = env.new_string(&serde_json::to_string(payload).unwrap())?;\n    let plugin_manager = env\n      .call_method(\n        activity,\n        \"getPluginManager\",\n        \"()Lapp/tauri/plugin/PluginManager;\",\n        &[],\n      )?\n      .l()?;\n\n    env.call_method(\n      plugin_manager,\n      \"runCommand\",\n      \"(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V\",\n      &[\n        id.into(),\n        (&plugin).into(),\n        (&command).into(),\n        (&data).into(),\n      ],\n    )?;\n\n    Ok(())\n  }\n\n  let handle = match handle.runtime() {\n    RuntimeOrDispatch::Runtime(r) => r.handle(),\n    RuntimeOrDispatch::RuntimeHandle(h) => h,\n    _ => unreachable!(),\n  };\n\n  let id: i32 = PENDING_PLUGIN_CALLS_ID.fetch_add(1, Ordering::Relaxed);\n  let plugin_name = name.to_string();\n  let command = command.as_ref().to_string();\n\n  PENDING_PLUGIN_CALLS\n    .get_or_init(Default::default)\n    .lock()\n    .unwrap()\n    .insert(id, Box::new(handler.clone()));\n\n  handle.run_on_android_context(move |env, activity, _webview| {\n    if let Err(e) = run::<R>(id, &plugin_name, command, &payload, env, activity) {\n      handler(Err(e.to_string().into()));\n    }\n  });\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri/src/plugin.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The Tauri plugin extension to expand Tauri functionality.\n\nuse crate::{\n  app::UriSchemeResponder,\n  ipc::{Invoke, InvokeHandler, ScopeObject, ScopeValue},\n  manager::webview::UriSchemeProtocol,\n  utils::config::PluginConfig,\n  webview::PageLoadPayload,\n  AppHandle, Error, RunEvent, Runtime, UriSchemeContext, Webview, Window,\n};\nuse serde::{\n  de::{Deserialize, DeserializeOwned, Deserializer, Error as DeError},\n  Serialize, Serializer,\n};\nuse serde_json::Value as JsonValue;\nuse tauri_macros::default_runtime;\nuse tauri_runtime::webview::InitializationScript;\nuse thiserror::Error;\nuse url::Url;\n\nuse std::{\n  borrow::Cow,\n  collections::HashMap,\n  fmt::{self, Debug},\n  sync::Arc,\n};\n\n/// Mobile APIs.\n#[cfg(mobile)]\npub mod mobile;\n\n/// The plugin interface.\npub trait Plugin<R: Runtime>: Send {\n  /// The plugin name. Used as key on the plugin config object.\n  fn name(&self) -> &'static str;\n\n  /// Initializes the plugin.\n  #[allow(unused_variables)]\n  fn initialize(\n    &mut self,\n    app: &AppHandle<R>,\n    config: JsonValue,\n  ) -> Result<(), Box<dyn std::error::Error>> {\n    Ok(())\n  }\n\n  /// Add the provided JavaScript to a list of scripts that should be run after the global object has been created,\n  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.\n  ///\n  /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,\n  /// so global variables must be assigned to `window` instead of implicitly declared.\n  ///\n  /// This is executed only on the main frame.\n  /// If you only want to run it in all frames, use [`Plugin::initialization_script_2`] to set that to false.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:** scripts are always added to subframes.\n  /// - **Android:** When [addDocumentStartJavaScript] is not supported,\n  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).\n  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.\n  ///\n  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)\n  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)\n  fn initialization_script(&self) -> Option<String> {\n    None\n  }\n\n  // TODO: Change `initialization_script` to this in v3\n  /// Same as [`Plugin::initialization_script`] but returns an [`InitializationScript`] instead\n  /// We plan to replace [`Plugin::initialization_script`] with this signature in v3\n  fn initialization_script_2(&self) -> Option<InitializationScript> {\n    self\n      .initialization_script()\n      .map(|script| InitializationScript {\n        script,\n        for_main_frame_only: true,\n      })\n  }\n\n  /// Callback invoked when the window is created.\n  #[allow(unused_variables)]\n  fn window_created(&mut self, window: Window<R>) {}\n\n  /// Callback invoked when the webview is created.\n  #[allow(unused_variables)]\n  fn webview_created(&mut self, webview: Webview<R>) {}\n\n  /// Callback invoked when webview tries to navigate to the given Url. Returning false cancels navigation.\n  #[allow(unused_variables)]\n  fn on_navigation(&mut self, webview: &Webview<R>, url: &Url) -> bool {\n    true\n  }\n\n  /// Callback invoked when the webview performs a navigation to a page.\n  #[allow(unused_variables)]\n  fn on_page_load(&mut self, webview: &Webview<R>, payload: &PageLoadPayload<'_>) {}\n\n  /// Callback invoked when the event loop receives a new event.\n  #[allow(unused_variables)]\n  fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {}\n\n  /// Extend commands to [`crate::Builder::invoke_handler`].\n  #[allow(unused_variables)]\n  fn extend_api(&mut self, invoke: Invoke<R>) -> bool {\n    false\n  }\n}\n\ntype SetupHook<R, C> =\n  dyn FnOnce(&AppHandle<R>, PluginApi<R, C>) -> Result<(), Box<dyn std::error::Error>> + Send;\ntype OnWindowReady<R> = dyn FnMut(Window<R>) + Send;\ntype OnWebviewReady<R> = dyn FnMut(Webview<R>) + Send;\ntype OnEvent<R> = dyn FnMut(&AppHandle<R>, &RunEvent) + Send;\ntype OnNavigation<R> = dyn Fn(&Webview<R>, &Url) -> bool + Send;\ntype OnPageLoad<R> = dyn FnMut(&Webview<R>, &PageLoadPayload<'_>) + Send;\ntype OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send;\n\n/// A handle to a plugin.\n#[derive(Debug)]\n#[allow(dead_code)]\npub struct PluginHandle<R: Runtime> {\n  name: &'static str,\n  handle: AppHandle<R>,\n}\n\nimpl<R: Runtime> Clone for PluginHandle<R> {\n  fn clone(&self) -> Self {\n    Self {\n      name: self.name,\n      handle: self.handle.clone(),\n    }\n  }\n}\n\nimpl<R: Runtime> PluginHandle<R> {\n  /// Returns the application handle.\n  pub fn app(&self) -> &AppHandle<R> {\n    &self.handle\n  }\n}\n\n/// Api exposed to the plugin setup hook.\n#[derive(Clone)]\n#[allow(dead_code)]\npub struct PluginApi<R: Runtime, C: DeserializeOwned> {\n  handle: AppHandle<R>,\n  name: &'static str,\n  raw_config: Arc<JsonValue>,\n  config: C,\n}\n\nimpl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {\n  /// Returns the plugin configuration.\n  pub fn config(&self) -> &C {\n    &self.config\n  }\n\n  /// Returns the application handle.\n  pub fn app(&self) -> &AppHandle<R> {\n    &self.handle\n  }\n\n  /// Gets the global scope defined on the permissions that are part of the app ACL.\n  pub fn scope<T: ScopeObject>(&self) -> crate::Result<ScopeValue<T>> {\n    self\n      .handle\n      .manager\n      .runtime_authority\n      .lock()\n      .unwrap()\n      .scope_manager\n      .get_global_scope_typed(&self.handle, self.name)\n  }\n}\n\n/// Errors that can happen during [`Builder`].\n#[derive(Debug, Clone, Hash, PartialEq, Error)]\n#[non_exhaustive]\npub enum BuilderError {\n  /// Plugin attempted to use a reserved name.\n  #[error(\"plugin uses reserved name: {0}\")]\n  ReservedName(String),\n}\n\nconst RESERVED_PLUGIN_NAMES: &[&str] = &[\"core\", \"tauri\"];\n\n/// Builds a [`TauriPlugin`].\n///\n/// This Builder offers a more concise way to construct Tauri plugins than implementing the Plugin trait directly.\n///\n/// # Conventions\n///\n/// When using the Builder Pattern it is encouraged to export a function called `init` that constructs and returns the plugin.\n/// While plugin authors can provide every possible way to construct a plugin,\n/// sticking to the `init` function convention helps users to quickly identify the correct function to call.\n///\n/// ```rust\n/// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};\n///\n/// pub fn init<R: Runtime>() -> TauriPlugin<R> {\n///   Builder::new(\"example\")\n///     .build()\n/// }\n/// ```\n///\n/// When plugins expose more complex configuration options, it can be helpful to provide a Builder instead:\n///\n/// ```rust\n/// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, Runtime};\n///\n/// pub struct Builder {\n///   option_a: String,\n///   option_b: String,\n///   option_c: bool\n/// }\n///\n/// impl Default for Builder {\n///   fn default() -> Self {\n///     Self {\n///       option_a: \"foo\".to_string(),\n///       option_b: \"bar\".to_string(),\n///       option_c: false\n///     }\n///   }\n/// }\n///\n/// impl Builder {\n///   pub fn new() -> Self {\n///     Default::default()\n///   }\n///\n///   pub fn option_a(mut self, option_a: String) -> Self {\n///     self.option_a = option_a;\n///     self\n///   }\n///\n///   pub fn option_b(mut self, option_b: String) -> Self {\n///     self.option_b = option_b;\n///     self\n///   }\n///\n///   pub fn option_c(mut self, option_c: bool) -> Self {\n///     self.option_c = option_c;\n///     self\n///   }\n///\n///   pub fn build<R: Runtime>(self) -> TauriPlugin<R> {\n///     PluginBuilder::new(\"example\")\n///       .setup(move |app_handle, api| {\n///         // use the options here to do stuff\n///         println!(\"a: {}, b: {}, c: {}\", self.option_a, self.option_b, self.option_c);\n///\n///         Ok(())\n///       })\n///       .build()\n///   }\n/// }\n/// ```\npub struct Builder<R: Runtime, C: DeserializeOwned = ()> {\n  name: &'static str,\n  invoke_handler: Box<InvokeHandler<R>>,\n  setup: Option<Box<SetupHook<R, C>>>,\n  js_init_script: Option<InitializationScript>,\n  on_navigation: Box<OnNavigation<R>>,\n  on_page_load: Box<OnPageLoad<R>>,\n  on_window_ready: Box<OnWindowReady<R>>,\n  on_webview_ready: Box<OnWebviewReady<R>>,\n  on_event: Box<OnEvent<R>>,\n  on_drop: Option<Box<OnDrop<R>>>,\n  uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,\n}\n\nimpl<R: Runtime, C: DeserializeOwned> Builder<R, C> {\n  /// Creates a new Plugin builder.\n  pub fn new(name: &'static str) -> Self {\n    Self {\n      name,\n      setup: None,\n      js_init_script: None,\n      invoke_handler: Box::new(|_| false),\n      on_navigation: Box::new(|_, _| true),\n      on_page_load: Box::new(|_, _| ()),\n      on_window_ready: Box::new(|_| ()),\n      on_webview_ready: Box::new(|_| ()),\n      on_event: Box::new(|_, _| ()),\n      on_drop: None,\n      uri_scheme_protocols: Default::default(),\n    }\n  }\n\n  /// Defines the JS message handler callback.\n  /// It is recommended you use the [tauri::generate_handler] to generate the input to this method, as the input type is not considered stable yet.\n  ///\n  /// # Examples\n  ///\n  /// ```rust\n  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};\n  ///\n  /// #[tauri::command]\n  /// async fn foobar<R: Runtime>(app: tauri::AppHandle<R>, window: tauri::Window<R>) -> Result<(), String> {\n  ///   println!(\"foobar\");\n  ///\n  ///   Ok(())\n  /// }\n  ///\n  /// fn init<R: Runtime>() -> TauriPlugin<R> {\n  ///   Builder::new(\"example\")\n  ///     .invoke_handler(tauri::generate_handler![foobar])\n  ///     .build()\n  /// }\n  ///\n  /// ```\n  /// [tauri::generate_handler]: ../macro.generate_handler.html\n  #[must_use]\n  pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self\n  where\n    F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,\n  {\n    self.invoke_handler = Box::new(invoke_handler);\n    self\n  }\n\n  /// Sets the provided JavaScript to be run after the global object has been created,\n  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.\n  ///\n  /// The script is wrapped into its own context with `(function () { /* your script here */ })();`,\n  /// so global variables must be assigned to `window` instead of implicitly declared.\n  ///\n  /// Note that calling this function multiple times overrides previous values.\n  ///\n  /// This is executed only on the main frame.\n  /// If you only want to run it in all frames, use [`Self::js_init_script_on_all_frames`] instead.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:** scripts are always added to subframes.\n  /// - **Android:** When [addDocumentStartJavaScript] is not supported,\n  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).\n  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.\n  ///\n  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)\n  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)\n  ///\n  /// # Examples\n  ///\n  /// ```rust\n  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};\n  ///\n  /// const INIT_SCRIPT: &str = r#\"\n  ///   if (window.location.origin === 'https://tauri.app') {\n  ///     console.log(\"hello world from js init script\");\n  ///\n  ///     window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };\n  ///   }\n  /// \"#;\n  ///\n  /// fn init<R: Runtime>() -> TauriPlugin<R> {\n  ///   Builder::new(\"example\")\n  ///     .js_init_script(INIT_SCRIPT)\n  ///     .build()\n  /// }\n  /// ```\n  #[must_use]\n  // TODO: Rename to `initialization_script` in v3\n  pub fn js_init_script(mut self, js_init_script: impl Into<String>) -> Self {\n    self.js_init_script = Some(InitializationScript {\n      script: js_init_script.into(),\n      for_main_frame_only: true,\n    });\n    self\n  }\n\n  /// Sets the provided JavaScript to be run after the global object has been created,\n  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.\n  ///\n  /// Since it runs on all top-level document and child frame page navigations,\n  /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.\n  ///\n  /// Note that calling this function multiple times overrides previous values.\n  ///\n  /// This is executed on all frames, main frame and also sub frames.\n  /// If you only want to run it in the main frame, use [`Self::js_init_script`] instead.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:** scripts are always added to subframes.\n  /// - **Android:** When [addDocumentStartJavaScript] is not supported,\n  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).\n  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.\n  ///\n  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)\n  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)\n  #[must_use]\n  pub fn js_init_script_on_all_frames(mut self, js_init_script: impl Into<String>) -> Self {\n    self.js_init_script = Some(InitializationScript {\n      script: js_init_script.into(),\n      for_main_frame_only: false,\n    });\n    self\n  }\n\n  /// Define a closure that runs when the plugin is registered.\n  ///\n  /// # Examples\n  ///\n  /// ```rust\n  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime, Manager};\n  /// use std::path::PathBuf;\n  ///\n  /// #[derive(Debug, Default)]\n  /// struct PluginState {\n  ///    dir: Option<PathBuf>\n  /// }\n  ///\n  /// fn init<R: Runtime>() -> TauriPlugin<R> {\n  /// Builder::new(\"example\")\n  ///   .setup(|app, api| {\n  ///     app.manage(PluginState::default());\n  ///\n  ///     Ok(())\n  ///   })\n  ///   .build()\n  /// }\n  /// ```\n  #[must_use]\n  pub fn setup<F>(mut self, setup: F) -> Self\n  where\n    F: FnOnce(&AppHandle<R>, PluginApi<R, C>) -> Result<(), Box<dyn std::error::Error>>\n      + Send\n      + 'static,\n  {\n    self.setup.replace(Box::new(setup));\n    self\n  }\n\n  /// Callback invoked when the webview tries to navigate to a URL. Returning false cancels the navigation.\n  ///\n  /// #Example\n  ///\n  /// ```\n  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};\n  ///\n  /// fn init<R: Runtime>() -> TauriPlugin<R> {\n  ///   Builder::new(\"example\")\n  ///     .on_navigation(|webview, url| {\n  ///       // allow the production URL or localhost on dev\n  ///       url.scheme() == \"tauri\" || (cfg!(dev) && url.host_str() == Some(\"localhost\"))\n  ///     })\n  ///     .build()\n  /// }\n  /// ```\n  #[must_use]\n  pub fn on_navigation<F>(mut self, on_navigation: F) -> Self\n  where\n    F: Fn(&Webview<R>, &Url) -> bool + Send + 'static,\n  {\n    self.on_navigation = Box::new(on_navigation);\n    self\n  }\n\n  /// Callback invoked when the webview performs a navigation to a page.\n  ///\n  /// # Examples\n  ///\n  /// ```rust\n  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};\n  ///\n  /// fn init<R: Runtime>() -> TauriPlugin<R> {\n  ///   Builder::new(\"example\")\n  ///     .on_page_load(|webview, payload| {\n  ///       println!(\"{:?} URL {} in webview {}\", payload.event(), payload.url(), webview.label());\n  ///     })\n  ///     .build()\n  /// }\n  /// ```\n  #[must_use]\n  pub fn on_page_load<F>(mut self, on_page_load: F) -> Self\n  where\n    F: FnMut(&Webview<R>, &PageLoadPayload<'_>) + Send + 'static,\n  {\n    self.on_page_load = Box::new(on_page_load);\n    self\n  }\n\n  /// Callback invoked when the window is created.\n  ///\n  /// # Examples\n  ///\n  /// ```rust\n  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};\n  ///\n  /// fn init<R: Runtime>() -> TauriPlugin<R> {\n  ///   Builder::new(\"example\")\n  ///     .on_window_ready(|window| {\n  ///       println!(\"created window {}\", window.label());\n  ///     })\n  ///     .build()\n  /// }\n  /// ```\n  #[must_use]\n  pub fn on_window_ready<F>(mut self, on_window_ready: F) -> Self\n  where\n    F: FnMut(Window<R>) + Send + 'static,\n  {\n    self.on_window_ready = Box::new(on_window_ready);\n    self\n  }\n\n  /// Callback invoked when the webview is created.\n  ///\n  /// # Examples\n  ///\n  /// ```rust\n  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};\n  ///\n  /// fn init<R: Runtime>() -> TauriPlugin<R> {\n  ///   Builder::new(\"example\")\n  ///     .on_webview_ready(|webview| {\n  ///       println!(\"created webview {}\", webview.label());\n  ///     })\n  ///     .build()\n  /// }\n  /// ```\n  #[must_use]\n  pub fn on_webview_ready<F>(mut self, on_webview_ready: F) -> Self\n  where\n    F: FnMut(Webview<R>) + Send + 'static,\n  {\n    self.on_webview_ready = Box::new(on_webview_ready);\n    self\n  }\n\n  /// Callback invoked when the event loop receives a new event.\n  ///\n  /// # Examples\n  ///\n  /// ```rust\n  /// use tauri::{plugin::{Builder, TauriPlugin}, RunEvent, Runtime};\n  ///\n  /// fn init<R: Runtime>() -> TauriPlugin<R> {\n  ///   Builder::new(\"example\")\n  ///     .on_event(|app_handle, event| {\n  ///       match event {\n  ///         RunEvent::ExitRequested { api, .. } => {\n  ///           // Prevents the app from exiting.\n  ///           // This will cause the core thread to continue running in the background even without any open windows.\n  ///           api.prevent_exit();\n  ///         }\n  ///         // Ignore all other cases.\n  ///         _ => {}\n  ///       }\n  ///     })\n  ///     .build()\n  /// }\n  /// ```\n  #[must_use]\n  pub fn on_event<F>(mut self, on_event: F) -> Self\n  where\n    F: FnMut(&AppHandle<R>, &RunEvent) + Send + 'static,\n  {\n    self.on_event = Box::new(on_event);\n    self\n  }\n\n  /// Callback invoked when the plugin is dropped.\n  ///\n  /// # Examples\n  ///\n  /// ```rust\n  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};\n  ///\n  /// fn init<R: Runtime>() -> TauriPlugin<R> {\n  ///   Builder::new(\"example\")\n  ///     .on_drop(|app| {\n  ///       println!(\"plugin has been dropped and is no longer running\");\n  ///       // you can run cleanup logic here\n  ///     })\n  ///     .build()\n  /// }\n  /// ```\n  #[must_use]\n  pub fn on_drop<F>(mut self, on_drop: F) -> Self\n  where\n    F: FnOnce(AppHandle<R>) + Send + 'static,\n  {\n    self.on_drop.replace(Box::new(on_drop));\n    self\n  }\n\n  /// Registers a URI scheme protocol available to all webviews.\n  ///\n  /// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,\n  /// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows\n  /// and [webkit-web-context-register-uri-scheme](https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-register-uri-scheme) on Linux.\n  ///\n  /// # Known limitations\n  ///\n  /// URI scheme protocols are registered when the webview is created. Due to this limitation, if the plugin is registered after a webview has been created, this protocol won't be available.\n  ///\n  /// # Arguments\n  ///\n  /// * `uri_scheme` The URI scheme to register, such as `example`.\n  /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.\n  ///\n  /// # Examples\n  ///\n  /// ```rust\n  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};\n  ///\n  /// fn init<R: Runtime>() -> TauriPlugin<R> {\n  ///   Builder::new(\"myplugin\")\n  ///     .register_uri_scheme_protocol(\"myscheme\", |_ctx, req| {\n  ///       http::Response::builder().body(Vec::new()).unwrap()\n  ///     })\n  ///     .build()\n  /// }\n  /// ```\n  ///\n  /// # Warning\n  ///\n  /// Pages loaded from a custom protocol will have a different Origin on different platforms.\n  /// Servers which enforce CORS will need to add the exact same Origin header (or `*`) in `Access-Control-Allow-Origin`\n  /// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the\n  /// different Origin headers across platforms:\n  ///\n  /// - macOS, iOS and Linux: `<scheme_name>://localhost/<path>` (so it will be `my-scheme://localhost/path/to/page).\n  /// - Windows and Android: `http://<scheme_name>.localhost/<path>` by default (so it will be `http://my-scheme.localhost/path/to/page`).\n  ///   To use `https` instead of `http`, use [`super::webview::WebviewBuilder::use_https_scheme`].\n  #[must_use]\n  pub fn register_uri_scheme_protocol<\n    N: Into<String>,\n    T: Into<Cow<'static, [u8]>>,\n    H: Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>) -> http::Response<T>\n      + Send\n      + Sync\n      + 'static,\n  >(\n    mut self,\n    uri_scheme: N,\n    protocol_handler: H,\n  ) -> Self {\n    self.uri_scheme_protocols.insert(\n      uri_scheme.into(),\n      Arc::new(UriSchemeProtocol {\n        handler: Box::new(move |ctx, request, responder| {\n          responder.respond(protocol_handler(ctx, request))\n        }),\n      }),\n    );\n    self\n  }\n\n  /// Similar to [`Self::register_uri_scheme_protocol`] but with an asynchronous responder that allows you\n  /// to process the request in a separate thread and respond asynchronously.\n  ///\n  /// # Arguments\n  ///\n  /// * `uri_scheme` The URI scheme to register, such as `example`.\n  /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.\n  ///\n  /// # Examples\n  ///\n  /// ```rust\n  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};\n  ///\n  /// fn init<R: Runtime>() -> TauriPlugin<R> {\n  ///   Builder::new(\"myplugin\")\n  ///     .register_asynchronous_uri_scheme_protocol(\"app-files\", |_ctx, request, responder| {\n  ///       // skip leading `/`\n  ///       let path = request.uri().path()[1..].to_string();\n  ///       std::thread::spawn(move || {\n  ///         if let Ok(data) = std::fs::read(path) {\n  ///           responder.respond(\n  ///             http::Response::builder()\n  ///               .body(data)\n  ///               .unwrap()\n  ///           );\n  ///         } else {\n  ///           responder.respond(\n  ///             http::Response::builder()\n  ///               .status(http::StatusCode::BAD_REQUEST)\n  ///               .header(http::header::CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())\n  ///               .body(\"failed to read file\".as_bytes().to_vec())\n  ///               .unwrap()\n  ///           );\n  ///         }\n  ///       });\n  ///     })\n  ///     .build()\n  /// }\n  /// ```\n  ///\n  /// # Warning\n  ///\n  /// Pages loaded from a custom protocol will have a different Origin on different platforms.\n  /// Servers which enforce CORS will need to add the exact same Origin header (or `*`) in `Access-Control-Allow-Origin`\n  /// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the\n  /// different Origin headers across platforms:\n  ///\n  /// - macOS, iOS and Linux: `<scheme_name>://localhost/<path>` (so it will be `my-scheme://localhost/path/to/page).\n  /// - Windows and Android: `http://<scheme_name>.localhost/<path>` by default (so it will be `http://my-scheme.localhost/path/to/page`).\n  ///   To use `https` instead of `http`, use [`super::webview::WebviewBuilder::use_https_scheme`].\n  #[must_use]\n  pub fn register_asynchronous_uri_scheme_protocol<\n    N: Into<String>,\n    H: Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync + 'static,\n  >(\n    mut self,\n    uri_scheme: N,\n    protocol_handler: H,\n  ) -> Self {\n    self.uri_scheme_protocols.insert(\n      uri_scheme.into(),\n      Arc::new(UriSchemeProtocol {\n        handler: Box::new(protocol_handler),\n      }),\n    );\n    self\n  }\n\n  /// Builds the [`TauriPlugin`].\n  pub fn try_build(self) -> Result<TauriPlugin<R, C>, BuilderError> {\n    if let Some(&reserved) = RESERVED_PLUGIN_NAMES.iter().find(|&r| r == &self.name) {\n      return Err(BuilderError::ReservedName(reserved.into()));\n    }\n\n    Ok(TauriPlugin {\n      name: self.name,\n      app: None,\n      invoke_handler: self.invoke_handler,\n      setup: self.setup,\n      js_init_script: self.js_init_script,\n      on_navigation: self.on_navigation,\n      on_page_load: self.on_page_load,\n      on_window_ready: self.on_window_ready,\n      on_webview_ready: self.on_webview_ready,\n      on_event: self.on_event,\n      on_drop: self.on_drop,\n      uri_scheme_protocols: self.uri_scheme_protocols,\n    })\n  }\n\n  /// Builds the [`TauriPlugin`].\n  ///\n  /// # Panics\n  ///\n  /// If the builder returns an error during [`Self::try_build`], then this method will panic.\n  pub fn build(self) -> TauriPlugin<R, C> {\n    self.try_build().expect(\"valid plugin\")\n  }\n}\n\n/// Plugin struct that is returned by the [`Builder`]. Should only be constructed through the builder.\npub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {\n  name: &'static str,\n  app: Option<AppHandle<R>>,\n  invoke_handler: Box<InvokeHandler<R>>,\n  setup: Option<Box<SetupHook<R, C>>>,\n  js_init_script: Option<InitializationScript>,\n  on_navigation: Box<OnNavigation<R>>,\n  on_page_load: Box<OnPageLoad<R>>,\n  on_window_ready: Box<OnWindowReady<R>>,\n  on_webview_ready: Box<OnWebviewReady<R>>,\n  on_event: Box<OnEvent<R>>,\n  on_drop: Option<Box<OnDrop<R>>>,\n  uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,\n}\n\nimpl<R: Runtime, C: DeserializeOwned> Drop for TauriPlugin<R, C> {\n  fn drop(&mut self) {\n    if let (Some(on_drop), Some(app)) = (self.on_drop.take(), self.app.take()) {\n      on_drop(app);\n    }\n  }\n}\n\nimpl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {\n  fn name(&self) -> &'static str {\n    self.name\n  }\n\n  fn initialize(\n    &mut self,\n    app: &AppHandle<R>,\n    config: JsonValue,\n  ) -> Result<(), Box<dyn std::error::Error>> {\n    self.app.replace(app.clone());\n    if let Some(s) = self.setup.take() {\n      (s)(\n        app,\n        PluginApi {\n          name: self.name,\n          handle: app.clone(),\n          raw_config: Arc::new(config.clone()),\n          config: serde_json::from_value(config).map_err(|err| {\n            format!(\n              \"Error deserializing 'plugins.{}' within your Tauri configuration: {err}\",\n              self.name\n            )\n          })?,\n        },\n      )?;\n    }\n\n    for (uri_scheme, protocol) in &self.uri_scheme_protocols {\n      app\n        .manager\n        .webview\n        .register_uri_scheme_protocol(uri_scheme, protocol.clone())\n    }\n    Ok(())\n  }\n\n  fn initialization_script(&self) -> Option<String> {\n    self\n      .js_init_script\n      .clone()\n      .map(|initialization_script| initialization_script.script)\n  }\n\n  fn initialization_script_2(&self) -> Option<InitializationScript> {\n    self.js_init_script.clone()\n  }\n\n  fn window_created(&mut self, window: Window<R>) {\n    (self.on_window_ready)(window)\n  }\n\n  fn webview_created(&mut self, webview: Webview<R>) {\n    (self.on_webview_ready)(webview)\n  }\n\n  fn on_navigation(&mut self, webview: &Webview<R>, url: &Url) -> bool {\n    (self.on_navigation)(webview, url)\n  }\n\n  fn on_page_load(&mut self, webview: &Webview<R>, payload: &PageLoadPayload<'_>) {\n    (self.on_page_load)(webview, payload)\n  }\n\n  fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {\n    (self.on_event)(app, event)\n  }\n\n  fn extend_api(&mut self, invoke: Invoke<R>) -> bool {\n    (self.invoke_handler)(invoke)\n  }\n}\n\n/// Plugin collection type.\n#[default_runtime(crate::Wry, wry)]\npub(crate) struct PluginStore<R: Runtime> {\n  store: Vec<Box<dyn Plugin<R>>>,\n}\n\nimpl<R: Runtime> fmt::Debug for PluginStore<R> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    let plugins: Vec<&str> = self.store.iter().map(|plugins| plugins.name()).collect();\n    f.debug_struct(\"PluginStore\")\n      .field(\"plugins\", &plugins)\n      .finish()\n  }\n}\n\nimpl<R: Runtime> Default for PluginStore<R> {\n  fn default() -> Self {\n    Self { store: Vec::new() }\n  }\n}\n\nimpl<R: Runtime> PluginStore<R> {\n  /// Adds a plugin to the store.\n  ///\n  /// Returns `true` if a plugin with the same name is already in the store.\n  pub fn register(&mut self, plugin: Box<dyn Plugin<R>>) -> bool {\n    let len = self.store.len();\n    self.store.retain(|p| p.name() != plugin.name());\n    let result = len != self.store.len();\n    self.store.push(plugin);\n    result\n  }\n\n  /// Removes the plugin with the given name from the store.\n  pub fn unregister(&mut self, plugin: &str) -> bool {\n    let len = self.store.len();\n    self.store.retain(|p| p.name() != plugin);\n    len != self.store.len()\n  }\n\n  /// Initializes the given plugin.\n  pub(crate) fn initialize(\n    &self,\n    plugin: &mut Box<dyn Plugin<R>>,\n    app: &AppHandle<R>,\n    config: &PluginConfig,\n  ) -> crate::Result<()> {\n    initialize(plugin, app, config)\n  }\n\n  /// Initializes all plugins in the store.\n  pub(crate) fn initialize_all(\n    &mut self,\n    app: &AppHandle<R>,\n    config: &PluginConfig,\n  ) -> crate::Result<()> {\n    self\n      .store\n      .iter_mut()\n      .try_for_each(|plugin| initialize(plugin, app, config))\n  }\n\n  /// Generates an initialization script from all plugins in the store.\n  pub(crate) fn initialization_script(&self) -> Vec<InitializationScript> {\n    self\n      .store\n      .iter()\n      .filter_map(|p| p.initialization_script_2())\n      .map(\n        |InitializationScript {\n           script,\n           for_main_frame_only,\n         }| InitializationScript {\n          script: format!(\"(function () {{ {script} }})();\"),\n          for_main_frame_only,\n        },\n      )\n      .collect()\n  }\n\n  /// Runs the created hook for all plugins in the store.\n  pub(crate) fn window_created(&mut self, window: Window<R>) {\n    self.store.iter_mut().for_each(|plugin| {\n      #[cfg(feature = \"tracing\")]\n      let _span = tracing::trace_span!(\"plugin::hooks::created\", name = plugin.name()).entered();\n      plugin.window_created(window.clone())\n    })\n  }\n\n  /// Runs the webview created hook for all plugins in the store.\n  pub(crate) fn webview_created(&mut self, webview: Webview<R>) {\n    self\n      .store\n      .iter_mut()\n      .for_each(|plugin| plugin.webview_created(webview.clone()))\n  }\n\n  pub(crate) fn on_navigation(&mut self, webview: &Webview<R>, url: &Url) -> bool {\n    for plugin in self.store.iter_mut() {\n      #[cfg(feature = \"tracing\")]\n      let _span =\n        tracing::trace_span!(\"plugin::hooks::on_navigation\", name = plugin.name()).entered();\n      if !plugin.on_navigation(webview, url) {\n        return false;\n      }\n    }\n    true\n  }\n\n  /// Runs the on_page_load hook for all plugins in the store.\n  pub(crate) fn on_page_load(&mut self, webview: &Webview<R>, payload: &PageLoadPayload<'_>) {\n    self.store.iter_mut().for_each(|plugin| {\n      #[cfg(feature = \"tracing\")]\n      let _span =\n        tracing::trace_span!(\"plugin::hooks::on_page_load\", name = plugin.name()).entered();\n      plugin.on_page_load(webview, payload)\n    })\n  }\n\n  /// Runs the on_event hook for all plugins in the store.\n  pub(crate) fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {\n    self\n      .store\n      .iter_mut()\n      .for_each(|plugin| plugin.on_event(app, event))\n  }\n\n  /// Runs the plugin `extend_api` hook if it exists. Returns whether the invoke message was handled or not.\n  ///\n  /// The message is not handled when the plugin exists **and** the command does not.\n  pub(crate) fn extend_api(&mut self, plugin: &str, invoke: Invoke<R>) -> bool {\n    for p in self.store.iter_mut() {\n      if p.name() == plugin {\n        #[cfg(feature = \"tracing\")]\n        let _span = tracing::trace_span!(\"plugin::hooks::ipc\", name = plugin).entered();\n        return p.extend_api(invoke);\n      }\n    }\n    invoke.resolver.reject(format!(\"plugin {plugin} not found\"));\n    true\n  }\n}\n\n#[cfg_attr(feature = \"tracing\", tracing::instrument(name = \"plugin::hooks::initialize\", skip(plugin, app), fields(name = plugin.name())))]\nfn initialize<R: Runtime>(\n  plugin: &mut Box<dyn Plugin<R>>,\n  app: &AppHandle<R>,\n  config: &PluginConfig,\n) -> crate::Result<()> {\n  plugin\n    .initialize(\n      app,\n      config.0.get(plugin.name()).cloned().unwrap_or_default(),\n    )\n    .map_err(|e| Error::PluginInitialization(plugin.name().to_string(), e.to_string()))\n}\n\n/// Permission state.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\n#[cfg_attr(feature = \"specta\", derive(specta::Type))]\npub enum PermissionState {\n  /// Permission access has been granted.\n  Granted,\n  /// Permission access has been denied.\n  Denied,\n  /// Permission must be requested.\n  #[default]\n  Prompt,\n  /// Permission must be requested, but you must explain to the user why your app needs that permission. **Android only**.\n  PromptWithRationale,\n}\n\nimpl std::fmt::Display for PermissionState {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    match self {\n      Self::Granted => write!(f, \"granted\"),\n      Self::Denied => write!(f, \"denied\"),\n      Self::Prompt => write!(f, \"prompt\"),\n      Self::PromptWithRationale => write!(f, \"prompt-with-rationale\"),\n    }\n  }\n}\n\nimpl Serialize for PermissionState {\n  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    serializer.serialize_str(self.to_string().as_ref())\n  }\n}\n\nimpl<'de> Deserialize<'de> for PermissionState {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let s = <String as Deserialize>::deserialize(deserializer)?;\n    match s.to_lowercase().as_str() {\n      \"granted\" => Ok(Self::Granted),\n      \"denied\" => Ok(Self::Denied),\n      \"prompt\" => Ok(Self::Prompt),\n      \"prompt-with-rationale\" => Ok(Self::PromptWithRationale),\n      _ => Err(DeError::custom(format!(\"unknown permission state '{s}'\"))),\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/process.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Types and functions related to child processes management.\n\nuse crate::Env;\n\nuse std::path::PathBuf;\n\n/// Finds the current running binary's path.\n///\n/// With exception to any following platform-specific behavior, the path is cached as soon as\n/// possible, and then used repeatedly instead of querying for a new path every time this function\n/// is called.\n///\n/// # Platform-specific behavior\n///\n/// ## Linux\n///\n/// On Linux, this function will **attempt** to detect if it's currently running from a\n/// valid [AppImage] and use that path instead.\n///\n/// ## macOS\n///\n/// On `macOS`, this function will return an error if the original path contained any symlinks\n/// due to less protection on macOS regarding symlinks. This behavior can be disabled by setting the\n/// `process-relaunch-dangerous-allow-symlink-macos` feature, although it is *highly discouraged*.\n///\n/// # Security\n///\n/// See [`tauri_utils::platform::current_exe`] for possible security implications.\n///\n/// # Examples\n///\n/// ```rust,no_run\n/// use tauri::{process::current_binary, Env, Manager};\n/// let current_binary_path = current_binary(&Env::default()).unwrap();\n///\n/// tauri::Builder::default()\n///   .setup(|app| {\n///     let current_binary_path = current_binary(&app.env())?;\n///     Ok(())\n///   });\n/// ```\n///\n/// [AppImage]: https://appimage.org/\npub fn current_binary(_env: &Env) -> std::io::Result<PathBuf> {\n  // if we are running from an AppImage, we ONLY want the set AppImage path\n  #[cfg(target_os = \"linux\")]\n  if let Some(app_image_path) = &_env.appimage {\n    return Ok(PathBuf::from(app_image_path));\n  }\n\n  tauri_utils::platform::current_exe()\n}\n\n/// Restarts the currently running binary.\n///\n/// See [`current_binary`] for platform specific behavior, and\n/// [`tauri_utils::platform::current_exe`] for possible security implications.\n///\n/// # Examples\n///\n/// ```rust,no_run\n/// use tauri::{process::restart, Env, Manager};\n///\n/// tauri::Builder::default()\n///   .setup(|app| {\n///     restart(&app.env());\n///     Ok(())\n///   });\n/// ```\npub fn restart(env: &Env) -> ! {\n  use std::process::{exit, Command};\n\n  if let Ok(path) = current_binary(env) {\n    // on macOS on updates the binary name might have changed\n    // so we'll read the Contents/Info.plist file to determine the binary path\n    #[cfg(target_os = \"macos\")]\n    restart_macos_app(&path, env);\n\n    if let Err(e) = Command::new(path).args(env.args_os.iter().skip(1)).spawn() {\n      log::error!(\"failed to restart app: {e}\");\n    }\n  }\n\n  exit(0);\n}\n\n#[cfg(target_os = \"macos\")]\nfn restart_macos_app(current_binary: &std::path::Path, env: &Env) {\n  use std::process::{exit, Command};\n\n  if let Some(macos_directory) = current_binary.parent() {\n    if macos_directory.components().next_back()\n      != Some(std::path::Component::Normal(std::ffi::OsStr::new(\"MacOS\")))\n    {\n      return;\n    }\n\n    if let Some(contents_directory) = macos_directory.parent() {\n      if contents_directory.components().next_back()\n        != Some(std::path::Component::Normal(std::ffi::OsStr::new(\n          \"Contents\",\n        )))\n      {\n        return;\n      }\n\n      if let Ok(info_plist) =\n        plist::from_file::<_, plist::Dictionary>(contents_directory.join(\"Info.plist\"))\n      {\n        if let Some(binary_name) = info_plist\n          .get(\"CFBundleExecutable\")\n          .and_then(|v| v.as_string())\n        {\n          if let Err(e) = Command::new(macos_directory.join(binary_name))\n            .args(env.args_os.iter().skip(1))\n            .spawn()\n          {\n            log::error!(\"failed to restart app: {e}\");\n          }\n\n          exit(0);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/protocol/asset.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{path::SafePathBuf, scope, webview::UriSchemeProtocolHandler};\nuse http::{header::*, status::StatusCode, Request, Response};\nuse http_range::HttpRange;\nuse std::{borrow::Cow, io::SeekFrom};\nuse tauri_utils::mime_type::MimeType;\nuse tokio::fs::File;\nuse tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};\n\npub fn get(scope: scope::fs::Scope, window_origin: String) -> UriSchemeProtocolHandler {\n  Box::new(\n    move |_, request, responder| match get_response(request, &scope, &window_origin) {\n      Ok(response) => responder.respond(response),\n      Err(e) => responder.respond(\n        http::Response::builder()\n          .status(http::StatusCode::INTERNAL_SERVER_ERROR)\n          .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())\n          .header(\"Access-Control-Allow-Origin\", &window_origin)\n          .body(e.to_string().into_bytes())\n          .unwrap(),\n      ),\n    },\n  )\n}\n\nfn get_response(\n  request: Request<Vec<u8>>,\n  scope: &scope::fs::Scope,\n  window_origin: &str,\n) -> Result<Response<Cow<'static, [u8]>>, Box<dyn std::error::Error>> {\n  // skip leading `/`\n  let path = percent_encoding::percent_decode(&request.uri().path().as_bytes()[1..])\n    .decode_utf8_lossy()\n    .to_string();\n\n  let mut resp = Response::builder().header(\"Access-Control-Allow-Origin\", window_origin);\n\n  if let Err(e) = SafePathBuf::new(path.clone().into()) {\n    log::error!(\"asset protocol path \\\"{}\\\" is not valid: {}\", path, e);\n    return resp.status(403).body(Vec::new().into()).map_err(Into::into);\n  }\n\n  if !scope.is_allowed(&path) {\n    log::error!(\"asset protocol not configured to allow the path: {}\", path);\n    return resp.status(403).body(Vec::new().into()).map_err(Into::into);\n  }\n\n  // Separate block for easier error handling\n  let mut file = match crate::async_runtime::safe_block_on(File::open(path.clone())) {\n    Ok(file) => file,\n    Err(e) => {\n      #[cfg(target_os = \"android\")]\n      {\n        if path.starts_with(\"/storage/emulated/0/Android/data/\") {\n          log::error!(\"Failed to open Android external storage file '{}': {}. This may be due to missing storage permissions.\", path, e);\n        }\n      }\n      return if e.kind() == std::io::ErrorKind::NotFound {\n        log::error!(\"File does not exist at path: {}\", path);\n        return resp.status(404).body(Vec::new().into()).map_err(Into::into);\n      } else if e.kind() == std::io::ErrorKind::PermissionDenied {\n        log::error!(\"Missing OS permission to access path \\\"{}\\\": {}\", path, e);\n        return resp.status(403).body(Vec::new().into()).map_err(Into::into);\n      } else {\n        Err(e.into())\n      };\n    }\n  };\n\n  let (mut file, len, mime_type, read_bytes) = crate::async_runtime::safe_block_on(async move {\n    // get file length\n    let len = {\n      let old_pos = file.stream_position().await?;\n      let len = file.seek(SeekFrom::End(0)).await?;\n      file.seek(SeekFrom::Start(old_pos)).await?;\n      len\n    };\n\n    // get file mime type\n    let (mime_type, read_bytes) = {\n      let nbytes = len.min(8192);\n      let mut magic_buf = Vec::with_capacity(nbytes as usize);\n      let old_pos = file.stream_position().await?;\n      (&mut file).take(nbytes).read_to_end(&mut magic_buf).await?;\n      file.seek(SeekFrom::Start(old_pos)).await?;\n      (\n        MimeType::parse(&magic_buf, &path),\n        // return the `magic_bytes` if we read the whole file\n        // to avoid reading it again later if this is not a range request\n        if len < 8192 { Some(magic_buf) } else { None },\n      )\n    };\n\n    Ok::<(File, u64, String, Option<Vec<u8>>), anyhow::Error>((file, len, mime_type, read_bytes))\n  })?;\n\n  resp = resp.header(CONTENT_TYPE, &mime_type);\n\n  // handle 206 (partial range) http requests\n  let response = if let Some(range_header) = request\n    .headers()\n    .get(\"range\")\n    .and_then(|r| r.to_str().map(|r| r.to_string()).ok())\n  {\n    resp = resp.header(ACCEPT_RANGES, \"bytes\");\n    resp = resp.header(ACCESS_CONTROL_EXPOSE_HEADERS, \"content-range\");\n\n    let not_satisfiable = || {\n      Response::builder()\n        .status(StatusCode::RANGE_NOT_SATISFIABLE)\n        .header(CONTENT_RANGE, format!(\"bytes */{len}\"))\n        .body(vec![].into())\n        .map_err(Into::into)\n    };\n\n    // parse range header\n    let ranges = if let Ok(ranges) = HttpRange::parse(&range_header, len) {\n      ranges\n        .iter()\n        // map the output to spec range <start-end>, example: 0-499\n        .map(|r| (r.start, r.start + r.length - 1))\n        .collect::<Vec<_>>()\n    } else {\n      return not_satisfiable();\n    };\n\n    /// The Maximum bytes we send in one range\n    const MAX_LEN: u64 = 1000 * 1024;\n\n    // single-part range header\n    if ranges.len() == 1 {\n      let &(start, mut end) = ranges.first().unwrap();\n\n      // check if a range is not satisfiable\n      //\n      // this should be already taken care of by the range parsing library\n      // but checking here again for extra assurance\n      if start >= len || end >= len || end < start {\n        return not_satisfiable();\n      }\n\n      // adjust end byte for MAX_LEN\n      end = start + (end - start).min(len - start).min(MAX_LEN - 1);\n\n      // calculate number of bytes needed to be read\n      let nbytes = end + 1 - start;\n\n      let buf = crate::async_runtime::safe_block_on(async move {\n        let mut buf = Vec::with_capacity(nbytes as usize);\n        file.seek(SeekFrom::Start(start)).await?;\n        file.take(nbytes).read_to_end(&mut buf).await?;\n        Ok::<Vec<u8>, anyhow::Error>(buf)\n      })?;\n\n      resp = resp.header(CONTENT_RANGE, format!(\"bytes {start}-{end}/{len}\"));\n      resp = resp.header(CONTENT_LENGTH, end + 1 - start);\n      resp = resp.status(StatusCode::PARTIAL_CONTENT);\n      resp.body(buf.into())\n    } else {\n      let ranges = ranges\n        .iter()\n        .filter_map(|&(start, mut end)| {\n          // filter out unsatisfiable ranges\n          //\n          // this should be already taken care of by the range parsing library\n          // but checking here again for extra assurance\n          if start >= len || end >= len || end < start {\n            None\n          } else {\n            // adjust end byte for MAX_LEN\n            end = start + (end - start).min(len - start).min(MAX_LEN - 1);\n            Some((start, end))\n          }\n        })\n        .collect::<Vec<_>>();\n\n      let boundary = random_boundary();\n      let boundary_sep = format!(\"\\r\\n--{boundary}\\r\\n\");\n      let boundary_closer = format!(\"\\r\\n--{boundary}\\r\\n\");\n\n      resp = resp.header(\n        CONTENT_TYPE,\n        format!(\"multipart/byteranges; boundary={boundary}\"),\n      );\n\n      let buf = crate::async_runtime::safe_block_on(async move {\n        // multi-part range header\n        let mut buf = Vec::new();\n\n        for (start, end) in ranges {\n          // a new range is being written, write the range boundary\n          buf.write_all(boundary_sep.as_bytes()).await?;\n\n          // write the needed headers `Content-Type` and `Content-Range`\n          buf\n            .write_all(format!(\"{CONTENT_TYPE}: {mime_type}\\r\\n\").as_bytes())\n            .await?;\n          buf\n            .write_all(format!(\"{CONTENT_RANGE}: bytes {start}-{end}/{len}\\r\\n\").as_bytes())\n            .await?;\n\n          // write the separator to indicate the start of the range body\n          buf.write_all(\"\\r\\n\".as_bytes()).await?;\n\n          // calculate number of bytes needed to be read\n          let nbytes = end + 1 - start;\n\n          let mut local_buf = Vec::with_capacity(nbytes as usize);\n          file.seek(SeekFrom::Start(start)).await?;\n          (&mut file).take(nbytes).read_to_end(&mut local_buf).await?;\n          buf.extend_from_slice(&local_buf);\n        }\n        // all ranges have been written, write the closing boundary\n        buf.write_all(boundary_closer.as_bytes()).await?;\n\n        Ok::<Vec<u8>, anyhow::Error>(buf)\n      })?;\n      resp.body(buf.into())\n    }\n  } else if request.method() == http::Method::HEAD {\n    // if the HEAD method is used, we should not return a body\n    resp = resp.header(CONTENT_LENGTH, len);\n    resp.body(Vec::new().into())\n  } else {\n    // avoid reading the file if we already read it\n    // as part of mime type detection\n    let buf = if let Some(b) = read_bytes {\n      b\n    } else {\n      crate::async_runtime::safe_block_on(async move {\n        let mut local_buf = Vec::with_capacity(len as usize);\n        file.read_to_end(&mut local_buf).await?;\n        Ok::<Vec<u8>, anyhow::Error>(local_buf)\n      })?\n    };\n    resp = resp.header(CONTENT_LENGTH, len);\n    resp.body(buf.into())\n  };\n\n  response.map_err(Into::into)\n}\n\nfn random_boundary() -> String {\n  let mut x = [0_u8; 30];\n  getrandom::fill(&mut x).expect(\"failed to get random bytes\");\n  (x[..])\n    .iter()\n    .map(|&x| format!(\"{x:x}\"))\n    .fold(String::new(), |mut a, x| {\n      a.push_str(x.as_str());\n      a\n    })\n}\n"
  },
  {
    "path": "crates/tauri/src/protocol/isolation.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::Assets;\nuse http::header::CONTENT_TYPE;\nuse serialize_to_javascript::Template;\nuse tauri_utils::{\n  assets::EmbeddedAssets,\n  config::{Csp, HeaderAddition},\n};\n\nuse std::sync::Arc;\n\nuse crate::{\n  manager::{set_csp, webview::PROCESS_IPC_MESSAGE_FN, AppManager},\n  webview::UriSchemeProtocolHandler,\n  Runtime,\n};\n\npub fn get<R: Runtime>(\n  manager: Arc<AppManager<R>>,\n  schema: &str,\n  assets: Arc<EmbeddedAssets>,\n  aes_gcm_key: [u8; 32],\n  window_origin: String,\n  use_https_scheme: bool,\n) -> UriSchemeProtocolHandler {\n  let frame_src = if cfg!(any(windows, target_os = \"android\")) {\n    let https = if use_https_scheme { \"https\" } else { \"http\" };\n    format!(\"{https}://{schema}.localhost\")\n  } else {\n    format!(\"{schema}:\")\n  };\n\n  let assets = assets as Arc<dyn Assets<R>>;\n\n  Box::new(move |_, request, responder| {\n    let response = match request_to_path(&request).as_str() {\n      \"index.html\" => match assets.get(&\"index.html\".into()) {\n        Some(asset) => {\n          let mut asset = String::from_utf8_lossy(asset.as_ref()).into_owned();\n          let csp_map = set_csp(\n            &mut asset,\n            &assets,\n            &\"index.html\".into(),\n            &manager,\n            Csp::Policy(format!(\"default-src 'none'; frame-src {frame_src}\")),\n          );\n          let csp = Csp::DirectiveMap(csp_map).to_string();\n\n          let template = tauri_utils::pattern::isolation::IsolationJavascriptRuntime {\n            runtime_aes_gcm_key: &aes_gcm_key,\n            origin: window_origin.clone(),\n            process_ipc_message_fn: PROCESS_IPC_MESSAGE_FN,\n          };\n          match template.render(asset.as_ref(), &Default::default()) {\n            Ok(asset) => http::Response::builder()\n              .add_configured_headers(manager.config.app.security.headers.as_ref())\n              .header(CONTENT_TYPE, mime::TEXT_HTML.as_ref())\n              .header(\"Content-Security-Policy\", csp)\n              .body(asset.into_string().into_bytes()),\n            Err(_) => http::Response::builder()\n              .status(http::StatusCode::INTERNAL_SERVER_ERROR)\n              .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref())\n              .body(Vec::new()),\n          }\n        }\n\n        None => http::Response::builder()\n          .status(http::StatusCode::NOT_FOUND)\n          .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref())\n          .body(Vec::new()),\n      },\n      _ => http::Response::builder()\n        .status(http::StatusCode::NOT_FOUND)\n        .header(CONTENT_TYPE, mime::TEXT_PLAIN.as_ref())\n        .body(Vec::new()),\n    };\n\n    if let Ok(r) = response {\n      responder.respond(r);\n    } else {\n      responder.respond(\n        http::Response::builder()\n          .status(http::StatusCode::INTERNAL_SERVER_ERROR)\n          .body(\"failed to get response\".as_bytes())\n          .unwrap(),\n      );\n    }\n  })\n}\n\nfn request_to_path(request: &http::Request<Vec<u8>>) -> String {\n  let path = request\n    .uri()\n    .path()\n    .trim_start_matches('/')\n    .trim_end_matches('/');\n\n  let path = percent_encoding::percent_decode(path.as_bytes())\n    .decode_utf8_lossy()\n    .to_string();\n\n  if path.is_empty() {\n    // if the url has no path, we should load `index.html`\n    \"index.html\".to_string()\n  } else {\n    // skip leading `/`\n    path.chars().skip(1).collect()\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/protocol/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Custom protocol handlers\n\n#[cfg(feature = \"protocol-asset\")]\npub mod asset;\n#[cfg(feature = \"isolation\")]\npub mod isolation;\npub mod tauri;\n"
  },
  {
    "path": "crates/tauri/src/protocol/tauri.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{borrow::Cow, sync::Arc};\n\nuse http::{header::CONTENT_TYPE, Request, Response as HttpResponse, StatusCode};\nuse tauri_utils::config::HeaderAddition;\n\nuse crate::{\n  manager::{webview::PROXY_DEV_SERVER, AppManager},\n  webview::{UriSchemeProtocolHandler, WebResourceRequestHandler},\n  Runtime,\n};\n\n#[cfg(all(dev, mobile))]\nuse std::{collections::HashMap, sync::Mutex};\n\n#[cfg(all(dev, mobile))]\n#[derive(Clone)]\nstruct CachedResponse {\n  status: http::StatusCode,\n  headers: http::HeaderMap,\n  body: bytes::Bytes,\n}\n\npub fn get<R: Runtime>(\n  #[allow(unused_variables)] manager: Arc<AppManager<R>>,\n  window_origin: &str,\n  web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,\n) -> UriSchemeProtocolHandler {\n  #[cfg(all(dev, mobile))]\n  let url = {\n    let mut url = manager\n      .get_app_url(window_origin.starts_with(\"https\"))\n      .as_str()\n      .to_string();\n    if url.ends_with('/') {\n      url.pop();\n    }\n    url\n  };\n\n  let window_origin = window_origin.to_string();\n\n  #[cfg(all(dev, mobile))]\n  let response_cache = Arc::new(Mutex::new(HashMap::new()));\n\n  Box::new(move |_, request, responder| {\n    match get_response(\n      request,\n      &manager,\n      &window_origin,\n      web_resource_request_handler.as_deref(),\n      #[cfg(all(dev, mobile))]\n      (&url, &response_cache),\n    ) {\n      Ok(response) => responder.respond(response),\n      Err(e) => responder.respond(\n        HttpResponse::builder()\n          .status(StatusCode::INTERNAL_SERVER_ERROR)\n          .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())\n          .header(\"Access-Control-Allow-Origin\", &window_origin)\n          .body(e.to_string().into_bytes())\n          .unwrap(),\n      ),\n    }\n  })\n}\n\nfn get_response<R: Runtime>(\n  #[allow(unused_mut)] mut request: Request<Vec<u8>>,\n  #[allow(unused_variables)] manager: &AppManager<R>,\n  window_origin: &str,\n  web_resource_request_handler: Option<&WebResourceRequestHandler>,\n  #[cfg(all(dev, mobile))] (url, response_cache): (\n    &str,\n    &Arc<Mutex<HashMap<String, CachedResponse>>>,\n  ),\n) -> Result<HttpResponse<Cow<'static, [u8]>>, Box<dyn std::error::Error>> {\n  // use the entire URI as we are going to proxy the request\n  let path = if PROXY_DEV_SERVER {\n    request.uri().to_string()\n  } else {\n    // ignore query string and fragment\n    request\n      .uri()\n      .to_string()\n      .split(&['?', '#'][..])\n      .next()\n      .unwrap()\n      .into()\n  };\n\n  let path = path\n    .strip_prefix(\"tauri://localhost\")\n    .map(|p| p.to_string())\n    // the `strip_prefix` only returns None when a request is made to `https://tauri.$P` on Windows and Android\n    // where `$P` is not `localhost/*`\n    .unwrap_or_default();\n\n  let mut builder = HttpResponse::builder()\n    .add_configured_headers(manager.config.app.security.headers.as_ref())\n    .header(\"Access-Control-Allow-Origin\", window_origin);\n\n  #[cfg(all(dev, mobile))]\n  let mut response = {\n    let decoded_path = percent_encoding::percent_decode(path.as_bytes())\n      .decode_utf8_lossy()\n      .to_string();\n    let url = format!(\n      \"{}/{}\",\n      url.trim_end_matches('/'),\n      decoded_path.trim_start_matches('/')\n    );\n\n    #[cfg(feature = \"rustls-tls\")]\n    if rustls::crypto::CryptoProvider::get_default().is_none() {\n      let _ = rustls::crypto::ring::default_provider().install_default();\n    }\n\n    let mut client = reqwest::ClientBuilder::new();\n\n    if url.starts_with(\"https://\") {\n      // we can't load env vars at runtime, gotta embed them in the lib\n      if let Some(cert_pem) = option_env!(\"TAURI_DEV_ROOT_CERTIFICATE\") {\n        #[cfg(any(\n          feature = \"native-tls\",\n          feature = \"native-tls-vendored\",\n          feature = \"rustls-tls\"\n        ))]\n        {\n          log::info!(\"adding dev server root certificate\");\n          let certificate = reqwest::Certificate::from_pem(cert_pem.as_bytes())\n            .expect(\"failed to parse TAURI_DEV_ROOT_CERTIFICATE\");\n          client = client.tls_certs_merge([certificate]);\n        }\n\n        #[cfg(not(any(\n          feature = \"native-tls\",\n          feature = \"native-tls-vendored\",\n          feature = \"rustls-tls\"\n        )))]\n        {\n          log::warn!(\n            \"the dev root-certificate-path option was provided, but you must enable one of the following Tauri features in Cargo.toml: native-tls, native-tls-vendored, rustls-tls\"\n          );\n        }\n      } else {\n        log::warn!(\n          \"loading HTTPS URL; you might need to provide a certificate via the `dev --root-certificate-path` option. You must enable one of the following Tauri features in Cargo.toml: native-tls, native-tls-vendored, rustls-tls\"\n        );\n      }\n    }\n\n    let mut proxy_builder = client\n      .build()\n      .unwrap()\n      .request(request.method().clone(), &url);\n    proxy_builder = proxy_builder.body(std::mem::take(request.body_mut()));\n    for (name, value) in request.headers() {\n      proxy_builder = proxy_builder.header(name, value);\n    }\n    proxy_builder = proxy_builder.body(request.body().clone());\n    match crate::async_runtime::safe_block_on(proxy_builder.send()) {\n      Ok(r) => {\n        let mut response_cache_ = response_cache.lock().unwrap();\n        let mut response = None;\n        if r.status() == http::StatusCode::NOT_MODIFIED {\n          response = response_cache_.get(&url);\n        }\n        let response = if let Some(r) = response {\n          r\n        } else {\n          let status = r.status();\n          let headers = r.headers().clone();\n          let body = crate::async_runtime::safe_block_on(r.bytes())?;\n          let response = CachedResponse {\n            status,\n            headers,\n            body,\n          };\n          response_cache_.insert(url.clone(), response);\n          response_cache_.get(&url).unwrap()\n        };\n        for (name, value) in &response.headers {\n          builder = builder.header(name, value);\n        }\n        builder\n          .status(response.status)\n          .body(response.body.to_vec().into())?\n      }\n      Err(e) => {\n        let error_message = format!(\n          \"Failed to request {}: {}{}\",\n          url.as_str(),\n          e,\n          if let Some(s) = e.status() {\n            format!(\"status code: {}\", s.as_u16())\n          } else if cfg!(target_os = \"ios\") {\n            \", did you grant local network permissions? That is required to reach the development server. Please grant the permission via the prompt or in `Settings > Privacy & Security > Local Network` and restart the app. See https://support.apple.com/en-us/102229 for more information.\".to_string()\n          } else {\n            \"\".to_string()\n          }\n        );\n        log::error!(\"{error_message}\");\n        return Err(error_message.into());\n      }\n    }\n  };\n\n  #[cfg(not(all(dev, mobile)))]\n  let mut response = {\n    let use_https_scheme = request.uri().scheme() == Some(&http::uri::Scheme::HTTPS);\n    let asset = manager.get_asset(path, use_https_scheme)?;\n    builder = builder.header(CONTENT_TYPE, &asset.mime_type);\n    if let Some(csp) = &asset.csp_header {\n      builder = builder.header(\"Content-Security-Policy\", csp);\n    }\n    builder.body(asset.bytes.into())?\n  };\n  if let Some(handler) = &web_resource_request_handler {\n    handler(request, &mut response);\n  }\n\n  Ok(response)\n}\n"
  },
  {
    "path": "crates/tauri/src/resources/mod.rs",
    "content": "// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// a modified version of https://github.com/denoland/deno/blob/0ae83847f498a2886ae32172e50fd5bdbab2f524/core/resources.rs#L220\n\npub(crate) mod plugin;\n\nuse std::{\n  any::{type_name, Any, TypeId},\n  borrow::Cow,\n  collections::BTreeMap,\n  sync::Arc,\n};\n\n/// Resources are Rust objects that are stored in [ResourceTable] and managed by tauri.\n///\n/// They are identified in JS by a numeric ID (the resource ID, or rid).\n/// Resources can be created in commands. Resources can also be retrieved in commands by\n/// their rid. Resources are thread-safe.\n///\n/// Resources are reference counted in Rust. This means that they can be\n/// cloned and passed around. When the last reference is dropped, the resource\n/// is automatically closed. As long as the resource exists in the resource\n/// table, the reference count is at least 1.\npub trait Resource: Any + 'static + Send + Sync {\n  /// Returns a string representation of the resource. The default implementation\n  /// returns the Rust type name, but specific resource types may override this\n  /// trait method.\n  fn name(&self) -> Cow<'_, str> {\n    type_name::<Self>().into()\n  }\n\n  /// Resources may implement the `close()` trait method if they need to do\n  /// resource specific clean-ups, such as cancelling pending futures, after a\n  /// resource has been removed from the resource table.\n  fn close(self: Arc<Self>) {}\n}\n\nimpl dyn Resource {\n  #[inline(always)]\n  fn is<T: Resource>(&self) -> bool {\n    self.type_id() == TypeId::of::<T>()\n  }\n\n  #[inline(always)]\n  pub(crate) fn downcast_arc<'a, T: Resource>(self: &'a Arc<Self>) -> Option<&'a Arc<T>> {\n    if self.is::<T>() {\n      // A resource is stored as `Arc<T>` in a BTreeMap\n      // and is safe to cast to `Arc<T>` because of the runtime\n      // check done in `self.is::<T>()`\n      let ptr = self as *const Arc<_> as *const Arc<T>;\n      Some(unsafe { &*ptr })\n    } else {\n      None\n    }\n  }\n}\n\n/// A `ResourceId` is an integer value referencing a resource. It could be\n/// considered to be the tauri equivalent of a `file descriptor` in POSIX like\n/// operating systems.\npub type ResourceId = u32;\n\n/// Map-like data structure storing Tauri's resources (equivalent to file\n/// descriptors).\n///\n/// Provides basic methods for element access. A resource can be of any type.\n/// Different types of resources can be stored in the same map, and provided\n/// with a name for description.\n///\n/// Each resource is identified through a _resource ID (rid)_, which acts as\n/// the key in the map.\n#[derive(Default)]\npub struct ResourceTable {\n  index: BTreeMap<ResourceId, Arc<dyn Resource>>,\n}\n\nimpl ResourceTable {\n  fn new_random_rid() -> u32 {\n    let mut bytes = [0_u8; 4];\n    getrandom::fill(&mut bytes).expect(\"failed to get random bytes\");\n    u32::from_ne_bytes(bytes)\n  }\n\n  /// Inserts resource into the resource table, which takes ownership of it.\n  ///\n  /// The resource type is erased at runtime and must be statically known\n  /// when retrieving it through `get()`.\n  ///\n  /// Returns a unique resource ID, which acts as a key for this resource.\n  pub fn add<T: Resource>(&mut self, resource: T) -> ResourceId {\n    self.add_arc(Arc::new(resource))\n  }\n\n  /// Inserts a `Arc`-wrapped resource into the resource table.\n  ///\n  /// The resource type is erased at runtime and must be statically known\n  /// when retrieving it through `get()`.\n  ///\n  /// Returns a unique resource ID, which acts as a key for this resource.\n  pub fn add_arc<T: Resource>(&mut self, resource: Arc<T>) -> ResourceId {\n    let resource = resource as Arc<dyn Resource>;\n    self.add_arc_dyn(resource)\n  }\n\n  /// Inserts a `Arc`-wrapped resource into the resource table.\n  ///\n  /// The resource type is erased at runtime and must be statically known\n  /// when retrieving it through `get()`.\n  ///\n  /// Returns a unique resource ID, which acts as a key for this resource.\n  pub fn add_arc_dyn(&mut self, resource: Arc<dyn Resource>) -> ResourceId {\n    let mut rid = Self::new_random_rid();\n    while self.index.contains_key(&rid) {\n      rid = Self::new_random_rid();\n    }\n\n    let removed_resource = self.index.insert(rid, resource);\n    assert!(removed_resource.is_none());\n    rid\n  }\n\n  /// Returns true if any resource with the given `rid` exists.\n  pub fn has(&self, rid: ResourceId) -> bool {\n    self.index.contains_key(&rid)\n  }\n\n  /// Returns a reference counted pointer to the resource of type `T` with the\n  /// given `rid`. If `rid` is not present or has a type different than `T`,\n  /// this function returns [`Error::BadResourceId`](crate::Error::BadResourceId).\n  pub fn get<T: Resource>(&self, rid: ResourceId) -> crate::Result<Arc<T>> {\n    self\n      .index\n      .get(&rid)\n      .and_then(|rc| rc.downcast_arc::<T>())\n      .cloned()\n      .ok_or_else(|| crate::Error::BadResourceId(rid))\n  }\n\n  /// Returns a reference counted pointer to the resource of the given `rid`.\n  /// If `rid` is not present, this function returns [`Error::BadResourceId`].\n  pub fn get_any(&self, rid: ResourceId) -> crate::Result<Arc<dyn Resource>> {\n    self\n      .index\n      .get(&rid)\n      .ok_or_else(|| crate::Error::BadResourceId(rid))\n      .cloned()\n  }\n\n  /// Replaces a resource with a new resource.\n  ///\n  /// Panics if the resource does not exist.\n  pub fn replace<T: Resource>(&mut self, rid: ResourceId, resource: T) {\n    let result = self\n      .index\n      .insert(rid, Arc::new(resource) as Arc<dyn Resource>);\n    assert!(result.is_some());\n  }\n\n  /// Removes a resource of type `T` from the resource table and returns it.\n  /// If a resource with the given `rid` exists but its type does not match `T`,\n  /// it is not removed from the resource table. Note that the resource's\n  /// `close()` method is *not* called.\n  ///\n  /// Also note that there might be a case where\n  /// the returned `Arc<T>` is referenced by other variables. That is, we cannot\n  /// assume that `Arc::strong_count(&returned_arc)` is always equal to 1 on success.\n  /// In particular, be really careful when you want to extract the inner value of\n  /// type `T` from `Arc<T>`.\n  pub fn take<T: Resource>(&mut self, rid: ResourceId) -> crate::Result<Arc<T>> {\n    let resource = self.get::<T>(rid)?;\n    self.index.remove(&rid);\n    Ok(resource)\n  }\n\n  /// Removes a resource from the resource table and returns it. Note that the\n  /// resource's `close()` method is *not* called.\n  ///\n  /// Also note that there might be a\n  /// case where the returned `Arc<T>` is referenced by other variables. That is,\n  /// we cannot assume that `Arc::strong_count(&returned_arc)` is always equal to 1\n  /// on success. In particular, be really careful when you want to extract the\n  /// inner value of type `T` from `Arc<T>`.\n  pub fn take_any(&mut self, rid: ResourceId) -> crate::Result<Arc<dyn Resource>> {\n    self\n      .index\n      .remove(&rid)\n      .ok_or_else(|| crate::Error::BadResourceId(rid))\n  }\n\n  /// Returns an iterator that yields a `(id, name)` pair for every resource\n  /// that's currently in the resource table. This can be used for debugging\n  /// purposes. Note that the order in\n  /// which items appear is not specified.\n  pub fn names(&self) -> impl Iterator<Item = (ResourceId, Cow<'_, str>)> {\n    self\n      .index\n      .iter()\n      .map(|(&id, resource)| (id, resource.name()))\n  }\n\n  /// Removes the resource with the given `rid` from the resource table. If the\n  /// only reference to this resource existed in the resource table, this will\n  /// cause the resource to be dropped. However, since resources are reference\n  /// counted, therefore pending ops are not automatically cancelled. A resource\n  /// may implement the `close()` method to perform clean-ups such as canceling\n  /// ops.\n  pub fn close(&mut self, rid: ResourceId) -> crate::Result<()> {\n    self\n      .index\n      .remove(&rid)\n      .ok_or_else(|| crate::Error::BadResourceId(rid))\n      .map(|resource| resource.close())\n  }\n\n  /// Removes and frees all resources stored. Note that the\n  /// resource's `close()` method is *not* called.\n  pub(crate) fn clear(&mut self) {\n    self.index.clear()\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/resources/plugin.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  command,\n  plugin::{Builder, TauriPlugin},\n  Manager, Runtime, Webview,\n};\n\nuse super::ResourceId;\n\n#[command(root = \"crate\")]\nfn close<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> crate::Result<()> {\n  let mut result = webview.resources_table().close(rid);\n  if result.is_err() {\n    result = webview.window().resources_table().close(rid);\n    if result.is_err() {\n      result = webview.app_handle().resources_table().close(rid);\n    }\n  }\n  result\n}\n\npub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {\n  Builder::new(\"resources\")\n    .invoke_handler(crate::generate_handler![\n      #![plugin(resources)]\n      close\n    ])\n    .build()\n}\n"
  },
  {
    "path": "crates/tauri/src/scope/fs.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  collections::{HashMap, HashSet},\n  fmt,\n  path::{Path, PathBuf, MAIN_SEPARATOR},\n  sync::{\n    atomic::{AtomicU32, Ordering},\n    Arc, Mutex,\n  },\n};\n\nuse tauri_utils::config::FsScope;\n\nuse crate::ScopeEventId;\n\npub use glob::Pattern;\n\n/// Scope change event.\n#[derive(Debug, Clone)]\npub enum Event {\n  /// A path has been allowed.\n  PathAllowed(PathBuf),\n  /// A path has been forbidden.\n  PathForbidden(PathBuf),\n}\n\ntype EventListener = Box<dyn Fn(&Event) + Send>;\n\n/// Scope for filesystem access.\n#[derive(Clone)]\npub struct Scope {\n  allowed_patterns: Arc<Mutex<HashSet<Pattern>>>,\n  forbidden_patterns: Arc<Mutex<HashSet<Pattern>>>,\n  event_listeners: Arc<Mutex<HashMap<ScopeEventId, EventListener>>>,\n  match_options: glob::MatchOptions,\n  next_event_id: Arc<AtomicU32>,\n}\n\nimpl Scope {\n  fn next_event_id(&self) -> u32 {\n    self.next_event_id.fetch_add(1, Ordering::Relaxed)\n  }\n}\n\nimpl fmt::Debug for Scope {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"Scope\")\n      .field(\n        \"allowed_patterns\",\n        &self\n          .allowed_patterns\n          .lock()\n          .unwrap()\n          .iter()\n          .map(|p| p.as_str())\n          .collect::<Vec<&str>>(),\n      )\n      .field(\n        \"forbidden_patterns\",\n        &self\n          .forbidden_patterns\n          .lock()\n          .unwrap()\n          .iter()\n          .map(|p| p.as_str())\n          .collect::<Vec<&str>>(),\n      )\n      .finish()\n  }\n}\n\nfn push_pattern<P: AsRef<Path>, F: Fn(&str) -> Result<Pattern, glob::PatternError>>(\n  list: &mut HashSet<Pattern>,\n  pattern: P,\n  f: F,\n) -> crate::Result<()> {\n  // Reconstruct pattern path components with appropraite separator\n  // so `some\\path/to/dir/**\\*` would be `some/path/to/dir/**/*` on Unix\n  // and  `some\\path\\to\\dir\\**\\*` on Windows.\n  let path: PathBuf = pattern.as_ref().components().collect();\n\n  // Add pattern as is to be matched with paths as is\n  let path_str = path.to_string_lossy();\n  list.insert(f(&path_str)?);\n\n  // On Windows, if path starts with a Prefix, try to strip it if possible\n  // so `\\\\?\\C:\\\\SomeDir` would result in a scope of:\n  //   - `\\\\?\\C:\\\\SomeDir`\n  //   - `C:\\\\SomeDir`\n  #[cfg(windows)]\n  {\n    use std::path::{Component, Prefix};\n\n    let mut components = path.components();\n\n    let is_unc = match components.next() {\n      Some(Component::Prefix(p)) => match p.kind() {\n        Prefix::VerbatimDisk(..) => true,\n        _ => false, // Other kinds of UNC paths\n      },\n      _ => false, // relative or empty\n    };\n\n    if is_unc {\n      // we remove UNC manually, instead of `dunce::simplified` because\n      // `path` could have `*` in it and that's not allowed on Windows and\n      // `dunce::simplified` will check that and return `path` as is without simplification\n      let simplified = path\n        .to_str()\n        .and_then(|s| s.get(4..))\n        .map_or(path.as_path(), Path::new);\n\n      let simplified_str = simplified.to_string_lossy();\n      if simplified_str != path_str {\n        list.insert(f(&simplified_str)?);\n      }\n    }\n  }\n\n  // Add canonicalized version of the pattern or canonicalized version of its parents\n  // so `/data/user/0/appid/assets/*` would be canonicalized to `/data/data/appid/assets/*`\n  // and can then be matched against any of them.\n  if let Some(p) = canonicalize_parent(path) {\n    list.insert(f(&p.to_string_lossy())?);\n  }\n\n  Ok(())\n}\n\n/// Attempt to canonicalize path or its parents in case we have a path like `/data/user/0/appid/**`\n/// where `**` obviously does not exist but we need to canonicalize the parent.\n///\n/// example: given the `/data/user/0/appid/assets/*` path,\n/// it's a glob pattern so it won't exist (std::fs::canonicalize() fails);\n///\n/// the second iteration needs to check `/data/user/0/appid/assets` and save the `*` component to append later.\n///\n/// if it also does not exist, a third iteration is required to check `/data/user/0/appid`\n/// with `assets/*` as the cached value (`checked_path` variable)\n/// on Android that gets canonicalized to `/data/data/appid` so the final value will be `/data/data/appid/assets/*`\n/// which is the value we want to check when we execute the `Scope::is_allowed` function\nfn canonicalize_parent(mut path: PathBuf) -> Option<PathBuf> {\n  let mut failed_components = None;\n\n  loop {\n    if let Ok(path) = path.canonicalize() {\n      break Some(if let Some(p) = failed_components {\n        path.join(p)\n      } else {\n        path\n      });\n    }\n\n    // grap the last component of the path\n    if let Some(mut last) = path.iter().next_back().map(PathBuf::from) {\n      // remove the last component of the path so the next iteration checks its parent\n      // if there is no more parent components, we failed to canonicalize\n      if !path.pop() {\n        break None;\n      }\n\n      // append the already checked path to the last component\n      // to construct `<last>/<checked_path>` and saved it for next iteration\n      if let Some(failed_components) = &failed_components {\n        last.push(failed_components);\n      }\n      failed_components.replace(last);\n    } else {\n      break None;\n    }\n  }\n}\nimpl Scope {\n  /// Creates a new scope from a [`FsScope`] configuration.\n  pub fn new<R: crate::Runtime, M: crate::Manager<R>>(\n    manager: &M,\n    scope: &FsScope,\n  ) -> crate::Result<Self> {\n    let mut allowed_patterns = HashSet::new();\n    for path in scope.allowed_paths() {\n      if let Ok(path) = manager.path().parse(path) {\n        push_pattern(&mut allowed_patterns, path, Pattern::new)?;\n      }\n    }\n\n    let mut forbidden_patterns = HashSet::new();\n    if let Some(forbidden_paths) = scope.forbidden_paths() {\n      for path in forbidden_paths {\n        if let Ok(path) = manager.path().parse(path) {\n          push_pattern(&mut forbidden_patterns, path, Pattern::new)?;\n        }\n      }\n    }\n\n    let require_literal_leading_dot = match scope {\n      FsScope::Scope {\n        require_literal_leading_dot: Some(require),\n        ..\n      } => *require,\n      // dotfiles are not supposed to be exposed by default on unix\n      #[cfg(unix)]\n      _ => true,\n      #[cfg(windows)]\n      _ => false,\n    };\n\n    Ok(Self {\n      allowed_patterns: Arc::new(Mutex::new(allowed_patterns)),\n      forbidden_patterns: Arc::new(Mutex::new(forbidden_patterns)),\n      event_listeners: Default::default(),\n      next_event_id: Default::default(),\n      match_options: glob::MatchOptions {\n        // this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt`\n        // see: <https://github.com/tauri-apps/tauri/security/advisories/GHSA-6mv3-wm7j-h4w5>\n        require_literal_separator: true,\n        require_literal_leading_dot,\n        ..Default::default()\n      },\n    })\n  }\n\n  /// The list of allowed patterns.\n  pub fn allowed_patterns(&self) -> HashSet<Pattern> {\n    self.allowed_patterns.lock().unwrap().clone()\n  }\n\n  /// The list of forbidden patterns.\n  pub fn forbidden_patterns(&self) -> HashSet<Pattern> {\n    self.forbidden_patterns.lock().unwrap().clone()\n  }\n\n  /// Listen to an event on this scope.\n  pub fn listen<F: Fn(&Event) + Send + 'static>(&self, f: F) -> ScopeEventId {\n    let id = self.next_event_id();\n    self.listen_with_id(id, f);\n    id\n  }\n\n  fn listen_with_id<F: Fn(&Event) + Send + 'static>(&self, id: ScopeEventId, f: F) {\n    self.event_listeners.lock().unwrap().insert(id, Box::new(f));\n  }\n\n  /// Listen to an event on this scope and immediately unlisten.\n  pub fn once<F: FnOnce(&Event) + Send + 'static>(&self, f: F) -> ScopeEventId {\n    let listerners = self.event_listeners.clone();\n    let handler = std::cell::Cell::new(Some(f));\n    let id = self.next_event_id();\n    self.listen_with_id(id, move |event| {\n      listerners.lock().unwrap().remove(&id);\n      let handler = handler\n        .take()\n        .expect(\"attempted to call handler more than once\");\n      handler(event)\n    });\n    id\n  }\n\n  /// Removes an event listener on this scope.\n  pub fn unlisten(&self, id: ScopeEventId) {\n    self.event_listeners.lock().unwrap().remove(&id);\n  }\n\n  fn emit(&self, event: Event) {\n    let listeners = self.event_listeners.lock().unwrap();\n    let handlers = listeners.values();\n    for listener in handlers {\n      listener(&event);\n    }\n  }\n\n  /// Extend the allowed patterns with the given directory.\n  ///\n  /// After this function has been called, the frontend will be able to use the Tauri API to read\n  /// the directory and all of its files. If `recursive` is `true`, subdirectories will be accessible too.\n  pub fn allow_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) -> crate::Result<()> {\n    let path = path.as_ref();\n    {\n      let mut list = self.allowed_patterns.lock().unwrap();\n\n      // allow the directory to be read\n      push_pattern(&mut list, path, escaped_pattern)?;\n      // allow its files and subdirectories to be read\n      push_pattern(&mut list, path, |p| {\n        escaped_pattern_with(p, if recursive { \"**\" } else { \"*\" })\n      })?;\n    }\n    self.emit(Event::PathAllowed(path.to_path_buf()));\n    Ok(())\n  }\n\n  /// Extend the allowed patterns with the given file path.\n  ///\n  /// After this function has been called, the frontend will be able to use the Tauri API to read the contents of this file.\n  pub fn allow_file<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {\n    let path = path.as_ref();\n    push_pattern(\n      &mut self.allowed_patterns.lock().unwrap(),\n      path,\n      escaped_pattern,\n    )?;\n    self.emit(Event::PathAllowed(path.to_path_buf()));\n    Ok(())\n  }\n\n  /// Set the given directory path to be forbidden by this scope.\n  ///\n  /// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.\n  pub fn forbid_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) -> crate::Result<()> {\n    let path = path.as_ref();\n    {\n      let mut list = self.forbidden_patterns.lock().unwrap();\n\n      // allow the directory to be read\n      push_pattern(&mut list, path, escaped_pattern)?;\n      // allow its files and subdirectories to be read\n      push_pattern(&mut list, path, |p| {\n        escaped_pattern_with(p, if recursive { \"**\" } else { \"*\" })\n      })?;\n    }\n    self.emit(Event::PathForbidden(path.to_path_buf()));\n    Ok(())\n  }\n\n  /// Set the given file path to be forbidden by this scope.\n  ///\n  /// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.\n  pub fn forbid_file<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {\n    let path = path.as_ref();\n    push_pattern(\n      &mut self.forbidden_patterns.lock().unwrap(),\n      path,\n      escaped_pattern,\n    )?;\n    self.emit(Event::PathForbidden(path.to_path_buf()));\n    Ok(())\n  }\n\n  /// Determines if the given path is allowed on this scope.\n  ///\n  /// Returns `false` if the path was explicitly forbidden or neither allowed nor forbidden.\n  ///\n  /// May return `false` if the path points to a broken symlink.\n  pub fn is_allowed<P: AsRef<Path>>(&self, path: P) -> bool {\n    let path = try_resolve_symlink_and_canonicalize(path);\n\n    if let Ok(path) = path {\n      let path: PathBuf = path.components().collect();\n      let forbidden = self\n        .forbidden_patterns\n        .lock()\n        .unwrap()\n        .iter()\n        .any(|p| p.matches_path_with(&path, self.match_options));\n\n      if forbidden {\n        false\n      } else {\n        let allowed = self\n          .allowed_patterns\n          .lock()\n          .unwrap()\n          .iter()\n          .any(|p| p.matches_path_with(&path, self.match_options));\n\n        allowed\n      }\n    } else {\n      false\n    }\n  }\n\n  /// Determines if the given path is explicitly forbidden on this scope.\n  ///\n  /// May return `true` if the path points to a broken symlink.\n  pub fn is_forbidden<P: AsRef<Path>>(&self, path: P) -> bool {\n    let path = try_resolve_symlink_and_canonicalize(path);\n\n    if let Ok(path) = path {\n      let path: PathBuf = path.components().collect();\n      self\n        .forbidden_patterns\n        .lock()\n        .unwrap()\n        .iter()\n        .any(|p| p.matches_path_with(&path, self.match_options))\n    } else {\n      true\n    }\n  }\n}\n\nfn try_resolve_symlink_and_canonicalize<P: AsRef<Path>>(path: P) -> crate::Result<PathBuf> {\n  let path = path.as_ref();\n  let path = if path.is_symlink() {\n    std::fs::read_link(path)?\n  } else {\n    path.to_path_buf()\n  };\n  if !path.exists() {\n    crate::Result::Ok(path)\n  } else {\n    std::fs::canonicalize(path).map_err(Into::into)\n  }\n}\n\nfn escaped_pattern(p: &str) -> Result<Pattern, glob::PatternError> {\n  Pattern::new(&glob::Pattern::escape(p))\n}\n\nfn escaped_pattern_with(p: &str, append: &str) -> Result<Pattern, glob::PatternError> {\n  if p.ends_with(MAIN_SEPARATOR) {\n    Pattern::new(&format!(\"{}{append}\", glob::Pattern::escape(p)))\n  } else {\n    Pattern::new(&format!(\n      \"{}{}{append}\",\n      glob::Pattern::escape(p),\n      MAIN_SEPARATOR\n    ))\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use std::collections::HashSet;\n\n  use glob::Pattern;\n\n  use super::{push_pattern, Scope};\n\n  fn new_scope() -> Scope {\n    Scope {\n      allowed_patterns: Default::default(),\n      forbidden_patterns: Default::default(),\n      event_listeners: Default::default(),\n      next_event_id: Default::default(),\n      match_options: glob::MatchOptions {\n        // this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt`\n        // see: <https://github.com/tauri-apps/tauri/security/advisories/GHSA-6mv3-wm7j-h4w5>\n        require_literal_separator: true,\n        // dotfiles are not supposed to be exposed by default on unix\n        #[cfg(unix)]\n        require_literal_leading_dot: true,\n        #[cfg(windows)]\n        require_literal_leading_dot: false,\n        ..Default::default()\n      },\n    }\n  }\n\n  #[test]\n  fn path_is_escaped() {\n    let scope = new_scope();\n    #[cfg(unix)]\n    {\n      scope.allow_directory(\"/home/tauri/**\", false).unwrap();\n      assert!(scope.is_allowed(\"/home/tauri/**\"));\n      assert!(scope.is_allowed(\"/home/tauri/**/file\"));\n      assert!(!scope.is_allowed(\"/home/tauri/anyfile\"));\n    }\n    #[cfg(windows)]\n    {\n      scope.allow_directory(\"C:\\\\home\\\\tauri\\\\**\", false).unwrap();\n      assert!(scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\"));\n      assert!(scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\\\\file\"));\n      assert!(!scope.is_allowed(\"C:\\\\home\\\\tauri\\\\anyfile\"));\n    }\n\n    let scope = new_scope();\n    #[cfg(unix)]\n    {\n      scope.allow_file(\"/home/tauri/**\").unwrap();\n      assert!(scope.is_allowed(\"/home/tauri/**\"));\n      assert!(!scope.is_allowed(\"/home/tauri/**/file\"));\n      assert!(!scope.is_allowed(\"/home/tauri/anyfile\"));\n    }\n    #[cfg(windows)]\n    {\n      scope.allow_file(\"C:\\\\home\\\\tauri\\\\**\").unwrap();\n      assert!(scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\"));\n      assert!(!scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\\\\file\"));\n      assert!(!scope.is_allowed(\"C:\\\\home\\\\tauri\\\\anyfile\"));\n    }\n\n    let scope = new_scope();\n    #[cfg(unix)]\n    {\n      scope.allow_directory(\"/home/tauri\", true).unwrap();\n      scope.forbid_directory(\"/home/tauri/**\", false).unwrap();\n      assert!(!scope.is_allowed(\"/home/tauri/**\"));\n      assert!(!scope.is_allowed(\"/home/tauri/**/file\"));\n      assert!(scope.is_allowed(\"/home/tauri/**/inner/file\"));\n      assert!(scope.is_allowed(\"/home/tauri/inner/folder/anyfile\"));\n      assert!(scope.is_allowed(\"/home/tauri/anyfile\"));\n    }\n    #[cfg(windows)]\n    {\n      scope.allow_directory(\"C:\\\\home\\\\tauri\", true).unwrap();\n      scope\n        .forbid_directory(\"C:\\\\home\\\\tauri\\\\**\", false)\n        .unwrap();\n      assert!(!scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\"));\n      assert!(!scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\\\\file\"));\n      assert!(scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\\\\inner\\\\file\"));\n      assert!(scope.is_allowed(\"C:\\\\home\\\\tauri\\\\inner\\\\folder\\\\anyfile\"));\n      assert!(scope.is_allowed(\"C:\\\\home\\\\tauri\\\\anyfile\"));\n    }\n\n    let scope = new_scope();\n    #[cfg(unix)]\n    {\n      scope.allow_directory(\"/home/tauri\", true).unwrap();\n      scope.forbid_file(\"/home/tauri/**\").unwrap();\n      assert!(!scope.is_allowed(\"/home/tauri/**\"));\n      assert!(scope.is_allowed(\"/home/tauri/**/file\"));\n      assert!(scope.is_allowed(\"/home/tauri/**/inner/file\"));\n      assert!(scope.is_allowed(\"/home/tauri/anyfile\"));\n    }\n    #[cfg(windows)]\n    {\n      scope.allow_directory(\"C:\\\\home\\\\tauri\", true).unwrap();\n      scope.forbid_file(\"C:\\\\home\\\\tauri\\\\**\").unwrap();\n      assert!(!scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\"));\n      assert!(scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\\\\file\"));\n      assert!(scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\\\\inner\\\\file\"));\n      assert!(scope.is_allowed(\"C:\\\\home\\\\tauri\\\\anyfile\"));\n    }\n\n    let scope = new_scope();\n    #[cfg(unix)]\n    {\n      scope.allow_directory(\"/home/tauri\", false).unwrap();\n      assert!(scope.is_allowed(\"/home/tauri/**\"));\n      assert!(!scope.is_allowed(\"/home/tauri/**/file\"));\n      assert!(!scope.is_allowed(\"/home/tauri/**/inner/file\"));\n      assert!(scope.is_allowed(\"/home/tauri/anyfile\"));\n    }\n    #[cfg(windows)]\n    {\n      scope.allow_directory(\"C:\\\\home\\\\tauri\", false).unwrap();\n      assert!(scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\"));\n      assert!(!scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\\\\file\"));\n      assert!(!scope.is_allowed(\"C:\\\\home\\\\tauri\\\\**\\\\inner\\\\file\"));\n      assert!(scope.is_allowed(\"C:\\\\home\\\\tauri\\\\anyfile\"));\n    }\n  }\n\n  #[cfg(windows)]\n  #[test]\n  fn windows_root_paths() {\n    let scope = new_scope();\n    {\n      // UNC network path\n      scope.allow_directory(\"\\\\\\\\localhost\\\\c$\", true).unwrap();\n      assert!(scope.is_allowed(\"\\\\\\\\localhost\\\\c$\"));\n      assert!(scope.is_allowed(\"\\\\\\\\localhost\\\\c$\\\\Windows\"));\n      assert!(scope.is_allowed(\"\\\\\\\\localhost\\\\c$\\\\NonExistentFile\"));\n      assert!(!scope.is_allowed(\"\\\\\\\\localhost\\\\d$\"));\n      assert!(!scope.is_allowed(\"\\\\\\\\OtherServer\\\\Share\"));\n    }\n\n    let scope = new_scope();\n    {\n      // Verbatim UNC network path\n      scope\n        .allow_directory(\"\\\\\\\\?\\\\UNC\\\\localhost\\\\c$\", true)\n        .unwrap();\n      assert!(scope.is_allowed(\"\\\\\\\\localhost\\\\c$\"));\n      assert!(scope.is_allowed(\"\\\\\\\\localhost\\\\c$\\\\Windows\"));\n      assert!(scope.is_allowed(\"\\\\\\\\?\\\\UNC\\\\localhost\\\\c$\\\\Windows\\\\NonExistentFile\"));\n      // A non-existent file cannot be canonicalized to a verbatim UNC path, so this will fail to match\n      assert!(!scope.is_allowed(\"\\\\\\\\localhost\\\\c$\\\\Windows\\\\NonExistentFile\"));\n      assert!(!scope.is_allowed(\"\\\\\\\\localhost\\\\d$\"));\n      assert!(!scope.is_allowed(\"\\\\\\\\OtherServer\\\\Share\"));\n    }\n\n    let scope = new_scope();\n    {\n      // Device namespace\n      scope.allow_file(\"\\\\\\\\.\\\\COM1\").unwrap();\n      assert!(scope.is_allowed(\"\\\\\\\\.\\\\COM1\"));\n      assert!(!scope.is_allowed(\"\\\\\\\\.\\\\COM2\"));\n    }\n\n    let scope = new_scope();\n    {\n      // Disk root\n      scope.allow_directory(\"C:\\\\\", true).unwrap();\n      assert!(scope.is_allowed(\"C:\\\\Windows\"));\n      assert!(scope.is_allowed(\"C:\\\\Windows\\\\system.ini\"));\n      assert!(scope.is_allowed(\"C:\\\\NonExistentFile\"));\n      assert!(!scope.is_allowed(\"D:\\\\home\"));\n    }\n\n    let scope = new_scope();\n    {\n      // Verbatim disk root\n      scope.allow_directory(\"\\\\\\\\?\\\\C:\\\\\", true).unwrap();\n      assert!(scope.is_allowed(\"C:\\\\Windows\"));\n      assert!(scope.is_allowed(\"C:\\\\Windows\\\\system.ini\"));\n      assert!(scope.is_allowed(\"C:\\\\NonExistentFile\"));\n      assert!(!scope.is_allowed(\"D:\\\\home\"));\n    }\n\n    let scope = new_scope();\n    {\n      // Verbatim path\n      scope.allow_file(\"\\\\\\\\?\\\\anyfile\").unwrap();\n      assert!(scope.is_allowed(\"\\\\\\\\?\\\\anyfile\"));\n      assert!(!scope.is_allowed(\"\\\\\\\\?\\\\otherfile\"));\n    }\n  }\n\n  #[test]\n  fn push_pattern_generated_paths() {\n    macro_rules! assert_pattern {\n      ($patterns:ident, $pattern:literal) => {\n        assert!($patterns.contains(&Pattern::new($pattern).unwrap()))\n      };\n    }\n\n    let mut patterns = HashSet::new();\n\n    #[cfg(not(windows))]\n    {\n      push_pattern(&mut patterns, \"/path/to/dir/\", Pattern::new).expect(\"failed to push pattern\");\n      push_pattern(&mut patterns, \"/path/to/dir/**\", Pattern::new).expect(\"failed to push pattern\");\n\n      assert_pattern!(patterns, \"/path/to/dir\");\n      assert_pattern!(patterns, \"/path/to/dir/**\");\n    }\n\n    #[cfg(windows)]\n    {\n      push_pattern(&mut patterns, \"C:\\\\path\\\\to\\\\dir\", Pattern::new)\n        .expect(\"failed to push pattern\");\n      push_pattern(&mut patterns, \"C:\\\\path\\\\to\\\\dir\\\\**\", Pattern::new)\n        .expect(\"failed to push pattern\");\n\n      assert_pattern!(patterns, \"C:\\\\path\\\\to\\\\dir\");\n      assert_pattern!(patterns, \"C:\\\\path\\\\to\\\\dir\\\\**\");\n      assert_pattern!(patterns, \"\\\\\\\\?\\\\C:\\\\path\\\\to\\\\dir\");\n      assert_pattern!(patterns, \"\\\\\\\\?\\\\C:\\\\path\\\\to\\\\dir\\\\**\");\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/scope/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/// FS scope.\npub mod fs;\n\nuse std::path::Path;\n\n/// Unique id of a scope event.\npub type ScopeEventId = u32;\n\n/// Managed state for all the core scopes in a tauri application.\npub struct Scopes {\n  #[cfg(feature = \"protocol-asset\")]\n  pub(crate) asset_protocol: fs::Scope,\n}\n\n#[allow(unused)]\nimpl Scopes {\n  /// Allows a directory on the scopes.\n  pub fn allow_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) -> crate::Result<()> {\n    #[cfg(feature = \"protocol-asset\")]\n    self.asset_protocol.allow_directory(path, recursive)?;\n    Ok(())\n  }\n\n  /// Allows a file on the scopes.\n  pub fn allow_file<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {\n    #[cfg(feature = \"protocol-asset\")]\n    self.asset_protocol.allow_file(path)?;\n    Ok(())\n  }\n\n  /// Forbids a file on the scopes.\n  pub fn forbid_file<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {\n    #[cfg(feature = \"protocol-asset\")]\n    self.asset_protocol.forbid_file(path)?;\n    Ok(())\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/state.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  any::{Any, TypeId},\n  collections::HashMap,\n  hash::BuildHasherDefault,\n  pin::Pin,\n  sync::Mutex,\n};\n\nuse crate::{\n  ipc::{CommandArg, CommandItem, InvokeError},\n  Runtime,\n};\n\n/// A guard for a state value.\n///\n/// See [`Manager::manage`](`crate::Manager::manage`) for usage examples.\npub struct State<'r, T: Send + Sync + 'static>(&'r T);\n\nimpl<'r, T: Send + Sync + 'static> State<'r, T> {\n  /// Retrieve a borrow to the underlying value with a lifetime of `'r`.\n  /// Using this method is typically unnecessary as `State` implements\n  /// [`std::ops::Deref`] with a [`std::ops::Deref::Target`] of `T`.\n  #[inline(always)]\n  pub fn inner(&self) -> &'r T {\n    self.0\n  }\n}\n\nimpl<T: Send + Sync + 'static> std::ops::Deref for State<'_, T> {\n  type Target = T;\n\n  #[inline(always)]\n  fn deref(&self) -> &T {\n    self.0\n  }\n}\n\nimpl<T: Send + Sync + 'static> Clone for State<'_, T> {\n  fn clone(&self) -> Self {\n    State(self.0)\n  }\n}\n\nimpl<T: Send + Sync + 'static + PartialEq> PartialEq for State<'_, T> {\n  fn eq(&self, other: &Self) -> bool {\n    self.0 == other.0\n  }\n}\n\nimpl<T: Send + Sync + std::fmt::Debug> std::fmt::Debug for State<'_, T> {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    f.debug_tuple(\"State\").field(&self.0).finish()\n  }\n}\n\nimpl<'r, 'de: 'r, T: Send + Sync + 'static, R: Runtime> CommandArg<'de, R> for State<'r, T> {\n  /// Grabs the [`State`] from the [`CommandItem`]. This will never fail.\n  fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {\n    command.message.state_ref().try_get().ok_or_else(|| {\n      InvokeError::from_anyhow(anyhow::anyhow!(\n        \"state not managed for field `{}` on command `{}`. You must call `.manage()` before using this command\",\n        command.key, command.name\n      ))\n    })\n  }\n}\n\n// Taken from: https://github.com/SergioBenitez/state/blob/556c1b94db8ce8427a0e72de7983ab5a9af4cc41/src/ident_hash.rs\n// This is a _super_ stupid hash. It just uses its input as the hash value. This\n// hash is meant to be used _only_ for \"prehashed\" values. In particular, we use\n// this so that hashing a TypeId is essentially a noop. This is because TypeIds\n// are already unique integers.\n#[derive(Default)]\nstruct IdentHash(u64);\n\nimpl std::hash::Hasher for IdentHash {\n  fn finish(&self) -> u64 {\n    self.0\n  }\n\n  fn write(&mut self, bytes: &[u8]) {\n    for byte in bytes {\n      self.write_u8(*byte);\n    }\n  }\n\n  fn write_u8(&mut self, i: u8) {\n    self.0 = (self.0 << 8) | (i as u64);\n  }\n\n  fn write_u64(&mut self, i: u64) {\n    self.0 = i;\n  }\n}\n\n/// Safety:\n/// - The `key` must equal to `(*value).type_id()`, see the safety doc in methods of [StateManager] for details.\n/// - Once you insert a value, you can't remove/mutated/move it anymore, see [StateManager::try_get] for details.\ntype TypeIdMap = HashMap<TypeId, Pin<Box<dyn Any + Sync + Send>>, BuildHasherDefault<IdentHash>>;\n\n/// The Tauri state manager.\n#[derive(Debug)]\npub struct StateManager {\n  map: Mutex<TypeIdMap>,\n}\n\nimpl StateManager {\n  pub(crate) fn new() -> Self {\n    Self {\n      map: Default::default(),\n    }\n  }\n\n  pub(crate) fn set<T: Send + Sync + 'static>(&self, state: T) -> bool {\n    let mut map = self.map.lock().unwrap();\n    let type_id = TypeId::of::<T>();\n    let already_set = map.contains_key(&type_id);\n    if !already_set {\n      let ptr = Box::new(state) as Box<dyn Any + Sync + Send>;\n      let pinned_ptr = Box::into_pin(ptr);\n      map.insert(\n        type_id,\n        // SAFETY: keep the type of the key is the same as the type of the value，\n        // see [try_get] methods for details.\n        pinned_ptr,\n      );\n    }\n    !already_set\n  }\n\n  /// SAFETY: Calling this method will move the `value`,\n  /// which will cause references obtained through [Self::try_get] to dangle.\n  pub(crate) unsafe fn unmanage<T: Send + Sync + 'static>(&self) -> Option<T> {\n    let mut map = self.map.lock().unwrap();\n    let type_id = TypeId::of::<T>();\n    let pinned_ptr = map.remove(&type_id)?;\n    // SAFETY: The caller decides to break the immovability/safety here, then OK, just let it go.\n    let ptr = unsafe { Pin::into_inner_unchecked(pinned_ptr) };\n    let value = unsafe {\n      ptr\n        .downcast::<T>()\n        // SAFETY: the type of the key is the same as the type of the value\n        .unwrap_unchecked()\n    };\n    Some(*value)\n  }\n\n  /// Gets the state associated with the specified type.\n  pub fn get<T: Send + Sync + 'static>(&self) -> State<'_, T> {\n    self\n      .try_get()\n      .unwrap_or_else(|| panic!(\"state not found for type {}\", std::any::type_name::<T>()))\n  }\n\n  /// Gets the state associated with the specified type.\n  pub fn try_get<T: Send + Sync + 'static>(&self) -> Option<State<'_, T>> {\n    let map = self.map.lock().unwrap();\n    let type_id = TypeId::of::<T>();\n    let ptr = map.get(&type_id)?;\n    let value = unsafe {\n      ptr\n        .downcast_ref::<T>()\n        // SAFETY: the type of the key is the same as the type of the value\n        .unwrap_unchecked()\n    };\n    // SAFETY: We ensure the lifetime of `value` is the same as [StateManager] and `value` will not be mutated/moved.\n    let v_ref = unsafe { &*(value as *const T) };\n    Some(State(v_ref))\n  }\n}\n\n// Ported from https://github.com/SergioBenitez/state/blob/556c1b94db8ce8427a0e72de7983ab5a9af4cc41/tests/main.rs\n#[cfg(test)]\nmod tests {\n  use super::StateManager;\n\n  use std::sync::{Arc, RwLock};\n  use std::thread;\n\n  // Tiny structures to test that dropping works as expected.\n  struct DroppingStruct(Arc<RwLock<bool>>);\n  struct DroppingStructWrap(#[allow(dead_code)] DroppingStruct);\n\n  impl Drop for DroppingStruct {\n    fn drop(&mut self) {\n      *self.0.write().unwrap() = true;\n    }\n  }\n\n  #[test]\n  #[should_panic(expected = \"state not found for type core::option::Option<alloc::string::String>\")]\n  fn get_panics() {\n    let state = StateManager::new();\n    state.get::<Option<String>>();\n  }\n\n  #[test]\n  fn simple_set_get() {\n    let state = StateManager::new();\n    assert!(state.set(1u32));\n    assert_eq!(*state.get::<u32>(), 1);\n  }\n\n  #[test]\n  fn simple_set_get_unmanage() {\n    let state = StateManager::new();\n    assert!(state.set(1u32));\n    assert_eq!(*state.get::<u32>(), 1);\n    // safety: the reference returned by `try_get` is already dropped.\n    assert!(unsafe { state.unmanage::<u32>() }.is_some());\n    assert!(unsafe { state.unmanage::<u32>() }.is_none());\n    assert_eq!(state.try_get::<u32>(), None);\n    assert!(state.set(2u32));\n    assert_eq!(*state.get::<u32>(), 2);\n  }\n\n  #[test]\n  fn dst_set_get() {\n    let state = StateManager::new();\n    assert!(state.set::<[u32; 4]>([1, 2, 3, 4u32]));\n    assert_eq!(*state.get::<[u32; 4]>(), [1, 2, 3, 4]);\n  }\n\n  #[test]\n  fn set_get_remote() {\n    let state = Arc::new(StateManager::new());\n    let sate_ = Arc::clone(&state);\n    thread::spawn(move || {\n      sate_.set(10isize);\n    })\n    .join()\n    .unwrap();\n\n    assert_eq!(*state.get::<isize>(), 10);\n  }\n\n  #[test]\n  fn two_put_get() {\n    let state = StateManager::new();\n    assert!(state.set(\"Hello, world!\".to_string()));\n\n    let s_old = state.get::<String>();\n    assert_eq!(*s_old, \"Hello, world!\");\n\n    assert!(!state.set::<String>(\"Bye bye!\".into()));\n    assert_eq!(*state.get::<String>(), \"Hello, world!\");\n    assert_eq!(state.get::<String>(), s_old);\n  }\n\n  #[test]\n  fn many_puts_only_one_succeeds() {\n    let state = Arc::new(StateManager::new());\n    let mut threads = vec![];\n    for _ in 0..1000 {\n      let state_ = Arc::clone(&state);\n      threads.push(thread::spawn(move || state_.set(10i64)))\n    }\n\n    let results: Vec<bool> = threads.into_iter().map(|t| t.join().unwrap()).collect();\n    assert_eq!(results.into_iter().filter(|&b| b).count(), 1);\n    assert_eq!(*state.get::<i64>(), 10);\n  }\n\n  // Ensure setting when already set doesn't cause a drop.\n  #[test]\n  fn test_no_drop_on_set() {\n    let state = StateManager::new();\n    let drop_flag = Arc::new(RwLock::new(false));\n    let dropping_struct = DroppingStruct(drop_flag.clone());\n\n    let _drop_flag_ignore = Arc::new(RwLock::new(false));\n    let _dropping_struct_ignore = DroppingStruct(_drop_flag_ignore);\n\n    state.set::<DroppingStruct>(dropping_struct);\n    assert!(!state.set::<DroppingStruct>(_dropping_struct_ignore));\n    assert!(!*drop_flag.read().unwrap());\n  }\n\n  // Ensure dropping a type_map drops its contents.\n  #[test]\n  fn drop_inners_on_drop() {\n    let drop_flag_a = Arc::new(RwLock::new(false));\n    let dropping_struct_a = DroppingStruct(drop_flag_a.clone());\n\n    let drop_flag_b = Arc::new(RwLock::new(false));\n    let dropping_struct_b = DroppingStructWrap(DroppingStruct(drop_flag_b.clone()));\n\n    {\n      let state = StateManager::new();\n      state.set(dropping_struct_a);\n      assert!(!*drop_flag_a.read().unwrap());\n\n      state.set(dropping_struct_b);\n      assert!(!*drop_flag_a.read().unwrap());\n      assert!(!*drop_flag_b.read().unwrap());\n    }\n\n    assert!(*drop_flag_a.read().unwrap());\n    assert!(*drop_flag_b.read().unwrap());\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/test/mock_runtime.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![allow(dead_code)]\n#![allow(missing_docs)]\n\nuse tauri_runtime::{\n  dpi::{PhysicalPosition, PhysicalSize, Position, Size},\n  monitor::Monitor,\n  webview::{DetachedWebview, PendingWebview},\n  window::{\n    CursorIcon, DetachedWindow, DetachedWindowWebview, PendingWindow, RawWindow, WindowBuilder,\n    WindowBuilderBase, WindowEvent, WindowId,\n  },\n  DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon, ProgressBarState,\n  Result, RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent,\n  WebviewDispatch, WindowDispatch, WindowEventId,\n};\n\n#[cfg(target_os = \"macos\")]\nuse tauri_utils::TitleBarStyle;\nuse tauri_utils::{config::WindowConfig, Theme};\nuse url::Url;\n\n#[cfg(windows)]\nuse windows::Win32::Foundation::HWND;\n\nuse std::{\n  cell::RefCell,\n  collections::HashMap,\n  fmt,\n  sync::{\n    atomic::{AtomicBool, AtomicU32, Ordering},\n    mpsc::{channel, sync_channel, Receiver, SyncSender},\n    Arc, Mutex,\n  },\n};\n\ntype ShortcutMap = HashMap<String, Box<dyn Fn() + Send + 'static>>;\n\nenum Message {\n  Task(Box<dyn FnOnce() + Send>),\n  CloseWindow(WindowId),\n  DestroyWindow(WindowId),\n}\n\nstruct Webview;\n\nstruct Window {\n  label: String,\n  webviews: Vec<Webview>,\n}\n\n#[derive(Clone)]\npub struct RuntimeContext {\n  is_running: Arc<AtomicBool>,\n  windows: Arc<RefCell<HashMap<WindowId, Window>>>,\n  shortcuts: Arc<Mutex<ShortcutMap>>,\n  run_tx: SyncSender<Message>,\n  next_window_id: Arc<AtomicU32>,\n  next_webview_id: Arc<AtomicU32>,\n  next_window_event_id: Arc<AtomicU32>,\n  next_webview_event_id: Arc<AtomicU32>,\n}\n\n// SAFETY: we ensure this type is only used on the main thread.\n#[allow(clippy::non_send_fields_in_send_ty)]\nunsafe impl Send for RuntimeContext {}\n\n// SAFETY: we ensure this type is only used on the main thread.\n#[allow(clippy::non_send_fields_in_send_ty)]\nunsafe impl Sync for RuntimeContext {}\n\nimpl RuntimeContext {\n  fn send_message(&self, message: Message) -> Result<()> {\n    if self.is_running.load(Ordering::Relaxed) {\n      self\n        .run_tx\n        .send(message)\n        .map_err(|_| Error::FailedToSendMessage)\n    } else {\n      match message {\n        Message::Task(task) => task(),\n        Message::CloseWindow(id) | Message::DestroyWindow(id) => {\n          self.windows.borrow_mut().remove(&id);\n        }\n      }\n      Ok(())\n    }\n  }\n\n  fn next_window_id(&self) -> WindowId {\n    self.next_window_id.fetch_add(1, Ordering::Relaxed).into()\n  }\n\n  fn next_webview_id(&self) -> u32 {\n    self.next_webview_id.fetch_add(1, Ordering::Relaxed)\n  }\n\n  fn next_window_event_id(&self) -> WindowEventId {\n    self.next_window_event_id.fetch_add(1, Ordering::Relaxed)\n  }\n\n  fn next_webview_event_id(&self) -> WindowEventId {\n    self.next_webview_event_id.fetch_add(1, Ordering::Relaxed)\n  }\n}\n\nimpl fmt::Debug for RuntimeContext {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"RuntimeContext\").finish()\n  }\n}\n\n#[derive(Debug, Clone)]\npub struct MockRuntimeHandle {\n  context: RuntimeContext,\n}\n\nimpl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {\n  type Runtime = MockRuntime;\n\n  fn create_proxy(&self) -> EventProxy {\n    EventProxy {}\n  }\n\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn set_activation_policy(\n    &self,\n    activation_policy: tauri_runtime::ActivationPolicy,\n  ) -> Result<()> {\n    Ok(())\n  }\n\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn set_dock_visibility(&self, visible: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn request_exit(&self, code: i32) -> Result<()> {\n    unimplemented!()\n  }\n\n  /// Create a new webview window.\n  fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(\n    &self,\n    pending: PendingWindow<T, Self::Runtime>,\n    _after_window_creation: Option<F>,\n  ) -> Result<DetachedWindow<T, Self::Runtime>> {\n    let id = self.context.next_window_id();\n\n    let (webview_id, webviews) = if let Some(w) = &pending.webview {\n      (Some(self.context.next_webview_id()), vec![Webview])\n    } else {\n      (None, Vec::new())\n    };\n\n    self.context.windows.borrow_mut().insert(\n      id,\n      Window {\n        label: pending.label.clone(),\n        webviews,\n      },\n    );\n\n    let webview = webview_id.map(|id| DetachedWindowWebview {\n      webview: DetachedWebview {\n        label: pending.label.clone(),\n        dispatcher: MockWebviewDispatcher {\n          id,\n          context: self.context.clone(),\n          url: Arc::new(Mutex::new(pending.webview.unwrap().url)),\n          last_evaluated_script: Default::default(),\n        },\n      },\n      use_https_scheme: false,\n    });\n\n    Ok(DetachedWindow {\n      id,\n      label: pending.label,\n      dispatcher: MockWindowDispatcher {\n        id,\n        context: self.context.clone(),\n      },\n      webview,\n    })\n  }\n\n  fn create_webview(\n    &self,\n    window_id: WindowId,\n    pending: PendingWebview<T, Self::Runtime>,\n  ) -> Result<DetachedWebview<T, Self::Runtime>> {\n    let id = self.context.next_webview_id();\n    let webview = Webview;\n    if let Some(w) = self.context.windows.borrow_mut().get_mut(&window_id) {\n      w.webviews.push(webview);\n    }\n\n    Ok(DetachedWebview {\n      label: pending.label,\n      dispatcher: MockWebviewDispatcher {\n        id,\n        context: self.context.clone(),\n        last_evaluated_script: Default::default(),\n        url: Arc::new(Mutex::new(pending.url)),\n      },\n    })\n  }\n\n  /// Run a task on the main thread.\n  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {\n    self.context.send_message(Message::Task(Box::new(f)))\n  }\n\n  fn display_handle(\n    &self,\n  ) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {\n    #[cfg(target_os = \"linux\")]\n    return Ok(unsafe {\n      raw_window_handle::DisplayHandle::borrow_raw(raw_window_handle::RawDisplayHandle::Xlib(\n        raw_window_handle::XlibDisplayHandle::new(None, 0),\n      ))\n    });\n    #[cfg(target_os = \"macos\")]\n    return Ok(unsafe {\n      raw_window_handle::DisplayHandle::borrow_raw(raw_window_handle::RawDisplayHandle::AppKit(\n        raw_window_handle::AppKitDisplayHandle::new(),\n      ))\n    });\n    #[cfg(windows)]\n    return Ok(unsafe {\n      raw_window_handle::DisplayHandle::borrow_raw(raw_window_handle::RawDisplayHandle::Windows(\n        raw_window_handle::WindowsDisplayHandle::new(),\n      ))\n    });\n    #[cfg(not(any(target_os = \"linux\", target_os = \"macos\", windows)))]\n    unimplemented!();\n  }\n\n  fn primary_monitor(&self) -> Option<Monitor> {\n    unimplemented!()\n  }\n\n  fn monitor_from_point(&self, x: f64, y: f64) -> Option<Monitor> {\n    unimplemented!()\n  }\n\n  fn available_monitors(&self) -> Vec<Monitor> {\n    unimplemented!()\n  }\n\n  fn set_theme(&self, theme: Option<Theme>) {\n    unimplemented!()\n  }\n\n  /// Shows the application, but does not automatically focus it.\n  #[cfg(target_os = \"macos\")]\n  fn show(&self) -> Result<()> {\n    Ok(())\n  }\n\n  /// Hides the application.\n  #[cfg(target_os = \"macos\")]\n  fn hide(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_device_event_filter(&self, _: DeviceEventFilter) {\n    // no-op\n  }\n\n  #[cfg(target_os = \"android\")]\n  fn find_class<'a>(\n    &self,\n    env: &mut jni::JNIEnv<'a>,\n    activity: &jni::objects::JObject<'_>,\n    name: impl Into<String>,\n  ) -> std::result::Result<jni::objects::JClass<'a>, jni::errors::Error> {\n    todo!()\n  }\n\n  #[cfg(target_os = \"android\")]\n  fn run_on_android_context<F>(&self, f: F)\n  where\n    F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject, &jni::objects::JObject) + Send + 'static,\n  {\n    todo!()\n  }\n\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  fn fetch_data_store_identifiers<F: FnOnce(Vec<[u8; 16]>) + Send + 'static>(\n    &self,\n    cb: F,\n  ) -> Result<()> {\n    todo!()\n  }\n\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  fn remove_data_store<F: FnOnce(Result<()>) + Send + 'static>(\n    &self,\n    uuid: [u8; 16],\n    cb: F,\n  ) -> Result<()> {\n    todo!()\n  }\n\n  fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {\n    Ok(PhysicalPosition::new(0.0, 0.0))\n  }\n}\n\n#[derive(Debug, Clone)]\npub struct MockWebviewDispatcher {\n  id: u32,\n  context: RuntimeContext,\n  url: Arc<Mutex<String>>,\n  last_evaluated_script: Arc<Mutex<Option<String>>>,\n}\n\nimpl MockWebviewDispatcher {\n  pub fn last_evaluated_script(&self) -> Option<String> {\n    self.last_evaluated_script.lock().unwrap().clone()\n  }\n}\n\n#[derive(Debug, Clone)]\npub struct MockWindowDispatcher {\n  id: WindowId,\n  context: RuntimeContext,\n}\n\n#[derive(Debug, Clone)]\npub struct MockWindowBuilder {}\n\nimpl WindowBuilderBase for MockWindowBuilder {}\n\nimpl WindowBuilder for MockWindowBuilder {\n  fn new() -> Self {\n    Self {}\n  }\n\n  fn with_config(config: &WindowConfig) -> Self {\n    Self {}\n  }\n\n  fn center(self) -> Self {\n    self\n  }\n\n  fn position(self, x: f64, y: f64) -> Self {\n    self\n  }\n\n  fn inner_size(self, min_width: f64, min_height: f64) -> Self {\n    self\n  }\n\n  fn min_inner_size(self, min_width: f64, min_height: f64) -> Self {\n    self\n  }\n\n  fn max_inner_size(self, max_width: f64, max_height: f64) -> Self {\n    self\n  }\n\n  fn inner_size_constraints(\n    self,\n    constraints: tauri_runtime::window::WindowSizeConstraints,\n  ) -> Self {\n    self\n  }\n\n  fn prevent_overflow(self) -> Self {\n    self\n  }\n\n  fn prevent_overflow_with_margin(self, margin: tauri_runtime::dpi::Size) -> Self {\n    self\n  }\n\n  fn resizable(self, resizable: bool) -> Self {\n    self\n  }\n\n  fn maximizable(self, resizable: bool) -> Self {\n    self\n  }\n\n  fn minimizable(self, resizable: bool) -> Self {\n    self\n  }\n\n  fn closable(self, resizable: bool) -> Self {\n    self\n  }\n\n  fn title<S: Into<String>>(self, title: S) -> Self {\n    self\n  }\n\n  fn fullscreen(self, fullscreen: bool) -> Self {\n    self\n  }\n\n  fn focused(self, focused: bool) -> Self {\n    self\n  }\n\n  fn focusable(self, focusable: bool) -> Self {\n    self\n  }\n\n  fn maximized(self, maximized: bool) -> Self {\n    self\n  }\n\n  fn visible(self, visible: bool) -> Self {\n    self\n  }\n\n  #[cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\"))]\n  #[cfg_attr(\n    docsrs,\n    doc(cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\")))\n  )]\n  fn transparent(self, transparent: bool) -> Self {\n    self\n  }\n\n  fn decorations(self, decorations: bool) -> Self {\n    self\n  }\n\n  fn always_on_bottom(self, always_on_bottom: bool) -> Self {\n    self\n  }\n\n  fn always_on_top(self, always_on_top: bool) -> Self {\n    self\n  }\n\n  fn visible_on_all_workspaces(self, visible_on_all_workspaces: bool) -> Self {\n    self\n  }\n\n  fn content_protected(self, protected: bool) -> Self {\n    self\n  }\n\n  fn icon(self, icon: Icon<'_>) -> Result<Self> {\n    Ok(self)\n  }\n\n  fn skip_taskbar(self, skip: bool) -> Self {\n    self\n  }\n\n  fn window_classname<S: Into<String>>(self, classname: S) -> Self {\n    self\n  }\n\n  fn shadow(self, enable: bool) -> Self {\n    self\n  }\n\n  #[cfg(windows)]\n  fn owner(self, owner: HWND) -> Self {\n    self\n  }\n\n  #[cfg(windows)]\n  fn parent(self, parent: HWND) -> Self {\n    self\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn parent(self, parent: *mut std::ffi::c_void) -> Self {\n    self\n  }\n\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn transient_for(self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self {\n    self\n  }\n\n  #[cfg(windows)]\n  fn drag_and_drop(self, enabled: bool) -> Self {\n    self\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn title_bar_style(self, style: TitleBarStyle) -> Self {\n    self\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn traffic_light_position<P: Into<Position>>(self, position: P) -> Self {\n    self\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn hidden_title(self, transparent: bool) -> Self {\n    self\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn tabbing_identifier(self, identifier: &str) -> Self {\n    self\n  }\n\n  fn theme(self, theme: Option<Theme>) -> Self {\n    self\n  }\n\n  fn has_icon(&self) -> bool {\n    false\n  }\n\n  fn get_theme(&self) -> Option<Theme> {\n    None\n  }\n\n  fn background_color(self, _color: tauri_utils::config::Color) -> Self {\n    self\n  }\n}\n\nimpl<T: UserEvent> WebviewDispatch<T> for MockWebviewDispatcher {\n  type Runtime = MockRuntime;\n\n  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {\n    self.context.send_message(Message::Task(Box::new(f)))\n  }\n\n  fn on_webview_event<F: Fn(&tauri_runtime::window::WebviewEvent) + Send + 'static>(\n    &self,\n    f: F,\n  ) -> tauri_runtime::WebviewEventId {\n    self.context.next_window_event_id()\n  }\n\n  fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()> {\n    Ok(())\n  }\n\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  fn open_devtools(&self) {}\n\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  fn close_devtools(&self) {}\n\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  fn is_devtools_open(&self) -> Result<bool> {\n    Ok(false)\n  }\n\n  fn set_zoom(&self, scale_factor: f64) -> Result<()> {\n    Ok(())\n  }\n\n  fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {\n    self\n      .last_evaluated_script\n      .lock()\n      .unwrap()\n      .replace(script.into());\n    Ok(())\n  }\n\n  fn url(&self) -> Result<String> {\n    Ok(self.url.lock().unwrap().clone())\n  }\n\n  fn bounds(&self) -> Result<tauri_runtime::dpi::Rect> {\n    Ok(tauri_runtime::dpi::Rect::default())\n  }\n\n  fn position(&self) -> Result<PhysicalPosition<i32>> {\n    Ok(PhysicalPosition { x: 0, y: 0 })\n  }\n\n  fn size(&self) -> Result<PhysicalSize<u32>> {\n    Ok(PhysicalSize {\n      width: 0,\n      height: 0,\n    })\n  }\n\n  fn navigate(&self, url: Url) -> Result<()> {\n    *self.url.lock().unwrap() = url.to_string();\n    Ok(())\n  }\n\n  fn reload(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn print(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn close(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_bounds(&self, bounds: tauri_runtime::dpi::Rect) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_size(&self, _size: Size) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_position(&self, _position: Position) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_focus(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn reparent(&self, window_id: WindowId) -> Result<()> {\n    Ok(())\n  }\n\n  fn cookies(&self) -> Result<Vec<tauri_runtime::Cookie<'static>>> {\n    Ok(Vec::new())\n  }\n\n  fn cookies_for_url(&self, url: Url) -> Result<Vec<tauri_runtime::Cookie<'static>>> {\n    Ok(Vec::new())\n  }\n\n  fn set_cookie(&self, cookie: tauri_runtime::Cookie<'_>) -> Result<()> {\n    Ok(())\n  }\n\n  fn delete_cookie(&self, cookie: tauri_runtime::Cookie<'_>) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_auto_resize(&self, auto_resize: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn clear_all_browsing_data(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn hide(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn show(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_background_color(&self, color: Option<tauri_utils::config::Color>) -> Result<()> {\n    Ok(())\n  }\n}\n\nimpl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {\n  type Runtime = MockRuntime;\n\n  type WindowBuilder = MockWindowBuilder;\n\n  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {\n    self.context.send_message(Message::Task(Box::new(f)))\n  }\n\n  fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> WindowEventId {\n    self.context.next_window_event_id()\n  }\n\n  fn scale_factor(&self) -> Result<f64> {\n    Ok(1.0)\n  }\n\n  fn inner_position(&self) -> Result<PhysicalPosition<i32>> {\n    Ok(PhysicalPosition { x: 0, y: 0 })\n  }\n\n  fn outer_position(&self) -> Result<PhysicalPosition<i32>> {\n    Ok(PhysicalPosition { x: 0, y: 0 })\n  }\n\n  fn inner_size(&self) -> Result<PhysicalSize<u32>> {\n    Ok(PhysicalSize {\n      width: 0,\n      height: 0,\n    })\n  }\n\n  fn outer_size(&self) -> Result<PhysicalSize<u32>> {\n    Ok(PhysicalSize {\n      width: 0,\n      height: 0,\n    })\n  }\n\n  fn is_fullscreen(&self) -> Result<bool> {\n    Ok(false)\n  }\n\n  fn is_minimized(&self) -> Result<bool> {\n    Ok(false)\n  }\n\n  fn is_maximized(&self) -> Result<bool> {\n    Ok(false)\n  }\n\n  fn is_focused(&self) -> Result<bool> {\n    Ok(false)\n  }\n\n  fn is_decorated(&self) -> Result<bool> {\n    Ok(false)\n  }\n\n  fn is_resizable(&self) -> Result<bool> {\n    Ok(false)\n  }\n\n  fn is_maximizable(&self) -> Result<bool> {\n    Ok(true)\n  }\n\n  fn is_minimizable(&self) -> Result<bool> {\n    Ok(true)\n  }\n\n  fn is_closable(&self) -> Result<bool> {\n    Ok(true)\n  }\n\n  fn is_visible(&self) -> Result<bool> {\n    Ok(true)\n  }\n\n  fn title(&self) -> Result<String> {\n    Ok(String::new())\n  }\n\n  fn current_monitor(&self) -> Result<Option<Monitor>> {\n    Ok(None)\n  }\n\n  fn primary_monitor(&self) -> Result<Option<Monitor>> {\n    Ok(None)\n  }\n\n  fn monitor_from_point(&self, x: f64, y: f64) -> Result<Option<Monitor>> {\n    Ok(None)\n  }\n\n  fn available_monitors(&self) -> Result<Vec<Monitor>> {\n    Ok(Vec::new())\n  }\n\n  fn theme(&self) -> Result<Theme> {\n    Ok(Theme::Light)\n  }\n\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn gtk_window(&self) -> Result<gtk::ApplicationWindow> {\n    unimplemented!()\n  }\n\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn default_vbox(&self) -> Result<gtk::Box> {\n    unimplemented!()\n  }\n\n  fn window_handle(\n    &self,\n  ) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {\n    #[cfg(target_os = \"linux\")]\n    return unsafe {\n      Ok(raw_window_handle::WindowHandle::borrow_raw(\n        raw_window_handle::RawWindowHandle::Xlib(raw_window_handle::XlibWindowHandle::new(0)),\n      ))\n    };\n    #[cfg(target_os = \"macos\")]\n    return unsafe {\n      Ok(raw_window_handle::WindowHandle::borrow_raw(\n        raw_window_handle::RawWindowHandle::AppKit(raw_window_handle::AppKitWindowHandle::new(\n          std::ptr::NonNull::from(&()).cast(),\n        )),\n      ))\n    };\n    #[cfg(windows)]\n    return unsafe {\n      Ok(raw_window_handle::WindowHandle::borrow_raw(\n        raw_window_handle::RawWindowHandle::Win32(raw_window_handle::Win32WindowHandle::new(\n          std::num::NonZeroIsize::MIN,\n        )),\n      ))\n    };\n    #[cfg(not(any(target_os = \"linux\", target_os = \"macos\", windows)))]\n    unimplemented!();\n  }\n\n  fn center(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn request_user_attention(&self, request_type: Option<UserAttentionType>) -> Result<()> {\n    Ok(())\n  }\n\n  fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(\n    &mut self,\n    pending: PendingWindow<T, Self::Runtime>,\n    _after_window_creation: Option<F>,\n  ) -> Result<DetachedWindow<T, Self::Runtime>> {\n    let id = self.context.next_window_id();\n\n    let (webview_id, webviews) = if let Some(w) = &pending.webview {\n      (Some(self.context.next_webview_id()), vec![Webview])\n    } else {\n      (None, Vec::new())\n    };\n\n    self.context.windows.borrow_mut().insert(\n      id,\n      Window {\n        label: pending.label.clone(),\n        webviews,\n      },\n    );\n\n    let webview = webview_id.map(|id| DetachedWindowWebview {\n      webview: DetachedWebview {\n        label: pending.label.clone(),\n        dispatcher: MockWebviewDispatcher {\n          id,\n          context: self.context.clone(),\n          url: Arc::new(Mutex::new(pending.webview.unwrap().url)),\n          last_evaluated_script: Default::default(),\n        },\n      },\n      use_https_scheme: false,\n    });\n\n    Ok(DetachedWindow {\n      id,\n      label: pending.label,\n      dispatcher: MockWindowDispatcher {\n        id,\n        context: self.context.clone(),\n      },\n      webview,\n    })\n  }\n\n  fn create_webview(\n    &mut self,\n    pending: PendingWebview<T, Self::Runtime>,\n  ) -> Result<DetachedWebview<T, Self::Runtime>> {\n    let id = self.context.next_webview_id();\n    let webview = Webview;\n    if let Some(w) = self.context.windows.borrow_mut().get_mut(&self.id) {\n      w.webviews.push(webview);\n    }\n\n    Ok(DetachedWebview {\n      label: pending.label,\n      dispatcher: MockWebviewDispatcher {\n        id,\n        context: self.context.clone(),\n        last_evaluated_script: Default::default(),\n        url: Arc::new(Mutex::new(pending.url)),\n      },\n    })\n  }\n\n  fn set_resizable(&self, resizable: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_maximizable(&self, maximizable: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_minimizable(&self, minimizable: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_closable(&self, closable: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_title<S: Into<String>>(&self, title: S) -> Result<()> {\n    Ok(())\n  }\n\n  fn maximize(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn unmaximize(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn minimize(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn unminimize(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn show(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn hide(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn close(&self) -> Result<()> {\n    self.context.send_message(Message::CloseWindow(self.id))?;\n    Ok(())\n  }\n\n  fn destroy(&self) -> Result<()> {\n    self.context.send_message(Message::DestroyWindow(self.id))?;\n    Ok(())\n  }\n\n  fn set_decorations(&self, decorations: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_shadow(&self, shadow: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_always_on_bottom(&self, always_on_bottom: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_always_on_top(&self, always_on_top: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_visible_on_all_workspaces(&self, visible_on_all_workspaces: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_content_protected(&self, protected: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_size(&self, size: Size) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_min_size(&self, size: Option<Size>) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_max_size(&self, size: Option<Size>) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_position(&self, position: Position) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_fullscreen(&self, fullscreen: bool) -> Result<()> {\n    Ok(())\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn set_simple_fullscreen(&self, enable: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_focus(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_focusable(&self, focusable: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_icon(&self, icon: Icon<'_>) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_skip_taskbar(&self, skip: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_cursor_grab(&self, grab: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_cursor_visible(&self, visible: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_cursor_icon(&self, icon: CursorIcon) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_ignore_cursor_events(&self, ignore: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn start_dragging(&self) -> Result<()> {\n    Ok(())\n  }\n\n  fn start_resize_dragging(&self, direction: tauri_runtime::ResizeDirection) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_badge_label(&self, label: Option<String>) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_overlay_icon(&self, icon: Option<Icon<'_>>) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_traffic_light_position(&self, position: Position) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_size_constraints(\n    &self,\n    constraints: tauri_runtime::window::WindowSizeConstraints,\n  ) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_theme(&self, theme: Option<Theme>) -> Result<()> {\n    Ok(())\n  }\n\n  fn set_enabled(&self, enabled: bool) -> Result<()> {\n    Ok(())\n  }\n\n  fn is_enabled(&self) -> Result<bool> {\n    Ok(true)\n  }\n\n  fn is_always_on_top(&self) -> Result<bool> {\n    Ok(false)\n  }\n\n  fn set_background_color(&self, color: Option<tauri_utils::config::Color>) -> Result<()> {\n    Ok(())\n  }\n}\n\n#[derive(Debug, Clone)]\npub struct EventProxy {}\n\nimpl<T: UserEvent> EventLoopProxy<T> for EventProxy {\n  fn send_event(&self, event: T) -> Result<()> {\n    Ok(())\n  }\n}\n\n#[derive(Debug)]\npub struct MockRuntime {\n  is_running: Arc<AtomicBool>,\n  pub context: RuntimeContext,\n  run_rx: Receiver<Message>,\n}\n\nimpl MockRuntime {\n  fn init() -> Self {\n    let is_running = Arc::new(AtomicBool::new(false));\n    let (tx, rx) = sync_channel(256);\n    let context = RuntimeContext {\n      is_running: is_running.clone(),\n      windows: Default::default(),\n      shortcuts: Default::default(),\n      run_tx: tx,\n      next_window_id: Default::default(),\n      next_webview_id: Default::default(),\n      next_window_event_id: Default::default(),\n      next_webview_event_id: Default::default(),\n    };\n    Self {\n      is_running,\n      context,\n      run_rx: rx,\n    }\n  }\n}\n\nimpl<T: UserEvent> Runtime<T> for MockRuntime {\n  type WindowDispatcher = MockWindowDispatcher;\n  type WebviewDispatcher = MockWebviewDispatcher;\n  type Handle = MockRuntimeHandle;\n  type EventLoopProxy = EventProxy;\n\n  fn new(_args: RuntimeInitArgs) -> Result<Self> {\n    Ok(Self::init())\n  }\n\n  #[cfg(any(\n    windows,\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn new_any_thread(_args: RuntimeInitArgs) -> Result<Self> {\n    Ok(Self::init())\n  }\n\n  fn create_proxy(&self) -> EventProxy {\n    EventProxy {}\n  }\n\n  fn handle(&self) -> Self::Handle {\n    MockRuntimeHandle {\n      context: self.context.clone(),\n    }\n  }\n\n  fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(\n    &self,\n    pending: PendingWindow<T, Self>,\n    _after_window_creation: Option<F>,\n  ) -> Result<DetachedWindow<T, Self>> {\n    let id = self.context.next_window_id();\n\n    let (webview_id, webviews) = if let Some(w) = &pending.webview {\n      (Some(self.context.next_webview_id()), vec![Webview])\n    } else {\n      (None, Vec::new())\n    };\n\n    self.context.windows.borrow_mut().insert(\n      id,\n      Window {\n        label: pending.label.clone(),\n        webviews,\n      },\n    );\n\n    let webview = webview_id.map(|id| DetachedWindowWebview {\n      webview: DetachedWebview {\n        label: pending.label.clone(),\n        dispatcher: MockWebviewDispatcher {\n          id,\n          context: self.context.clone(),\n          url: Arc::new(Mutex::new(pending.webview.unwrap().url)),\n          last_evaluated_script: Default::default(),\n        },\n      },\n      use_https_scheme: false,\n    });\n\n    Ok(DetachedWindow {\n      id,\n      label: pending.label,\n      dispatcher: MockWindowDispatcher {\n        id,\n        context: self.context.clone(),\n      },\n      webview,\n    })\n  }\n\n  fn create_webview(\n    &self,\n    window_id: WindowId,\n    pending: PendingWebview<T, Self>,\n  ) -> Result<DetachedWebview<T, Self>> {\n    let id = self.context.next_webview_id();\n    let webview = Webview;\n    if let Some(w) = self.context.windows.borrow_mut().get_mut(&window_id) {\n      w.webviews.push(webview);\n    }\n\n    Ok(DetachedWebview {\n      label: pending.label,\n      dispatcher: MockWebviewDispatcher {\n        id,\n        context: self.context.clone(),\n        last_evaluated_script: Default::default(),\n        url: Arc::new(Mutex::new(pending.url)),\n      },\n    })\n  }\n\n  fn primary_monitor(&self) -> Option<Monitor> {\n    unimplemented!()\n  }\n\n  fn monitor_from_point(&self, x: f64, y: f64) -> Option<Monitor> {\n    unimplemented!()\n  }\n\n  fn available_monitors(&self) -> Vec<Monitor> {\n    unimplemented!()\n  }\n\n  fn set_theme(&self, theme: Option<Theme>) {\n    unimplemented!()\n  }\n\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn set_activation_policy(&mut self, activation_policy: tauri_runtime::ActivationPolicy) {}\n\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn set_dock_visibility(&mut self, visible: bool) {}\n\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn show(&self) {}\n\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn hide(&self) {}\n\n  fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {}\n\n  #[cfg(any(\n    target_os = \"macos\",\n    windows,\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn run_iteration<F: FnMut(RunEvent<T>)>(&mut self, callback: F) {}\n\n  fn run_return<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) -> i32 {\n    self.run(callback);\n\n    0\n  }\n\n  fn run<F: FnMut(RunEvent<T>) + 'static>(self, mut callback: F) {\n    self.is_running.store(true, Ordering::Relaxed);\n    callback(RunEvent::Ready);\n\n    loop {\n      if let Ok(m) = self.run_rx.try_recv() {\n        match m {\n          Message::Task(p) => p(),\n          Message::CloseWindow(id) => {\n            let label = self\n              .context\n              .windows\n              .borrow()\n              .get(&id)\n              .map(|w| w.label.clone());\n            if let Some(label) = label {\n              let (tx, rx) = channel();\n              callback(RunEvent::WindowEvent {\n                label,\n                event: WindowEvent::CloseRequested { signal_tx: tx },\n              });\n\n              let should_prevent = matches!(rx.try_recv(), Ok(true));\n              if !should_prevent {\n                self.context.windows.borrow_mut().remove(&id);\n\n                let is_empty = self.context.windows.borrow().is_empty();\n                if is_empty {\n                  let (tx, rx) = channel();\n                  callback(RunEvent::ExitRequested { code: None, tx });\n\n                  let recv = rx.try_recv();\n                  let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));\n\n                  if !should_prevent {\n                    break;\n                  }\n                }\n              }\n            }\n          }\n          Message::DestroyWindow(id) => {\n            let removed = self.context.windows.borrow_mut().remove(&id).is_some();\n            if removed {\n              let is_empty = self.context.windows.borrow().is_empty();\n              if is_empty {\n                let (tx, rx) = channel();\n                callback(RunEvent::ExitRequested { code: None, tx });\n\n                let recv = rx.try_recv();\n                let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));\n\n                if !should_prevent {\n                  break;\n                }\n              }\n            }\n          }\n        }\n      }\n\n      callback(RunEvent::MainEventsCleared);\n\n      std::thread::sleep(std::time::Duration::from_secs(1));\n    }\n\n    callback(RunEvent::Exit);\n  }\n\n  fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {\n    Ok(PhysicalPosition::new(0.0, 0.0))\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/test/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Utilities for unit testing on Tauri applications.\n//!\n//! # Stability\n//!\n//! This module is unstable.\n//!\n//! # Examples\n//!\n//! ```rust\n//! use tauri::test::{mock_builder, mock_context, noop_assets};\n//!\n//! #[tauri::command]\n//! fn ping() -> &'static str {\n//!     \"pong\"\n//! }\n//!\n//! fn create_app<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::App<R> {\n//!     builder\n//!         .invoke_handler(tauri::generate_handler![ping])\n//!         // remove the string argument to use your app's config file\n//!         .build(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n//!         .expect(\"failed to build app\")\n//! }\n//!\n//! fn main() {\n//!     // Use `tauri::Builder::default()` to use the default runtime rather than the `MockRuntime`;\n//!     // let app = create_app(tauri::Builder::default());\n//!     let app = create_app(mock_builder());\n//!     let webview = tauri::WebviewWindowBuilder::new(&app, \"main\", Default::default()).build().unwrap();\n//!\n//!     // run the `ping` command and assert it returns `pong`\n//!     let res = tauri::test::get_ipc_response(\n//!         &webview,\n//!         tauri::webview::InvokeRequest {\n//!             cmd: \"ping\".into(),\n//!             callback: tauri::ipc::CallbackFn(0),\n//!             error: tauri::ipc::CallbackFn(1),\n//!             // alternatively use \"tauri://localhost\"\n//!             url: \"http://tauri.localhost\".parse().unwrap(),\n//!             body: tauri::ipc::InvokeBody::default(),\n//!             headers: Default::default(),\n//!             invoke_key: tauri::test::INVOKE_KEY.to_string(),\n//!         },\n//!     ).map(|b| b.deserialize::<String>().unwrap());\n//! }\n//! ```\n\n#![allow(unused_variables)]\n\nmod mock_runtime;\npub use mock_runtime::*;\nuse serde::Serialize;\nuse serialize_to_javascript::DefaultTemplate;\n\nuse std::{borrow::Cow, collections::HashMap, fmt::Debug};\n\nuse crate::{\n  ipc::{InvokeError, InvokeResponse, InvokeResponseBody},\n  webview::InvokeRequest,\n  App, Assets, Builder, Context, Pattern, Runtime, Webview,\n};\nuse tauri_utils::{\n  acl::resolved::Resolved,\n  assets::{AssetKey, AssetsIter, CspHash},\n  config::{AppConfig, Config},\n};\n\n/// The invoke key used for tests.\npub const INVOKE_KEY: &str = \"__invoke-key__\";\n\n/// An empty [`Assets`] implementation.\npub struct NoopAsset {\n  assets: HashMap<String, Vec<u8>>,\n  csp_hashes: Vec<CspHash<'static>>,\n}\n\nimpl<R: Runtime> Assets<R> for NoopAsset {\n  fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {\n    None\n  }\n\n  fn iter(&self) -> Box<AssetsIter<'_>> {\n    Box::new(\n      self\n        .assets\n        .iter()\n        .map(|(k, b)| (Cow::Borrowed(k.as_str()), Cow::Borrowed(b.as_slice()))),\n    )\n  }\n\n  fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {\n    Box::new(self.csp_hashes.iter().copied())\n  }\n}\n\n/// Creates a new empty [`Assets`] implementation.\npub fn noop_assets() -> NoopAsset {\n  NoopAsset {\n    assets: Default::default(),\n    csp_hashes: Default::default(),\n  }\n}\n\n/// Creates a new [`crate::Context`] for testing.\npub fn mock_context<R: Runtime, A: Assets<R>>(assets: A) -> crate::Context<R> {\n  Context {\n    config: Config {\n      schema: None,\n      product_name: Default::default(),\n      main_binary_name: Default::default(),\n      version: Default::default(),\n      identifier: Default::default(),\n      app: AppConfig {\n        with_global_tauri: Default::default(),\n        windows: Vec::new(),\n        security: Default::default(),\n        tray_icon: None,\n        macos_private_api: false,\n        enable_gtk_app_id: false,\n      },\n      bundle: Default::default(),\n      build: Default::default(),\n      plugins: Default::default(),\n    },\n    assets: Box::new(assets),\n    default_window_icon: None,\n    app_icon: None,\n    #[cfg(all(desktop, feature = \"tray-icon\"))]\n    tray_icon: None,\n    package_info: crate::PackageInfo {\n      name: \"test\".into(),\n      version: \"0.1.0\".parse().unwrap(),\n      authors: \"Tauri\",\n      description: \"Tauri test\",\n      crate_name: \"test\",\n    },\n    pattern: Pattern::Brownfield,\n    runtime_authority: crate::runtime_authority!(Default::default(), Resolved::default()),\n    plugin_global_api_scripts: None,\n\n    #[cfg(dev)]\n    config_parent: None,\n  }\n}\n\n/// Creates a new [`Builder`] using the [`MockRuntime`].\n///\n/// To use a dummy [`Context`], see [`mock_app`].\n///\n/// # Examples\n///\n/// ```rust\n/// #[cfg(test)]\n/// fn do_something() {\n///   let app = tauri::test::mock_builder()\n///     // remove the string argument to use your app's config file\n///     .build(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n///     .unwrap();\n/// }\n/// ```\npub fn mock_builder() -> Builder<MockRuntime> {\n  let mut builder = Builder::<MockRuntime>::new().enable_macos_default_menu(false);\n\n  builder.invoke_initialization_script = crate::app::InvokeInitializationScript {\n    process_ipc_message_fn: crate::manager::webview::PROCESS_IPC_MESSAGE_FN,\n    os_name: std::env::consts::OS,\n    fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND,\n    invoke_key: INVOKE_KEY,\n  }\n  .render_default(&Default::default())\n  .unwrap()\n  .into_string();\n\n  builder.invoke_key = INVOKE_KEY.to_string();\n\n  builder\n}\n\n/// Creates a new [`App`] for testing using the [`mock_context`] with a [`noop_assets`].\npub fn mock_app() -> App<MockRuntime> {\n  mock_builder().build(mock_context(noop_assets())).unwrap()\n}\n\n/// Executes the given IPC message and assert the response matches the expected value.\n///\n/// # Examples\n///\n/// ```rust\n/// use tauri::test::{mock_builder, mock_context, noop_assets};\n///\n/// #[tauri::command]\n/// fn ping() -> &'static str {\n///     \"pong\"\n/// }\n///\n/// fn create_app<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::App<R> {\n///     builder\n///         .invoke_handler(tauri::generate_handler![ping])\n///         // remove the string argument to use your app's config file\n///         .build(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n///         .expect(\"failed to build app\")\n/// }\n///\n/// fn main() {\n///     let app = create_app(mock_builder());\n///     let webview = tauri::WebviewWindowBuilder::new(&app, \"main\", Default::default()).build().unwrap();\n///\n///     // run the `ping` command and assert it returns `pong`\n///     tauri::test::assert_ipc_response(\n///         &webview,\n///         tauri::webview::InvokeRequest {\n///             cmd: \"ping\".into(),\n///             callback: tauri::ipc::CallbackFn(0),\n///             error: tauri::ipc::CallbackFn(1),\n///             url: \"http://tauri.localhost\".parse().unwrap(),\n///             body: tauri::ipc::InvokeBody::default(),\n///             headers: Default::default(),\n///             invoke_key: tauri::test::INVOKE_KEY.to_string(),\n///         },\n///       Ok(\"pong\")\n///     );\n/// }\n/// ```\npub fn assert_ipc_response<\n  T: Serialize + Debug + Send + Sync + 'static,\n  W: AsRef<Webview<MockRuntime>>,\n>(\n  webview: &W,\n  request: InvokeRequest,\n  expected: Result<T, T>,\n) {\n  let response =\n    get_ipc_response(webview, request).map(|b| b.deserialize::<serde_json::Value>().unwrap());\n  assert_eq!(\n    response,\n    expected\n      .map(|e| serde_json::to_value(e).unwrap())\n      .map_err(|e| serde_json::to_value(e).unwrap())\n  );\n}\n\n#[allow(clippy::needless_doctest_main)]\n/// Executes the given IPC message and get the return value.\n///\n/// # Examples\n///\n/// ```rust\n/// use tauri::test::{mock_builder, mock_context, noop_assets};\n///\n/// #[tauri::command]\n/// fn ping() -> &'static str {\n///     \"pong\"\n/// }\n///\n/// fn create_app<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::App<R> {\n///     builder\n///         .invoke_handler(tauri::generate_handler![ping])\n///         // remove the string argument to use your app's config file\n///         .build(tauri::generate_context!(\"test/fixture/src-tauri/tauri.conf.json\"))\n///         .expect(\"failed to build app\")\n/// }\n///\n/// fn main() {\n///     let app = create_app(mock_builder());\n///     let webview = tauri::WebviewWindowBuilder::new(&app, \"main\", Default::default()).build().unwrap();\n///\n///     // run the `ping` command and assert it returns `pong`\n///     let res = tauri::test::get_ipc_response(\n///         &webview,\n///         tauri::webview::InvokeRequest {\n///             cmd: \"ping\".into(),\n///             callback: tauri::ipc::CallbackFn(0),\n///             error: tauri::ipc::CallbackFn(1),\n///             url: \"http://tauri.localhost\".parse().unwrap(),\n///             body: tauri::ipc::InvokeBody::default(),\n///             headers: Default::default(),\n///             invoke_key: tauri::test::INVOKE_KEY.to_string(),\n///         },\n///     );\n///     assert!(res.is_ok());\n///     assert_eq!(res.unwrap().deserialize::<String>().unwrap(), String::from(\"pong\"));\n/// }\n///```\npub fn get_ipc_response<W: AsRef<Webview<MockRuntime>>>(\n  webview: &W,\n  request: InvokeRequest,\n) -> Result<InvokeResponseBody, serde_json::Value> {\n  let (tx, rx) = std::sync::mpsc::sync_channel(1);\n  webview.as_ref().clone().on_message(\n    request,\n    Box::new(move |_window, _cmd, response, _callback, _error| {\n      tx.send(response).unwrap();\n    }),\n  );\n\n  let res = rx.recv().expect(\"Failed to receive result from command\");\n  match res {\n    InvokeResponse::Ok(b) => Ok(b),\n    InvokeResponse::Err(InvokeError(v)) => Err(v),\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use std::time::Duration;\n\n  use super::mock_app;\n\n  #[test]\n  fn run_app() {\n    let app = mock_app();\n\n    let w = crate::WebviewWindowBuilder::new(&app, \"main\", Default::default())\n      .build()\n      .unwrap();\n\n    std::thread::spawn(move || {\n      std::thread::sleep(Duration::from_secs(1));\n      w.close().unwrap();\n    });\n\n    app.run(|_app, event| {\n      println!(\"{event:?}\");\n    });\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/tray/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Tray icon types and utilities.\n\npub(crate) mod plugin;\n\nuse crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener};\nuse crate::menu::ContextMenu;\nuse crate::menu::MenuEvent;\nuse crate::resources::Resource;\nuse crate::{\n  image::Image, menu::run_item_main_thread, AppHandle, Manager, PhysicalPosition, Rect, Runtime,\n};\nuse crate::{ResourceId, UnsafeSend};\nuse serde::Serialize;\nuse std::path::Path;\npub use tray_icon::TrayIconId;\n\n/// Describes the mouse button state.\n#[derive(Default, Clone, Copy, PartialEq, Eq, Debug, Serialize)]\npub enum MouseButtonState {\n  /// Mouse button pressed.\n  #[default]\n  Up,\n  /// Mouse button released.\n  Down,\n}\n\nimpl From<tray_icon::MouseButtonState> for MouseButtonState {\n  fn from(value: tray_icon::MouseButtonState) -> Self {\n    match value {\n      tray_icon::MouseButtonState::Up => MouseButtonState::Up,\n      tray_icon::MouseButtonState::Down => MouseButtonState::Down,\n    }\n  }\n}\n\n/// Describes which mouse button triggered the event..\n#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Default)]\npub enum MouseButton {\n  /// Left mouse button.\n  #[default]\n  Left,\n  /// Right mouse button.\n  Right,\n  /// Middle mouse button.\n  Middle,\n}\n\nimpl From<tray_icon::MouseButton> for MouseButton {\n  fn from(value: tray_icon::MouseButton) -> Self {\n    match value {\n      tray_icon::MouseButton::Left => MouseButton::Left,\n      tray_icon::MouseButton::Right => MouseButton::Right,\n      tray_icon::MouseButton::Middle => MouseButton::Middle,\n    }\n  }\n}\n\n/// Describes a tray icon event.\n///\n/// ## Platform-specific:\n///\n/// - **Linux**: Unsupported. The event is not emitted even though the icon is shown\n///   and will still show a context menu on right click.\n#[derive(Debug, Clone, Serialize)]\n#[serde(tag = \"type\")]\n#[non_exhaustive]\npub enum TrayIconEvent {\n  /// A click happened on the tray icon.\n  #[serde(rename_all = \"camelCase\")]\n  Click {\n    /// Id of the tray icon which triggered this event.\n    id: TrayIconId,\n    /// Physical Position of this event.\n    position: PhysicalPosition<f64>,\n    /// Position and size of the tray icon.\n    rect: Rect,\n    /// Mouse button that triggered this event.\n    button: MouseButton,\n    /// Mouse button state when this event was triggered.\n    button_state: MouseButtonState,\n  },\n  /// A double click happened on the tray icon. **Windows Only**\n  DoubleClick {\n    /// Id of the tray icon which triggered this event.\n    id: TrayIconId,\n    /// Physical Position of this event.\n    position: PhysicalPosition<f64>,\n    /// Position and size of the tray icon.\n    rect: Rect,\n    /// Mouse button that triggered this event.\n    button: MouseButton,\n  },\n  /// The mouse entered the tray icon region.\n  Enter {\n    /// Id of the tray icon which triggered this event.\n    id: TrayIconId,\n    /// Physical Position of this event.\n    position: PhysicalPosition<f64>,\n    /// Position and size of the tray icon.\n    rect: Rect,\n  },\n  /// The mouse moved over the tray icon region.\n  Move {\n    /// Id of the tray icon which triggered this event.\n    id: TrayIconId,\n    /// Physical Position of this event.\n    position: PhysicalPosition<f64>,\n    /// Position and size of the tray icon.\n    rect: Rect,\n  },\n  /// The mouse left the tray icon region.\n  Leave {\n    /// Id of the tray icon which triggered this event.\n    id: TrayIconId,\n    /// Physical Position of this event.\n    position: PhysicalPosition<f64>,\n    /// Position and size of the tray icon.\n    rect: Rect,\n  },\n}\n\nimpl TrayIconEvent {\n  /// Get the id of the tray icon that triggered this event.\n  pub fn id(&self) -> &TrayIconId {\n    match self {\n      TrayIconEvent::Click { id, .. } => id,\n      TrayIconEvent::DoubleClick { id, .. } => id,\n      TrayIconEvent::Enter { id, .. } => id,\n      TrayIconEvent::Move { id, .. } => id,\n      TrayIconEvent::Leave { id, .. } => id,\n    }\n  }\n}\n\nimpl From<tray_icon::TrayIconEvent> for TrayIconEvent {\n  fn from(value: tray_icon::TrayIconEvent) -> Self {\n    match value {\n      tray_icon::TrayIconEvent::Click {\n        id,\n        position,\n        rect,\n        button,\n        button_state,\n      } => TrayIconEvent::Click {\n        id,\n        position,\n        rect: Rect {\n          position: rect.position.into(),\n          size: rect.size.into(),\n        },\n        button: button.into(),\n        button_state: button_state.into(),\n      },\n      tray_icon::TrayIconEvent::DoubleClick {\n        id,\n        position,\n        rect,\n        button,\n      } => TrayIconEvent::DoubleClick {\n        id,\n        position,\n        rect: Rect {\n          position: rect.position.into(),\n          size: rect.size.into(),\n        },\n        button: button.into(),\n      },\n      tray_icon::TrayIconEvent::Enter { id, position, rect } => TrayIconEvent::Enter {\n        id,\n        position,\n        rect: Rect {\n          position: rect.position.into(),\n          size: rect.size.into(),\n        },\n      },\n      tray_icon::TrayIconEvent::Move { id, position, rect } => TrayIconEvent::Move {\n        id,\n        position,\n        rect: Rect {\n          position: rect.position.into(),\n          size: rect.size.into(),\n        },\n      },\n      tray_icon::TrayIconEvent::Leave { id, position, rect } => TrayIconEvent::Leave {\n        id,\n        position,\n        rect: Rect {\n          position: rect.position.into(),\n          size: rect.size.into(),\n        },\n      },\n      _ => todo!(),\n    }\n  }\n}\n\n/// [`TrayIcon`] builder struct and associated methods.\n#[derive(Default)]\npub struct TrayIconBuilder<R: Runtime> {\n  on_menu_event: Option<GlobalMenuEventListener<AppHandle<R>>>,\n  on_tray_icon_event: Option<GlobalTrayIconEventListener<TrayIcon<R>>>,\n  inner: tray_icon::TrayIconBuilder,\n}\n\nimpl<R: Runtime> TrayIconBuilder<R> {\n  /// Creates a new tray icon builder.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Sometimes the icon won't be visible unless a menu is set.\n  ///   Setting an empty [`Menu`](crate::menu::Menu) is enough.\n  pub fn new() -> Self {\n    Self {\n      inner: tray_icon::TrayIconBuilder::new(),\n      on_menu_event: None,\n      on_tray_icon_event: None,\n    }\n  }\n\n  /// Creates a new tray icon builder with the specified id.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Sometimes the icon won't be visible unless a menu is set.\n  ///   Setting an empty [`Menu`](crate::menu::Menu) is enough.\n  pub fn with_id<I: Into<TrayIconId>>(id: I) -> Self {\n    let mut builder = Self::new();\n    builder.inner = builder.inner.with_id(id);\n    builder\n  }\n\n  /// Set the a menu for this tray icon.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux**: once a menu is set, it cannot be removed or replaced but you can change its content.\n  pub fn menu<M: ContextMenu>(mut self, menu: &M) -> Self {\n    self.inner = self.inner.with_menu(menu.inner_context_owned());\n    self\n  }\n\n  /// Set an icon for this tray icon.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Sometimes the icon won't be visible unless a menu is set.\n  ///   Setting an empty [`Menu`](crate::menu::Menu) is enough.\n  pub fn icon(mut self, icon: Image<'_>) -> Self {\n    let icon = icon.try_into().ok();\n    if let Some(icon) = icon {\n      self.inner = self.inner.with_icon(icon);\n    }\n    self\n  }\n\n  /// Set a tooltip for this tray icon.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Unsupported.\n  pub fn tooltip<S: AsRef<str>>(mut self, s: S) -> Self {\n    self.inner = self.inner.with_tooltip(s);\n    self\n  }\n\n  /// Set the tray icon title.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** The title will not be shown unless there is an icon\n  ///   as well.  The title is useful for numerical and other frequently\n  ///   updated information.  In general, it shouldn't be shown unless a\n  ///   user requests it as it can take up a significant amount of space\n  ///   on the user's panel.  This may not be shown in all visualizations.\n  /// - **Windows:** Unsupported.\n  pub fn title<S: AsRef<str>>(mut self, title: S) -> Self {\n    self.inner = self.inner.with_title(title);\n    self\n  }\n\n  /// Set tray icon temp dir path. **Linux only**.\n  ///\n  /// On Linux, we need to write the icon to the disk and usually it will\n  /// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.\n  pub fn temp_dir_path<P: AsRef<Path>>(mut self, s: P) -> Self {\n    self.inner = self.inner.with_temp_dir_path(s);\n    self\n  }\n\n  /// Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.\n  pub fn icon_as_template(mut self, is_template: bool) -> Self {\n    self.inner = self.inner.with_icon_as_template(is_template);\n    self\n  }\n\n  /// Whether to show the tray menu on left click or not, default is `true`.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Unsupported.\n  #[deprecated(\n    since = \"2.2.0\",\n    note = \"Use `TrayIconBuilder::show_menu_on_left_click` instead.\"\n  )]\n  pub fn menu_on_left_click(mut self, enable: bool) -> Self {\n    self.inner = self.inner.with_menu_on_left_click(enable);\n    self\n  }\n\n  /// Whether to show the tray menu on left click or not, default is `true`.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Unsupported.\n  pub fn show_menu_on_left_click(mut self, enable: bool) -> Self {\n    self.inner = self.inner.with_menu_on_left_click(enable);\n    self\n  }\n\n  /// Set a handler for menu events.\n  ///\n  /// Note that this handler is called for any menu event,\n  /// whether it is coming from this window, another window or from the tray icon menu.\n  pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Sync + Send + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.on_menu_event.replace(Box::new(f));\n    self\n  }\n\n  /// Set a handler for this tray icon events.\n  pub fn on_tray_icon_event<F: Fn(&TrayIcon<R>, TrayIconEvent) + Sync + Send + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.on_tray_icon_event.replace(Box::new(f));\n    self\n  }\n\n  /// Access the unique id that will be assigned to the tray icon\n  /// this builder will create.\n  pub fn id(&self) -> &TrayIconId {\n    self.inner.id()\n  }\n\n  pub(crate) fn build_inner(\n    self,\n    app_handle: &AppHandle<R>,\n  ) -> crate::Result<(TrayIcon<R>, ResourceId)> {\n    let id = self.id().clone();\n\n    // SAFETY:\n    // the menu within this builder was created on main thread\n    // and will be accessed on the main thread\n    let unsafe_builder = UnsafeSend(self.inner);\n\n    let (tx, rx) = std::sync::mpsc::channel();\n    let unsafe_tray = app_handle\n      .run_on_main_thread(move || {\n        // SAFETY: will only be accessed on main thread\n        let _ = tx.send(unsafe_builder.take().build().map(UnsafeSend));\n      })\n      .and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))??;\n\n    let icon = TrayIcon {\n      id,\n      inner: unsafe_tray.take(),\n      app_handle: app_handle.clone(),\n    };\n\n    let rid = icon.register(\n      &icon.app_handle,\n      self.on_menu_event,\n      self.on_tray_icon_event,\n    );\n\n    Ok((icon, rid))\n  }\n\n  /// Builds and adds a new [`TrayIcon`] to the system tray.\n  pub fn build<M: Manager<R>>(self, manager: &M) -> crate::Result<TrayIcon<R>> {\n    let (icon, _rid) = self.build_inner(manager.app_handle())?;\n    Ok(icon)\n  }\n}\n\n/// Tray icon struct and associated methods.\n///\n/// This type is reference-counted and the icon is removed when the last instance is dropped.\n///\n/// See [TrayIconBuilder] to construct this type.\n#[tauri_macros::default_runtime(crate::Wry, wry)]\npub struct TrayIcon<R: Runtime> {\n  id: TrayIconId,\n  inner: tray_icon::TrayIcon,\n  app_handle: AppHandle<R>,\n}\n\nimpl<R: Runtime> Clone for TrayIcon<R> {\n  fn clone(&self) -> Self {\n    Self {\n      id: self.id.clone(),\n      inner: self.inner.clone(),\n      app_handle: self.app_handle.clone(),\n    }\n  }\n}\n\n/// # Safety\n///\n/// We make sure it always runs on the main thread.\nunsafe impl<R: Runtime> Sync for TrayIcon<R> {}\nunsafe impl<R: Runtime> Send for TrayIcon<R> {}\n\nimpl<R: Runtime> TrayIcon<R> {\n  fn register(\n    &self,\n    app_handle: &AppHandle<R>,\n    on_menu_event: Option<GlobalMenuEventListener<AppHandle<R>>>,\n    on_tray_icon_event: Option<GlobalTrayIconEventListener<TrayIcon<R>>>,\n  ) -> ResourceId {\n    if let Some(handler) = on_menu_event {\n      app_handle\n        .manager\n        .menu\n        .global_event_listeners\n        .lock()\n        .unwrap()\n        .push(handler);\n    }\n\n    if let Some(handler) = on_tray_icon_event {\n      app_handle\n        .manager\n        .tray\n        .event_listeners\n        .lock()\n        .unwrap()\n        .insert(self.id.clone(), handler);\n    }\n\n    let rid = app_handle.resources_table().add(self.clone());\n    app_handle\n      .manager\n      .tray\n      .icons\n      .lock()\n      .unwrap()\n      .push((self.id().clone(), rid));\n    rid\n  }\n\n  /// The application handle associated with this type.\n  pub fn app_handle(&self) -> &AppHandle<R> {\n    &self.app_handle\n  }\n\n  /// Register a handler for menu events.\n  ///\n  /// Note that this handler is called for any menu event,\n  /// whether it is coming from this window, another window or from the tray icon menu.\n  pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Sync + Send + 'static>(&self, f: F) {\n    self\n      .app_handle\n      .manager\n      .menu\n      .global_event_listeners\n      .lock()\n      .unwrap()\n      .push(Box::new(f));\n  }\n\n  /// Register a handler for this tray icon events.\n  pub fn on_tray_icon_event<F: Fn(&TrayIcon<R>, TrayIconEvent) + Sync + Send + 'static>(\n    &self,\n    f: F,\n  ) {\n    self\n      .app_handle\n      .manager\n      .tray\n      .event_listeners\n      .lock()\n      .unwrap()\n      .insert(self.id.clone(), Box::new(f));\n  }\n\n  /// Returns the id associated with this tray icon.\n  pub fn id(&self) -> &TrayIconId {\n    &self.id\n  }\n\n  /// Sets a new tray icon. If `None` is provided, it will remove the icon.\n  pub fn set_icon(&self, icon: Option<Image<'_>>) -> crate::Result<()> {\n    let icon = match icon {\n      Some(i) => Some(i.try_into()?),\n      None => None,\n    };\n    run_item_main_thread!(self, |self_: Self| self_.inner.set_icon(icon))?.map_err(Into::into)\n  }\n\n  /// Sets a new tray menu.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux**: once a menu is set it cannot be removed so `None` has no effect\n  pub fn set_menu<M: ContextMenu + 'static>(&self, menu: Option<M>) -> crate::Result<()> {\n    run_item_main_thread!(self, |self_: Self| {\n      self_.inner.set_menu(menu.map(|m| m.inner_context_owned()))\n    })\n  }\n\n  /// Sets the tooltip for this tray icon.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** Unsupported\n  pub fn set_tooltip<S: AsRef<str>>(&self, tooltip: Option<S>) -> crate::Result<()> {\n    let s = tooltip.map(|s| s.as_ref().to_string());\n    run_item_main_thread!(self, |self_: Self| self_.inner.set_tooltip(s))?.map_err(Into::into)\n  }\n\n  /// Sets the title for this tray icon.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux:** The title will not be shown unless there is an icon\n  ///   as well.  The title is useful for numerical and other frequently\n  ///   updated information.  In general, it shouldn't be shown unless a\n  ///   user requests it as it can take up a significant amount of space\n  ///   on the user's panel.  This may not be shown in all visualizations.\n  /// - **Windows:** Unsupported\n  pub fn set_title<S: AsRef<str>>(&self, title: Option<S>) -> crate::Result<()> {\n    let s = title.map(|s| s.as_ref().to_string());\n    run_item_main_thread!(self, |self_: Self| self_.inner.set_title(s))\n  }\n\n  /// Show or hide this tray icon.\n  pub fn set_visible(&self, visible: bool) -> crate::Result<()> {\n    run_item_main_thread!(self, |self_: Self| self_.inner.set_visible(visible))?.map_err(Into::into)\n  }\n\n  /// Sets the tray icon temp dir path. **Linux only**.\n  ///\n  /// On Linux, we need to write the icon to the disk and usually it will\n  /// be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.\n  pub fn set_temp_dir_path<P: AsRef<Path>>(&self, path: Option<P>) -> crate::Result<()> {\n    #[allow(unused)]\n    let p = path.map(|p| p.as_ref().to_path_buf());\n    #[cfg(target_os = \"linux\")]\n    run_item_main_thread!(self, |self_: Self| self_.inner.set_temp_dir_path(p))?;\n    Ok(())\n  }\n\n  /// Sets the current icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.\n  pub fn set_icon_as_template(&self, #[allow(unused)] is_template: bool) -> crate::Result<()> {\n    #[cfg(target_os = \"macos\")]\n    run_item_main_thread!(self, |self_: Self| {\n      self_.inner.set_icon_as_template(is_template)\n    })?;\n    Ok(())\n  }\n\n  /// Disable or enable showing the tray menu on left click.\n  ///\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux**: Unsupported.\n  pub fn set_show_menu_on_left_click(&self, #[allow(unused)] enable: bool) -> crate::Result<()> {\n    #[cfg(any(target_os = \"macos\", windows))]\n    run_item_main_thread!(self, |self_: Self| {\n      self_.inner.set_show_menu_on_left_click(enable)\n    })?;\n    Ok(())\n  }\n\n  /// Get tray icon rect.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux**: Unsupported, always returns `None`.\n  pub fn rect(&self) -> crate::Result<Option<crate::Rect>> {\n    run_item_main_thread!(self, |self_: Self| {\n      self_.inner.rect().map(|rect| Rect {\n        position: rect.position.into(),\n        size: rect.size.into(),\n      })\n    })\n  }\n\n  /// Do something with the inner [`tray_icon::TrayIcon`] on main thread\n  ///\n  /// Note that `tray-icon` crate may be updated in minor releases of Tauri.\n  /// Therefore, it’s recommended to pin Tauri to at least a minor version when you’re using `with_inner_tray_icon`.\n  pub fn with_inner_tray_icon<F, T>(&self, f: F) -> crate::Result<T>\n  where\n    F: FnOnce(&tray_icon::TrayIcon) -> T + Send + 'static,\n    T: Send + 'static,\n  {\n    run_item_main_thread!(self, |self_: Self| { f(&self_.inner) })\n  }\n}\n\nimpl<R: Runtime> Resource for TrayIcon<R> {\n  fn close(self: std::sync::Arc<Self>) {\n    let mut icons = self.app_handle.manager.tray.icons.lock().unwrap();\n    for (i, (tray_icon_id, _rid)) in icons.iter_mut().enumerate() {\n      if tray_icon_id == &self.id {\n        icons.swap_remove(i);\n        return;\n      }\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  #[test]\n  fn tray_event_json_serialization() {\n    // NOTE: if this test is ever changed, you probably need to change `TrayIconEvent` in JS as well\n\n    use super::*;\n    let event = TrayIconEvent::Click {\n      button: MouseButton::Left,\n      button_state: MouseButtonState::Down,\n      id: TrayIconId::new(\"id\"),\n      position: crate::PhysicalPosition::default(),\n      rect: crate::Rect {\n        position: tray_icon::Rect::default().position.into(),\n        size: tray_icon::Rect::default().size.into(),\n      },\n    };\n\n    let value = serde_json::to_value(&event).unwrap();\n    assert_eq!(\n      value,\n      serde_json::json!({\n          \"type\": \"Click\",\n          \"button\": \"Left\",\n          \"buttonState\": \"Down\",\n          \"id\": \"id\",\n          \"position\": {\n              \"x\": 0.0,\n              \"y\": 0.0,\n          },\n          \"rect\": {\n              \"size\": {\n                  \"Physical\": {\n                      \"width\": 0,\n                      \"height\": 0,\n                  }\n              },\n              \"position\": {\n                  \"Physical\": {\n                      \"x\": 0,\n                      \"y\": 0,\n                  }\n              },\n          }\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/tray/plugin.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::PathBuf;\n\nuse anyhow::Context;\nuse serde::Deserialize;\n\nuse crate::{\n  command,\n  image::JsImage,\n  ipc::Channel,\n  menu::{plugin::ItemKind, Menu, Submenu},\n  plugin::{Builder, TauriPlugin},\n  resources::ResourceId,\n  tray::TrayIconBuilder,\n  AppHandle, Manager, Runtime, Webview,\n};\n\nuse super::{TrayIcon, TrayIconEvent};\n\n#[derive(Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct TrayIconOptions {\n  id: Option<String>,\n  menu: Option<(ResourceId, ItemKind)>,\n  icon: Option<JsImage>,\n  tooltip: Option<String>,\n  title: Option<String>,\n  temp_dir_path: Option<PathBuf>,\n  icon_as_template: Option<bool>,\n  menu_on_left_click: Option<bool>,\n  show_menu_on_left_click: Option<bool>,\n}\n\n#[command(root = \"crate\")]\nfn new<R: Runtime>(\n  webview: Webview<R>,\n  options: TrayIconOptions,\n  handler: Channel<TrayIconEvent>,\n) -> crate::Result<(ResourceId, String)> {\n  let mut builder = if let Some(id) = options.id {\n    TrayIconBuilder::<R>::with_id(id)\n  } else {\n    TrayIconBuilder::<R>::new()\n  };\n\n  builder = builder.on_tray_icon_event(move |_tray, e| {\n    let _ = handler.send(e);\n  });\n\n  let resources_table = webview.resources_table();\n\n  if let Some((rid, kind)) = options.menu {\n    match kind {\n      ItemKind::Menu => {\n        let menu = resources_table.get::<Menu<R>>(rid)?;\n        builder = builder.menu(&*menu);\n      }\n      ItemKind::Submenu => {\n        let submenu = resources_table.get::<Submenu<R>>(rid)?;\n        builder = builder.menu(&*submenu);\n      }\n      _ => return Err(anyhow::anyhow!(\"unexpected menu item kind\").into()),\n    };\n  }\n  if let Some(icon) = options.icon {\n    builder = builder.icon(icon.into_img(&resources_table)?.as_ref().clone());\n  }\n  if let Some(tooltip) = options.tooltip {\n    builder = builder.tooltip(tooltip);\n  }\n  if let Some(title) = options.title {\n    builder = builder.title(title);\n  }\n  if let Some(temp_dir_path) = options.temp_dir_path {\n    builder = builder.temp_dir_path(temp_dir_path);\n  }\n  if let Some(icon_as_template) = options.icon_as_template {\n    builder = builder.icon_as_template(icon_as_template);\n  }\n  #[allow(deprecated)]\n  if let Some(menu_on_left_click) = options.menu_on_left_click {\n    builder = builder.menu_on_left_click(menu_on_left_click);\n  }\n  if let Some(show_menu_on_left_click) = options.show_menu_on_left_click {\n    builder = builder.show_menu_on_left_click(show_menu_on_left_click);\n  }\n\n  let (tray, rid) = builder.build_inner(webview.app_handle())?;\n  let id = tray.id().as_ref().to_string();\n\n  Ok((rid, id))\n}\n\n#[command(root = \"crate\")]\nfn get_by_id<R: Runtime>(app: AppHandle<R>, id: &str) -> Option<ResourceId> {\n  app.manager.tray.tray_resource_by_id(id)\n}\n\n#[command(root = \"crate\")]\nfn remove_by_id<R: Runtime>(app: AppHandle<R>, id: &str) -> crate::Result<()> {\n  app\n    .remove_tray_by_id(id)\n    .with_context(|| format!(\"Can't find a tray associated with this id: {id}\"))?;\n  Ok(())\n}\n\n#[command(root = \"crate\")]\nfn set_icon<R: Runtime>(\n  app: AppHandle<R>,\n  webview: Webview<R>,\n  rid: ResourceId,\n  icon: Option<JsImage>,\n) -> crate::Result<()> {\n  let resources_table = app.resources_table();\n  let tray = resources_table.get::<TrayIcon<R>>(rid)?;\n  let webview_resources_table = webview.resources_table();\n  let icon = match icon {\n    Some(i) => Some(i.into_img(&webview_resources_table)?.as_ref().clone()),\n    None => None,\n  };\n  tray.set_icon(icon)\n}\n\n#[command(root = \"crate\")]\nfn set_menu<R: Runtime>(\n  app: AppHandle<R>,\n  webview: Webview<R>,\n  rid: ResourceId,\n  menu: Option<(ResourceId, ItemKind)>,\n) -> crate::Result<()> {\n  let resources_table = app.resources_table();\n  let tray = resources_table.get::<TrayIcon<R>>(rid)?;\n  if let Some((rid, kind)) = menu {\n    let webview_resources_table = webview.resources_table();\n    match kind {\n      ItemKind::Menu => {\n        let menu = webview_resources_table.get::<Menu<R>>(rid)?;\n        tray.set_menu(Some((*menu).clone()))?;\n      }\n      ItemKind::Submenu => {\n        let submenu = webview_resources_table.get::<Submenu<R>>(rid)?;\n        tray.set_menu(Some((*submenu).clone()))?;\n      }\n      _ => return Err(anyhow::anyhow!(\"unexpected menu item kind\").into()),\n    };\n  } else {\n    tray.set_menu(None::<Menu<R>>)?;\n  }\n  Ok(())\n}\n\n#[command(root = \"crate\")]\nfn set_tooltip<R: Runtime>(\n  app: AppHandle<R>,\n  rid: ResourceId,\n  tooltip: Option<String>,\n) -> crate::Result<()> {\n  let resources_table = app.resources_table();\n  let tray = resources_table.get::<TrayIcon<R>>(rid)?;\n  tray.set_tooltip(tooltip)\n}\n\n#[command(root = \"crate\")]\nfn set_title<R: Runtime>(\n  app: AppHandle<R>,\n  rid: ResourceId,\n  title: Option<String>,\n) -> crate::Result<()> {\n  let resources_table = app.resources_table();\n  let tray = resources_table.get::<TrayIcon<R>>(rid)?;\n  tray.set_title(title)\n}\n\n#[command(root = \"crate\")]\nfn set_visible<R: Runtime>(app: AppHandle<R>, rid: ResourceId, visible: bool) -> crate::Result<()> {\n  let resources_table = app.resources_table();\n  let tray = resources_table.get::<TrayIcon<R>>(rid)?;\n  tray.set_visible(visible)\n}\n\n#[command(root = \"crate\")]\nfn set_temp_dir_path<R: Runtime>(\n  app: AppHandle<R>,\n  rid: ResourceId,\n  path: Option<PathBuf>,\n) -> crate::Result<()> {\n  let resources_table = app.resources_table();\n  let tray = resources_table.get::<TrayIcon<R>>(rid)?;\n  tray.set_temp_dir_path(path)\n}\n\n#[command(root = \"crate\")]\nfn set_icon_as_template<R: Runtime>(\n  app: AppHandle<R>,\n  rid: ResourceId,\n  as_template: bool,\n) -> crate::Result<()> {\n  let resources_table = app.resources_table();\n  let tray = resources_table.get::<TrayIcon<R>>(rid)?;\n  tray.set_icon_as_template(as_template)\n}\n\n#[command(root = \"crate\")]\nfn set_show_menu_on_left_click<R: Runtime>(\n  app: AppHandle<R>,\n  rid: ResourceId,\n  on_left: bool,\n) -> crate::Result<()> {\n  let resources_table = app.resources_table();\n  let tray = resources_table.get::<TrayIcon<R>>(rid)?;\n  tray.set_show_menu_on_left_click(on_left)\n}\n\npub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {\n  Builder::new(\"tray\")\n    .invoke_handler(crate::generate_handler![\n      #![plugin(tray)]\n      new,\n      get_by_id,\n      remove_by_id,\n      set_icon,\n      set_menu,\n      set_tooltip,\n      set_title,\n      set_visible,\n      set_temp_dir_path,\n      set_icon_as_template,\n      set_show_menu_on_left_click,\n    ])\n    .build()\n}\n"
  },
  {
    "path": "crates/tauri/src/vibrancy/macos.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![allow(deprecated)]\n\nuse crate::utils::config::WindowEffectsConfig;\nuse crate::window::{Effect, EffectState};\nuse raw_window_handle::HasWindowHandle;\nuse window_vibrancy::{NSVisualEffectMaterial, NSVisualEffectState};\n\npub fn apply_effects(window: impl HasWindowHandle, effects: WindowEffectsConfig) {\n  let WindowEffectsConfig {\n    effects,\n    radius,\n    state,\n    ..\n  } = effects;\n  let effect = if let Some(effect) = effects.into_iter().find(|e| {\n    matches!(\n      e,\n      Effect::AppearanceBased\n        | Effect::Light\n        | Effect::Dark\n        | Effect::MediumLight\n        | Effect::UltraDark\n        | Effect::Titlebar\n        | Effect::Selection\n        | Effect::Menu\n        | Effect::Popover\n        | Effect::Sidebar\n        | Effect::HeaderView\n        | Effect::Sheet\n        | Effect::WindowBackground\n        | Effect::HudWindow\n        | Effect::FullScreenUI\n        | Effect::Tooltip\n        | Effect::ContentBackground\n        | Effect::UnderWindowBackground\n        | Effect::UnderPageBackground\n    )\n  }) {\n    effect\n  } else {\n    return;\n  };\n\n  window_vibrancy::apply_vibrancy(\n    window,\n    match effect {\n      Effect::AppearanceBased => NSVisualEffectMaterial::AppearanceBased,\n      Effect::Light => NSVisualEffectMaterial::Light,\n      Effect::Dark => NSVisualEffectMaterial::Dark,\n      Effect::MediumLight => NSVisualEffectMaterial::MediumLight,\n      Effect::UltraDark => NSVisualEffectMaterial::UltraDark,\n      Effect::Titlebar => NSVisualEffectMaterial::Titlebar,\n      Effect::Selection => NSVisualEffectMaterial::Selection,\n      Effect::Menu => NSVisualEffectMaterial::Menu,\n      Effect::Popover => NSVisualEffectMaterial::Popover,\n      Effect::Sidebar => NSVisualEffectMaterial::Sidebar,\n      Effect::HeaderView => NSVisualEffectMaterial::HeaderView,\n      Effect::Sheet => NSVisualEffectMaterial::Sheet,\n      Effect::WindowBackground => NSVisualEffectMaterial::WindowBackground,\n      Effect::HudWindow => NSVisualEffectMaterial::HudWindow,\n      Effect::FullScreenUI => NSVisualEffectMaterial::FullScreenUI,\n      Effect::Tooltip => NSVisualEffectMaterial::Tooltip,\n      Effect::ContentBackground => NSVisualEffectMaterial::ContentBackground,\n      Effect::UnderWindowBackground => NSVisualEffectMaterial::UnderWindowBackground,\n      Effect::UnderPageBackground => NSVisualEffectMaterial::UnderPageBackground,\n      _ => unreachable!(),\n    },\n    state.map(|s| match s {\n      EffectState::FollowsWindowActiveState => NSVisualEffectState::FollowsWindowActiveState,\n      EffectState::Active => NSVisualEffectState::Active,\n      EffectState::Inactive => NSVisualEffectState::Inactive,\n    }),\n    radius,\n  );\n}\n"
  },
  {
    "path": "crates/tauri/src/vibrancy/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![allow(unused)]\n\nuse tauri_utils::config::WindowEffectsConfig;\n\nuse crate::{Runtime, Window};\n\n#[cfg(target_os = \"macos\")]\nmod macos;\n#[cfg(windows)]\nmod windows;\n\npub fn set_window_effects<R: Runtime>(\n  window: &Window<R>,\n  effects: Option<WindowEffectsConfig>,\n) -> crate::Result<()> {\n  if let Some(_effects) = effects {\n    #[cfg(windows)]\n    windows::apply_effects(window, _effects);\n    #[cfg(target_os = \"macos\")]\n    macos::apply_effects(window, _effects);\n  } else {\n    #[cfg(windows)]\n    windows::clear_effects(window);\n  }\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri/src/vibrancy/windows.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![allow(non_snake_case)]\n#![allow(non_camel_case_types)]\n#![allow(clippy::upper_case_acronyms)]\n\nuse std::ffi::c_void;\n\nuse crate::utils::config::WindowEffectsConfig;\nuse crate::window::{Color, Effect};\nuse raw_window_handle::HasWindowHandle;\nuse windows::Win32::Foundation::HWND;\n\npub fn apply_effects(window: impl HasWindowHandle, effects: WindowEffectsConfig) {\n  let WindowEffectsConfig { effects, color, .. } = effects;\n  let effect = if let Some(effect) = effects.iter().find(|e| {\n    matches!(\n      e,\n      Effect::Mica\n        | Effect::MicaDark\n        | Effect::MicaLight\n        | Effect::Acrylic\n        | Effect::Blur\n        | Effect::Tabbed\n        | Effect::TabbedDark\n        | Effect::TabbedLight\n    )\n  }) {\n    effect\n  } else {\n    return;\n  };\n\n  match effect {\n    Effect::Blur => window_vibrancy::apply_blur(window, color.map(Into::into)),\n    Effect::Acrylic => window_vibrancy::apply_acrylic(window, color.map(Into::into)),\n    Effect::Mica => window_vibrancy::apply_mica(window, None),\n    Effect::MicaDark => window_vibrancy::apply_mica(window, Some(true)),\n    Effect::MicaLight => window_vibrancy::apply_mica(window, Some(false)),\n    Effect::Tabbed => window_vibrancy::apply_tabbed(window, None),\n    Effect::TabbedDark => window_vibrancy::apply_tabbed(window, Some(true)),\n    Effect::TabbedLight => window_vibrancy::apply_tabbed(window, Some(false)),\n    _ => unreachable!(),\n  };\n}\n\npub fn clear_effects(window: impl HasWindowHandle) {\n  window_vibrancy::clear_blur(&window);\n  window_vibrancy::clear_acrylic(&window);\n  window_vibrancy::clear_mica(&window);\n}\n"
  },
  {
    "path": "crates/tauri/src/webview/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The Tauri webview types and functions.\n\npub(crate) mod plugin;\nmod webview_window;\n\npub use webview_window::{WebviewWindow, WebviewWindowBuilder};\n\n/// Cookie crate used for [`Webview::set_cookie`] and [`Webview::delete_cookie`].\n///\n/// # Stability\n///\n/// This re-exported crate is still on an alpha release and might receive updates in minor Tauri releases.\npub use cookie;\nuse http::HeaderMap;\nuse serde::Serialize;\nuse tauri_macros::default_runtime;\npub use tauri_runtime::webview::{NewWindowFeatures, PageLoadEvent, ScrollBarStyle};\n// Remove this re-export in v3\npub use tauri_runtime::Cookie;\n#[cfg(desktop)]\nuse tauri_runtime::{\n  dpi::{PhysicalPosition, PhysicalSize, Position, Size},\n  WindowDispatch,\n};\nuse tauri_runtime::{\n  webview::{DetachedWebview, InitializationScript, PendingWebview, WebviewAttributes},\n  WebviewDispatch,\n};\npub use tauri_utils::config::Color;\nuse tauri_utils::config::{BackgroundThrottlingPolicy, WebviewUrl, WindowConfig};\npub use url::Url;\n\nuse crate::{\n  app::{UriSchemeResponder, WebviewEvent},\n  event::{EmitArgs, EventTarget},\n  ipc::{\n    CallbackFn, CommandArg, CommandItem, CommandScope, GlobalScope, Invoke, InvokeBody,\n    InvokeError, InvokeMessage, InvokeResolver, Origin, OwnedInvokeResponder, ScopeObject,\n  },\n  manager::AppManager,\n  path::SafePathBuf,\n  sealed::{ManagerBase, RuntimeOrDispatch},\n  AppHandle, Emitter, Event, EventId, EventLoopMessage, EventName, Listener, Manager,\n  ResourceTable, Runtime, Window,\n};\n\nuse std::{\n  borrow::Cow,\n  hash::{Hash, Hasher},\n  path::{Path, PathBuf},\n  sync::{Arc, Mutex, MutexGuard},\n};\n\npub(crate) type WebResourceRequestHandler =\n  dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;\npub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send;\npub(crate) type NewWindowHandler<R> =\n  dyn Fn(Url, NewWindowFeatures) -> NewWindowResponse<R> + Send + Sync;\npub(crate) type UriSchemeProtocolHandler =\n  Box<dyn Fn(&str, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync>;\npub(crate) type OnPageLoad<R> = dyn Fn(Webview<R>, PageLoadPayload<'_>) + Send + Sync + 'static;\npub(crate) type OnDocumentTitleChanged<R> = dyn Fn(Webview<R>, String) + Send + 'static;\npub(crate) type DownloadHandler<R> = dyn Fn(Webview<R>, DownloadEvent<'_>) -> bool + Send + Sync;\n\n#[derive(Clone, Serialize)]\npub(crate) struct CreatedEvent {\n  pub(crate) label: String,\n}\n\n/// Download event for the [`WebviewBuilder#method.on_download`] hook.\n#[non_exhaustive]\npub enum DownloadEvent<'a> {\n  /// Download requested.\n  Requested {\n    /// The url being downloaded.\n    url: Url,\n    /// Represents where the file will be downloaded to.\n    /// Can be used to set the download location by assigning a new path to it.\n    /// The assigned path _must_ be absolute.\n    destination: &'a mut PathBuf,\n  },\n  /// Download finished.\n  Finished {\n    /// The URL of the original download request.\n    url: Url,\n    /// Potentially representing the filesystem path the file was downloaded to.\n    ///\n    /// A value of `None` being passed instead of a `PathBuf` does not necessarily indicate that the download\n    /// did not succeed, and may instead indicate some other failure - always check the third parameter if you need to\n    /// know if the download succeeded.\n    ///\n    /// ## Platform-specific:\n    ///\n    /// - **macOS**: The second parameter indicating the path the file was saved to is always empty, due to API\n    ///   limitations.\n    path: Option<PathBuf>,\n    /// Indicates if the download succeeded or not.\n    success: bool,\n  },\n}\n\n/// The payload for the [`WebviewBuilder::on_page_load`] hook.\n#[derive(Debug, Clone)]\npub struct PageLoadPayload<'a> {\n  pub(crate) url: &'a Url,\n  pub(crate) event: PageLoadEvent,\n}\n\nimpl<'a> PageLoadPayload<'a> {\n  /// The page URL.\n  pub fn url(&self) -> &'a Url {\n    self.url\n  }\n\n  /// The page load event.\n  pub fn event(&self) -> PageLoadEvent {\n    self.event\n  }\n}\n\n/// The IPC invoke request.\n///\n/// # Stability\n///\n/// This struct is **NOT** part of the public stable API and is only meant to be used\n/// by internal code and external testing/fuzzing tools or custom invoke systems.\n#[derive(Debug)]\npub struct InvokeRequest {\n  /// The invoke command.\n  pub cmd: String,\n  /// The success callback.\n  pub callback: CallbackFn,\n  /// The error callback.\n  pub error: CallbackFn,\n  /// URL of the frame that requested this command.\n  pub url: Url,\n  /// The body of the request.\n  pub body: InvokeBody,\n  /// The request headers.\n  pub headers: HeaderMap,\n  /// The invoke key. Must match what was passed to the app manager.\n  pub invoke_key: String,\n}\n\n/// The platform webview handle. Accessed with [`Webview#method.with_webview`];\n#[cfg(feature = \"wry\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"wry\")))]\npub struct PlatformWebview(tauri_runtime_wry::Webview);\n\n#[cfg(feature = \"wry\")]\nimpl PlatformWebview {\n  /// Returns [`webkit2gtk::WebView`] handle.\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  #[cfg_attr(\n    docsrs,\n    doc(cfg(any(\n      target_os = \"linux\",\n      target_os = \"dragonfly\",\n      target_os = \"freebsd\",\n      target_os = \"netbsd\",\n      target_os = \"openbsd\"\n    )))\n  )]\n  pub fn inner(&self) -> webkit2gtk::WebView {\n    self.0.clone()\n  }\n\n  /// Returns the WebView2 controller.\n  #[cfg(windows)]\n  #[cfg_attr(docsrs, doc(cfg(windows)))]\n  pub fn controller(\n    &self,\n  ) -> webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Controller {\n    self.0.controller.clone()\n  }\n\n  /// Returns the WebView2 environment.\n  #[cfg(windows)]\n  #[cfg_attr(docsrs, doc(cfg(windows)))]\n  pub fn environment(\n    &self,\n  ) -> webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment {\n    self.0.environment.clone()\n  }\n\n  /// Returns the [WKWebView] handle.\n  ///\n  /// [WKWebView]: https://developer.apple.com/documentation/webkit/wkwebview\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(target_os = \"macos\", target_os = \"ios\"))))]\n  pub fn inner(&self) -> *mut std::ffi::c_void {\n    self.0.webview\n  }\n\n  /// Returns WKWebView [controller] handle.\n  ///\n  /// [controller]: https://developer.apple.com/documentation/webkit/wkusercontentcontroller\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(target_os = \"macos\", target_os = \"ios\"))))]\n  pub fn controller(&self) -> *mut std::ffi::c_void {\n    self.0.manager\n  }\n\n  /// Returns [NSWindow] associated with the WKWebView webview.\n  ///\n  /// [NSWindow]: https://developer.apple.com/documentation/appkit/nswindow\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  pub fn ns_window(&self) -> *mut std::ffi::c_void {\n    self.0.ns_window\n  }\n\n  /// Returns [UIViewController] used by the WKWebView webview NSWindow.\n  ///\n  /// [UIViewController]: https://developer.apple.com/documentation/uikit/uiviewcontroller\n  #[cfg(target_os = \"ios\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"ios\")))]\n  pub fn view_controller(&self) -> *mut std::ffi::c_void {\n    self.0.view_controller\n  }\n\n  /// Returns handle for JNI execution.\n  #[cfg(target_os = \"android\")]\n  pub fn jni_handle(&self) -> tauri_runtime_wry::wry::JniHandle {\n    self.0\n  }\n}\n\n/// Response for the new window request handler.\npub enum NewWindowResponse<R: Runtime> {\n  /// Allow the window to be opened with the default implementation.\n  Allow,\n  /// Allow the window to be opened, with the given window.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// **Linux**: The webview must be related to the caller webview. See [`WebviewBuilder::related_view`].\n  /// **Windows**: The webview must use the same environment as the caller webview. See [`WebviewBuilder::environment`].\n  /// **macOS**: The webview must use the same webview configuration as the caller webview. See [`WebviewBuilder::with_webview_configuration`] and [`NewWindowFeatures::webview_configuration`].\n  Create {\n    /// Window that was created.\n    window: crate::WebviewWindow<R>,\n  },\n  /// Deny the window from being opened.\n  Deny,\n}\n\nmacro_rules! unstable_struct {\n    (#[doc = $doc:expr] $($tokens:tt)*) => {\n      #[cfg(any(test, feature = \"unstable\"))]\n      #[cfg_attr(docsrs, doc(cfg(feature = \"unstable\")))]\n      #[doc = $doc]\n      pub $($tokens)*\n\n      #[cfg(not(any(test, feature = \"unstable\")))]\n      pub(crate) $($tokens)*\n    }\n}\n\nunstable_struct!(\n  #[doc = \"A builder for a webview.\"]\n  struct WebviewBuilder<R: Runtime> {\n    pub(crate) label: String,\n    pub(crate) webview_attributes: WebviewAttributes,\n    pub(crate) web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,\n    pub(crate) navigation_handler: Option<Box<NavigationHandler>>,\n    pub(crate) new_window_handler: Option<Box<NewWindowHandler<R>>>,\n    pub(crate) on_page_load_handler: Option<Box<OnPageLoad<R>>>,\n    pub(crate) document_title_changed_handler: Option<Box<OnDocumentTitleChanged<R>>>,\n    pub(crate) download_handler: Option<Arc<DownloadHandler<R>>>,\n  }\n);\n\n#[cfg_attr(not(feature = \"unstable\"), allow(dead_code))]\nimpl<R: Runtime> WebviewBuilder<R> {\n  /// Initializes a webview builder with the given webview label and URL to load.\n  ///\n  /// # Known issues\n  ///\n  /// On Windows, this function deadlocks when used in a synchronous command or event handlers, see [the Webview2 issue].\n  /// You should use `async` commands and separate threads when creating webviews.\n  ///\n  /// # Examples\n  ///\n  /// - Create a webview in the setup hook:\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\ntauri::Builder::default()\n  .setup(|app| {\n    let window = tauri::window::WindowBuilder::new(app, \"label\").build()?;\n    let webview_builder = tauri::webview::WebviewBuilder::new(\"label\", tauri::WebviewUrl::App(\"index.html\".into()));\n    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap());\n    Ok(())\n  });\n```\n  \"####\n  )]\n  ///\n  /// - Create a webview in a separate thread:\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\ntauri::Builder::default()\n  .setup(|app| {\n    let handle = app.handle().clone();\n    std::thread::spawn(move || {\n      let window = tauri::window::WindowBuilder::new(&handle, \"label\").build().unwrap();\n      let webview_builder = tauri::webview::WebviewBuilder::new(\"label\", tauri::WebviewUrl::App(\"index.html\".into()));\n      window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap());\n    });\n    Ok(())\n  });\n```\n   \"####\n  )]\n  ///\n  /// - Create a webview in a command:\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\n#[tauri::command]\nasync fn create_window(app: tauri::AppHandle) {\n  let window = tauri::window::WindowBuilder::new(&app, \"label\").build().unwrap();\n  let webview_builder = tauri::webview::WebviewBuilder::new(\"label\", tauri::WebviewUrl::External(\"https://tauri.app/\".parse().unwrap()));\n  window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap());\n}\n```\n  \"####\n  )]\n  ///\n  /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583\n  pub fn new<L: Into<String>>(label: L, url: WebviewUrl) -> Self {\n    Self {\n      label: label.into(),\n      webview_attributes: WebviewAttributes::new(url),\n      web_resource_request_handler: None,\n      navigation_handler: None,\n      new_window_handler: None,\n      on_page_load_handler: None,\n      document_title_changed_handler: None,\n      download_handler: None,\n    }\n  }\n\n  /// Initializes a webview builder from a [`WindowConfig`] from tauri.conf.json.\n  /// Keep in mind that you can't create 2 webviews with the same `label` so make sure\n  /// that the initial webview was closed or change the label of the new [`WebviewBuilder`].\n  ///\n  /// # Known issues\n  ///\n  /// On Windows, this function deadlocks when used in a synchronous command or event handlers, see [the Webview2 issue].\n  /// You should use `async` commands and separate threads when creating webviews.\n  ///\n  /// # Examples\n  ///\n  /// - Create a webview in a command:\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\n#[tauri::command]\nasync fn create_window(app: tauri::AppHandle) {\n  let window = tauri::window::WindowBuilder::new(&app, \"label\").build().unwrap();\n  let webview_builder = tauri::webview::WebviewBuilder::from_config(&app.config().app.windows.get(0).unwrap().clone());\n  window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap());\n}\n```\n  \"####\n  )]\n  ///\n  /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583\n  pub fn from_config(config: &WindowConfig) -> Self {\n    let mut config = config.to_owned();\n\n    if let Some(data_directory) = &config.data_directory {\n      let resolve_data_dir_res = dirs::data_local_dir()\n        .or({\n          #[cfg(feature = \"tracing\")]\n          tracing::error!(\"failed to resolve data directory\");\n          None\n        })\n        .and_then(|local_dir| {\n          SafePathBuf::new(data_directory.clone())\n            .inspect_err(|_err| {\n              #[cfg(feature = \"tracing\")]\n              tracing::error!(\n                \"data_directory `{}` is not a safe path, ignoring config. Validation error was: {_err}\",\n                data_directory.display()\n              );\n            })\n            .map(|p| (local_dir, p))\n            .ok()\n        })\n        .and_then(|(local_dir, data_directory)| {\n          if data_directory.as_ref().is_relative() {\n            Some(local_dir.join(&config.label).join(data_directory.as_ref()))\n            } else {\n              #[cfg(feature = \"tracing\")]\n              tracing::error!(\n                \"data_directory `{}` is not a relative path, ignoring config.\",\n                data_directory.display()\n              );\n              None\n            }\n        });\n      if let Some(resolved_data_directory) = resolve_data_dir_res {\n        config.data_directory = Some(resolved_data_directory);\n      }\n    }\n\n    Self {\n      label: config.label.clone(),\n      webview_attributes: WebviewAttributes::from(&config),\n      web_resource_request_handler: None,\n      navigation_handler: None,\n      new_window_handler: None,\n      on_page_load_handler: None,\n      document_title_changed_handler: None,\n      download_handler: None,\n    }\n  }\n\n  /// Defines a closure to be executed when the webview makes an HTTP request for a web resource, allowing you to modify the response.\n  ///\n  /// Currently only implemented for the `tauri` URI protocol.\n  ///\n  /// **NOTE:** Currently this is **not** executed when using external URLs such as a development server,\n  /// but it might be implemented in the future. **Always** check the request URL.\n  ///\n  /// # Examples\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust,no_run\nuse tauri::{\n  utils::config::{Csp, CspDirectiveSources, WebviewUrl},\n  window::WindowBuilder,\n  webview::WebviewBuilder,\n};\nuse http::header::HeaderValue;\nuse std::collections::HashMap;\ntauri::Builder::default()\n  .setup(|app| {\n    let window = tauri::window::WindowBuilder::new(app, \"label\").build()?;\n\n    let webview_builder = WebviewBuilder::new(\"core\", WebviewUrl::App(\"index.html\".into()))\n      .on_web_resource_request(|request, response| {\n        if request.uri().scheme_str() == Some(\"tauri\") {\n          // if we have a CSP header, Tauri is loading an HTML file\n          //  for this example, let's dynamically change the CSP\n          if let Some(csp) = response.headers_mut().get_mut(\"Content-Security-Policy\") {\n            // use the tauri helper to parse the CSP policy to a map\n            let mut csp_map: HashMap<String, CspDirectiveSources> = Csp::Policy(csp.to_str().unwrap().to_string()).into();\n            csp_map.entry(\"script-src\".to_string()).or_insert_with(Default::default).push(\"'unsafe-inline'\");\n            // use the tauri helper to get a CSP string from the map\n            let csp_string = Csp::from(csp_map).to_string();\n            *csp = HeaderValue::from_str(&csp_string).unwrap();\n          }\n        }\n      });\n\n    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;\n\n    Ok(())\n  });\n```\n  \"####\n  )]\n  pub fn on_web_resource_request<\n    F: Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync + 'static,\n  >(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.web_resource_request_handler.replace(Box::new(f));\n    self\n  }\n\n  /// Defines a closure to be executed when the webview navigates to a URL. Returning `false` cancels the navigation.\n  ///\n  /// # Examples\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust,no_run\nuse tauri::{\n  utils::config::{Csp, CspDirectiveSources, WebviewUrl},\n  window::WindowBuilder,\n  webview::WebviewBuilder,\n};\nuse http::header::HeaderValue;\nuse std::collections::HashMap;\ntauri::Builder::default()\n  .setup(|app| {\n    let window = tauri::window::WindowBuilder::new(app, \"label\").build()?;\n\n    let webview_builder = WebviewBuilder::new(\"core\", WebviewUrl::App(\"index.html\".into()))\n      .on_navigation(|url| {\n        // allow the production URL or localhost on dev\n        url.scheme() == \"tauri\" || (cfg!(dev) && url.host_str() == Some(\"localhost\"))\n      });\n\n    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;\n    Ok(())\n  });\n```\n  \"####\n  )]\n  pub fn on_navigation<F: Fn(&Url) -> bool + Send + 'static>(mut self, f: F) -> Self {\n    self.navigation_handler.replace(Box::new(f));\n    self\n  }\n\n  /// Set a new window request handler to decide if incoming url is allowed to be opened.\n  ///\n  /// A new window is requested to be opened by the [window.open] API.\n  ///\n  /// The closure take the URL to open and the window features object and returns [`NewWindowResponse`] to determine whether the window should open.\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust,no_run\nuse tauri::{\n  utils::config::{Csp, CspDirectiveSources, WebviewUrl},\n  window::WindowBuilder,\n  webview::WebviewBuilder,\n};\nuse http::header::HeaderValue;\nuse std::collections::HashMap;\ntauri::Builder::default()\n  .setup(|app| {\n    let window = tauri::window::WindowBuilder::new(app, \"label\").build()?;\n\n    let app_ = app.handle().clone();\n    let webview_builder = WebviewBuilder::new(\"core\", WebviewUrl::App(\"index.html\".into()))\n      .on_new_window(move |url, features| {\n        let builder = tauri::WebviewWindowBuilder::new(\n          &app_,\n          // note: add an ID counter or random label generator to support multiple opened windows at the same time\n          \"opened-window\",\n          tauri::WebviewUrl::External(\"about:blank\".parse().unwrap()),\n        )\n        .window_features(features)\n        .on_document_title_changed(|window, title| {\n          window.set_title(&title).unwrap();\n        })\n        .title(url.as_str());\n\n        let window = builder.build().unwrap();\n        tauri::webview::NewWindowResponse::Create { window }\n      });\n\n    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;\n    Ok(())\n  });\n```\n  \"####\n  )]\n  ///\n  /// # Platform-specific\n  ///\n  /// - **Android / iOS**: Not supported.\n  /// - **Windows**: The closure is executed on a separate thread to prevent a deadlock.\n  ///\n  /// [window.open]: https://developer.mozilla.org/en-US/docs/Web/API/Window/open\n  pub fn on_new_window<\n    F: Fn(Url, NewWindowFeatures) -> NewWindowResponse<R> + Send + Sync + 'static,\n  >(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.new_window_handler.replace(Box::new(f));\n    self\n  }\n\n  /// Defines a closure to be executed when document title change.\n  pub fn on_document_title_changed<F: Fn(Webview<R>, String) + Send + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.document_title_changed_handler.replace(Box::new(f));\n    self\n  }\n\n  /// Set a download event handler to be notified when a download is requested or finished.\n  ///\n  /// Returning `false` prevents the download from happening on a [`DownloadEvent::Requested`] event.\n  ///\n  /// # Examples\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust,no_run\nuse tauri::{\n  utils::config::{Csp, CspDirectiveSources, WebviewUrl},\n  window::WindowBuilder,\n  webview::{DownloadEvent, WebviewBuilder},\n};\n\ntauri::Builder::default()\n  .setup(|app| {\n    let window = WindowBuilder::new(app, \"label\").build()?;\n    let webview_builder = WebviewBuilder::new(\"core\", WebviewUrl::App(\"index.html\".into()))\n      .on_download(|webview, event| {\n        match event {\n          DownloadEvent::Requested { url, destination } => {\n            println!(\"downloading {}\", url);\n            *destination = \"/home/tauri/target/path\".into();\n          }\n          DownloadEvent::Finished { url, path, success } => {\n            println!(\"downloaded {} to {:?}, success: {}\", url, path, success);\n          }\n          _ => (),\n        }\n        // let the download start\n        true\n      });\n\n    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;\n    Ok(())\n  });\n```\n  \"####\n  )]\n  pub fn on_download<F: Fn(Webview<R>, DownloadEvent<'_>) -> bool + Send + Sync + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.download_handler.replace(Arc::new(f));\n    self\n  }\n\n  /// Defines a closure to be executed when a page load event is triggered.\n  /// The event can be either [`PageLoadEvent::Started`] if the page has started loading\n  /// or [`PageLoadEvent::Finished`] when the page finishes loading.\n  ///\n  /// # Examples\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust,no_run\nuse tauri::{\n  utils::config::{Csp, CspDirectiveSources, WebviewUrl},\n  window::WindowBuilder,\n  webview::{PageLoadEvent, WebviewBuilder},\n};\nuse http::header::HeaderValue;\nuse std::collections::HashMap;\ntauri::Builder::default()\n  .setup(|app| {\n    let window = tauri::window::WindowBuilder::new(app, \"label\").build()?;\n    let webview_builder = WebviewBuilder::new(\"core\", WebviewUrl::App(\"index.html\".into()))\n      .on_page_load(|webview, payload| {\n        match payload.event() {\n          PageLoadEvent::Started => {\n            println!(\"{} finished loading\", payload.url());\n          }\n          PageLoadEvent::Finished => {\n            println!(\"{} finished loading\", payload.url());\n          }\n        }\n      });\n    let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;\n    Ok(())\n  });\n```\n  \"####\n  )]\n  pub fn on_page_load<F: Fn(Webview<R>, PageLoadPayload<'_>) + Send + Sync + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.on_page_load_handler.replace(Box::new(f));\n    self\n  }\n\n  pub(crate) fn into_pending_webview<M: Manager<R>>(\n    mut self,\n    manager: &M,\n    window_label: &str,\n  ) -> crate::Result<PendingWebview<EventLoopMessage, R>> {\n    let mut pending = PendingWebview::new(self.webview_attributes, self.label.clone())?;\n    pending.navigation_handler = self.navigation_handler.take();\n    pending.new_window_handler = self.new_window_handler.take().map(|handler| {\n      Box::new(\n        move |url, features: NewWindowFeatures| match handler(url, features) {\n          NewWindowResponse::Allow => tauri_runtime::webview::NewWindowResponse::Allow,\n          #[cfg(mobile)]\n          NewWindowResponse::Create { window: _ } => {\n            tauri_runtime::webview::NewWindowResponse::Allow\n          }\n          #[cfg(desktop)]\n          NewWindowResponse::Create { window } => {\n            tauri_runtime::webview::NewWindowResponse::Create {\n              window_id: window.window.window.id,\n            }\n          }\n          NewWindowResponse::Deny => tauri_runtime::webview::NewWindowResponse::Deny,\n        },\n      )\n        as Box<\n          dyn Fn(Url, NewWindowFeatures) -> tauri_runtime::webview::NewWindowResponse\n            + Send\n            + Sync\n            + 'static,\n        >\n    });\n\n    if let Some(document_title_changed_handler) = self.document_title_changed_handler.take() {\n      let label = pending.label.clone();\n      let manager = manager.manager_owned();\n      pending\n        .document_title_changed_handler\n        .replace(Box::new(move |title| {\n          if let Some(w) = manager.get_webview(&label) {\n            document_title_changed_handler(w, title);\n          }\n        }));\n    }\n    pending.web_resource_request_handler = self.web_resource_request_handler.take();\n\n    if let Some(download_handler) = self.download_handler.take() {\n      let label = pending.label.clone();\n      let manager = manager.manager_owned();\n      pending.download_handler.replace(Arc::new(move |event| {\n        if let Some(w) = manager.get_webview(&label) {\n          download_handler(\n            w,\n            match event {\n              tauri_runtime::webview::DownloadEvent::Requested { url, destination } => {\n                DownloadEvent::Requested { url, destination }\n              }\n              tauri_runtime::webview::DownloadEvent::Finished { url, path, success } => {\n                DownloadEvent::Finished { url, path, success }\n              }\n            },\n          )\n        } else {\n          false\n        }\n      }));\n    }\n\n    let label_ = pending.label.clone();\n    let manager_ = manager.manager_owned();\n    pending\n      .on_page_load_handler\n      .replace(Box::new(move |url, event| {\n        if let Some(w) = manager_.get_webview(&label_) {\n          if let Some(handler) = self.on_page_load_handler.as_ref() {\n            handler(w, PageLoadPayload { url: &url, event });\n          }\n        }\n      }));\n\n    manager\n      .manager()\n      .webview\n      .prepare_webview(manager, pending, window_label)\n  }\n\n  /// Creates a new webview on the given window.\n  #[cfg(desktop)]\n  pub(crate) fn build(\n    self,\n    window: Window<R>,\n    position: Position,\n    size: Size,\n  ) -> crate::Result<Webview<R>> {\n    let app_manager = window.manager();\n\n    let mut pending = self.into_pending_webview(&window, window.label())?;\n\n    pending.webview_attributes.bounds = Some(tauri_runtime::dpi::Rect { size, position });\n\n    let use_https_scheme = pending.webview_attributes.use_https_scheme;\n\n    let webview = match &mut window.runtime() {\n      RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_webview(pending),\n      _ => unimplemented!(),\n    }\n    .map(|webview| {\n      app_manager\n        .webview\n        .attach_webview(window.clone(), webview, use_https_scheme)\n    })?;\n\n    Ok(webview)\n  }\n}\n\n/// Webview attributes.\nimpl<R: Runtime> WebviewBuilder<R> {\n  /// Sets whether clicking an inactive window also clicks through to the webview.\n  #[must_use]\n  pub fn accept_first_mouse(mut self, accept: bool) -> Self {\n    self.webview_attributes.accept_first_mouse = accept;\n    self\n  }\n\n  /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,\n  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.\n  ///\n  /// Since it runs on all top-level document navigations,\n  /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.\n  ///\n  /// This is executed only on the main frame.\n  /// If you only want to run it in all frames, use [`Self::initialization_script_for_all_frames`] instead.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:** scripts are always added to subframes.\n  /// - **Android:** When [addDocumentStartJavaScript] is not supported,\n  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).\n  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.\n  ///\n  /// # Examples\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust\nuse tauri::{WindowBuilder, Runtime};\n\nconst INIT_SCRIPT: &str = r#\"\n  if (window.location.origin === 'https://tauri.app') {\n    console.log(\"hello world from js init script\");\n\n    window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };\n  }\n\"#;\n\nfn main() {\n  tauri::Builder::default()\n    .setup(|app| {\n      let window = tauri::window::WindowBuilder::new(app, \"label\").build()?;\n      let webview_builder = tauri::webview::WebviewBuilder::new(\"label\", tauri::WebviewUrl::App(\"index.html\".into()))\n        .initialization_script(INIT_SCRIPT);\n      let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;\n      Ok(())\n    });\n}\n```\n  \"####\n  )]\n  ///\n  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)\n  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)\n  #[must_use]\n  pub fn initialization_script(mut self, script: impl Into<String>) -> Self {\n    self\n      .webview_attributes\n      .initialization_scripts\n      .push(InitializationScript {\n        script: script.into(),\n        for_main_frame_only: true,\n      });\n    self\n  }\n\n  /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,\n  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.\n  ///\n  /// Since it runs on all top-level document navigations and also child frame page navigations,\n  /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.\n  ///\n  /// This is executed on all frames (main frame and also sub frames).\n  /// If you only want to run the script in the main frame, use [`Self::initialization_script`] instead.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Android:** When [addDocumentStartJavaScript] is not supported,\n  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).\n  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.\n  ///\n  /// # Examples\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust\nuse tauri::{WindowBuilder, Runtime};\n\nconst INIT_SCRIPT: &str = r#\"\n  if (window.location.origin === 'https://tauri.app') {\n    console.log(\"hello world from js init script\");\n\n    window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };\n  }\n\"#;\n\nfn main() {\n  tauri::Builder::default()\n    .setup(|app| {\n      let window = tauri::window::WindowBuilder::new(app, \"label\").build()?;\n      let webview_builder = tauri::webview::WebviewBuilder::new(\"label\", tauri::WebviewUrl::App(\"index.html\".into()))\n        .initialization_script_for_all_frames(INIT_SCRIPT);\n      let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;\n      Ok(())\n    });\n}\n```\n  \"####\n  )]\n  ///\n  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)\n  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)\n  #[must_use]\n  pub fn initialization_script_for_all_frames(mut self, script: impl Into<String>) -> Self {\n    self\n      .webview_attributes\n      .initialization_scripts\n      .push(InitializationScript {\n        script: script.into(),\n        for_main_frame_only: false,\n      });\n    self\n  }\n\n  /// Set the user agent for the webview\n  #[must_use]\n  pub fn user_agent(mut self, user_agent: &str) -> Self {\n    self.webview_attributes.user_agent = Some(user_agent.to_string());\n    self\n  }\n\n  /// Set additional arguments for the webview.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS / Linux / Android / iOS**: Unsupported.\n  ///\n  /// ## Warning\n  ///\n  /// By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`\n  /// so if you use this method, you also need to disable these components by yourself if you want.\n  #[must_use]\n  pub fn additional_browser_args(mut self, additional_args: &str) -> Self {\n    self.webview_attributes.additional_browser_args = Some(additional_args.to_string());\n    self\n  }\n\n  /// Data directory for the webview.\n  #[must_use]\n  pub fn data_directory(mut self, data_directory: PathBuf) -> Self {\n    self\n      .webview_attributes\n      .data_directory\n      .replace(data_directory);\n    self\n  }\n\n  /// Disables the drag and drop handler. This is required to use HTML5 drag and drop APIs on the frontend on Windows.\n  #[must_use]\n  pub fn disable_drag_drop_handler(mut self) -> Self {\n    self.webview_attributes.drag_drop_handler_enabled = false;\n    self\n  }\n\n  /// Enables clipboard access for the page rendered on **Linux** and **Windows**.\n  ///\n  /// **macOS** doesn't provide such method and is always enabled by default,\n  /// but you still need to add menu item accelerators to use shortcuts.\n  #[must_use]\n  pub fn enable_clipboard_access(mut self) -> Self {\n    self.webview_attributes.clipboard = true;\n    self\n  }\n\n  /// Enable or disable incognito mode for the WebView.\n  ///\n  ///  ## Platform-specific:\n  ///\n  ///  - **Windows**: Requires WebView2 Runtime version 101.0.1210.39 or higher, does nothing on older versions,\n  ///    see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/archive?tabs=dotnetcsharp#10121039\n  ///  - **Android**: Unsupported.\n  ///  - **macOS / iOS**: Uses the nonPersistent DataStore\n  #[must_use]\n  pub fn incognito(mut self, incognito: bool) -> Self {\n    self.webview_attributes.incognito = incognito;\n    self\n  }\n\n  /// Set a proxy URL for the WebView for all network requests.\n  ///\n  /// Must be either a `http://` or a `socks5://` URL.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.\n  #[must_use]\n  pub fn proxy_url(mut self, url: Url) -> Self {\n    self.webview_attributes.proxy_url = Some(url);\n    self\n  }\n\n  /// Enable or disable transparency for the WebView.\n  #[cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\"))]\n  #[cfg_attr(\n    docsrs,\n    doc(cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\")))\n  )]\n  #[must_use]\n  pub fn transparent(mut self, transparent: bool) -> Self {\n    self.webview_attributes.transparent = transparent;\n    self\n  }\n\n  /// Whether the webview should be focused or not.\n  #[must_use]\n  pub fn focused(mut self, focus: bool) -> Self {\n    self.webview_attributes.focus = focus;\n    self\n  }\n\n  /// Sets the webview to automatically grow and shrink its size and position when the parent window resizes.\n  #[must_use]\n  pub fn auto_resize(mut self) -> Self {\n    self.webview_attributes.auto_resize = true;\n    self\n  }\n\n  /// Whether page zooming by hotkeys and mousewheel should be enabled or not.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.\n  /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `Ctrl/Cmd + [- = +]` hotkeys or mousewheel events,\n  ///   20% in each step, ranging from 20% to 1000%. Requires `core:webview:allow-set-webview-zoom` permission\n  ///\n  /// - **Android / iOS**: Unsupported.\n  #[must_use]\n  pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {\n    self.webview_attributes.zoom_hotkeys_enabled = enabled;\n    self\n  }\n\n  /// Whether browser extensions can be installed for the webview process\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)\n  /// - **MacOS / Linux / iOS / Android** - Unsupported.\n  #[must_use]\n  pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {\n    self.webview_attributes.browser_extensions_enabled = enabled;\n    self\n  }\n\n  /// Set the path from which to load extensions from. Extensions stored in this path should be unpacked Chrome extensions on Windows, and compiled `.so` extensions on Linux.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: Browser extensions must first be enabled. See [`browser_extensions_enabled`](Self::browser_extensions_enabled)\n  /// - **MacOS / iOS / Android** - Unsupported.\n  #[must_use]\n  pub fn extensions_path(mut self, path: impl AsRef<Path>) -> Self {\n    self.webview_attributes.extensions_path = Some(path.as_ref().to_path_buf());\n    self\n  }\n\n  /// Initialize the WebView with a custom data store identifier.\n  /// Can be used as a replacement for data_directory not being available in WKWebView.\n  ///\n  /// - **macOS / iOS**: Available on macOS >= 14 and iOS >= 17\n  /// - **Windows / Linux / Android**: Unsupported.\n  ///\n  /// Note: Enable incognito mode to use the `nonPersistent` DataStore.\n  #[must_use]\n  pub fn data_store_identifier(mut self, data_store_identifier: [u8; 16]) -> Self {\n    self.webview_attributes.data_store_identifier = Some(data_store_identifier);\n    self\n  }\n\n  /// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.\n  ///\n  /// ## Note\n  ///\n  /// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.\n  ///\n  /// ## Warning\n  ///\n  /// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.\n  #[must_use]\n  pub fn use_https_scheme(mut self, enabled: bool) -> Self {\n    self.webview_attributes.use_https_scheme = enabled;\n    self\n  }\n\n  /// Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default.\n  ///\n  /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - macOS: This will call private functions on **macOS**\n  /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.\n  /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.\n  #[must_use]\n  pub fn devtools(mut self, enabled: bool) -> Self {\n    self.webview_attributes.devtools.replace(enabled);\n    self\n  }\n\n  /// Set the webview background color.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS / iOS**: Not implemented.\n  /// - **Windows**: On Windows 7, alpha channel is ignored.\n  /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored.\n  #[must_use]\n  pub fn background_color(mut self, color: Color) -> Self {\n    self.webview_attributes.background_color = Some(color);\n    self\n  }\n\n  /// Change the default background throttling behaviour.\n  ///\n  /// By default, browsers use a suspend policy that will throttle timers and even unload\n  /// the whole tab (view) to free resources after roughly 5 minutes when a view became\n  /// minimized or hidden. This will pause all tasks until the documents visibility state\n  /// changes back from hidden to visible by bringing the view back to the foreground.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.\n  /// - **iOS**: Supported since version 17.0+.\n  /// - **macOS**: Supported since version 14.0+.\n  ///\n  /// see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578\n  #[must_use]\n  pub fn background_throttling(mut self, policy: BackgroundThrottlingPolicy) -> Self {\n    self.webview_attributes.background_throttling = Some(policy);\n    self\n  }\n\n  /// Whether JavaScript should be disabled.\n  #[must_use]\n  pub fn disable_javascript(mut self) -> Self {\n    self.webview_attributes.javascript_disabled = true;\n    self\n  }\n\n  /// Specifies the native scrollbar style to use with the webview.\n  /// CSS styles that modify the scrollbar are applied on top of the native appearance configured here.\n  ///\n  /// Defaults to [`ScrollBarStyle::Default`], which is the browser default.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows**:\n  ///   - [`ScrollBarStyle::FluentOverlay`] requires WebView2 Runtime version 125.0.2535.41 or higher,\n  ///     and does nothing on older versions.\n  ///   - This option must be given the same value for all webviews that target the same data directory. Use\n  ///     [`WebviewBuilder::data_directory`] to change data directories if needed.\n  /// - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.\n  #[must_use]\n  pub fn scroll_bar_style(mut self, style: ScrollBarStyle) -> Self {\n    self.webview_attributes = self.webview_attributes.scroll_bar_style(style);\n    self\n  }\n\n  /// Whether to show a link preview when long pressing on links. Available on macOS and iOS only.\n  ///\n  /// Default is true.\n  ///\n  /// See https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / Windows / Android:** Unsupported.\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {\n    self.webview_attributes = self\n      .webview_attributes\n      .allow_link_preview(allow_link_preview);\n    self\n  }\n\n  /// Allows overriding the keyboard accessory view on iOS.\n  /// Returning `None` effectively removes the view.\n  ///\n  /// The closure parameter is the webview instance.\n  ///\n  /// The accessory view is the view that appears above the keyboard when a text input element is focused.\n  /// It usually displays a view with \"Done\", \"Next\" buttons.\n  ///\n  /// # Stability\n  ///\n  /// This relies on [`objc2_ui_kit`] which does not provide a stable API yet, so it can receive breaking changes in minor releases.\n  #[cfg(target_os = \"ios\")]\n  pub fn with_input_accessory_view_builder<\n    F: Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>\n      + Send\n      + Sync\n      + 'static,\n  >(\n    mut self,\n    builder: F,\n  ) -> Self {\n    self\n      .webview_attributes\n      .input_accessory_view_builder\n      .replace(tauri_runtime::webview::InputAccessoryViewBuilder::new(\n        Box::new(builder),\n      ));\n    self\n  }\n\n  /// Set the environment for the webview.\n  /// Useful if you need to share the same environment, for instance when using the [`Self::on_new_window`].\n  #[cfg(all(feature = \"wry\", windows))]\n  pub fn with_environment(\n    mut self,\n    environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,\n  ) -> Self {\n    self.webview_attributes.environment.replace(environment);\n    self\n  }\n\n  /// Creates a new webview sharing the same web process with the provided webview.\n  /// Useful if you need to link a webview to another, for instance when using the [`Self::on_new_window`].\n  #[cfg(all(\n    feature = \"wry\",\n    any(\n      target_os = \"linux\",\n      target_os = \"dragonfly\",\n      target_os = \"freebsd\",\n      target_os = \"netbsd\",\n      target_os = \"openbsd\",\n    )\n  ))]\n  pub fn with_related_view(mut self, related_view: webkit2gtk::WebView) -> Self {\n    self.webview_attributes.related_view.replace(related_view);\n    self\n  }\n\n  /// Set the webview configuration.\n  /// Useful if you need to share the use a predefined webview configuration, for instance when using the [`Self::on_new_window`].\n  #[cfg(target_os = \"macos\")]\n  pub fn with_webview_configuration(\n    mut self,\n    webview_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,\n  ) -> Self {\n    self\n      .webview_attributes\n      .webview_configuration\n      .replace(webview_configuration);\n    self\n  }\n}\n\n/// Webview.\n#[default_runtime(crate::Wry, wry)]\npub struct Webview<R: Runtime> {\n  pub(crate) window: Arc<Mutex<Window<R>>>,\n  /// The webview created by the runtime.\n  pub(crate) webview: DetachedWebview<EventLoopMessage, R>,\n  /// The manager to associate this webview with.\n  pub(crate) manager: Arc<AppManager<R>>,\n  pub(crate) app_handle: AppHandle<R>,\n  pub(crate) resources_table: Arc<Mutex<ResourceTable>>,\n  use_https_scheme: bool,\n}\n\nimpl<R: Runtime> std::fmt::Debug for Webview<R> {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    f.debug_struct(\"Window\")\n      .field(\"window\", &self.window.lock().unwrap())\n      .field(\"webview\", &self.webview)\n      .field(\"use_https_scheme\", &self.use_https_scheme)\n      .finish()\n  }\n}\n\nimpl<R: Runtime> Clone for Webview<R> {\n  fn clone(&self) -> Self {\n    Self {\n      window: self.window.clone(),\n      webview: self.webview.clone(),\n      manager: self.manager.clone(),\n      app_handle: self.app_handle.clone(),\n      resources_table: self.resources_table.clone(),\n      use_https_scheme: self.use_https_scheme,\n    }\n  }\n}\n\nimpl<R: Runtime> Hash for Webview<R> {\n  /// Only use the [`Webview`]'s label to represent its hash.\n  fn hash<H: Hasher>(&self, state: &mut H) {\n    self.webview.label.hash(state)\n  }\n}\n\nimpl<R: Runtime> Eq for Webview<R> {}\nimpl<R: Runtime> PartialEq for Webview<R> {\n  /// Only use the [`Webview`]'s label to compare equality.\n  fn eq(&self, other: &Self) -> bool {\n    self.webview.label.eq(&other.webview.label)\n  }\n}\n\n/// Base webview functions.\nimpl<R: Runtime> Webview<R> {\n  /// Create a new webview that is attached to the window.\n  pub(crate) fn new(\n    window: Window<R>,\n    webview: DetachedWebview<EventLoopMessage, R>,\n    use_https_scheme: bool,\n  ) -> Self {\n    Self {\n      manager: window.manager.clone(),\n      app_handle: window.app_handle.clone(),\n      window: Arc::new(Mutex::new(window)),\n      webview,\n      resources_table: Default::default(),\n      use_https_scheme,\n    }\n  }\n\n  /// Initializes a webview builder with the given window label and URL to load on the webview.\n  ///\n  /// Data URLs are only supported with the `webview-data-url` feature flag.\n  #[cfg(feature = \"unstable\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"unstable\")))]\n  pub fn builder<L: Into<String>>(label: L, url: WebviewUrl) -> WebviewBuilder<R> {\n    WebviewBuilder::new(label.into(), url)\n  }\n\n  /// Runs the given closure on the main thread.\n  pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {\n    self\n      .webview\n      .dispatcher\n      .run_on_main_thread(f)\n      .map_err(Into::into)\n  }\n\n  /// The webview label.\n  pub fn label(&self) -> &str {\n    &self.webview.label\n  }\n\n  /// Whether the webview was configured to use the HTTPS scheme or not.\n  pub(crate) fn use_https_scheme(&self) -> bool {\n    self.use_https_scheme\n  }\n\n  /// Registers a webview event listener.\n  pub fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) {\n    self\n      .webview\n      .dispatcher\n      .on_webview_event(move |event| f(&event.clone().into()));\n  }\n\n  /// Resolves the given command scope for this webview on the currently loaded URL.\n  ///\n  /// If the command is not allowed, returns None.\n  ///\n  /// If the scope cannot be deserialized to the given type, an error is returned.\n  ///\n  /// In a command context this can be directly resolved from the command arguments via [CommandScope]:\n  ///\n  /// ```\n  /// use tauri::ipc::CommandScope;\n  ///\n  /// #[derive(Debug, serde::Deserialize)]\n  /// struct ScopeType {\n  ///   some_value: String,\n  /// }\n  /// #[tauri::command]\n  /// fn my_command(scope: CommandScope<ScopeType>) {\n  ///   // check scope\n  /// }\n  /// ```\n  ///\n  /// # Examples\n  ///\n  /// ```\n  /// use tauri::Manager;\n  ///\n  /// #[derive(Debug, serde::Deserialize)]\n  /// struct ScopeType {\n  ///   some_value: String,\n  /// }\n  ///\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let webview = app.get_webview_window(\"main\").unwrap();\n  ///     let scope = webview.resolve_command_scope::<ScopeType>(\"my-plugin\", \"read\");\n  ///     Ok(())\n  ///   });\n  /// ```\n  pub fn resolve_command_scope<T: ScopeObject>(\n    &self,\n    plugin: &str,\n    command: &str,\n  ) -> crate::Result<Option<ResolvedScope<T>>> {\n    let current_url = self.url()?;\n    let is_local = self.is_local_url(&current_url);\n    let origin = if is_local {\n      Origin::Local\n    } else {\n      Origin::Remote { url: current_url }\n    };\n\n    let cmd_name = format!(\"plugin:{plugin}|{command}\");\n    let resolved_access = self\n      .manager()\n      .runtime_authority\n      .lock()\n      .unwrap()\n      .resolve_access(&cmd_name, self.window().label(), self.label(), &origin);\n\n    if let Some(access) = resolved_access {\n      let scope_ids = access\n        .iter()\n        .filter_map(|cmd| cmd.scope_id)\n        .collect::<Vec<_>>();\n\n      let command_scope = CommandScope::resolve(self, scope_ids)?;\n      let global_scope = GlobalScope::resolve(self, plugin)?;\n\n      Ok(Some(ResolvedScope {\n        global_scope,\n        command_scope,\n      }))\n    } else {\n      Ok(None)\n    }\n  }\n}\n\n/// Desktop webview setters and actions.\n#[cfg(desktop)]\nimpl<R: Runtime> Webview<R> {\n  /// Opens the dialog to prints the contents of the webview.\n  /// Currently only supported on macOS on `wry`.\n  /// `window.print()` works on all platforms.\n  pub fn print(&self) -> crate::Result<()> {\n    self.webview.dispatcher.print().map_err(Into::into)\n  }\n\n  /// Get the cursor position relative to the top-left hand corner of the desktop.\n  ///\n  /// Note that the top-left hand corner of the desktop is not necessarily the same as the screen.\n  /// If the user uses a desktop with multiple monitors,\n  /// the top-left hand corner of the desktop is the top-left hand corner of the main monitor on Windows and macOS\n  /// or the top-left of the leftmost monitor on X11.\n  ///\n  /// The coordinates can be negative if the top-left hand corner of the window is outside of the visible screen region.\n  pub fn cursor_position(&self) -> crate::Result<PhysicalPosition<f64>> {\n    self.app_handle.cursor_position()\n  }\n\n  /// Closes this webview.\n  pub fn close(&self) -> crate::Result<()> {\n    self.webview.dispatcher.close()?;\n    self.manager().on_webview_close(self.label());\n    Ok(())\n  }\n\n  /// Resizes this webview.\n  pub fn set_bounds(&self, bounds: tauri_runtime::dpi::Rect) -> crate::Result<()> {\n    self\n      .webview\n      .dispatcher\n      .set_bounds(bounds)\n      .map_err(Into::into)\n  }\n\n  /// Resizes this webview.\n  pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> {\n    self\n      .webview\n      .dispatcher\n      .set_size(size.into())\n      .map_err(Into::into)\n  }\n\n  /// Sets this webviews's position.\n  pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {\n    self\n      .webview\n      .dispatcher\n      .set_position(position.into())\n      .map_err(Into::into)\n  }\n\n  /// Focus the webview.\n  pub fn set_focus(&self) -> crate::Result<()> {\n    self.webview.dispatcher.set_focus().map_err(Into::into)\n  }\n\n  /// Hide the webview.\n  pub fn hide(&self) -> crate::Result<()> {\n    self.webview.dispatcher.hide().map_err(Into::into)\n  }\n\n  /// Show the webview.\n  pub fn show(&self) -> crate::Result<()> {\n    self.webview.dispatcher.show().map_err(Into::into)\n  }\n\n  /// Move the webview to the given window.\n  pub fn reparent(&self, window: &Window<R>) -> crate::Result<()> {\n    #[cfg(not(feature = \"unstable\"))]\n    {\n      if self.window_ref().is_webview_window() || window.is_webview_window() {\n        return Err(crate::Error::CannotReparentWebviewWindow);\n      }\n    }\n\n    *self.window.lock().unwrap() = window.clone();\n    self.webview.dispatcher.reparent(window.window.id)?;\n    Ok(())\n  }\n\n  /// Sets whether the webview should automatically grow and shrink its size and position when the parent window resizes.\n  pub fn set_auto_resize(&self, auto_resize: bool) -> crate::Result<()> {\n    self\n      .webview\n      .dispatcher\n      .set_auto_resize(auto_resize)\n      .map_err(Into::into)\n  }\n\n  /// Returns the bounds of the webviews's client area.\n  pub fn bounds(&self) -> crate::Result<tauri_runtime::dpi::Rect> {\n    self.webview.dispatcher.bounds().map_err(Into::into)\n  }\n\n  /// Returns the webview position.\n  ///\n  /// - For child webviews, returns the position of the top-left hand corner of the webviews's client area relative to the top-left hand corner of the parent window.\n  /// - For webview window, returns the inner position of the window.\n  pub fn position(&self) -> crate::Result<PhysicalPosition<i32>> {\n    self.webview.dispatcher.position().map_err(Into::into)\n  }\n\n  /// Returns the physical size of the webviews's client area.\n  pub fn size(&self) -> crate::Result<PhysicalSize<u32>> {\n    self.webview.dispatcher.size().map_err(Into::into)\n  }\n}\n\n/// Webview APIs.\nimpl<R: Runtime> Webview<R> {\n  /// The window that is hosting this webview.\n  pub fn window(&self) -> Window<R> {\n    self.window.lock().unwrap().clone()\n  }\n\n  /// A reference to the window that is hosting this webview.\n  pub fn window_ref(&self) -> MutexGuard<'_, Window<R>> {\n    self.window.lock().unwrap()\n  }\n\n  pub(crate) fn window_label(&self) -> String {\n    self.window_ref().label().to_string()\n  }\n\n  /// Executes a closure, providing it with the webview handle that is specific to the current platform.\n  ///\n  /// The closure is executed on the main thread.\n  ///\n  /// Note that `webview2-com`, `webkit2gtk`, `objc2_web_kit` and similar crates may be updated in minor releases of Tauri.\n  /// Therefore it's recommended to pin Tauri to at least a minor version when you're using `with_webview`.\n  ///\n  /// # Examples\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust,no_run\nuse tauri::Manager;\n\ntauri::Builder::default()\n  .setup(|app| {\n    let main_webview = app.get_webview(\"main\").unwrap();\n    main_webview.with_webview(|webview| {\n      #[cfg(target_os = \"linux\")]\n      {\n        // see <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/struct.WebView.html>\n        // and <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/trait.WebViewExt.html>\n        use webkit2gtk::WebViewExt;\n        webview.inner().set_zoom_level(4.);\n      }\n\n      #[cfg(windows)]\n      unsafe {\n        // see https://docs.rs/webview2-com/0.19.1/webview2_com/Microsoft/Web/WebView2/Win32/struct.ICoreWebView2Controller.html\n        webview.controller().SetZoomFactor(4.).unwrap();\n      }\n\n      #[cfg(target_os = \"macos\")]\n      unsafe {\n        let view: &objc2_web_kit::WKWebView = &*webview.inner().cast();\n        let controller: &objc2_web_kit::WKUserContentController = &*webview.controller().cast();\n        let window: &objc2_app_kit::NSWindow = &*webview.ns_window().cast();\n\n        view.setPageZoom(4.);\n        controller.removeAllUserScripts();\n        let bg_color = objc2_app_kit::NSColor::colorWithDeviceRed_green_blue_alpha(0.5, 0.2, 0.4, 1.);\n        window.setBackgroundColor(Some(&bg_color));\n      }\n\n      #[cfg(target_os = \"android\")]\n      {\n        use jni::objects::JValue;\n        webview.jni_handle().exec(|env, _, webview| {\n          env.call_method(webview, \"zoomBy\", \"(F)V\", &[JValue::Float(4.)]).unwrap();\n        })\n      }\n    });\n    Ok(())\n});\n```\n  \"####\n  )]\n  #[cfg(feature = \"wry\")]\n  #[cfg_attr(docsrs, doc(feature = \"wry\"))]\n  pub fn with_webview<F: FnOnce(PlatformWebview) + Send + 'static>(\n    &self,\n    f: F,\n  ) -> crate::Result<()> {\n    self\n      .webview\n      .dispatcher\n      .with_webview(|w| f(PlatformWebview(*w.downcast().unwrap())))\n      .map_err(Into::into)\n  }\n\n  /// Returns the current url of the webview.\n  pub fn url(&self) -> crate::Result<Url> {\n    self\n      .webview\n      .dispatcher\n      .url()\n      .map(|url| url.parse().map_err(crate::Error::InvalidUrl))?\n  }\n\n  /// Navigates the webview to the defined url.\n  pub fn navigate(&self, url: Url) -> crate::Result<()> {\n    self.webview.dispatcher.navigate(url).map_err(Into::into)\n  }\n\n  /// Reloads the current page.\n  pub fn reload(&self) -> crate::Result<()> {\n    self.webview.dispatcher.reload().map_err(Into::into)\n  }\n\n  fn is_local_url(&self, current_url: &Url) -> bool {\n    let uses_https = current_url.scheme() == \"https\";\n\n    // if from `tauri://` custom protocol\n    ({\n      let protocol_url = self.manager().tauri_protocol_url(uses_https);\n      current_url.scheme() == protocol_url.scheme()\n      && current_url.domain() == protocol_url.domain()\n    }) ||\n\n    // or if relative to `devUrl` or `frontendDist`\n      self\n          .manager()\n          .get_app_url(uses_https)\n          .make_relative(current_url)\n          .is_some()\n\n      // or from a custom protocol registered by the user\n      || ({\n        let scheme = current_url.scheme();\n        let protocols = self.manager().webview.uri_scheme_protocols.lock().unwrap();\n\n        #[cfg(all(not(windows), not(target_os = \"android\")))]\n        let local = protocols.contains_key(scheme);\n\n        // on window and android, custom protocols are `http://<protocol-name>.path/to/route`\n        // so we check using the first part of the domain\n        #[cfg(any(windows, target_os = \"android\"))]\n        let local = {\n          let protocol_url = self.manager().tauri_protocol_url(uses_https);\n          let maybe_protocol = current_url\n            .domain()\n            .and_then(|d| d .split_once('.'))\n            .unwrap_or_default()\n            .0;\n\n          protocols.contains_key(maybe_protocol) && scheme == protocol_url.scheme()\n        };\n\n        local\n      })\n  }\n\n  /// Handles this window receiving an [`InvokeRequest`].\n  pub fn on_message(self, request: InvokeRequest, responder: Box<OwnedInvokeResponder<R>>) {\n    let manager = self.manager_owned();\n    let is_local = self.is_local_url(&request.url);\n\n    // ensure the passed key matches what our manager should have injected\n    let expected = manager.invoke_key();\n    if request.invoke_key != expected {\n      #[cfg(feature = \"tracing\")]\n      tracing::error!(\n        \"__TAURI_INVOKE_KEY__ expected {expected} but received {}\",\n        request.invoke_key\n      );\n\n      #[cfg(not(feature = \"tracing\"))]\n      eprintln!(\n        \"__TAURI_INVOKE_KEY__ expected {expected} but received {}\",\n        request.invoke_key\n      );\n\n      return;\n    }\n\n    let resolver = InvokeResolver::new(\n      self.clone(),\n      Arc::new(Mutex::new(Some(Box::new(\n        move |webview: Webview<R>, cmd, response, callback, error| {\n          responder(webview, cmd, response, callback, error);\n        },\n      )))),\n      request.cmd.clone(),\n      request.callback,\n      request.error,\n    );\n\n    #[cfg(mobile)]\n    let app_handle = self.app_handle.clone();\n\n    let message = InvokeMessage::new(\n      self,\n      manager.state(),\n      request.cmd.to_string(),\n      request.body,\n      request.headers,\n    );\n\n    let acl_origin = if is_local {\n      Origin::Local\n    } else {\n      Origin::Remote {\n        url: request.url.clone(),\n      }\n    };\n    let (resolved_acl, has_app_acl_manifest) = {\n      let runtime_authority = manager.runtime_authority.lock().unwrap();\n      let acl = runtime_authority.resolve_access(\n        &request.cmd,\n        message.webview.window_ref().label(),\n        message.webview.label(),\n        &acl_origin,\n      );\n      (acl, runtime_authority.has_app_manifest())\n    };\n\n    let mut invoke = Invoke {\n      message,\n      resolver: resolver.clone(),\n      acl: resolved_acl,\n    };\n\n    let plugin_command = request.cmd.strip_prefix(\"plugin:\").map(|raw_command| {\n      let mut tokens = raw_command.split('|');\n      // safe to unwrap: split always has a least one item\n      let plugin = tokens.next().unwrap();\n      let command = tokens.next().map(|c| c.to_string()).unwrap_or_default();\n      (plugin, command)\n    });\n\n    // we only check ACL on plugin commands or if the app defined its ACL manifest\n    if (plugin_command.is_some() || has_app_acl_manifest)\n      // TODO: Remove this special check in v3\n      && request.cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND\n      && invoke.acl.is_none()\n    {\n      #[cfg(debug_assertions)]\n      {\n        let (key, command_name) = plugin_command\n          .clone()\n          .unwrap_or_else(|| (tauri_utils::acl::APP_ACL_KEY, request.cmd.clone()));\n        invoke.resolver.reject(\n          manager\n            .runtime_authority\n            .lock()\n            .unwrap()\n            .resolve_access_message(\n              key,\n              &command_name,\n              invoke.message.webview.window().label(),\n              invoke.message.webview.label(),\n              &acl_origin,\n            ),\n        );\n      }\n      #[cfg(not(debug_assertions))]\n      invoke\n        .resolver\n        .reject(format!(\"Command {} not allowed by ACL\", request.cmd));\n      return;\n    }\n\n    if let Some((plugin, command_name)) = plugin_command {\n      invoke.message.command = command_name;\n\n      let command = invoke.message.command.clone();\n\n      #[cfg(mobile)]\n      let message = invoke.message.clone();\n\n      #[allow(unused_mut)]\n      let mut handled = manager.extend_api(plugin, invoke);\n\n      #[cfg(mobile)]\n      {\n        if !handled {\n          handled = true;\n\n          fn load_channels<R: Runtime>(payload: &serde_json::Value, webview: &Webview<R>) {\n            use std::str::FromStr;\n\n            if let serde_json::Value::Object(map) = payload {\n              for v in map.values() {\n                if let serde_json::Value::String(s) = v {\n                  let _ = crate::ipc::JavaScriptChannelId::from_str(s)\n                    .map(|id| id.channel_on::<R, ()>(webview.clone()));\n                }\n              }\n            }\n          }\n\n          let payload = message.payload.into_json();\n          // initialize channels\n          load_channels(&payload, &message.webview);\n\n          let resolver_ = resolver.clone();\n          if let Err(e) = crate::plugin::mobile::run_command(\n            plugin,\n            &app_handle,\n            heck::AsLowerCamelCase(message.command).to_string(),\n            payload,\n            move |response| match response {\n              Ok(r) => resolver_.resolve(r),\n              Err(e) => resolver_.reject(e),\n            },\n          ) {\n            resolver.reject(e.to_string());\n            return;\n          }\n        }\n      }\n\n      if !handled {\n        resolver.reject(format!(\"Command {command} not found\"));\n      }\n    } else {\n      let command = invoke.message.command.clone();\n      let handled = manager.run_invoke_handler(invoke);\n      if !handled {\n        resolver.reject(format!(\"Command {command} not found\"));\n      }\n    }\n  }\n\n  /// Evaluates JavaScript on this window.\n  pub fn eval(&self, js: impl Into<String>) -> crate::Result<()> {\n    self\n      .webview\n      .dispatcher\n      .eval_script(js.into())\n      .map_err(Into::into)\n  }\n\n  /// Register a JS event listener and return its identifier.\n  pub(crate) fn listen_js(\n    &self,\n    event: EventName<&str>,\n    target: EventTarget,\n    handler: CallbackFn,\n  ) -> crate::Result<EventId> {\n    let listeners = self.manager().listeners();\n\n    let id = listeners.next_event_id();\n\n    self.eval(crate::event::listen_js_script(\n      listeners.listeners_object_name(),\n      &serde_json::to_string(&target)?,\n      event,\n      id,\n      handler,\n    ))?;\n\n    listeners.listen_js(event, self.label(), target, id);\n\n    Ok(id)\n  }\n\n  /// Unregister a JS event listener.\n  pub(crate) fn unlisten_js(&self, event: EventName<&str>, id: EventId) -> crate::Result<()> {\n    let listeners = self.manager().listeners();\n\n    listeners.unlisten_js(event, id);\n\n    Ok(())\n  }\n\n  pub(crate) fn emit_js(&self, emit_args: &EmitArgs, ids: &[u32]) -> crate::Result<()> {\n    self.eval(crate::event::emit_js_script(\n      self.manager().listeners().function_name(),\n      emit_args,\n      &serde_json::to_string(ids)?,\n    )?)?;\n    Ok(())\n  }\n\n  /// Opens the developer tools window (Web Inspector).\n  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Only supported on macOS 10.15+.\n  ///   This is a private API on macOS, so you cannot use this if your application will be published on the App Store.\n  ///\n  /// # Examples\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust,no_run\nuse tauri::Manager;\ntauri::Builder::default()\n  .setup(|app| {\n    #[cfg(debug_assertions)]\n    app.get_webview(\"main\").unwrap().open_devtools();\n    Ok(())\n  });\n```\n  \"####\n  )]\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = \"devtools\"))))]\n  pub fn open_devtools(&self) {\n    self.webview.dispatcher.open_devtools();\n  }\n\n  /// Closes the developer tools window (Web Inspector).\n  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Only supported on macOS 10.15+.\n  ///   This is a private API on macOS, so you cannot use this if your application will be published on the App Store.\n  /// - **Windows:** Unsupported.\n  ///\n  /// # Examples\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust,no_run\nuse tauri::Manager;\ntauri::Builder::default()\n  .setup(|app| {\n    #[cfg(debug_assertions)]\n    {\n      let webview = app.get_webview(\"main\").unwrap();\n      webview.open_devtools();\n      std::thread::spawn(move || {\n        std::thread::sleep(std::time::Duration::from_secs(10));\n        webview.close_devtools();\n      });\n    }\n    Ok(())\n  });\n```\n  \"####\n  )]\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = \"devtools\"))))]\n  pub fn close_devtools(&self) {\n    self.webview.dispatcher.close_devtools();\n  }\n\n  /// Checks if the developer tools window (Web Inspector) is opened.\n  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Only supported on macOS 10.15+.\n  ///   This is a private API on macOS, so you cannot use this if your application will be published on the App Store.\n  /// - **Windows:** Unsupported.\n  ///\n  /// # Examples\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust,no_run\nuse tauri::Manager;\ntauri::Builder::default()\n  .setup(|app| {\n    #[cfg(debug_assertions)]\n    {\n      let webview = app.get_webview(\"main\").unwrap();\n      if !webview.is_devtools_open() {\n        webview.open_devtools();\n      }\n    }\n    Ok(())\n  });\n```\n  \"####\n  )]\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = \"devtools\"))))]\n  pub fn is_devtools_open(&self) -> bool {\n    self\n      .webview\n      .dispatcher\n      .is_devtools_open()\n      .unwrap_or_default()\n  }\n\n  /// Set the webview zoom level\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Android**: Not supported.\n  /// - **macOS**: available on macOS 11+ only.\n  /// - **iOS**: available on iOS 14+ only.\n  pub fn set_zoom(&self, scale_factor: f64) -> crate::Result<()> {\n    self\n      .webview\n      .dispatcher\n      .set_zoom(scale_factor)\n      .map_err(Into::into)\n  }\n\n  /// Specify the webview background color.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS / iOS**: Not implemented.\n  /// - **Windows**:\n  ///   - On Windows 7, transparency is not supported and the alpha value will be ignored.\n  ///   - On Windows higher than 7: translucent colors are not supported so any alpha value other than `0` will be replaced by `255`\n  pub fn set_background_color(&self, color: Option<Color>) -> crate::Result<()> {\n    self\n      .webview\n      .dispatcher\n      .set_background_color(color)\n      .map_err(Into::into)\n  }\n\n  /// Clear all browsing data for this webview.\n  pub fn clear_all_browsing_data(&self) -> crate::Result<()> {\n    self\n      .webview\n      .dispatcher\n      .clear_all_browsing_data()\n      .map_err(Into::into)\n  }\n\n  /// Returns all cookies in the runtime's cookie store including HTTP-only and secure cookies.\n  ///\n  /// Note that cookies will only be returned for URLs with an http or https scheme.\n  /// Cookies set through javascript for local files\n  /// (such as those served from the tauri://) protocol are not currently supported.\n  ///\n  /// # Stability\n  ///\n  /// See [Self::cookies].\n  ///\n  /// # Known issues\n  ///\n  /// See [Self::cookies].\n  pub fn cookies_for_url(&self, url: Url) -> crate::Result<Vec<Cookie<'static>>> {\n    self\n      .webview\n      .dispatcher\n      .cookies_for_url(url)\n      .map_err(Into::into)\n  }\n\n  /// Returns all cookies in the runtime's cookie store for all URLs including HTTP-only and secure cookies.\n  ///\n  /// Note that cookies will only be returned for URLs with an http or https scheme.\n  /// Cookies set through javascript for local files\n  /// (such as those served from the tauri://) protocol are not currently supported.\n  ///\n  /// # Stability\n  ///\n  /// The return value of this function leverages [`tauri_runtime::Cookie`] which re-exports the cookie crate.\n  /// This dependency might receive updates in minor Tauri releases.\n  ///\n  /// # Known issues\n  ///\n  /// On Windows, this function deadlocks when used in a synchronous command or event handlers, see [the Webview2 issue].\n  /// You should use `async` commands and separate threads when reading cookies.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Android**: Unsupported, always returns an empty [`Vec`].\n  ///\n  /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583\n  pub fn cookies(&self) -> crate::Result<Vec<Cookie<'static>>> {\n    self.webview.dispatcher.cookies().map_err(Into::into)\n  }\n\n  /// Set a cookie for the webview.\n  ///\n  /// # Stability\n  ///\n  /// See [Self::cookies].\n  pub fn set_cookie(&self, cookie: Cookie<'_>) -> crate::Result<()> {\n    self\n      .webview\n      .dispatcher\n      .set_cookie(cookie)\n      .map_err(Into::into)\n  }\n\n  /// Delete a cookie for the webview.\n  ///\n  /// # Stability\n  ///\n  /// See [Self::cookies].\n  pub fn delete_cookie(&self, cookie: Cookie<'_>) -> crate::Result<()> {\n    self\n      .webview\n      .dispatcher\n      .delete_cookie(cookie)\n      .map_err(Into::into)\n  }\n}\n\nimpl<R: Runtime> Listener<R> for Webview<R> {\n  /// Listen to an event on this webview.\n  ///\n  /// # Examples\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\nuse tauri::{Manager, Listener};\n\ntauri::Builder::default()\n  .setup(|app| {\n    let webview = app.get_webview(\"main\").unwrap();\n    webview.listen(\"component-loaded\", move |event| {\n      println!(\"webview just loaded a component\");\n    });\n\n    Ok(())\n  });\n```\n  \"####\n  )]\n  fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId\n  where\n    F: Fn(Event) + Send + 'static,\n  {\n    let event = EventName::new(event.into()).unwrap();\n    self.manager.listen(\n      event,\n      EventTarget::Webview {\n        label: self.label().to_string(),\n      },\n      handler,\n    )\n  }\n\n  /// Listen to an event on this webview only once.\n  ///\n  /// See [`Self::listen`] for more information.\n  fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId\n  where\n    F: FnOnce(Event) + Send + 'static,\n  {\n    let event = EventName::new(event.into()).unwrap();\n    self.manager.once(\n      event,\n      EventTarget::Webview {\n        label: self.label().to_string(),\n      },\n      handler,\n    )\n  }\n\n  /// Unlisten to an event on this webview.\n  ///\n  /// # Examples\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\nuse tauri::{Manager, Listener};\n\ntauri::Builder::default()\n  .setup(|app| {\n    let webview = app.get_webview(\"main\").unwrap();\n    let webview_ = webview.clone();\n    let handler = webview.listen(\"component-loaded\", move |event| {\n      println!(\"webview just loaded a component\");\n\n      // we no longer need to listen to the event\n      // we also could have used `webview.once` instead\n      webview_.unlisten(event.id());\n    });\n\n    // stop listening to the event when you do not need it anymore\n    webview.unlisten(handler);\n\n    Ok(())\n  });\n```\n  \"####\n  )]\n  fn unlisten(&self, id: EventId) {\n    self.manager.unlisten(id)\n  }\n}\n\nimpl<R: Runtime> Emitter<R> for Webview<R> {}\n\nimpl<R: Runtime> Manager<R> for Webview<R> {\n  fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {\n    self\n      .resources_table\n      .lock()\n      .expect(\"poisoned window resources table\")\n  }\n}\n\nimpl<R: Runtime> ManagerBase<R> for Webview<R> {\n  fn manager(&self) -> &AppManager<R> {\n    &self.manager\n  }\n\n  fn manager_owned(&self) -> Arc<AppManager<R>> {\n    self.manager.clone()\n  }\n\n  fn runtime(&self) -> RuntimeOrDispatch<'_, R> {\n    self.app_handle.runtime()\n  }\n\n  fn managed_app_handle(&self) -> &AppHandle<R> {\n    &self.app_handle\n  }\n}\n\nimpl<'de, R: Runtime> CommandArg<'de, R> for Webview<R> {\n  /// Grabs the [`Webview`] from the [`CommandItem`]. This will never fail.\n  fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {\n    Ok(command.message.webview())\n  }\n}\n\n/// Resolved scope that can be obtained via [`Webview::resolve_command_scope`].\npub struct ResolvedScope<T: ScopeObject> {\n  command_scope: CommandScope<T>,\n  global_scope: GlobalScope<T>,\n}\n\nimpl<T: ScopeObject> ResolvedScope<T> {\n  /// The global plugin scope.\n  pub fn global_scope(&self) -> &GlobalScope<T> {\n    &self.global_scope\n  }\n\n  /// The command-specific scope.\n  pub fn command_scope(&self) -> &CommandScope<T> {\n    &self.command_scope\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  #[test]\n  fn webview_is_send_sync() {\n    crate::test_utils::assert_send::<super::Webview>();\n    crate::test_utils::assert_sync::<super::Webview>();\n  }\n\n  #[cfg(target_os = \"macos\")]\n  #[test]\n  fn test_webview_window_has_set_simple_fullscreen_method() {\n    use crate::test::{mock_builder, mock_context, noop_assets};\n\n    // Create a mock app with proper context\n    let app = mock_builder().build(mock_context(noop_assets())).unwrap();\n\n    // Get or create a webview window\n    let webview_window =\n      crate::WebviewWindowBuilder::new(&app, \"test\", crate::WebviewUrl::default())\n        .build()\n        .unwrap();\n\n    // This should compile if set_simple_fullscreen exists\n    let result = webview_window.set_simple_fullscreen(true);\n\n    // We expect this to work without panicking\n    assert!(result.is_ok(), \"set_simple_fullscreen should succeed\");\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/webview/plugin.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The tauri plugin to create and manipulate windows from JS.\n\nuse crate::{\n  plugin::{Builder, TauriPlugin},\n  Runtime,\n};\n\n#[cfg(desktop)]\nmod desktop_commands {\n\n  use serde::Serialize;\n  use tauri_runtime::dpi::{Position, Size};\n  use tauri_utils::config::WindowConfig;\n\n  use super::*;\n  use crate::{\n    command, sealed::ManagerBase, webview::Color, AppHandle, Webview, WebviewWindowBuilder,\n  };\n\n  #[derive(Serialize)]\n  pub struct WebviewRef {\n    window_label: String,\n    label: String,\n  }\n\n  #[command(root = \"crate\")]\n  pub async fn get_all_webviews<R: Runtime>(app: AppHandle<R>) -> Vec<WebviewRef> {\n    app\n      .manager()\n      .webviews()\n      .values()\n      .map(|webview| WebviewRef {\n        window_label: webview.window_ref().label().into(),\n        label: webview.label().into(),\n      })\n      .collect()\n  }\n\n  #[command(root = \"crate\")]\n  pub async fn create_webview_window<R: Runtime>(\n    app: AppHandle<R>,\n    options: WindowConfig,\n  ) -> crate::Result<()> {\n    WebviewWindowBuilder::from_config(&app, &options)?.build()?;\n    Ok(())\n  }\n\n  #[cfg(not(feature = \"unstable\"))]\n  #[command(root = \"crate\")]\n  pub async fn create_webview() -> crate::Result<()> {\n    Err(crate::Error::UnstableFeatureNotSupported)\n  }\n\n  #[cfg(feature = \"unstable\")]\n  #[command(root = \"crate\")]\n  pub async fn create_webview<R: Runtime>(\n    app: AppHandle<R>,\n    window_label: String,\n    options: WindowConfig,\n  ) -> crate::Result<()> {\n    use anyhow::Context;\n\n    let window = app\n      .manager()\n      .get_window(&window_label)\n      .ok_or(crate::Error::WindowNotFound)?;\n\n    let x = options.x.context(\"missing parameter `options.x`\")?;\n    let y = options.y.context(\"missing parameter `options.y`\")?;\n    let width = options.width;\n    let height = options.height;\n\n    let builder = crate::webview::WebviewBuilder::from_config(&options);\n\n    window.add_child(\n      builder,\n      tauri_runtime::dpi::LogicalPosition::new(x, y),\n      tauri_runtime::dpi::LogicalSize::new(width, height),\n    )?;\n\n    Ok(())\n  }\n\n  fn get_webview<R: Runtime>(\n    webview: Webview<R>,\n    label: Option<String>,\n  ) -> crate::Result<Webview<R>> {\n    match label {\n      Some(l) if !l.is_empty() => webview\n        .manager()\n        .get_webview(&l)\n        .ok_or(crate::Error::WebviewNotFound),\n      _ => Ok(webview),\n    }\n  }\n\n  macro_rules! getter {\n    ($cmd: ident, $ret: ty) => {\n      getter!($cmd, $cmd, $ret)\n    };\n    ($fn: ident, $cmd: ident, $ret: ty) => {\n      #[command(root = \"crate\")]\n      pub async fn $fn<R: Runtime>(\n        webview: Webview<R>,\n        label: Option<String>,\n      ) -> crate::Result<$ret> {\n        get_webview(webview, label)?.$cmd().map_err(Into::into)\n      }\n    };\n  }\n\n  macro_rules! setter {\n    ($cmd: ident) => {\n      setter!($cmd, $cmd);\n    };\n    ($fn: ident, $cmd: ident) => {\n      #[command(root = \"crate\")]\n      pub async fn $fn<R: Runtime>(\n        webview: Webview<R>,\n        label: Option<String>,\n      ) -> crate::Result<()> {\n        get_webview(webview, label)?.$cmd().map_err(Into::into)\n      }\n    };\n    ($fn: ident, $cmd: ident, $input: ty) => {\n      #[command(root = \"crate\")]\n      pub async fn $fn<R: Runtime>(\n        webview: Webview<R>,\n        label: Option<String>,\n        value: $input,\n      ) -> crate::Result<()> {\n        get_webview(webview, label)?.$cmd(value).map_err(Into::into)\n      }\n    };\n  }\n\n  // TODO\n  getter!(\n    webview_position,\n    position,\n    tauri_runtime::dpi::PhysicalPosition<i32>\n  );\n  getter!(webview_size, size, tauri_runtime::dpi::PhysicalSize<u32>);\n  //getter!(is_focused, bool);\n\n  setter!(print);\n  setter!(webview_close, close);\n  setter!(set_webview_size, set_size, Size);\n  setter!(set_webview_position, set_position, Position);\n  setter!(set_webview_focus, set_focus);\n  setter!(set_webview_auto_resize, set_auto_resize, bool);\n  setter!(webview_hide, hide);\n  setter!(webview_show, show);\n  setter!(set_webview_zoom, set_zoom, f64);\n  setter!(\n    set_webview_background_color,\n    set_background_color,\n    Option<Color>\n  );\n  setter!(clear_all_browsing_data, clear_all_browsing_data);\n\n  #[command(root = \"crate\")]\n  pub async fn reparent<R: Runtime>(\n    webview: crate::Webview<R>,\n    label: Option<String>,\n    window: String,\n  ) -> crate::Result<()> {\n    let webview = get_webview(webview, label)?;\n    if let Some(window) = webview.manager.get_window(&window) {\n      webview.reparent(&window)?;\n    }\n    Ok(())\n  }\n\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  #[command(root = \"crate\")]\n  pub async fn internal_toggle_devtools<R: Runtime>(\n    webview: crate::Webview<R>,\n    label: Option<String>,\n  ) -> crate::Result<()> {\n    let webview = get_webview(webview, label)?;\n    if webview.is_devtools_open() {\n      webview.close_devtools();\n    } else {\n      webview.open_devtools();\n    }\n    Ok(())\n  }\n}\n\n/// Initializes the plugin.\npub fn init<R: Runtime>() -> TauriPlugin<R> {\n  #[allow(unused_mut)]\n  let mut init_script = String::new();\n  // window.print works on Linux/Windows; need to use the API on macOS\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  {\n    init_script.push_str(include_str!(\"./scripts/print.js\"));\n  }\n\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  {\n    use serialize_to_javascript::{default_template, DefaultTemplate, Template};\n\n    #[derive(Template)]\n    #[default_template(\"./scripts/toggle-devtools.js\")]\n    struct Devtools<'a> {\n      os_name: &'a str,\n    }\n\n    init_script.push_str(\n      &Devtools {\n        os_name: std::env::consts::OS,\n      }\n      .render_default(&Default::default())\n      .unwrap()\n      .into_string(),\n    );\n  }\n\n  let mut builder = Builder::new(\"webview\");\n  if !init_script.is_empty() {\n    builder = builder.js_init_script(init_script);\n  }\n\n  builder\n    .invoke_handler(\n      #[cfg(desktop)]\n      crate::generate_handler![\n        #![plugin(webview)]\n        desktop_commands::create_webview,\n        desktop_commands::create_webview_window,\n        // getters\n        desktop_commands::get_all_webviews,\n        desktop_commands::webview_position,\n        desktop_commands::webview_size,\n        // setters\n        desktop_commands::webview_close,\n        desktop_commands::set_webview_size,\n        desktop_commands::set_webview_position,\n        desktop_commands::set_webview_focus,\n        desktop_commands::set_webview_auto_resize,\n        desktop_commands::set_webview_background_color,\n        desktop_commands::set_webview_zoom,\n        desktop_commands::webview_hide,\n        desktop_commands::webview_show,\n        desktop_commands::print,\n        desktop_commands::reparent,\n        desktop_commands::clear_all_browsing_data,\n        #[cfg(any(debug_assertions, feature = \"devtools\"))]\n        desktop_commands::internal_toggle_devtools,\n      ],\n      #[cfg(mobile)]\n      |invoke| {\n        invoke\n          .resolver\n          .reject(\"Webview API not available on mobile\");\n        true\n      },\n    )\n    .build()\n}\n"
  },
  {
    "path": "crates/tauri/src/webview/scripts/print.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nwindow.print = function () {\n  return window.__TAURI_INTERNALS__.invoke('plugin:webview|print')\n}\n"
  },
  {
    "path": "crates/tauri/src/webview/scripts/toggle-devtools.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n;(function () {\n  function toggleDevtoolsHotkey() {\n    const osName = __TEMPLATE_os_name__\n\n    const isHotkey =\n      osName === 'macos'\n        ? (event) => event.metaKey && event.altKey && event.code === 'KeyI'\n        : (event) => event.ctrlKey && event.shiftKey && event.code === 'KeyI'\n\n    document.addEventListener('keydown', (event) => {\n      if (isHotkey(event)) {\n        window.__TAURI_INTERNALS__.invoke(\n          'plugin:webview|internal_toggle_devtools'\n        )\n      }\n    })\n  }\n\n  if (\n    document.readyState === 'complete'\n    || document.readyState === 'interactive'\n  ) {\n    toggleDevtoolsHotkey()\n  } else {\n    window.addEventListener('DOMContentLoaded', toggleDevtoolsHotkey, true)\n  }\n})()\n"
  },
  {
    "path": "crates/tauri/src/webview/scripts/zoom-hotkey.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nconst OS_NAME = __TEMPLATE_os_name__\n\nlet zoomLevel = 1\n\nconst MAX_ZOOM_LEVEL = 10\nconst MIN_ZOOM_LEVEL = 0.2\n\nwindow.addEventListener('keydown', (event) => {\n  if (OS_NAME === 'macos' ? event.metaKey : event.ctrlKey) {\n    if (event.key === '-') {\n      zoomLevel -= 0.2\n    } else if (event.key === '=' || event.key === '+') {\n      zoomLevel += 0.2\n    } else if (event.key === '0') {\n      zoomLevel = 1\n    } else {\n      return\n    }\n    zoomLevel = Math.min(Math.max(zoomLevel, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL)\n    window.__TAURI_INTERNALS__.invoke('plugin:webview|set_webview_zoom', {\n      value: zoomLevel\n    })\n  }\n})\n\nwindow.addEventListener('mousewheel', (event) => {\n  if (event.ctrlKey) {\n    event.preventDefault()\n    if (event.deltaY < 0) {\n      zoomLevel += 0.2\n    } else {\n      zoomLevel -= 0.2\n    }\n    zoomLevel = Math.min(Math.max(zoomLevel, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL)\n    window.__TAURI_INTERNALS__.invoke('plugin:webview|set_webview_zoom', {\n      value: zoomLevel\n    })\n  }\n})\n"
  },
  {
    "path": "crates/tauri/src/webview/webview_window.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! [`Window`] that hosts a single [`Webview`].\n\nuse std::{\n  borrow::Cow,\n  path::{Path, PathBuf},\n  sync::{Arc, MutexGuard},\n};\n\nuse crate::{\n  event::EventTarget,\n  ipc::ScopeObject,\n  runtime::dpi::{PhysicalPosition, PhysicalSize},\n  webview::{NewWindowResponse, ScrollBarStyle},\n  window::Monitor,\n  Emitter, EventName, Listener, ResourceTable, Window,\n};\n#[cfg(desktop)]\nuse crate::{\n  image::Image,\n  menu::{ContextMenu, Menu},\n  runtime::{\n    dpi::{Position, Size},\n    window::CursorIcon,\n    UserAttentionType,\n  },\n};\nuse tauri_runtime::webview::NewWindowFeatures;\nuse tauri_utils::config::{BackgroundThrottlingPolicy, Color, WebviewUrl, WindowConfig};\nuse url::Url;\n\nuse crate::{\n  ipc::{CommandArg, CommandItem, InvokeError, OwnedInvokeResponder},\n  manager::AppManager,\n  sealed::{ManagerBase, RuntimeOrDispatch},\n  webview::{Cookie, PageLoadPayload, WebviewBuilder, WebviewEvent},\n  window::WindowBuilder,\n  AppHandle, Event, EventId, Manager, Runtime, Webview, WindowEvent,\n};\n\nuse tauri_macros::default_runtime;\n\n#[cfg(windows)]\nuse windows::Win32::Foundation::HWND;\n\nuse super::{DownloadEvent, ResolvedScope};\n\n/// A builder for [`WebviewWindow`], a window that hosts a single webview.\npub struct WebviewWindowBuilder<'a, R: Runtime, M: Manager<R>> {\n  window_builder: WindowBuilder<'a, R, M>,\n  webview_builder: WebviewBuilder<R>,\n}\n\nimpl<'a, R: Runtime, M: Manager<R>> WebviewWindowBuilder<'a, R, M> {\n  /// Initializes a webview window builder with the given window label.\n  ///\n  /// # Known issues\n  ///\n  /// On Windows, this function deadlocks when used in a synchronous command and event handlers, see [the Webview2 issue].\n  /// You should use `async` commands and separate threads when creating windows.\n  ///\n  /// # Examples\n  ///\n  /// - Create a window in the setup hook:\n  ///\n  /// ```\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let webview_window = tauri::WebviewWindowBuilder::new(app, \"label\", tauri::WebviewUrl::App(\"index.html\".into()))\n  ///       .build()?;\n  ///     Ok(())\n  ///   });\n  /// ```\n  ///\n  /// - Create a window in a separate thread:\n  ///\n  /// ```\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let handle = app.handle().clone();\n  ///     std::thread::spawn(move || {\n  ///       let webview_window = tauri::WebviewWindowBuilder::new(&handle, \"label\", tauri::WebviewUrl::App(\"index.html\".into()))\n  ///         .build()\n  ///         .unwrap();\n  ///     });\n  ///     Ok(())\n  ///   });\n  /// ```\n  ///\n  /// - Create a window in a command:\n  ///\n  /// ```\n  /// #[tauri::command]\n  /// async fn create_window(app: tauri::AppHandle) {\n  ///   let webview_window = tauri::WebviewWindowBuilder::new(&app, \"label\", tauri::WebviewUrl::App(\"index.html\".into()))\n  ///     .build()\n  ///     .unwrap();\n  /// }\n  /// ```\n  ///\n  /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583\n  pub fn new<L: Into<String>>(manager: &'a M, label: L, url: WebviewUrl) -> Self {\n    let label = label.into();\n    Self {\n      window_builder: WindowBuilder::new(manager, &label),\n      webview_builder: WebviewBuilder::new(&label, url),\n    }\n  }\n\n  /// Initializes a webview window builder from a [`WindowConfig`] from tauri.conf.json.\n  /// Keep in mind that you can't create 2 windows with the same `label` so make sure\n  /// that the initial window was closed or change the label of the cloned [`WindowConfig`].\n  ///\n  /// # Known issues\n  ///\n  /// On Windows, this function deadlocks when used in a synchronous command or event handlers, see [the Webview2 issue].\n  /// You should use `async` commands and separate threads when creating windows.\n  ///\n  /// # Examples\n  ///\n  /// - Create a window in a command:\n  ///\n  /// ```\n  /// #[tauri::command]\n  /// async fn reopen_window(app: tauri::AppHandle) {\n  ///   let webview_window = tauri::WebviewWindowBuilder::from_config(&app, &app.config().app.windows.get(0).unwrap())\n  ///     .unwrap()\n  ///     .build()\n  ///     .unwrap();\n  /// }\n  /// ```\n  ///\n  /// - Create a window in a command from a config with a specific label, and change its label so multiple instances can exist:\n  ///\n  /// ```\n  /// #[tauri::command]\n  /// async fn open_window_multiple(app: tauri::AppHandle) {\n  ///   let mut conf = app.config().app.windows.iter().find(|c| c.label == \"template-for-multiwindow\").unwrap().clone();\n  ///   // This should be a unique label for all windows. For example, we can use a random suffix:\n  ///   let mut buf = [0u8; 1];\n  ///   assert_eq!(getrandom::fill(&mut buf), Ok(()));\n  ///   conf.label = format!(\"my-multiwindow-{}\", buf[0]);\n  ///   let webview_window = tauri::WebviewWindowBuilder::from_config(&app, &conf)\n  ///     .unwrap()\n  ///     .build()\n  ///     .unwrap();\n  /// }\n  /// ```\n  ///\n  /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583\n  pub fn from_config(manager: &'a M, config: &WindowConfig) -> crate::Result<Self> {\n    Ok(Self {\n      window_builder: WindowBuilder::from_config(manager, config)?,\n      webview_builder: WebviewBuilder::from_config(config),\n    })\n  }\n\n  /// Registers a global menu event listener.\n  ///\n  /// Note that this handler is called for any menu event,\n  /// whether it is coming from this window, another window or from the tray icon menu.\n  ///\n  /// Also note that this handler will not be called if\n  /// the window used to register it was closed.\n  ///\n  /// # Examples\n  /// ```\n  /// use tauri::menu::{Menu, Submenu, MenuItem};\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let handle = app.handle();\n  ///     let save_menu_item = MenuItem::new(handle, \"Save\", true, None::<&str>)?;\n  ///     let menu = Menu::with_items(handle, &[\n  ///       &Submenu::with_items(handle, \"File\", true, &[\n  ///         &save_menu_item,\n  ///       ])?,\n  ///     ])?;\n  ///     let webview_window = tauri::WebviewWindowBuilder::new(app, \"editor\", tauri::WebviewUrl::App(\"index.html\".into()))\n  ///       .menu(menu)\n  ///       .on_menu_event(move |window, event| {\n  ///         if event.id == save_menu_item.id() {\n  ///           // save menu item\n  ///         }\n  ///       })\n  ///       .build()\n  ///       .unwrap();\n  ///\n  ///     Ok(())\n  ///   });\n  /// ```\n  #[cfg(desktop)]\n  pub fn on_menu_event<F: Fn(&crate::Window<R>, crate::menu::MenuEvent) + Send + Sync + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.window_builder = self.window_builder.on_menu_event(f);\n    self\n  }\n\n  /// Defines a closure to be executed when the webview makes an HTTP request for a web resource, allowing you to modify the response.\n  ///\n  /// Currently only implemented for the `tauri` URI protocol.\n  ///\n  /// **NOTE:** Currently this is **not** executed when using external URLs such as a development server,\n  /// but it might be implemented in the future. **Always** check the request URL.\n  ///\n  /// # Examples\n  /// ```rust,no_run\n  /// use tauri::{\n  ///   utils::config::{Csp, CspDirectiveSources, WebviewUrl},\n  ///   webview::WebviewWindowBuilder,\n  /// };\n  /// use http::header::HeaderValue;\n  /// use std::collections::HashMap;\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let webview_window = WebviewWindowBuilder::new(app, \"core\", WebviewUrl::App(\"index.html\".into()))\n  ///       .on_web_resource_request(|request, response| {\n  ///         if request.uri().scheme_str() == Some(\"tauri\") {\n  ///           // if we have a CSP header, Tauri is loading an HTML file\n  ///           //  for this example, let's dynamically change the CSP\n  ///           if let Some(csp) = response.headers_mut().get_mut(\"Content-Security-Policy\") {\n  ///             // use the tauri helper to parse the CSP policy to a map\n  ///             let mut csp_map: HashMap<String, CspDirectiveSources> = Csp::Policy(csp.to_str().unwrap().to_string()).into();\n  ///             csp_map.entry(\"script-src\".to_string()).or_insert_with(Default::default).push(\"'unsafe-inline'\");\n  ///             // use the tauri helper to get a CSP string from the map\n  ///             let csp_string = Csp::from(csp_map).to_string();\n  ///             *csp = HeaderValue::from_str(&csp_string).unwrap();\n  ///           }\n  ///         }\n  ///       })\n  ///       .build()?;\n  ///     Ok(())\n  ///   });\n  /// ```\n  pub fn on_web_resource_request<\n    F: Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync + 'static,\n  >(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.webview_builder = self.webview_builder.on_web_resource_request(f);\n    self\n  }\n\n  /// Defines a closure to be executed when the webview navigates to a URL. Returning `false` cancels the navigation.\n  ///\n  /// # Examples\n  /// ```rust,no_run\n  /// use tauri::{\n  ///   utils::config::{Csp, CspDirectiveSources, WebviewUrl},\n  ///   webview::WebviewWindowBuilder,\n  /// };\n  /// use http::header::HeaderValue;\n  /// use std::collections::HashMap;\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let webview_window = WebviewWindowBuilder::new(app, \"core\", WebviewUrl::App(\"index.html\".into()))\n  ///       .on_navigation(|url| {\n  ///         // allow the production URL or localhost on dev\n  ///         url.scheme() == \"tauri\" || (cfg!(dev) && url.host_str() == Some(\"localhost\"))\n  ///       })\n  ///       .build()?;\n  ///     Ok(())\n  ///   });\n  /// ```\n  pub fn on_navigation<F: Fn(&Url) -> bool + Send + 'static>(mut self, f: F) -> Self {\n    self.webview_builder = self.webview_builder.on_navigation(f);\n    self\n  }\n\n  /// Set a new window request handler to decide if incoming url is allowed to be opened.\n  ///\n  /// A new window is requested to be opened by the [window.open] API.\n  ///\n  /// The closure take the URL to open and the window features object and returns [`NewWindowResponse`] to determine whether the window should open.\n  ///\n  /// # Examples\n  /// ```rust,no_run\n  /// use tauri::{\n  ///   utils::config::WebviewUrl,\n  ///   webview::WebviewWindowBuilder,\n  /// };\n  /// use http::header::HeaderValue;\n  /// use std::collections::HashMap;\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let app_ = app.handle().clone();\n  ///     let webview_window = WebviewWindowBuilder::new(app, \"core\", WebviewUrl::App(\"index.html\".into()))\n  ///       .on_new_window(move |url, features| {\n  ///         let builder = tauri::WebviewWindowBuilder::new(\n  ///           &app_,\n  ///           // note: add an ID counter or random label generator to support multiple opened windows at the same time\n  ///           \"opened-window\",\n  ///           tauri::WebviewUrl::External(\"about:blank\".parse().unwrap()),\n  ///         )\n  ///         .window_features(features)\n  ///         .on_document_title_changed(|window, title| {\n  ///           window.set_title(&title).unwrap();\n  ///         })\n  ///         .title(url.as_str());\n  ///\n  ///         let window = builder.build().unwrap();\n  ///         tauri::webview::NewWindowResponse::Create { window }\n  ///       })\n  ///       .build()?;\n  ///     Ok(())\n  ///   });\n  /// ```\n  ///\n  /// # Platform-specific\n  ///\n  /// - **Android / iOS**: Not supported.\n  /// - **Windows**: The closure is executed on a separate thread to prevent a deadlock.\n  ///\n  /// [window.open]: https://developer.mozilla.org/en-US/docs/Web/API/Window/open\n  pub fn on_new_window<\n    F: Fn(Url, NewWindowFeatures) -> NewWindowResponse<R> + Send + Sync + 'static,\n  >(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.webview_builder = self.webview_builder.on_new_window(f);\n    self\n  }\n\n  /// Defines a closure to be executed when the document title changes.\n  ///\n  /// Note that it may run before or after the navigation event.\n  pub fn on_document_title_changed<F: Fn(WebviewWindow<R>, String) + Send + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.webview_builder = self\n      .webview_builder\n      .on_document_title_changed(move |webview, url| {\n        f(\n          WebviewWindow {\n            window: webview.window(),\n            webview,\n          },\n          url,\n        )\n      });\n    self\n  }\n\n  /// Set a download event handler to be notified when a download is requested or finished.\n  ///\n  /// Returning `false` prevents the download from happening on a [`DownloadEvent::Requested`] event.\n  ///\n  /// # Examples\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust,no_run\nuse tauri::{\n  utils::config::{Csp, CspDirectiveSources, WebviewUrl},\n  webview::{DownloadEvent, WebviewWindowBuilder},\n};\n\ntauri::Builder::default()\n  .setup(|app| {\n    let handle = app.handle();\n    let webview_window = WebviewWindowBuilder::new(handle, \"core\", WebviewUrl::App(\"index.html\".into()))\n      .on_download(|webview, event| {\n        match event {\n          DownloadEvent::Requested { url, destination } => {\n            println!(\"downloading {}\", url);\n            *destination = \"/home/tauri/target/path\".into();\n          }\n          DownloadEvent::Finished { url, path, success } => {\n            println!(\"downloaded {} to {:?}, success: {}\", url, path, success);\n          }\n          _ => (),\n        }\n        // let the download start\n        true\n      })\n      .build()?;\n\n    Ok(())\n  });\n```\n  \"####\n  )]\n  pub fn on_download<F: Fn(Webview<R>, DownloadEvent<'_>) -> bool + Send + Sync + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.webview_builder.download_handler.replace(Arc::new(f));\n    self\n  }\n\n  /// Defines a closure to be executed when a page load event is triggered.\n  /// The event can be either [`tauri_runtime::webview::PageLoadEvent::Started`] if the page has started loading\n  /// or [`tauri_runtime::webview::PageLoadEvent::Finished`] when the page finishes loading.\n  ///\n  /// # Examples\n  /// ```rust,no_run\n  /// use tauri::{\n  ///   utils::config::{Csp, CspDirectiveSources, WebviewUrl},\n  ///   webview::{PageLoadEvent, WebviewWindowBuilder},\n  /// };\n  /// use http::header::HeaderValue;\n  /// use std::collections::HashMap;\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let webview_window = WebviewWindowBuilder::new(app, \"core\", WebviewUrl::App(\"index.html\".into()))\n  ///       .on_page_load(|window, payload| {\n  ///         match payload.event() {\n  ///           PageLoadEvent::Started => {\n  ///             println!(\"{} finished loading\", payload.url());\n  ///           }\n  ///           PageLoadEvent::Finished => {\n  ///             println!(\"{} finished loading\", payload.url());\n  ///           }\n  ///         }\n  ///       })\n  ///       .build()?;\n  ///     Ok(())\n  ///   });\n  /// ```\n  pub fn on_page_load<F: Fn(WebviewWindow<R>, PageLoadPayload<'_>) + Send + Sync + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.webview_builder = self.webview_builder.on_page_load(move |webview, payload| {\n      f(\n        WebviewWindow {\n          window: webview.window(),\n          webview,\n        },\n        payload,\n      )\n    });\n    self\n  }\n\n  /// Creates a new window.\n  pub fn build(self) -> crate::Result<WebviewWindow<R>> {\n    let (window, webview) = self.window_builder.with_webview(self.webview_builder)?;\n    Ok(WebviewWindow { window, webview })\n  }\n}\n\n/// Desktop APIs.\n#[cfg(desktop)]\nimpl<'a, R: Runtime, M: Manager<R>> WebviewWindowBuilder<'a, R, M> {\n  /// Sets the menu for the window.\n  #[must_use]\n  pub fn menu(mut self, menu: crate::menu::Menu<R>) -> Self {\n    self.window_builder = self.window_builder.menu(menu);\n    self\n  }\n\n  /// Show window in the center of the screen.\n  #[must_use]\n  pub fn center(mut self) -> Self {\n    self.window_builder = self.window_builder.center();\n    self\n  }\n\n  /// The initial position of the window in logical pixels.\n  #[must_use]\n  pub fn position(mut self, x: f64, y: f64) -> Self {\n    self.window_builder = self.window_builder.position(x, y);\n    self\n  }\n\n  /// Window size in logical pixels.\n  #[must_use]\n  pub fn inner_size(mut self, width: f64, height: f64) -> Self {\n    self.window_builder = self.window_builder.inner_size(width, height);\n    self\n  }\n\n  /// Window min inner size in logical pixels.\n  #[must_use]\n  pub fn min_inner_size(mut self, min_width: f64, min_height: f64) -> Self {\n    self.window_builder = self.window_builder.min_inner_size(min_width, min_height);\n    self\n  }\n\n  /// Window max inner size in logical pixels.\n  #[must_use]\n  pub fn max_inner_size(mut self, max_width: f64, max_height: f64) -> Self {\n    self.window_builder = self.window_builder.max_inner_size(max_width, max_height);\n    self\n  }\n\n  /// Window inner size constraints.\n  #[must_use]\n  pub fn inner_size_constraints(\n    mut self,\n    constraints: tauri_runtime::window::WindowSizeConstraints,\n  ) -> Self {\n    self.window_builder = self.window_builder.inner_size_constraints(constraints);\n    self\n  }\n\n  /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size)\n  /// on creation, which means the window size will be limited to `monitor size - taskbar size`\n  ///\n  /// **NOTE**: The overflow check is only performed on window creation, resizes can still overflow\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  #[must_use]\n  pub fn prevent_overflow(mut self) -> Self {\n    self.window_builder = self.window_builder.prevent_overflow();\n    self\n  }\n\n  /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size)\n  /// on creation with a margin, which means the window size will be limited to `monitor size - taskbar size - margin size`\n  ///\n  /// **NOTE**: The overflow check is only performed on window creation, resizes can still overflow\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  #[must_use]\n  pub fn prevent_overflow_with_margin(mut self, margin: impl Into<Size>) -> Self {\n    self.window_builder = self.window_builder.prevent_overflow_with_margin(margin);\n    self\n  }\n\n  /// Whether the window is resizable or not.\n  /// When resizable is set to false, native window's maximize button is automatically disabled.\n  #[must_use]\n  pub fn resizable(mut self, resizable: bool) -> Self {\n    self.window_builder = self.window_builder.resizable(resizable);\n    self\n  }\n\n  /// Whether the window's native maximize button is enabled or not.\n  /// If resizable is set to false, this setting is ignored.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Disables the \"zoom\" button in the window titlebar, which is also used to enter fullscreen mode.\n  /// - **Linux / iOS / Android:** Unsupported.\n  #[must_use]\n  pub fn maximizable(mut self, maximizable: bool) -> Self {\n    self.window_builder = self.window_builder.maximizable(maximizable);\n    self\n  }\n\n  /// Whether the window's native minimize button is enabled or not.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  #[must_use]\n  pub fn minimizable(mut self, minimizable: bool) -> Self {\n    self.window_builder = self.window_builder.minimizable(minimizable);\n    self\n  }\n\n  /// Whether the window's native close button is enabled or not.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** \"GTK+ will do its best to convince the window manager not to show a close button.\n  ///   Depending on the system, this function may not have any effect when called on a window that is already visible\"\n  /// - **iOS / Android:** Unsupported.\n  #[must_use]\n  pub fn closable(mut self, closable: bool) -> Self {\n    self.window_builder = self.window_builder.closable(closable);\n    self\n  }\n\n  /// The title of the window in the title bar.\n  #[must_use]\n  pub fn title<S: Into<String>>(mut self, title: S) -> Self {\n    self.window_builder = self.window_builder.title(title);\n    self\n  }\n\n  /// Whether to start the window in fullscreen or not.\n  #[must_use]\n  pub fn fullscreen(mut self, fullscreen: bool) -> Self {\n    self.window_builder = self.window_builder.fullscreen(fullscreen);\n    self\n  }\n\n  /// Sets the window to be initially focused.\n  #[must_use]\n  #[deprecated(\n    since = \"1.2.0\",\n    note = \"The window is automatically focused by default. This function Will be removed in 3.0.0. Use `focused` instead.\"\n  )]\n  pub fn focus(mut self) -> Self {\n    self.window_builder = self.window_builder.focused(true);\n    self.webview_builder = self.webview_builder.focused(true);\n    self\n  }\n\n  /// Whether the window will be focusable or not.\n  #[must_use]\n  pub fn focusable(mut self, focusable: bool) -> Self {\n    self.window_builder = self.window_builder.focusable(focusable);\n    self\n  }\n\n  /// Whether the window will be initially focused or not.\n  #[must_use]\n  pub fn focused(mut self, focused: bool) -> Self {\n    self.window_builder = self.window_builder.focused(focused);\n    self.webview_builder = self.webview_builder.focused(focused);\n    self\n  }\n\n  /// Whether the window should be maximized upon creation.\n  #[must_use]\n  pub fn maximized(mut self, maximized: bool) -> Self {\n    self.window_builder = self.window_builder.maximized(maximized);\n    self\n  }\n\n  /// Whether the window should be immediately visible upon creation.\n  #[must_use]\n  pub fn visible(mut self, visible: bool) -> Self {\n    self.window_builder = self.window_builder.visible(visible);\n    self\n  }\n\n  /// Forces a theme or uses the system settings if None was provided.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS**: Only supported on macOS 10.14+.\n  #[must_use]\n  pub fn theme(mut self, theme: Option<crate::Theme>) -> Self {\n    self.window_builder = self.window_builder.theme(theme);\n    self\n  }\n\n  /// Whether the window should have borders and bars.\n  #[must_use]\n  pub fn decorations(mut self, decorations: bool) -> Self {\n    self.window_builder = self.window_builder.decorations(decorations);\n    self\n  }\n\n  /// Whether the window should always be below other windows.\n  #[must_use]\n  pub fn always_on_bottom(mut self, always_on_bottom: bool) -> Self {\n    self.window_builder = self.window_builder.always_on_bottom(always_on_bottom);\n    self\n  }\n\n  /// Whether the window should always be on top of other windows.\n  #[must_use]\n  pub fn always_on_top(mut self, always_on_top: bool) -> Self {\n    self.window_builder = self.window_builder.always_on_top(always_on_top);\n    self\n  }\n\n  /// Whether the window will be visible on all workspaces or virtual desktops.\n  #[must_use]\n  pub fn visible_on_all_workspaces(mut self, visible_on_all_workspaces: bool) -> Self {\n    self.window_builder = self\n      .window_builder\n      .visible_on_all_workspaces(visible_on_all_workspaces);\n    self\n  }\n\n  /// Prevents the window contents from being captured by other apps.\n  #[must_use]\n  pub fn content_protected(mut self, protected: bool) -> Self {\n    self.window_builder = self.window_builder.content_protected(protected);\n    self\n  }\n\n  /// Sets the window icon.\n  pub fn icon(mut self, icon: Image<'a>) -> crate::Result<Self> {\n    self.window_builder = self.window_builder.icon(icon)?;\n    Ok(self)\n  }\n\n  /// Sets whether or not the window icon should be hidden from the taskbar.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS**: Unsupported.\n  #[must_use]\n  pub fn skip_taskbar(mut self, skip: bool) -> Self {\n    self.window_builder = self.window_builder.skip_taskbar(skip);\n    self\n  }\n\n  /// Sets custom name for Windows' window class.  **Windows only**.\n  #[must_use]\n  pub fn window_classname<S: Into<String>>(mut self, classname: S) -> Self {\n    self.window_builder = self.window_builder.window_classname(classname);\n    self\n  }\n\n  /// Sets whether or not the window has shadow.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:**\n  ///   - `false` has no effect on decorated window, shadows are always ON.\n  ///   - `true` will make undecorated window have a 1px white border,\n  ///     and on Windows 11, it will have a rounded corners.\n  /// - **Linux:** Unsupported.\n  #[must_use]\n  pub fn shadow(mut self, enable: bool) -> Self {\n    self.window_builder = self.window_builder.shadow(enable);\n    self\n  }\n\n  /// Sets a parent to the window to be created.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows**: This sets the passed parent as an owner window to the window to be created.\n  ///   From [MSDN owned windows docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows):\n  ///     - An owned window is always above its owner in the z-order.\n  ///     - The system automatically destroys an owned window when its owner is destroyed.\n  ///     - An owned window is hidden when its owner is minimized.\n  /// - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\n  /// - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>\n  pub fn parent(mut self, parent: &WebviewWindow<R>) -> crate::Result<Self> {\n    self.window_builder = self.window_builder.parent(&parent.window)?;\n    Ok(self)\n  }\n\n  /// Set an owner to the window to be created.\n  ///\n  /// From MSDN:\n  /// - An owned window is always above its owner in the z-order.\n  /// - The system automatically destroys an owned window when its owner is destroyed.\n  /// - An owned window is hidden when its owner is minimized.\n  ///\n  /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>\n  #[cfg(windows)]\n  pub fn owner(mut self, owner: &WebviewWindow<R>) -> crate::Result<Self> {\n    self.window_builder = self.window_builder.owner(&owner.window)?;\n    Ok(self)\n  }\n\n  /// Set an owner to the window to be created.\n  ///\n  /// From MSDN:\n  /// - An owned window is always above its owner in the z-order.\n  /// - The system automatically destroys an owned window when its owner is destroyed.\n  /// - An owned window is hidden when its owner is minimized.\n  ///\n  /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>\n  #[cfg(windows)]\n  #[must_use]\n  pub fn owner_raw(mut self, owner: HWND) -> Self {\n    self.window_builder = self.window_builder.owner_raw(owner);\n    self\n  }\n\n  /// Sets a parent to the window to be created.\n  ///\n  /// A child window has the WS_CHILD style and is confined to the client area of its parent window.\n  ///\n  /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>\n  #[cfg(windows)]\n  #[must_use]\n  pub fn parent_raw(mut self, parent: HWND) -> Self {\n    self.window_builder = self.window_builder.parent_raw(parent);\n    self\n  }\n\n  /// Sets a parent to the window to be created.\n  ///\n  /// See <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  pub fn parent_raw(mut self, parent: *mut std::ffi::c_void) -> Self {\n    self.window_builder = self.window_builder.parent_raw(parent);\n    self\n  }\n\n  /// Sets the window to be created transient for parent.\n  ///\n  /// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  pub fn transient_for(mut self, parent: &WebviewWindow<R>) -> crate::Result<Self> {\n    self.window_builder = self.window_builder.transient_for(&parent.window)?;\n    Ok(self)\n  }\n\n  /// Sets the window to be created transient for parent.\n  ///\n  /// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  #[must_use]\n  pub fn transient_for_raw(mut self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self {\n    self.window_builder = self.window_builder.transient_for_raw(parent);\n    self\n  }\n\n  /// Enables or disables drag and drop support.\n  #[cfg(windows)]\n  #[must_use]\n  pub fn drag_and_drop(mut self, enabled: bool) -> Self {\n    self.window_builder = self.window_builder.drag_and_drop(enabled);\n    self\n  }\n\n  /// Sets the [`crate::TitleBarStyle`].\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  pub fn title_bar_style(mut self, style: crate::TitleBarStyle) -> Self {\n    self.window_builder = self.window_builder.title_bar_style(style);\n    self\n  }\n\n  /// Change the position of the window controls on macOS.\n  ///\n  /// Requires titleBarStyle: Overlay and decorations: true.\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  pub fn traffic_light_position<P: Into<Position>>(mut self, position: P) -> Self {\n    self.webview_builder.webview_attributes = self\n      .webview_builder\n      .webview_attributes\n      .traffic_light_position(position.into());\n    self\n  }\n\n  /// Whether to show a link preview when long pressing on links. Available on macOS and iOS only.\n  ///\n  /// Default is true.\n  ///\n  /// See https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / Windows / Android:** Unsupported.\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {\n    self.webview_builder = self.webview_builder.allow_link_preview(allow_link_preview);\n    self\n  }\n\n  /// Hide the window title.\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  pub fn hidden_title(mut self, hidden: bool) -> Self {\n    self.window_builder = self.window_builder.hidden_title(hidden);\n    self\n  }\n\n  /// Defines the window [tabbing identifier] for macOS.\n  ///\n  /// Windows with matching tabbing identifiers will be grouped together.\n  /// If the tabbing identifier is not set, automatic tabbing will be disabled.\n  ///\n  /// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  pub fn tabbing_identifier(mut self, identifier: &str) -> Self {\n    self.window_builder = self.window_builder.tabbing_identifier(identifier);\n    self\n  }\n\n  /// Sets window effects.\n  ///\n  /// Requires the window to be transparent.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>\n  /// - **Linux**: Unsupported\n  pub fn effects(mut self, effects: crate::utils::config::WindowEffectsConfig) -> Self {\n    self.window_builder = self.window_builder.effects(effects);\n    self\n  }\n}\n\n/// Webview attributes.\nimpl<R: Runtime, M: Manager<R>> WebviewWindowBuilder<'_, R, M> {\n  /// Sets whether clicking an inactive window also clicks through to the webview.\n  #[must_use]\n  pub fn accept_first_mouse(mut self, accept: bool) -> Self {\n    self.webview_builder = self.webview_builder.accept_first_mouse(accept);\n    self\n  }\n\n  /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,\n  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.\n  ///\n  /// Since it runs on all top-level document navigations,\n  /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.\n  ///\n  /// This is executed only on the main frame.\n  /// If you only want to run it in all frames, use [Self::initialization_script_for_all_frames] instead.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:** scripts are always added to subframes.\n  /// - **Android:** When [addDocumentStartJavaScript] is not supported,\n  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).\n  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.\n  ///\n  /// # Examples\n  ///\n  /// ```rust\n  /// const INIT_SCRIPT: &str = r#\"\n  ///   if (window.location.origin === 'https://tauri.app') {\n  ///     console.log(\"hello world from js init script\");\n  ///\n  ///     window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };\n  ///   }\n  /// \"#;\n  ///\n  /// fn main() {\n  ///   tauri::Builder::default()\n  ///     .setup(|app| {\n  ///       let webview = tauri::WebviewWindowBuilder::new(app, \"label\", tauri::WebviewUrl::App(\"index.html\".into()))\n  ///         .initialization_script(INIT_SCRIPT)\n  ///         .build()?;\n  ///       Ok(())\n  ///     });\n  /// }\n  /// ```\n  #[must_use]\n  pub fn initialization_script(mut self, script: impl Into<String>) -> Self {\n    self.webview_builder = self.webview_builder.initialization_script(script);\n    self\n  }\n\n  /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,\n  /// but before the HTML document has been parsed and before any other script included by the HTML document is run.\n  ///\n  /// Since it runs on all top-level document navigations and also child frame page navigations,\n  /// it's recommended to check the `window.location` to guard your script from running on unexpected origins.\n  ///\n  /// This is executed on all frames (main frame and also sub frames).\n  /// If you only want to run the script in the main frame, use [Self::initialization_script] instead.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Android:** When [addDocumentStartJavaScript] is not supported,\n  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).\n  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.\n  ///\n  /// # Examples\n  ///\n  /// ```rust\n  /// const INIT_SCRIPT: &str = r#\"\n  ///   if (window.location.origin === 'https://tauri.app') {\n  ///     console.log(\"hello world from js init script\");\n  ///\n  ///     window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };\n  ///   }\n  /// \"#;\n  ///\n  /// fn main() {\n  ///   tauri::Builder::default()\n  ///     .setup(|app| {\n  ///       let webview = tauri::WebviewWindowBuilder::new(app, \"label\", tauri::WebviewUrl::App(\"index.html\".into()))\n  ///         .initialization_script_for_all_frames(INIT_SCRIPT)\n  ///         .build()?;\n  ///       Ok(())\n  ///     });\n  /// }\n  /// ```\n  #[must_use]\n  pub fn initialization_script_for_all_frames(mut self, script: impl Into<String>) -> Self {\n    self.webview_builder = self\n      .webview_builder\n      .initialization_script_for_all_frames(script);\n    self\n  }\n\n  /// Set the user agent for the webview\n  #[must_use]\n  pub fn user_agent(mut self, user_agent: &str) -> Self {\n    self.webview_builder = self.webview_builder.user_agent(user_agent);\n    self\n  }\n\n  /// Set additional arguments for the webview.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS / Linux / Android / iOS**: Unsupported.\n  ///\n  /// ## Warning\n  ///\n  /// By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`\n  /// so if you use this method, you also need to disable these components by yourself if you want.\n  #[must_use]\n  pub fn additional_browser_args(mut self, additional_args: &str) -> Self {\n    self.webview_builder = self\n      .webview_builder\n      .additional_browser_args(additional_args);\n    self\n  }\n\n  /// Data directory for the webview.\n  #[must_use]\n  pub fn data_directory(mut self, data_directory: PathBuf) -> Self {\n    self.webview_builder = self.webview_builder.data_directory(data_directory);\n    self\n  }\n\n  /// Disables the drag and drop handler. This is required to use HTML5 drag and drop APIs on the frontend on Windows.\n  #[must_use]\n  pub fn disable_drag_drop_handler(mut self) -> Self {\n    self.webview_builder = self.webview_builder.disable_drag_drop_handler();\n    self\n  }\n\n  /// Enables clipboard access for the page rendered on **Linux** and **Windows**.\n  ///\n  /// **macOS** doesn't provide such method and is always enabled by default,\n  /// but you still need to add menu item accelerators to use shortcuts.\n  #[must_use]\n  pub fn enable_clipboard_access(mut self) -> Self {\n    self.webview_builder = self.webview_builder.enable_clipboard_access();\n    self\n  }\n\n  /// Enable or disable incognito mode for the WebView..\n  ///\n  ///  ## Platform-specific:\n  ///\n  ///  **Android**: Unsupported.\n  #[must_use]\n  pub fn incognito(mut self, incognito: bool) -> Self {\n    self.webview_builder = self.webview_builder.incognito(incognito);\n    self\n  }\n\n  /// Sets the webview to automatically grow and shrink its size and position when the parent window resizes.\n  #[must_use]\n  pub fn auto_resize(mut self) -> Self {\n    self.webview_builder = self.webview_builder.auto_resize();\n    self\n  }\n\n  /// Set a proxy URL for the WebView for all network requests.\n  ///\n  /// Must be either a `http://` or a `socks5://` URL.\n  #[must_use]\n  pub fn proxy_url(mut self, url: Url) -> Self {\n    self.webview_builder = self.webview_builder.proxy_url(url);\n    self\n  }\n\n  /// Whether the window should be transparent. If this is true, writing colors\n  /// with alpha values different than `1.0` will produce a transparent window.\n  #[cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\"))]\n  #[cfg_attr(\n    docsrs,\n    doc(cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\")))\n  )]\n  #[must_use]\n  pub fn transparent(mut self, transparent: bool) -> Self {\n    #[cfg(desktop)]\n    {\n      self.window_builder = self.window_builder.transparent(transparent);\n    }\n    self.webview_builder = self.webview_builder.transparent(transparent);\n    self\n  }\n\n  /// Whether page zooming by hotkeys and mousewheel should be enabled or not.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.\n  /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `Ctrl/Cmd + [- = +]` hotkeys or mousewheel events,\n  ///   20% in each step, ranging from 20% to 1000%. Requires `core:webview:allow-set-webview-zoom` permission\n  ///\n  /// - **Android / iOS**: Unsupported.\n  #[must_use]\n  pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {\n    self.webview_builder = self.webview_builder.zoom_hotkeys_enabled(enabled);\n    self\n  }\n\n  /// Whether browser extensions can be installed for the webview process\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)\n  /// - **MacOS / Linux / iOS / Android** - Unsupported.\n  #[must_use]\n  pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {\n    self.webview_builder = self.webview_builder.browser_extensions_enabled(enabled);\n    self\n  }\n\n  /// Set the path from which to load extensions from. Extensions stored in this path should be unpacked Chrome extensions on Windows, and compiled `.so` extensions on Linux.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: Browser extensions must first be enabled. See [`browser_extensions_enabled`](Self::browser_extensions_enabled)\n  /// - **MacOS / iOS / Android** - Unsupported.\n  #[must_use]\n  pub fn extensions_path(mut self, path: impl AsRef<Path>) -> Self {\n    self.webview_builder = self.webview_builder.extensions_path(path);\n    self\n  }\n\n  /// Initialize the WebView with a custom data store identifier.\n  /// Can be used as a replacement for data_directory not being available in WKWebView.\n  ///\n  /// - **macOS / iOS**: Available on macOS >= 14 and iOS >= 17\n  /// - **Windows / Linux / Android**: Unsupported.\n  #[must_use]\n  pub fn data_store_identifier(mut self, data_store_identifier: [u8; 16]) -> Self {\n    self.webview_builder = self\n      .webview_builder\n      .data_store_identifier(data_store_identifier);\n    self\n  }\n\n  /// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.\n  ///\n  /// ## Note\n  ///\n  /// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.\n  ///\n  /// ## Warning\n  ///\n  /// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.\n  #[must_use]\n  pub fn use_https_scheme(mut self, enabled: bool) -> Self {\n    self.webview_builder = self.webview_builder.use_https_scheme(enabled);\n    self\n  }\n\n  /// Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default.\n  ///\n  /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - macOS: This will call private functions on **macOS**.\n  /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.\n  /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.\n  #[must_use]\n  pub fn devtools(mut self, enabled: bool) -> Self {\n    self.webview_builder = self.webview_builder.devtools(enabled);\n    self\n  }\n\n  /// Set the window and webview background color.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Android / iOS:** Unsupported for the window layer.\n  /// - **macOS / iOS**: Not implemented for the webview layer.\n  /// - **Windows**:\n  ///   - alpha channel is ignored for the window layer.\n  ///   - On Windows 7, alpha channel is ignored for the webview layer.\n  ///   - On Windows 8 and newer, if alpha channel is not `0`, it will be ignored.\n  #[must_use]\n  pub fn background_color(mut self, color: Color) -> Self {\n    self.window_builder = self.window_builder.background_color(color);\n    self.webview_builder = self.webview_builder.background_color(color);\n    self\n  }\n\n  /// Change the default background throttling behaviour.\n  ///\n  /// By default, browsers use a suspend policy that will throttle timers and even unload\n  /// the whole tab (view) to free resources after roughly 5 minutes when a view became\n  /// minimized or hidden. This will pause all tasks until the documents visibility state\n  /// changes back from hidden to visible by bringing the view back to the foreground.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.\n  /// - **iOS**: Supported since version 17.0+.\n  /// - **macOS**: Supported since version 14.0+.\n  ///\n  /// see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578\n  #[must_use]\n  pub fn background_throttling(mut self, policy: BackgroundThrottlingPolicy) -> Self {\n    self.webview_builder = self.webview_builder.background_throttling(policy);\n    self\n  }\n\n  /// Whether JavaScript should be disabled.\n  #[must_use]\n  pub fn disable_javascript(mut self) -> Self {\n    self.webview_builder = self.webview_builder.disable_javascript();\n    self\n  }\n\n  /// Specifies the native scrollbar style to use with the webview.\n  /// CSS styles that modifier the scrollbar are applied on top of the native appearance configured here.\n  ///\n  /// Defaults to [`ScrollBarStyle::Default`], which is the browser default.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows**:\n  ///   - [`ScrollBarStyle::FluentOverlay`] requires WebView2 Runtime version 125.0.2535.41 or higher,\n  ///     and does nothing on older versions.\n  ///   - This option must be given the same value for all webviews that target the same data directory. Use\n  ///     [`WebviewWindowBuilder::data_directory`] to change data directories if needed.\n  /// - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.\n  #[must_use]\n  pub fn scroll_bar_style(mut self, style: ScrollBarStyle) -> Self {\n    self.webview_builder = self.webview_builder.scroll_bar_style(style);\n    self\n  }\n\n  /// Allows overriding the keyboard accessory view on iOS.\n  /// Returning `None` effectively removes the view.\n  ///\n  /// The closure parameter is the webview instance.\n  ///\n  /// The accessory view is the view that appears above the keyboard when a text input element is focused.\n  /// It usually displays a view with \"Done\", \"Next\" buttons.\n  ///\n  /// # Examples\n  ///\n  /// ```\n  /// fn main() {\n  ///   tauri::Builder::default()\n  ///     .setup(|app| {\n  ///       let mut builder = tauri::WebviewWindowBuilder::new(app, \"label\", tauri::WebviewUrl::App(\"index.html\".into()));\n  ///       #[cfg(target_os = \"ios\")]\n  ///       {\n  ///         window_builder = window_builder.with_input_accessory_view_builder(|_webview| unsafe {\n  ///           let mtm = objc2::MainThreadMarker::new_unchecked();\n  ///           let button = objc2_ui_kit::UIButton::buttonWithType(objc2_ui_kit::UIButtonType(1), mtm);\n  ///           button.setTitle_forState(\n  ///             Some(&objc2_foundation::NSString::from_str(\"Tauri\")),\n  ///             objc2_ui_kit::UIControlState(0),\n  ///           );\n  ///           Some(button.downcast().unwrap())\n  ///         });\n  ///       }\n  ///       let webview = builder.build()?;\n  ///       Ok(())\n  ///     });\n  /// }\n  /// ```\n  ///\n  /// # Stability\n  ///\n  /// This relies on [`objc2_ui_kit`] which does not provide a stable API yet, so it can receive breaking changes in minor releases.\n  #[cfg(target_os = \"ios\")]\n  pub fn with_input_accessory_view_builder<\n    F: Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>\n      + Send\n      + Sync\n      + 'static,\n  >(\n    mut self,\n    builder: F,\n  ) -> Self {\n    self.webview_builder = self\n      .webview_builder\n      .with_input_accessory_view_builder(builder);\n    self\n  }\n\n  /// Set the environment for the webview.\n  /// Useful if you need to share the same environment, for instance when using the [`Self::on_new_window`].\n  #[cfg(all(feature = \"wry\", windows))]\n  pub fn with_environment(\n    mut self,\n    environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,\n  ) -> Self {\n    self.webview_builder = self.webview_builder.with_environment(environment);\n    self\n  }\n\n  /// Creates a new webview sharing the same web process with the provided webview.\n  /// Useful if you need to link a webview to another, for instance when using the [`Self::on_new_window`].\n  #[cfg(all(\n    feature = \"wry\",\n    any(\n      target_os = \"linux\",\n      target_os = \"dragonfly\",\n      target_os = \"freebsd\",\n      target_os = \"netbsd\",\n      target_os = \"openbsd\",\n    )\n  ))]\n  pub fn with_related_view(mut self, related_view: webkit2gtk::WebView) -> Self {\n    self.webview_builder = self.webview_builder.with_related_view(related_view);\n    self\n  }\n\n  /// Set the webview configuration.\n  /// Useful if you need to share the same webview configuration, for instance when using the [`Self::on_new_window`].\n  #[cfg(target_os = \"macos\")]\n  pub fn with_webview_configuration(\n    mut self,\n    webview_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,\n  ) -> Self {\n    self.webview_builder = self\n      .webview_builder\n      .with_webview_configuration(webview_configuration);\n    self\n  }\n\n  /// Set the window features.\n  /// Useful if you need to share the same window features, for instance when using the [`Self::on_new_window`].\n  #[cfg(any(\n    target_os = \"macos\",\n    windows,\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  pub fn window_features(mut self, features: NewWindowFeatures) -> Self {\n    if let Some(position) = features.position() {\n      self.window_builder = self.window_builder.position(position.x, position.y);\n    }\n\n    if let Some(size) = features.size() {\n      self.window_builder = self.window_builder.inner_size(size.width, size.height);\n    }\n\n    #[cfg(target_os = \"macos\")]\n    {\n      self.webview_builder = self\n        .webview_builder\n        .with_webview_configuration(features.opener().target_configuration.clone());\n    }\n\n    #[cfg(all(feature = \"wry\", windows))]\n    {\n      self.webview_builder = self\n        .webview_builder\n        .with_environment(features.opener().environment.clone());\n    }\n\n    #[cfg(all(\n      feature = \"wry\",\n      any(\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\",\n      )\n    ))]\n    {\n      self.webview_builder = self\n        .webview_builder\n        .with_related_view(features.opener().webview.clone());\n    }\n    self\n  }\n}\n\n/// A type that wraps a [`Window`] together with a [`Webview`].\n#[default_runtime(crate::Wry, wry)]\n#[derive(Debug)]\npub struct WebviewWindow<R: Runtime> {\n  pub(crate) window: Window<R>,\n  pub(crate) webview: Webview<R>,\n}\n\nimpl<R: Runtime> AsRef<Webview<R>> for WebviewWindow<R> {\n  fn as_ref(&self) -> &Webview<R> {\n    &self.webview\n  }\n}\n\nimpl<R: Runtime> Clone for WebviewWindow<R> {\n  fn clone(&self) -> Self {\n    Self {\n      window: self.window.clone(),\n      webview: self.webview.clone(),\n    }\n  }\n}\n\nimpl<R: Runtime> Eq for WebviewWindow<R> {}\nimpl<R: Runtime> PartialEq for WebviewWindow<R> {\n  /// Only use the [`Webview`]'s label to compare equality.\n  fn eq(&self, other: &Self) -> bool {\n    self.webview.eq(&other.webview)\n  }\n}\n\nimpl<R: Runtime> raw_window_handle::HasWindowHandle for WebviewWindow<R> {\n  fn window_handle(\n    &self,\n  ) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {\n    Ok(unsafe {\n      raw_window_handle::WindowHandle::borrow_raw(self.window.window_handle()?.as_raw())\n    })\n  }\n}\n\nimpl<R: Runtime> raw_window_handle::HasDisplayHandle for WebviewWindow<R> {\n  fn display_handle(\n    &self,\n  ) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {\n    self.webview.app_handle.display_handle()\n  }\n}\n\nimpl<'de, R: Runtime> CommandArg<'de, R> for WebviewWindow<R> {\n  /// Grabs the [`Window`] from the [`CommandItem`]. This will never fail.\n  fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {\n    let webview = command.message.webview();\n    let window = webview.window();\n    if window.is_webview_window() {\n      return Ok(Self { window, webview });\n    }\n\n    Err(InvokeError::from(\"current webview is not a WebviewWindow\"))\n  }\n}\n\n/// Base webview window functions.\nimpl<R: Runtime> WebviewWindow<R> {\n  /// Initializes a [`WebviewWindowBuilder`] with the given window label and webview URL.\n  ///\n  /// Data URLs are only supported with the `webview-data-url` feature flag.\n  pub fn builder<M: Manager<R>, L: Into<String>>(\n    manager: &M,\n    label: L,\n    url: WebviewUrl,\n  ) -> WebviewWindowBuilder<'_, R, M> {\n    WebviewWindowBuilder::new(manager, label, url)\n  }\n\n  /// Runs the given closure on the main thread.\n  pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {\n    self.webview.run_on_main_thread(f)\n  }\n\n  /// The webview label.\n  pub fn label(&self) -> &str {\n    self.webview.label()\n  }\n\n  /// Registers a window event listener.\n  pub fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) {\n    self.window.on_window_event(f);\n  }\n\n  /// Registers a webview event listener.\n  pub fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) {\n    self.webview.on_webview_event(f);\n  }\n\n  /// Resolves the given command scope for this webview on the currently loaded URL.\n  ///\n  /// If the command is not allowed, returns None.\n  ///\n  /// If the scope cannot be deserialized to the given type, an error is returned.\n  ///\n  /// In a command context this can be directly resolved from the command arguments via [crate::ipc::CommandScope]:\n  ///\n  /// ```\n  /// use tauri::ipc::CommandScope;\n  ///\n  /// #[derive(Debug, serde::Deserialize)]\n  /// struct ScopeType {\n  ///   some_value: String,\n  /// }\n  /// #[tauri::command]\n  /// fn my_command(scope: CommandScope<ScopeType>) {\n  ///   // check scope\n  /// }\n  /// ```\n  ///\n  /// # Examples\n  ///\n  /// ```\n  /// use tauri::Manager;\n  ///\n  /// #[derive(Debug, serde::Deserialize)]\n  /// struct ScopeType {\n  ///   some_value: String,\n  /// }\n  ///\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let webview = app.get_webview_window(\"main\").unwrap();\n  ///     let scope = webview.resolve_command_scope::<ScopeType>(\"my-plugin\", \"read\");\n  ///     Ok(())\n  ///   });\n  /// ```\n  pub fn resolve_command_scope<T: ScopeObject>(\n    &self,\n    plugin: &str,\n    command: &str,\n  ) -> crate::Result<Option<ResolvedScope<T>>> {\n    self.webview.resolve_command_scope(plugin, command)\n  }\n}\n\n/// Menu APIs\n#[cfg(desktop)]\nimpl<R: Runtime> WebviewWindow<R> {\n  /// Registers a global menu event listener.\n  ///\n  /// Note that this handler is called for any menu event,\n  /// whether it is coming from this window, another window or from the tray icon menu.\n  ///\n  /// Also note that this handler will not be called if\n  /// the window used to register it was closed.\n  ///\n  /// # Examples\n  ///\n  /// ```\n  /// use tauri::menu::{Menu, Submenu, MenuItem};\n  /// use tauri::{WebviewWindowBuilder, WebviewUrl};\n  ///\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let handle = app.handle();\n  ///     let save_menu_item = MenuItem::new(handle, \"Save\", true, None::<&str>)?;\n  ///     let menu = Menu::with_items(handle, &[\n  ///       &Submenu::with_items(handle, \"File\", true, &[\n  ///         &save_menu_item,\n  ///       ])?,\n  ///     ])?;\n  ///     let webview_window = WebviewWindowBuilder::new(app, \"editor\", WebviewUrl::default())\n  ///       .menu(menu)\n  ///       .build()\n  ///       .unwrap();\n  ///\n  ///     webview_window.on_menu_event(move |window, event| {\n  ///       if event.id == save_menu_item.id() {\n  ///           // save menu item\n  ///       }\n  ///     });\n  ///\n  ///     Ok(())\n  ///   });\n  /// ```\n  pub fn on_menu_event<F: Fn(&crate::Window<R>, crate::menu::MenuEvent) + Send + Sync + 'static>(\n    &self,\n    f: F,\n  ) {\n    self.window.on_menu_event(f)\n  }\n\n  /// Returns this window menu.\n  pub fn menu(&self) -> Option<Menu<R>> {\n    self.window.menu()\n  }\n\n  /// Sets the window menu and returns the previous one.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS:** Unsupported. The menu on macOS is app-wide and not specific to one\n  ///   window, if you need to set it, use [`AppHandle::set_menu`] instead.\n  #[cfg_attr(target_os = \"macos\", allow(unused_variables))]\n  pub fn set_menu(&self, menu: Menu<R>) -> crate::Result<Option<Menu<R>>> {\n    self.window.set_menu(menu)\n  }\n\n  /// Removes the window menu and returns it.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS:** Unsupported. The menu on macOS is app-wide and not specific to one\n  ///   window, if you need to remove it, use [`AppHandle::remove_menu`] instead.\n  pub fn remove_menu(&self) -> crate::Result<Option<Menu<R>>> {\n    self.window.remove_menu()\n  }\n\n  /// Hides the window menu.\n  pub fn hide_menu(&self) -> crate::Result<()> {\n    self.window.hide_menu()\n  }\n\n  /// Shows the window menu.\n  pub fn show_menu(&self) -> crate::Result<()> {\n    self.window.show_menu()\n  }\n\n  /// Shows the window menu.\n  pub fn is_menu_visible(&self) -> crate::Result<bool> {\n    self.window.is_menu_visible()\n  }\n\n  /// Shows the specified menu as a context menu at the cursor position.\n  pub fn popup_menu<M: ContextMenu>(&self, menu: &M) -> crate::Result<()> {\n    self.window.popup_menu(menu)\n  }\n\n  /// Shows the specified menu as a context menu at the specified position.\n  ///\n  /// The position is relative to the window's top-left corner.\n  pub fn popup_menu_at<M: ContextMenu, P: Into<Position>>(\n    &self,\n    menu: &M,\n    position: P,\n  ) -> crate::Result<()> {\n    self.window.popup_menu_at(menu, position)\n  }\n}\n\n/// Window getters.\nimpl<R: Runtime> WebviewWindow<R> {\n  /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.\n  pub fn scale_factor(&self) -> crate::Result<f64> {\n    self.window.scale_factor()\n  }\n\n  /// Returns the position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop.\n  pub fn inner_position(&self) -> crate::Result<PhysicalPosition<i32>> {\n    self.window.inner_position()\n  }\n\n  /// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop.\n  pub fn outer_position(&self) -> crate::Result<PhysicalPosition<i32>> {\n    self.window.outer_position()\n  }\n\n  /// Returns the physical size of the window's client area.\n  ///\n  /// The client area is the content of the window, excluding the title bar and borders.\n  pub fn inner_size(&self) -> crate::Result<PhysicalSize<u32>> {\n    self.window.inner_size()\n  }\n\n  /// Returns the physical size of the entire window.\n  ///\n  /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead.\n  pub fn outer_size(&self) -> crate::Result<PhysicalSize<u32>> {\n    self.window.outer_size()\n  }\n\n  /// Gets the window's current fullscreen state.\n  pub fn is_fullscreen(&self) -> crate::Result<bool> {\n    self.window.is_fullscreen()\n  }\n\n  /// Gets the window's current minimized state.\n  pub fn is_minimized(&self) -> crate::Result<bool> {\n    self.window.is_minimized()\n  }\n\n  /// Gets the window's current maximized state.\n  pub fn is_maximized(&self) -> crate::Result<bool> {\n    self.window.is_maximized()\n  }\n\n  /// Gets the window's current focus state.\n  pub fn is_focused(&self) -> crate::Result<bool> {\n    self.window.is_focused()\n  }\n\n  /// Gets the window's current decoration state.\n  pub fn is_decorated(&self) -> crate::Result<bool> {\n    self.window.is_decorated()\n  }\n\n  /// Gets the window's current resizable state.\n  pub fn is_resizable(&self) -> crate::Result<bool> {\n    self.window.is_resizable()\n  }\n\n  /// Whether the window is enabled or disabled.\n  pub fn is_enabled(&self) -> crate::Result<bool> {\n    self.webview.window().is_enabled()\n  }\n\n  /// Determines if this window should always be on top of other windows.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  pub fn is_always_on_top(&self) -> crate::Result<bool> {\n    self.webview.window().is_always_on_top()\n  }\n\n  /// Gets the window's native maximize button state\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  pub fn is_maximizable(&self) -> crate::Result<bool> {\n    self.window.is_maximizable()\n  }\n\n  /// Gets the window's native minimize button state\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  pub fn is_minimizable(&self) -> crate::Result<bool> {\n    self.window.is_minimizable()\n  }\n\n  /// Gets the window's native close button state\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  pub fn is_closable(&self) -> crate::Result<bool> {\n    self.window.is_closable()\n  }\n\n  /// Gets the window's current visibility state.\n  pub fn is_visible(&self) -> crate::Result<bool> {\n    self.window.is_visible()\n  }\n\n  /// Gets the window's current title.\n  pub fn title(&self) -> crate::Result<String> {\n    self.window.title()\n  }\n\n  /// Returns the monitor on which the window currently resides.\n  ///\n  /// Returns None if current monitor can't be detected.\n  pub fn current_monitor(&self) -> crate::Result<Option<Monitor>> {\n    self.window.current_monitor()\n  }\n\n  /// Returns the primary monitor of the system.\n  ///\n  /// Returns None if it can't identify any monitor as a primary one.\n  pub fn primary_monitor(&self) -> crate::Result<Option<Monitor>> {\n    self.window.primary_monitor()\n  }\n\n  /// Returns the monitor that contains the given point.\n  pub fn monitor_from_point(&self, x: f64, y: f64) -> crate::Result<Option<Monitor>> {\n    self.window.monitor_from_point(x, y)\n  }\n\n  /// Returns the list of all the monitors available on the system.\n  pub fn available_monitors(&self) -> crate::Result<Vec<Monitor>> {\n    self.window.available_monitors()\n  }\n\n  /// Returns the native handle that is used by this window.\n  #[cfg(target_os = \"macos\")]\n  pub fn ns_window(&self) -> crate::Result<*mut std::ffi::c_void> {\n    self.window.ns_window()\n  }\n\n  /// Returns the pointer to the content view of this window.\n  #[cfg(target_os = \"macos\")]\n  pub fn ns_view(&self) -> crate::Result<*mut std::ffi::c_void> {\n    self.window.ns_view()\n  }\n\n  /// Returns the native handle that is used by this window.\n  #[cfg(windows)]\n  pub fn hwnd(&self) -> crate::Result<HWND> {\n    self.window.hwnd()\n  }\n\n  /// Returns the `ApplicationWindow` from gtk crate that is used by this window.\n  ///\n  /// Note that this type can only be used on the main thread.\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  pub fn gtk_window(&self) -> crate::Result<gtk::ApplicationWindow> {\n    self.window.gtk_window()\n  }\n\n  /// Returns the vertical [`gtk::Box`] that is added by default as the sole child of this window.\n  ///\n  /// Note that this type can only be used on the main thread.\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  pub fn default_vbox(&self) -> crate::Result<gtk::Box> {\n    self.window.default_vbox()\n  }\n\n  /// Returns the current window theme.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS**: Only supported on macOS 10.14+.\n  pub fn theme(&self) -> crate::Result<crate::Theme> {\n    self.window.theme()\n  }\n}\n\n/// Desktop window getters.\n#[cfg(desktop)]\nimpl<R: Runtime> WebviewWindow<R> {\n  /// Get the cursor position relative to the top-left hand corner of the desktop.\n  ///\n  /// Note that the top-left hand corner of the desktop is not necessarily the same as the screen.\n  /// If the user uses a desktop with multiple monitors,\n  /// the top-left hand corner of the desktop is the top-left hand corner of the main monitor on Windows and macOS\n  /// or the top-left of the leftmost monitor on X11.\n  ///\n  /// The coordinates can be negative if the top-left hand corner of the window is outside of the visible screen region.\n  pub fn cursor_position(&self) -> crate::Result<PhysicalPosition<f64>> {\n    self.webview.cursor_position()\n  }\n}\n\n/// Desktop window setters and actions.\n#[cfg(desktop)]\nimpl<R: Runtime> WebviewWindow<R> {\n  /// Centers the window.\n  pub fn center(&self) -> crate::Result<()> {\n    self.window.center()\n  }\n\n  /// Requests user attention to the window, this has no effect if the application\n  /// is already focused. How requesting for user attention manifests is platform dependent,\n  /// see `UserAttentionType` for details.\n  ///\n  /// Providing `None` will unset the request for user attention. Unsetting the request for\n  /// user attention might not be done automatically by the WM when the window receives input.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** `None` has no effect.\n  /// - **Linux:** Urgency levels have the same effect.\n  pub fn request_user_attention(\n    &self,\n    request_type: Option<UserAttentionType>,\n  ) -> crate::Result<()> {\n    self.window.request_user_attention(request_type)\n  }\n\n  /// Determines if this window should be resizable.\n  /// When resizable is set to false, native window's maximize button is automatically disabled.\n  pub fn set_resizable(&self, resizable: bool) -> crate::Result<()> {\n    self.window.set_resizable(resizable)\n  }\n\n  /// Enable or disable the window.\n  pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {\n    self.webview.window().set_enabled(enabled)\n  }\n\n  /// Determines if this window's native maximize button should be enabled.\n  /// If resizable is set to false, this setting is ignored.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Disables the \"zoom\" button in the window titlebar, which is also used to enter fullscreen mode.\n  /// - **Linux / iOS / Android:** Unsupported.\n  pub fn set_maximizable(&self, maximizable: bool) -> crate::Result<()> {\n    self.window.set_maximizable(maximizable)\n  }\n\n  /// Determines if this window's native minimize button should be enabled.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  pub fn set_minimizable(&self, minimizable: bool) -> crate::Result<()> {\n    self.window.set_minimizable(minimizable)\n  }\n\n  /// Determines if this window's native close button should be enabled.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** \"GTK+ will do its best to convince the window manager not to show a close button.\n  ///   Depending on the system, this function may not have any effect when called on a window that is already visible\"\n  /// - **iOS / Android:** Unsupported.\n  pub fn set_closable(&self, closable: bool) -> crate::Result<()> {\n    self.window.set_closable(closable)\n  }\n\n  /// Set this window's title.\n  pub fn set_title(&self, title: &str) -> crate::Result<()> {\n    self.window.set_title(title)\n  }\n\n  /// Maximizes this window.\n  pub fn maximize(&self) -> crate::Result<()> {\n    self.window.maximize()\n  }\n\n  /// Un-maximizes this window.\n  pub fn unmaximize(&self) -> crate::Result<()> {\n    self.window.unmaximize()\n  }\n\n  /// Minimizes this window.\n  pub fn minimize(&self) -> crate::Result<()> {\n    self.window.minimize()\n  }\n\n  /// Un-minimizes this window.\n  pub fn unminimize(&self) -> crate::Result<()> {\n    self.window.unminimize()\n  }\n\n  /// Show this window.\n  pub fn show(&self) -> crate::Result<()> {\n    self.window.show()\n  }\n\n  /// Hide this window.\n  pub fn hide(&self) -> crate::Result<()> {\n    self.window.hide()\n  }\n\n  /// Closes this window. It emits [`crate::RunEvent::CloseRequested`] first like a user-initiated close request so you can intercept it.\n  pub fn close(&self) -> crate::Result<()> {\n    self.window.close()\n  }\n\n  /// Destroys this window. Similar to [`Self::close`] but does not emit any events and force close the window instead.\n  pub fn destroy(&self) -> crate::Result<()> {\n    self.window.destroy()\n  }\n\n  /// Determines if this window should be [decorated].\n  ///\n  /// [decorated]: https://en.wikipedia.org/wiki/Window_(computing)#Window_decoration\n  pub fn set_decorations(&self, decorations: bool) -> crate::Result<()> {\n    self.window.set_decorations(decorations)\n  }\n\n  /// Determines if this window should have shadow.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:**\n  ///   - `false` has no effect on decorated window, shadow are always ON.\n  ///   - `true` will make undecorated window have a 1px white border,\n  ///     and on Windows 11, it will have a rounded corners.\n  /// - **Linux:** Unsupported.\n  pub fn set_shadow(&self, enable: bool) -> crate::Result<()> {\n    self.window.set_shadow(enable)\n  }\n\n  /// Sets window effects, pass [`None`] to clear any effects applied if possible.\n  ///\n  /// Requires the window to be transparent.\n  ///\n  /// See [`crate::window::EffectsBuilder`] for a convenient builder for [`crate::utils::config::WindowEffectsConfig`].\n  ///\n  ///\n  /// ```rust,no_run\n  /// use tauri::{Manager, window::{Color, Effect, EffectState, EffectsBuilder}};\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let webview_window = app.get_webview_window(\"main\").unwrap();\n  ///     webview_window.set_effects(\n  ///       EffectsBuilder::new()\n  ///         .effect(Effect::Popover)\n  ///         .state(EffectState::Active)\n  ///         .radius(5.)\n  ///         .color(Color(0, 0, 0, 255))\n  ///         .build(),\n  ///     )?;\n  ///     Ok(())\n  ///   });\n  /// ```\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>\n  /// - **Linux**: Unsupported\n  pub fn set_effects<E: Into<Option<crate::utils::config::WindowEffectsConfig>>>(\n    &self,\n    effects: E,\n  ) -> crate::Result<()> {\n    self.window.set_effects(effects)\n  }\n\n  /// Determines if this window should always be below other windows.\n  pub fn set_always_on_bottom(&self, always_on_bottom: bool) -> crate::Result<()> {\n    self.window.set_always_on_bottom(always_on_bottom)\n  }\n\n  /// Determines if this window should always be on top of other windows.\n  pub fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> {\n    self.window.set_always_on_top(always_on_top)\n  }\n\n  /// Sets whether the window should be visible on all workspaces or virtual desktops.\n  pub fn set_visible_on_all_workspaces(\n    &self,\n    visible_on_all_workspaces: bool,\n  ) -> crate::Result<()> {\n    self\n      .window\n      .set_visible_on_all_workspaces(visible_on_all_workspaces)\n  }\n\n  /// Prevents the window contents from being captured by other apps.\n  pub fn set_content_protected(&self, protected: bool) -> crate::Result<()> {\n    self.window.set_content_protected(protected)\n  }\n\n  /// Resizes this window.\n  pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> {\n    self.window.set_size(size.into())\n  }\n\n  /// Sets this window's minimum inner size.\n  pub fn set_min_size<S: Into<Size>>(&self, size: Option<S>) -> crate::Result<()> {\n    self.window.set_min_size(size.map(|s| s.into()))\n  }\n\n  /// Sets this window's maximum inner size.\n  pub fn set_max_size<S: Into<Size>>(&self, size: Option<S>) -> crate::Result<()> {\n    self.window.set_max_size(size.map(|s| s.into()))\n  }\n\n  /// Sets this window's minimum inner width.\n  pub fn set_size_constraints(\n    &self,\n    constraints: tauri_runtime::window::WindowSizeConstraints,\n  ) -> crate::Result<()> {\n    self.window.set_size_constraints(constraints)\n  }\n\n  /// Sets this window's position.\n  pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {\n    self.window.set_position(position)\n  }\n\n  /// Determines if this window should be fullscreen.\n  pub fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {\n    self.window.set_fullscreen(fullscreen)\n  }\n\n  /// Toggles a fullscreen mode that doesn't require a new macOS space.\n  /// Returns a boolean indicating whether the transition was successful (this won't work if the window was already in the native fullscreen).\n  ///\n  /// This is how fullscreen used to work on macOS in versions before Lion.\n  /// And allows the user to have a fullscreen window without using another space or taking control over the entire monitor.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Uses native simple fullscreen mode.\n  /// - **Other platforms:** Falls back to [`Self::set_fullscreen`].\n  pub fn set_simple_fullscreen(&self, enable: bool) -> crate::Result<()> {\n    self.window.set_simple_fullscreen(enable)\n  }\n\n  /// Bring the window to front and focus.\n  pub fn set_focus(&self) -> crate::Result<()> {\n    self.window.set_focus()\n  }\n\n  /// Sets whether the window can be focused.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS**: If the window is already focused, it is not possible to unfocus it after calling `set_focusable(false)`.\n  ///   In this case, you might consider calling [`Window::set_focus`] but it will move the window to the back i.e. at the bottom in terms of z-order.\n  pub fn set_focusable(&self, focusable: bool) -> crate::Result<()> {\n    self.window.set_focusable(focusable)\n  }\n\n  /// Sets this window' icon.\n  pub fn set_icon(&self, icon: Image<'_>) -> crate::Result<()> {\n    self.window.set_icon(icon)\n  }\n\n  /// Sets the window background color.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **iOS / Android:** Unsupported.\n  /// - **macOS**: Not implemented for the webview layer..\n  /// - **Windows**:\n  ///   - alpha channel is ignored for the window layer.\n  ///   - On Windows 7, transparency is not supported and the alpha value will be ignored for the webview layer..\n  ///   - On Windows 8 and newer: translucent colors are not supported so any alpha value other than `0` will be replaced by `255` for the webview layer.\n  pub fn set_background_color(&self, color: Option<Color>) -> crate::Result<()> {\n    self.window.set_background_color(color)?;\n    self.webview.set_background_color(color)\n  }\n\n  /// Whether to hide the window icon from the taskbar or not.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Unsupported.\n  pub fn set_skip_taskbar(&self, skip: bool) -> crate::Result<()> {\n    self.window.set_skip_taskbar(skip)\n  }\n\n  /// Grabs the cursor, preventing it from leaving the window.\n  ///\n  /// There's no guarantee that the cursor will be hidden. You should\n  /// hide it by yourself if you want so.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Unsupported.\n  /// - **macOS:** This locks the cursor in a fixed location, which looks visually awkward.\n  pub fn set_cursor_grab(&self, grab: bool) -> crate::Result<()> {\n    self.window.set_cursor_grab(grab)\n  }\n\n  /// Modifies the cursor's visibility.\n  ///\n  /// If `false`, this will hide the cursor. If `true`, this will show the cursor.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:** The cursor is only hidden within the confines of the window.\n  /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is\n  ///   outside of the window.\n  pub fn set_cursor_visible(&self, visible: bool) -> crate::Result<()> {\n    self.window.set_cursor_visible(visible)\n  }\n\n  /// Modifies the cursor icon of the window.\n  pub fn set_cursor_icon(&self, icon: CursorIcon) -> crate::Result<()> {\n    self.window.set_cursor_icon(icon)\n  }\n\n  /// Changes the position of the cursor in window coordinates.\n  pub fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {\n    self.window.set_cursor_position(position)\n  }\n\n  /// Ignores the window cursor events.\n  pub fn set_ignore_cursor_events(&self, ignore: bool) -> crate::Result<()> {\n    self.window.set_ignore_cursor_events(ignore)\n  }\n\n  /// Starts dragging the window.\n  pub fn start_dragging(&self) -> crate::Result<()> {\n    self.window.start_dragging()\n  }\n\n  /// Sets the overlay icon on the taskbar **Windows only**. Using `None` will remove the icon\n  ///\n  /// The overlay icon can be unique for each window.\n  #[cfg(target_os = \"windows\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"windows\")))]\n  pub fn set_overlay_icon(&self, icon: Option<Image<'_>>) -> crate::Result<()> {\n    self.window.set_overlay_icon(icon)\n  }\n\n  /// Sets the taskbar badge count. Using `0` or `None` will remove the badge\n  ///\n  /// ## Platform-specific\n  /// - **Windows:** Unsupported, use [`WebviewWindow::set_overlay_icon`] instead.\n  /// - **iOS:** iOS expects i32, the value will be clamped to i32::MIN, i32::MAX.\n  /// - **Android:** Unsupported.\n  pub fn set_badge_count(&self, count: Option<i64>) -> crate::Result<()> {\n    self.window.set_badge_count(count)\n  }\n\n  /// Sets the taskbar badge label **macOS only**. Using `None` will remove the badge\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  pub fn set_badge_label(&self, label: Option<String>) -> crate::Result<()> {\n    self.window.set_badge_label(label)\n  }\n\n  /// Sets the taskbar progress state.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / macOS**: Progress bar is app-wide and not specific to this window.\n  /// - **Linux**: Only supported desktop environments with `libunity` (e.g. GNOME).\n  /// - **iOS / Android:** Unsupported.\n  pub fn set_progress_bar(\n    &self,\n    progress_state: crate::window::ProgressBarState,\n  ) -> crate::Result<()> {\n    self.window.set_progress_bar(progress_state)\n  }\n\n  /// Sets the title bar style. **macOS only**.\n  pub fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> crate::Result<()> {\n    self.window.set_title_bar_style(style)\n  }\n\n  /// Sets the theme for this window.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / macOS**: Theme is app-wide and not specific to this window.\n  /// - **iOS / Android:** Unsupported.\n  pub fn set_theme(&self, theme: Option<tauri_utils::Theme>) -> crate::Result<()> {\n    self.window.set_theme(theme)\n  }\n}\n\n/// Desktop webview setters and actions.\n#[cfg(desktop)]\nimpl<R: Runtime> WebviewWindow<R> {\n  /// Opens the dialog to prints the contents of the webview.\n  /// Currently only supported on macOS on `wry`.\n  /// `window.print()` works on all platforms.\n  pub fn print(&self) -> crate::Result<()> {\n    self.webview.print()\n  }\n}\n\n/// Webview APIs.\nimpl<R: Runtime> WebviewWindow<R> {\n  /// Executes a closure, providing it with the webview handle that is specific to the current platform.\n  ///\n  /// The closure is executed on the main thread.\n  ///\n  /// Note that `webview2-com`, `webkit2gtk`, `objc2_web_kit` and similar crates may be updated in minor releases of Tauri.\n  /// Therefore it's recommended to pin Tauri to at least a minor version when you're using `with_webview`.\n  ///\n  /// # Examples\n  ///\n  /// ```rust,no_run\n  /// use tauri::Manager;\n  ///\n  /// fn main() {\n  ///   tauri::Builder::default()\n  ///     .setup(|app| {\n  ///       let main_webview = app.get_webview_window(\"main\").unwrap();\n  ///       main_webview.with_webview(|webview| {\n  ///         #[cfg(target_os = \"linux\")]\n  ///         {\n  ///           // see <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/struct.WebView.html>\n  ///           // and <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/trait.WebViewExt.html>\n  ///           use webkit2gtk::WebViewExt;\n  ///           webview.inner().set_zoom_level(4.);\n  ///         }\n  ///\n  ///         #[cfg(windows)]\n  ///         unsafe {\n  ///           // see <https://docs.rs/webview2-com/0.19.1/webview2_com/Microsoft/Web/WebView2/Win32/struct.ICoreWebView2Controller.html>\n  ///           webview.controller().SetZoomFactor(4.).unwrap();\n  ///         }\n  ///\n  ///         #[cfg(target_os = \"macos\")]\n  ///         unsafe {\n  ///           let view: &objc2_web_kit::WKWebView = &*webview.inner().cast();\n  ///           let controller: &objc2_web_kit::WKUserContentController = &*webview.controller().cast();\n  ///           let window: &objc2_app_kit::NSWindow = &*webview.ns_window().cast();\n  ///\n  ///           view.setPageZoom(4.);\n  ///           controller.removeAllUserScripts();\n  ///           let bg_color = objc2_app_kit::NSColor::colorWithDeviceRed_green_blue_alpha(0.5, 0.2, 0.4, 1.);\n  ///           window.setBackgroundColor(Some(&bg_color));\n  ///         }\n  ///\n  ///         #[cfg(target_os = \"android\")]\n  ///         {\n  ///           use jni::objects::JValue;\n  ///           webview.jni_handle().exec(|env, _, webview| {\n  ///             env.call_method(webview, \"zoomBy\", \"(F)V\", &[JValue::Float(4.)]).unwrap();\n  ///           })\n  ///         }\n  ///       });\n  ///       Ok(())\n  ///   });\n  /// }\n  /// ```\n  #[allow(clippy::needless_doctest_main)] // To avoid a large diff\n  #[cfg(feature = \"wry\")]\n  #[cfg_attr(docsrs, doc(feature = \"wry\"))]\n  pub fn with_webview<F: FnOnce(crate::webview::PlatformWebview) + Send + 'static>(\n    &self,\n    f: F,\n  ) -> crate::Result<()> {\n    self.webview.with_webview(f)\n  }\n\n  /// Returns the current url of the webview.\n  pub fn url(&self) -> crate::Result<Url> {\n    self.webview.url()\n  }\n\n  /// Navigates the webview to the defined url.\n  pub fn navigate(&self, url: Url) -> crate::Result<()> {\n    self.webview.navigate(url)\n  }\n\n  /// Reloads the current page.\n  pub fn reload(&self) -> crate::Result<()> {\n    self.webview.reload()\n  }\n\n  /// Handles this window receiving an [`crate::webview::InvokeRequest`].\n  pub fn on_message(\n    self,\n    request: crate::webview::InvokeRequest,\n    responder: Box<OwnedInvokeResponder<R>>,\n  ) {\n    self.webview.on_message(request, responder)\n  }\n\n  /// Evaluates JavaScript on this window.\n  pub fn eval(&self, js: impl Into<String>) -> crate::Result<()> {\n    self.webview.eval(js)\n  }\n\n  /// Opens the developer tools window (Web Inspector).\n  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Only supported on macOS 10.15+.\n  ///   This is a private API on macOS, so you cannot use this if your application will be published on the App Store.\n  ///\n  /// # Examples\n  ///\n  /// ```rust,no_run\n  /// use tauri::Manager;\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     #[cfg(debug_assertions)]\n  ///     app.get_webview_window(\"main\").unwrap().open_devtools();\n  ///     Ok(())\n  ///   });\n  /// ```\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = \"devtools\"))))]\n  pub fn open_devtools(&self) {\n    self.webview.open_devtools();\n  }\n\n  /// Closes the developer tools window (Web Inspector).\n  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Only supported on macOS 10.15+.\n  ///   This is a private API on macOS, so you cannot use this if your application will be published on the App Store.\n  /// - **Windows:** Unsupported.\n  ///\n  /// # Examples\n  ///\n  /// ```rust,no_run\n  /// use tauri::Manager;\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     #[cfg(debug_assertions)]\n  ///     {\n  ///       let webview = app.get_webview_window(\"main\").unwrap();\n  ///       webview.open_devtools();\n  ///       std::thread::spawn(move || {\n  ///         std::thread::sleep(std::time::Duration::from_secs(10));\n  ///         webview.close_devtools();\n  ///       });\n  ///     }\n  ///     Ok(())\n  ///   });\n  /// ```\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = \"devtools\"))))]\n  pub fn close_devtools(&self) {\n    self.webview.close_devtools();\n  }\n\n  /// Checks if the developer tools window (Web Inspector) is opened.\n  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Only supported on macOS 10.15+.\n  ///   This is a private API on macOS, so you cannot use this if your application will be published on the App Store.\n  /// - **Windows:** Unsupported.\n  ///\n  /// # Examples\n  ///\n  /// ```rust,no_run\n  /// use tauri::Manager;\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     #[cfg(debug_assertions)]\n  ///     {\n  ///       let webview = app.get_webview_window(\"main\").unwrap();\n  ///       if !webview.is_devtools_open() {\n  ///         webview.open_devtools();\n  ///       }\n  ///     }\n  ///     Ok(())\n  ///   });\n  /// ```\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(debug_assertions, feature = \"devtools\"))))]\n  pub fn is_devtools_open(&self) -> bool {\n    self.webview.is_devtools_open()\n  }\n\n  /// Set the webview zoom level\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Android**: Not supported.\n  /// - **macOS**: available on macOS 11+ only.\n  /// - **iOS**: available on iOS 14+ only.\n  pub fn set_zoom(&self, scale_factor: f64) -> crate::Result<()> {\n    self.webview.set_zoom(scale_factor)\n  }\n\n  /// Clear all browsing data for this webview window.\n  pub fn clear_all_browsing_data(&self) -> crate::Result<()> {\n    self.webview.clear_all_browsing_data()\n  }\n\n  /// Returns all cookies in the runtime's cookie store including HTTP-only and secure cookies.\n  ///\n  /// Note that cookies will only be returned for URLs with an http or https scheme.\n  /// Cookies set through javascript for local files\n  /// (such as those served from the tauri://) protocol are not currently supported.\n  ///\n  /// # Stability\n  ///\n  /// See [Self::cookies].\n  ///\n  /// # Known issues\n  ///\n  /// See [Self::cookies].\n  pub fn cookies_for_url(&self, url: Url) -> crate::Result<Vec<Cookie<'static>>> {\n    self.webview.cookies_for_url(url)\n  }\n\n  /// Returns all cookies in the runtime's cookie store for all URLs including HTTP-only and secure cookies.\n  ///\n  /// Note that cookies will only be returned for URLs with an http or https scheme.\n  /// Cookies set through javascript for local files\n  /// (such as those served from the tauri://) protocol are not currently supported.\n  ///\n  /// # Stability\n  ///\n  /// The return value of this function leverages [`tauri_runtime::Cookie`] which re-exports the cookie crate.\n  /// This dependency might receive updates in minor Tauri releases.\n  ///\n  /// # Known issues\n  ///\n  /// On Windows, this function deadlocks when used in a synchronous command or event handlers, see [the Webview2 issue].\n  /// You should use `async` commands and separate threads when reading cookies.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Android**: Unsupported, always returns an empty [`Vec`].\n  ///\n  /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583\n  pub fn cookies(&self) -> crate::Result<Vec<Cookie<'static>>> {\n    self.webview.cookies()\n  }\n\n  /// Set a cookie for the webview.\n  ///\n  /// # Stability\n  ///\n  /// See [Self::cookies].\n  pub fn set_cookie(&self, cookie: Cookie<'_>) -> crate::Result<()> {\n    self.webview.set_cookie(cookie)\n  }\n\n  /// Delete a cookie for the webview.\n  ///\n  /// # Stability\n  ///\n  /// See [Self::cookies].\n  pub fn delete_cookie(&self, cookie: Cookie<'_>) -> crate::Result<()> {\n    self.webview.delete_cookie(cookie)\n  }\n}\n\nimpl<R: Runtime> Listener<R> for WebviewWindow<R> {\n  /// Listen to an event on this webview window.\n  ///\n  /// # Examples\n  ///\n  /// ```\n  /// use tauri::{Manager, Listener};\n  ///\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let webview_window = app.get_webview_window(\"main\").unwrap();\n  ///     webview_window.listen(\"component-loaded\", move |event| {\n  ///       println!(\"window just loaded a component\");\n  ///     });\n  ///\n  ///     Ok(())\n  ///   });\n  /// ```\n  fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId\n  where\n    F: Fn(Event) + Send + 'static,\n  {\n    let event = EventName::new(event.into()).unwrap();\n    self.manager().listen(\n      event,\n      EventTarget::WebviewWindow {\n        label: self.label().to_string(),\n      },\n      handler,\n    )\n  }\n\n  /// Listen to an event on this window webview only once.\n  ///\n  /// See [`Self::listen`] for more information.\n  fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId\n  where\n    F: FnOnce(Event) + Send + 'static,\n  {\n    let event = EventName::new(event.into()).unwrap();\n    self.manager().once(\n      event,\n      EventTarget::WebviewWindow {\n        label: self.label().to_string(),\n      },\n      handler,\n    )\n  }\n\n  /// Unlisten to an event on this webview window.\n  ///\n  /// # Examples\n  /// ```\n  /// use tauri::{Manager, Listener};\n  ///\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     let webview_window = app.get_webview_window(\"main\").unwrap();\n  ///     let webview_window_ = webview_window.clone();\n  ///     let handler = webview_window.listen(\"component-loaded\", move |event| {\n  ///       println!(\"webview_window just loaded a component\");\n  ///\n  ///       // we no longer need to listen to the event\n  ///       // we also could have used `webview_window.once` instead\n  ///       webview_window_.unlisten(event.id());\n  ///     });\n  ///\n  ///     // stop listening to the event when you do not need it anymore\n  ///     webview_window.unlisten(handler);\n  ///\n  ///     Ok(())\n  /// });\n  /// ```\n  fn unlisten(&self, id: EventId) {\n    self.manager().unlisten(id)\n  }\n}\n\nimpl<R: Runtime> Emitter<R> for WebviewWindow<R> {}\n\nimpl<R: Runtime> Manager<R> for WebviewWindow<R> {\n  fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {\n    self\n      .webview\n      .resources_table\n      .lock()\n      .expect(\"poisoned window resources table\")\n  }\n}\n\nimpl<R: Runtime> ManagerBase<R> for WebviewWindow<R> {\n  fn manager(&self) -> &AppManager<R> {\n    self.webview.manager()\n  }\n\n  fn manager_owned(&self) -> Arc<AppManager<R>> {\n    self.webview.manager_owned()\n  }\n\n  fn runtime(&self) -> RuntimeOrDispatch<'_, R> {\n    self.webview.runtime()\n  }\n\n  fn managed_app_handle(&self) -> &AppHandle<R> {\n    self.webview.managed_app_handle()\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/window/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The Tauri window types and functions.\n\npub(crate) mod plugin;\n\nuse tauri_runtime::{\n  dpi::{PhysicalPosition, PhysicalRect, PhysicalSize},\n  webview::PendingWebview,\n};\npub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState};\n\n#[cfg(desktop)]\npub use crate::runtime::ProgressBarStatus;\n\nuse crate::{\n  app::AppHandle,\n  event::{Event, EventId, EventTarget},\n  ipc::{CommandArg, CommandItem, InvokeError},\n  manager::{AppManager, EmitPayload},\n  runtime::{\n    monitor::Monitor as RuntimeMonitor,\n    window::{DetachedWindow, PendingWindow, WindowBuilder as _},\n    RuntimeHandle, WindowDispatch,\n  },\n  sealed::{ManagerBase, RuntimeOrDispatch},\n  utils::config::{WindowConfig, WindowEffectsConfig},\n  webview::WebviewBuilder,\n  Emitter, EventLoopMessage, EventName, Listener, Manager, ResourceTable, Runtime, Theme, Webview,\n  WindowEvent,\n};\n#[cfg(desktop)]\nuse crate::{\n  image::Image,\n  menu::{ContextMenu, Menu, MenuId},\n  runtime::{\n    dpi::{Position, Size},\n    UserAttentionType,\n  },\n  CursorIcon,\n};\n\nuse serde::Serialize;\n#[cfg(windows)]\nuse windows::Win32::Foundation::HWND;\n\nuse tauri_macros::default_runtime;\n\nuse std::{\n  fmt,\n  hash::{Hash, Hasher},\n  sync::{Arc, Mutex, MutexGuard},\n};\n\n/// Monitor descriptor.\n#[derive(Debug, Clone, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct Monitor {\n  pub(crate) name: Option<String>,\n  pub(crate) size: PhysicalSize<u32>,\n  pub(crate) position: PhysicalPosition<i32>,\n  pub(crate) work_area: PhysicalRect<i32, u32>,\n  pub(crate) scale_factor: f64,\n}\n\nimpl From<RuntimeMonitor> for Monitor {\n  fn from(monitor: RuntimeMonitor) -> Self {\n    Self {\n      name: monitor.name,\n      size: monitor.size,\n      position: monitor.position,\n      work_area: monitor.work_area,\n      scale_factor: monitor.scale_factor,\n    }\n  }\n}\n\nimpl Monitor {\n  /// Returns a human-readable name of the monitor.\n  /// Returns None if the monitor doesn't exist anymore.\n  pub fn name(&self) -> Option<&String> {\n    self.name.as_ref()\n  }\n\n  /// Returns the monitor's resolution.\n  pub fn size(&self) -> &PhysicalSize<u32> {\n    &self.size\n  }\n\n  /// Returns the top-left corner position of the monitor relative to the larger full screen area.\n  pub fn position(&self) -> &PhysicalPosition<i32> {\n    &self.position\n  }\n\n  /// Returns the monitor's work_area.\n  pub fn work_area(&self) -> &PhysicalRect<i32, u32> {\n    &self.work_area\n  }\n\n  /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.\n  pub fn scale_factor(&self) -> f64 {\n    self.scale_factor\n  }\n}\n\nmacro_rules! unstable_struct {\n    (#[doc = $doc:expr] $($tokens:tt)*) => {\n      #[cfg(feature = \"unstable\")]\n      #[cfg_attr(docsrs, doc(cfg(feature = \"unstable\")))]\n      #[doc = $doc]\n      pub $($tokens)*\n\n      #[cfg(not(feature = \"unstable\"))]\n      pub(crate) $($tokens)*\n    }\n}\n\nunstable_struct!(\n  #[doc = \"A builder for a window managed by Tauri.\"]\n  struct WindowBuilder<'a, R: Runtime, M: Manager<R>> {\n    manager: &'a M,\n    pub(crate) label: String,\n    pub(crate) window_builder:\n      <R::WindowDispatcher as WindowDispatch<EventLoopMessage>>::WindowBuilder,\n    #[cfg(desktop)]\n    pub(crate) menu: Option<Menu<R>>,\n    #[cfg(desktop)]\n    on_menu_event: Option<crate::app::GlobalMenuEventListener<Window<R>>>,\n    window_effects: Option<WindowEffectsConfig>,\n  }\n);\n\nimpl<R: Runtime, M: Manager<R>> fmt::Debug for WindowBuilder<'_, R, M> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"WindowBuilder\")\n      .field(\"label\", &self.label)\n      .field(\"window_builder\", &self.window_builder)\n      .finish()\n  }\n}\n\n#[cfg_attr(not(feature = \"unstable\"), allow(dead_code))]\nimpl<'a, R: Runtime, M: Manager<R>> WindowBuilder<'a, R, M> {\n  /// Initializes a window builder with the given window label.\n  ///\n  /// # Known issues\n  ///\n  /// On Windows, this function deadlocks when used in a synchronous command or event handlers, see [the Webview2 issue].\n  /// You should use `async` commands and separate threads when creating windows.\n  ///\n  /// # Examples\n  ///\n  /// - Create a window in the setup hook:\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\ntauri::Builder::default()\n  .setup(|app| {\n    let window = tauri::window::WindowBuilder::new(app, \"label\")\n      .build()?;\n    Ok(())\n  });\n```\n  \"####\n  )]\n  /// - Create a window in a separate thread:\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\ntauri::Builder::default()\n  .setup(|app| {\n    let handle = app.handle().clone();\n    std::thread::spawn(move || {\n      let window = tauri::window::WindowBuilder::new(&handle, \"label\")\n        .build()\n        .unwrap();\n    });\n    Ok(())\n  });\n```\n  \"####\n  )]\n  ///\n  /// - Create a window in a command:\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\n#[tauri::command]\nasync fn create_window(app: tauri::AppHandle) {\n  let window = tauri::window::WindowBuilder::new(&app, \"label\")\n    .build()\n    .unwrap();\n}\n```\n  \"####\n  )]\n  ///\n  /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583\n  pub fn new<L: Into<String>>(manager: &'a M, label: L) -> Self {\n    Self {\n      manager,\n      label: label.into(),\n      window_builder: <R::WindowDispatcher as WindowDispatch<EventLoopMessage>>::WindowBuilder::new(\n      ),\n      #[cfg(desktop)]\n      menu: None,\n      #[cfg(desktop)]\n      on_menu_event: None,\n      window_effects: None,\n    }\n  }\n\n  /// Initializes a window builder from a [`WindowConfig`] from tauri.conf.json.\n  /// Keep in mind that you can't create 2 windows with the same `label` so make sure\n  /// that the initial window was closed or change the label of the new [`WindowBuilder`].\n  ///\n  /// # Known issues\n  ///\n  /// On Windows, this function deadlocks when used in a synchronous command or event handlers, see [the Webview2 issue].\n  /// You should use `async` commands and separate threads when creating windows.\n  ///\n  /// # Examples\n  ///\n  /// - Create a window in a command:\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\n#[tauri::command]\nasync fn reopen_window(app: tauri::AppHandle) {\n  let window = tauri::window::WindowBuilder::from_config(&app, &app.config().app.windows.get(0).unwrap().clone())\n    .unwrap()\n    .build()\n    .unwrap();\n}\n```\n  \"####\n  )]\n  ///\n  /// [the Webview2 issue]: https://github.com/tauri-apps/wry/issues/583\n  pub fn from_config(manager: &'a M, config: &WindowConfig) -> crate::Result<Self> {\n    #[cfg_attr(not(windows), allow(unused_mut))]\n    let mut builder = Self {\n      manager,\n      label: config.label.clone(),\n      window_effects: config.window_effects.clone(),\n      window_builder:\n        <R::WindowDispatcher as WindowDispatch<EventLoopMessage>>::WindowBuilder::with_config(\n          config,\n        ),\n      #[cfg(desktop)]\n      menu: None,\n      #[cfg(desktop)]\n      on_menu_event: None,\n    };\n\n    #[cfg(desktop)]\n    if let Some(parent) = &config.parent {\n      let window = manager\n        .manager()\n        .get_window(parent)\n        .ok_or(crate::Error::WindowNotFound)?;\n      builder = builder.parent(&window)?;\n    }\n\n    Ok(builder)\n  }\n\n  /// Registers a global menu event listener.\n  ///\n  /// Note that this handler is called for any menu event,\n  /// whether it is coming from this window, another window or from the tray icon menu.\n  ///\n  /// Also note that this handler will not be called if\n  /// the window used to register it was closed.\n  ///\n  /// # Examples\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\nuse tauri::menu::{Menu, Submenu, MenuItem};\ntauri::Builder::default()\n  .setup(|app| {\n    let handle = app.handle();\n    let save_menu_item = MenuItem::new(handle, \"Save\", true, None::<&str>)?;\n    let menu = Menu::with_items(handle, &[\n      &Submenu::with_items(handle, \"File\", true, &[\n        &save_menu_item,\n      ])?,\n    ])?;\n    let window = tauri::window::WindowBuilder::new(app, \"editor\")\n      .menu(menu)\n      .on_menu_event(move |window, event| {\n        if event.id == save_menu_item.id() {\n          // save menu item\n        }\n      })\n      .build()\n      .unwrap();\n  ///\n    Ok(())\n  });\n```\"####\n  )]\n  #[cfg(desktop)]\n  pub fn on_menu_event<F: Fn(&Window<R>, crate::menu::MenuEvent) + Send + Sync + 'static>(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.on_menu_event.replace(Box::new(f));\n    self\n  }\n\n  /// Creates this window with a webview with it.\n  #[cfg_attr(\n    feature = \"tracing\",\n    tracing::instrument(name = \"webview::create\", skip_all)\n  )]\n  pub(crate) fn with_webview(\n    self,\n    webview: WebviewBuilder<R>,\n  ) -> crate::Result<(Window<R>, Webview<R>)> {\n    let pending_webview = webview.into_pending_webview(self.manager, &self.label)?;\n    let window = self.build_internal(Some(pending_webview))?;\n\n    let webview = window.webviews().first().unwrap().clone();\n\n    Ok((window, webview))\n  }\n\n  /// Creates a new window.\n  pub fn build(self) -> crate::Result<Window<R>> {\n    self.build_internal(None)\n  }\n\n  /// Creates a new window with an optional webview.\n  fn build_internal(\n    self,\n    webview: Option<PendingWebview<EventLoopMessage, R>>,\n  ) -> crate::Result<Window<R>> {\n    #[cfg(desktop)]\n    let theme = self.window_builder.get_theme();\n\n    let mut pending = PendingWindow::new(self.window_builder, self.label)?;\n    if let Some(webview) = webview {\n      pending.set_webview(webview);\n    }\n\n    let app_manager = self.manager.manager();\n\n    let pending = app_manager.window.prepare_window(pending)?;\n\n    #[cfg(desktop)]\n    let window_menu = {\n      let is_app_wide = self.menu.is_none();\n      self\n        .menu\n        .or_else(|| self.manager.app_handle().menu())\n        .map(|menu| WindowMenu { is_app_wide, menu })\n    };\n\n    #[cfg(desktop)]\n    let handler = app_manager\n      .menu\n      .prepare_window_menu_creation_handler(window_menu.as_ref(), theme);\n    #[cfg(not(desktop))]\n    #[allow(clippy::type_complexity)]\n    let handler: Option<Box<dyn Fn(tauri_runtime::window::RawWindow<'_>) + Send>> = None;\n\n    let window = match &mut self.manager.runtime() {\n      RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending, handler),\n      RuntimeOrDispatch::RuntimeHandle(handle) => handle.create_window(pending, handler),\n      RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_window(pending, handler),\n    }\n    .map(|detached_window| {\n      let window = app_manager.window.attach_window(\n        self.manager.app_handle().clone(),\n        detached_window.clone(),\n        #[cfg(desktop)]\n        window_menu,\n      );\n\n      if let Some(webview) = detached_window.webview {\n        app_manager.webview.attach_webview(\n          window.clone(),\n          webview.webview,\n          webview.use_https_scheme,\n        );\n      }\n\n      window\n    })?;\n\n    #[cfg(desktop)]\n    if let Some(handler) = self.on_menu_event {\n      window.on_menu_event(handler);\n    }\n\n    let app_manager = self.manager.manager_owned();\n    let window_label = window.label().to_string();\n    let window_ = window.clone();\n    // run on the main thread to fix a deadlock on webview.eval if the tracing feature is enabled\n    let _ = window.run_on_main_thread(move || {\n      if let Some(effects) = self.window_effects {\n        _ = crate::vibrancy::set_window_effects(&window_, Some(effects));\n      }\n      let event = crate::EventName::from_str(\"tauri://window-created\");\n      let payload = Some(crate::webview::CreatedEvent {\n        label: window_label,\n      });\n      let _ = app_manager.emit(event, EmitPayload::Serialize(&payload));\n    });\n\n    Ok(window)\n  }\n}\n\n/// Desktop APIs.\n#[cfg(desktop)]\n#[cfg_attr(not(feature = \"unstable\"), allow(dead_code))]\nimpl<'a, R: Runtime, M: Manager<R>> WindowBuilder<'a, R, M> {\n  /// Sets the menu for the window.\n  #[must_use]\n  pub fn menu(mut self, menu: Menu<R>) -> Self {\n    self.menu.replace(menu);\n    self\n  }\n\n  /// Show window in the center of the screen.\n  #[must_use]\n  pub fn center(mut self) -> Self {\n    self.window_builder = self.window_builder.center();\n    self\n  }\n\n  /// The initial position of the window in logical pixels.\n  #[must_use]\n  pub fn position(mut self, x: f64, y: f64) -> Self {\n    self.window_builder = self.window_builder.position(x, y);\n    self\n  }\n\n  /// Window size in logical pixels.\n  #[must_use]\n  pub fn inner_size(mut self, width: f64, height: f64) -> Self {\n    self.window_builder = self.window_builder.inner_size(width, height);\n    self\n  }\n\n  /// Window min inner size in logical pixels.\n  #[must_use]\n  pub fn min_inner_size(mut self, min_width: f64, min_height: f64) -> Self {\n    self.window_builder = self.window_builder.min_inner_size(min_width, min_height);\n    self\n  }\n\n  /// Window max inner size in logical pixels.\n  #[must_use]\n  pub fn max_inner_size(mut self, max_width: f64, max_height: f64) -> Self {\n    self.window_builder = self.window_builder.max_inner_size(max_width, max_height);\n    self\n  }\n\n  /// Window inner size constraints.\n  #[must_use]\n  pub fn inner_size_constraints(\n    mut self,\n    constraints: tauri_runtime::window::WindowSizeConstraints,\n  ) -> Self {\n    self.window_builder = self.window_builder.inner_size_constraints(constraints);\n    self\n  }\n\n  /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size)\n  /// on creation, which means the window size will be limited to `monitor size - taskbar size`\n  ///\n  /// **NOTE**: The overflow check is only performed on window creation, resizes can still overflow\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  #[must_use]\n  pub fn prevent_overflow(mut self) -> Self {\n    self.window_builder = self.window_builder.prevent_overflow();\n    self\n  }\n\n  /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size)\n  /// on creation with a margin, which means the window size will be limited to `monitor size - taskbar size - margin size`\n  ///\n  /// **NOTE**: The overflow check is only performed on window creation, resizes can still overflow\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  #[must_use]\n  pub fn prevent_overflow_with_margin(mut self, margin: impl Into<Size>) -> Self {\n    self.window_builder = self\n      .window_builder\n      .prevent_overflow_with_margin(margin.into());\n    self\n  }\n\n  /// Whether the window is resizable or not.\n  /// When resizable is set to false, native window's maximize button is automatically disabled.\n  #[must_use]\n  pub fn resizable(mut self, resizable: bool) -> Self {\n    self.window_builder = self.window_builder.resizable(resizable);\n    self\n  }\n\n  /// Whether the window's native maximize button is enabled or not.\n  /// If resizable is set to false, this setting is ignored.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Disables the \"zoom\" button in the window titlebar, which is also used to enter fullscreen mode.\n  /// - **Linux / iOS / Android:** Unsupported.\n  #[must_use]\n  pub fn maximizable(mut self, maximizable: bool) -> Self {\n    self.window_builder = self.window_builder.maximizable(maximizable);\n    self\n  }\n\n  /// Whether the window's native minimize button is enabled or not.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  #[must_use]\n  pub fn minimizable(mut self, minimizable: bool) -> Self {\n    self.window_builder = self.window_builder.minimizable(minimizable);\n    self\n  }\n\n  /// Whether the window's native close button is enabled or not.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** \"GTK+ will do its best to convince the window manager not to show a close button.\n  ///   Depending on the system, this function may not have any effect when called on a window that is already visible\"\n  /// - **iOS / Android:** Unsupported.\n  #[must_use]\n  pub fn closable(mut self, closable: bool) -> Self {\n    self.window_builder = self.window_builder.closable(closable);\n    self\n  }\n\n  /// The title of the window in the title bar.\n  #[must_use]\n  pub fn title<S: Into<String>>(mut self, title: S) -> Self {\n    self.window_builder = self.window_builder.title(title);\n    self\n  }\n\n  /// Whether to start the window in fullscreen or not.\n  #[must_use]\n  pub fn fullscreen(mut self, fullscreen: bool) -> Self {\n    self.window_builder = self.window_builder.fullscreen(fullscreen);\n    self\n  }\n\n  /// Sets the window to be initially focused.\n  #[must_use]\n  #[deprecated(\n    since = \"1.2.0\",\n    note = \"The window is automatically focused by default. This function Will be removed in 3.0.0. Use `focused` instead.\"\n  )]\n  pub fn focus(mut self) -> Self {\n    self.window_builder = self.window_builder.focused(true);\n    self\n  }\n\n  /// Whether the window will be initially focused or not.\n  #[must_use]\n  pub fn focused(mut self, focused: bool) -> Self {\n    self.window_builder = self.window_builder.focused(focused);\n    self\n  }\n\n  /// Whether the window will be focusable or not.\n  #[must_use]\n  pub fn focusable(mut self, focusable: bool) -> Self {\n    self.window_builder = self.window_builder.focusable(focusable);\n    self\n  }\n\n  /// Whether the window should be maximized upon creation.\n  #[must_use]\n  pub fn maximized(mut self, maximized: bool) -> Self {\n    self.window_builder = self.window_builder.maximized(maximized);\n    self\n  }\n\n  /// Whether the window should be immediately visible upon creation.\n  #[must_use]\n  pub fn visible(mut self, visible: bool) -> Self {\n    self.window_builder = self.window_builder.visible(visible);\n    self\n  }\n\n  /// Forces a theme or uses the system settings if None was provided.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS**: Only supported on macOS 10.14+.\n  #[must_use]\n  pub fn theme(mut self, theme: Option<Theme>) -> Self {\n    self.window_builder = self.window_builder.theme(theme);\n    self\n  }\n\n  /// Whether the window should be transparent. If this is true, writing colors\n  /// with alpha values different than `1.0` will produce a transparent window.\n  #[cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\"))]\n  #[cfg_attr(\n    docsrs,\n    doc(cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\")))\n  )]\n  #[must_use]\n  pub fn transparent(mut self, transparent: bool) -> Self {\n    self.window_builder = self.window_builder.transparent(transparent);\n    self\n  }\n\n  /// Whether the window should have borders and bars.\n  #[must_use]\n  pub fn decorations(mut self, decorations: bool) -> Self {\n    self.window_builder = self.window_builder.decorations(decorations);\n    self\n  }\n\n  /// Whether the window should always be below other windows.\n  #[must_use]\n  pub fn always_on_bottom(mut self, always_on_bottom: bool) -> Self {\n    self.window_builder = self.window_builder.always_on_bottom(always_on_bottom);\n    self\n  }\n\n  /// Whether the window should always be on top of other windows.\n  #[must_use]\n  pub fn always_on_top(mut self, always_on_top: bool) -> Self {\n    self.window_builder = self.window_builder.always_on_top(always_on_top);\n    self\n  }\n\n  /// Whether the window will be visible on all workspaces or virtual desktops.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows / iOS / Android:** Unsupported.\n  #[must_use]\n  pub fn visible_on_all_workspaces(mut self, visible_on_all_workspaces: bool) -> Self {\n    self.window_builder = self\n      .window_builder\n      .visible_on_all_workspaces(visible_on_all_workspaces);\n    self\n  }\n\n  /// Prevents the window contents from being captured by other apps.\n  #[must_use]\n  pub fn content_protected(mut self, protected: bool) -> Self {\n    self.window_builder = self.window_builder.content_protected(protected);\n    self\n  }\n\n  /// Sets the window icon.\n  pub fn icon(mut self, icon: Image<'a>) -> crate::Result<Self> {\n    self.window_builder = self.window_builder.icon(icon.into())?;\n    Ok(self)\n  }\n\n  /// Sets whether or not the window icon should be hidden from the taskbar.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS**: Unsupported.\n  #[must_use]\n  pub fn skip_taskbar(mut self, skip: bool) -> Self {\n    self.window_builder = self.window_builder.skip_taskbar(skip);\n    self\n  }\n\n  /// Sets custom name for Windows' window class. **Windows only**.\n  #[must_use]\n  pub fn window_classname<S: Into<String>>(mut self, classname: S) -> Self {\n    self.window_builder = self.window_builder.window_classname(classname);\n    self\n  }\n\n  /// Sets whether or not the window has shadow.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:**\n  ///   - `false` has no effect on decorated window, shadows are always ON.\n  ///   - `true` will make undecorated window have a 1px white border,\n  ///     and on Windows 11, it will have a rounded corners.\n  /// - **Linux:** Unsupported.\n  #[must_use]\n  pub fn shadow(mut self, enable: bool) -> Self {\n    self.window_builder = self.window_builder.shadow(enable);\n    self\n  }\n\n  /// Sets a parent to the window to be created.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows**: This sets the passed parent as an owner window to the window to be created.\n  ///   From [MSDN owned windows docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows):\n  ///     - An owned window is always above its owner in the z-order.\n  ///     - The system automatically destroys an owned window when its owner is destroyed.\n  ///     - An owned window is hidden when its owner is minimized.\n  /// - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\n  /// - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>\n  pub fn parent(mut self, parent: &Window<R>) -> crate::Result<Self> {\n    #[cfg(windows)]\n    {\n      self.window_builder = self.window_builder.owner(parent.hwnd()?);\n    }\n\n    #[cfg(any(\n      target_os = \"linux\",\n      target_os = \"dragonfly\",\n      target_os = \"freebsd\",\n      target_os = \"netbsd\",\n      target_os = \"openbsd\"\n    ))]\n    {\n      self.window_builder = self.window_builder.transient_for(&parent.gtk_window()?);\n    }\n\n    #[cfg(target_os = \"macos\")]\n    {\n      self.window_builder = self.window_builder.parent(parent.ns_window()?);\n    }\n\n    Ok(self)\n  }\n\n  /// Set an owner to the window to be created.\n  ///\n  /// From MSDN:\n  /// - An owned window is always above its owner in the z-order.\n  /// - The system automatically destroys an owned window when its owner is destroyed.\n  /// - An owned window is hidden when its owner is minimized.\n  ///\n  /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>\n  #[cfg(windows)]\n  pub fn owner(mut self, owner: &Window<R>) -> crate::Result<Self> {\n    self.window_builder = self.window_builder.owner(owner.hwnd()?);\n    Ok(self)\n  }\n\n  /// Set an owner to the window to be created.\n  ///\n  /// From MSDN:\n  /// - An owned window is always above its owner in the z-order.\n  /// - The system automatically destroys an owned window when its owner is destroyed.\n  /// - An owned window is hidden when its owner is minimized.\n  ///\n  /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>\n  ///\n  /// **Note:** This is a low level API. See [`Self::parent`] for a higher level wrapper for Tauri windows.\n  #[cfg(windows)]\n  #[must_use]\n  pub fn owner_raw(mut self, owner: HWND) -> Self {\n    self.window_builder = self.window_builder.owner(owner);\n    self\n  }\n\n  /// Sets a parent to the window to be created.\n  ///\n  /// A child window has the WS_CHILD style and is confined to the client area of its parent window.\n  ///\n  /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>\n  ///\n  /// **Note:** This is a low level API. See [`Self::parent`] for a higher level wrapper for Tauri windows.\n  #[cfg(windows)]\n  #[must_use]\n  pub fn parent_raw(mut self, parent: HWND) -> Self {\n    self.window_builder = self.window_builder.parent(parent);\n    self\n  }\n\n  /// Sets a parent to the window to be created.\n  ///\n  /// See <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>\n  ///\n  /// **Note:** This is a low level API. See [`Self::parent`] for a higher level wrapper for Tauri windows.\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  pub fn parent_raw(mut self, parent: *mut std::ffi::c_void) -> Self {\n    self.window_builder = self.window_builder.parent(parent);\n    self\n  }\n\n  /// Sets the window to be created transient for parent.\n  ///\n  /// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\n  ///\n  /// **Note:** This is a low level API. See [`Self::parent`] for a higher level wrapper for Tauri windows.\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  pub fn transient_for(mut self, parent: &Window<R>) -> crate::Result<Self> {\n    self.window_builder = self.window_builder.transient_for(&parent.gtk_window()?);\n    Ok(self)\n  }\n\n  /// Sets the window to be created transient for parent.\n  ///\n  /// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\n  ///\n  /// **Note:** This is a low level API. See [`Self::parent`] and [`Self::transient_for`] for higher level wrappers for Tauri windows.\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  #[must_use]\n  pub fn transient_for_raw(mut self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self {\n    self.window_builder = self.window_builder.transient_for(parent);\n    self\n  }\n\n  /// Enables or disables drag and drop support.\n  #[cfg(windows)]\n  #[must_use]\n  pub fn drag_and_drop(mut self, enabled: bool) -> Self {\n    self.window_builder = self.window_builder.drag_and_drop(enabled);\n    self\n  }\n\n  /// Sets the [`crate::TitleBarStyle`].\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  pub fn title_bar_style(mut self, style: crate::TitleBarStyle) -> Self {\n    self.window_builder = self.window_builder.title_bar_style(style);\n    self\n  }\n\n  /// Hide the window title.\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  pub fn hidden_title(mut self, hidden: bool) -> Self {\n    self.window_builder = self.window_builder.hidden_title(hidden);\n    self\n  }\n\n  /// Defines the window [tabbing identifier] for macOS.\n  ///\n  /// Windows with matching tabbing identifiers will be grouped together.\n  /// If the tabbing identifier is not set, automatic tabbing will be disabled.\n  ///\n  /// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  pub fn tabbing_identifier(mut self, identifier: &str) -> Self {\n    self.window_builder = self.window_builder.tabbing_identifier(identifier);\n    self\n  }\n\n  /// Sets window effects.\n  ///\n  /// Requires the window to be transparent.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>\n  /// - **Linux**: Unsupported\n  pub fn effects(mut self, effects: WindowEffectsConfig) -> Self {\n    self.window_effects.replace(effects);\n    self\n  }\n}\n\nimpl<R: Runtime, M: Manager<R>> WindowBuilder<'_, R, M> {\n  /// Set the window and webview background color.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: alpha channel is ignored.\n  #[must_use]\n  pub fn background_color(mut self, color: Color) -> Self {\n    self.window_builder = self.window_builder.background_color(color);\n    self\n  }\n}\n/// A wrapper struct to hold the window menu state\n/// and whether it is global per-app or specific to this window.\n#[cfg(desktop)]\npub(crate) struct WindowMenu<R: Runtime> {\n  pub(crate) is_app_wide: bool,\n  pub(crate) menu: Menu<R>,\n}\n\n// TODO: expand these docs since this is a pretty important type\n/// A window managed by Tauri.\n///\n/// This type also implements [`Manager`] which allows you to manage other windows attached to\n/// the same application.\n#[default_runtime(crate::Wry, wry)]\npub struct Window<R: Runtime> {\n  /// The window created by the runtime.\n  pub(crate) window: DetachedWindow<EventLoopMessage, R>,\n  /// The manager to associate this window with.\n  pub(crate) manager: Arc<AppManager<R>>,\n  pub(crate) app_handle: AppHandle<R>,\n  // The menu set for this window\n  #[cfg(desktop)]\n  pub(crate) menu: Arc<Mutex<Option<WindowMenu<R>>>>,\n  pub(crate) resources_table: Arc<Mutex<ResourceTable>>,\n}\n\nimpl<R: Runtime> std::fmt::Debug for Window<R> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"Window\")\n      .field(\"window\", &self.window)\n      .field(\"manager\", &self.manager)\n      .field(\"app_handle\", &self.app_handle)\n      .finish()\n  }\n}\n\nimpl<R: Runtime> raw_window_handle::HasWindowHandle for Window<R> {\n  fn window_handle(\n    &self,\n  ) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {\n    self.window.dispatcher.window_handle()\n  }\n}\n\nimpl<R: Runtime> raw_window_handle::HasDisplayHandle for Window<R> {\n  fn display_handle(\n    &self,\n  ) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {\n    self.app_handle.display_handle()\n  }\n}\n\nimpl<R: Runtime> Clone for Window<R> {\n  fn clone(&self) -> Self {\n    Self {\n      window: self.window.clone(),\n      manager: self.manager.clone(),\n      app_handle: self.app_handle.clone(),\n      #[cfg(desktop)]\n      menu: self.menu.clone(),\n      resources_table: self.resources_table.clone(),\n    }\n  }\n}\n\nimpl<R: Runtime> Hash for Window<R> {\n  /// Only use the [`Window`]'s label to represent its hash.\n  fn hash<H: Hasher>(&self, state: &mut H) {\n    self.window.label.hash(state)\n  }\n}\n\nimpl<R: Runtime> Eq for Window<R> {}\nimpl<R: Runtime> PartialEq for Window<R> {\n  /// Only use the [`Window`]'s label to compare equality.\n  fn eq(&self, other: &Self) -> bool {\n    self.window.label.eq(&other.window.label)\n  }\n}\n\nimpl<R: Runtime> Manager<R> for Window<R> {\n  fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {\n    self\n      .resources_table\n      .lock()\n      .expect(\"poisoned window resources table\")\n  }\n}\n\nimpl<R: Runtime> ManagerBase<R> for Window<R> {\n  fn manager(&self) -> &AppManager<R> {\n    &self.manager\n  }\n\n  fn manager_owned(&self) -> Arc<AppManager<R>> {\n    self.manager.clone()\n  }\n\n  fn runtime(&self) -> RuntimeOrDispatch<'_, R> {\n    RuntimeOrDispatch::Dispatch(self.window.dispatcher.clone())\n  }\n\n  fn managed_app_handle(&self) -> &AppHandle<R> {\n    &self.app_handle\n  }\n}\n\nimpl<'de, R: Runtime> CommandArg<'de, R> for Window<R> {\n  /// Grabs the [`Window`] from the [`CommandItem`]. This will never fail.\n  fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {\n    Ok(command.message.webview().window())\n  }\n}\n\n/// Base window functions.\nimpl<R: Runtime> Window<R> {\n  /// Create a new window that is attached to the manager.\n  pub(crate) fn new(\n    manager: Arc<AppManager<R>>,\n    window: DetachedWindow<EventLoopMessage, R>,\n    app_handle: AppHandle<R>,\n    #[cfg(desktop)] menu: Option<WindowMenu<R>>,\n  ) -> Self {\n    Self {\n      window,\n      manager,\n      app_handle,\n      #[cfg(desktop)]\n      menu: Arc::new(std::sync::Mutex::new(menu)),\n      resources_table: Default::default(),\n    }\n  }\n\n  /// Initializes a window builder with the given window label.\n  ///\n  /// Data URLs are only supported with the `webview-data-url` feature flag.\n  #[cfg(feature = \"unstable\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"unstable\")))]\n  pub fn builder<M: Manager<R>, L: Into<String>>(manager: &M, label: L) -> WindowBuilder<'_, R, M> {\n    WindowBuilder::new(manager, label.into())\n  }\n\n  /// Adds a new webview as a child of this window.\n  #[cfg(any(test, all(desktop, feature = \"unstable\")))]\n  #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = \"unstable\"))))]\n  pub fn add_child<P: Into<Position>, S: Into<Size>>(\n    &self,\n    webview_builder: WebviewBuilder<R>,\n    position: P,\n    size: S,\n  ) -> crate::Result<Webview<R>> {\n    use std::sync::mpsc::channel;\n\n    let (tx, rx) = channel();\n    let position = position.into();\n    let size = size.into();\n    let window_ = self.clone();\n    self.run_on_main_thread(move || {\n      let res = webview_builder.build(window_, position, size);\n      tx.send(res).unwrap();\n    })?;\n    rx.recv().unwrap()\n  }\n\n  /// List of webviews associated with this window.\n  pub fn webviews(&self) -> Vec<Webview<R>> {\n    self\n      .manager\n      .webview\n      .webviews_lock()\n      .values()\n      .filter(|w| w.window_label() == self.label())\n      .cloned()\n      .collect()\n  }\n\n  pub(crate) fn is_webview_window(&self) -> bool {\n    self.webviews().iter().all(|w| w.label() == self.label())\n  }\n\n  /// Runs the given closure on the main thread.\n  pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .run_on_main_thread(f)\n      .map_err(Into::into)\n  }\n\n  /// The label of this window.\n  pub fn label(&self) -> &str {\n    &self.window.label\n  }\n\n  /// Registers a window event listener.\n  pub fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) {\n    self\n      .window\n      .dispatcher\n      .on_window_event(move |event| f(&event.clone().into()));\n  }\n}\n\n/// Menu APIs\n#[cfg(desktop)]\nimpl<R: Runtime> Window<R> {\n  /// Registers a global menu event listener.\n  ///\n  /// Note that this handler is called for any menu event,\n  /// whether it is coming from this window, another window or from the tray icon menu.\n  ///\n  /// Also note that this handler will not be called if\n  /// the window used to register it was closed.\n  ///\n  /// # Examples\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\nuse tauri::menu::{Menu, Submenu, MenuItem};\ntauri::Builder::default()\n  .setup(|app| {\n    let handle = app.handle();\n    let save_menu_item = MenuItem::new(handle, \"Save\", true, None::<&str>)?;\n    let menu = Menu::with_items(handle, &[\n      &Submenu::with_items(handle, \"File\", true, &[\n        &save_menu_item,\n      ])?,\n    ])?;\n    let window = tauri::window::WindowBuilder::new(app, \"editor\")\n      .menu(menu)\n      .build()\n      .unwrap();\n\n    window.on_menu_event(move |window, event| {\n      if event.id == save_menu_item.id() {\n          // save menu item\n      }\n    });\n\n    Ok(())\n  });\n```\n  \"####\n  )]\n  pub fn on_menu_event<F: Fn(&Window<R>, crate::menu::MenuEvent) + Send + Sync + 'static>(\n    &self,\n    f: F,\n  ) {\n    self\n      .manager\n      .menu\n      .event_listeners\n      .lock()\n      .unwrap()\n      .insert(self.label().to_string(), Box::new(f));\n  }\n\n  pub(crate) fn menu_lock(&self) -> std::sync::MutexGuard<'_, Option<WindowMenu<R>>> {\n    self.menu.lock().expect(\"poisoned window\")\n  }\n\n  #[cfg_attr(target_os = \"macos\", allow(dead_code))]\n  pub(crate) fn has_app_wide_menu(&self) -> bool {\n    self\n      .menu_lock()\n      .as_ref()\n      .map(|m| m.is_app_wide)\n      .unwrap_or(false)\n  }\n\n  #[cfg_attr(target_os = \"macos\", allow(dead_code))]\n  pub(crate) fn is_menu_in_use<I: PartialEq<MenuId>>(&self, id: &I) -> bool {\n    self\n      .menu_lock()\n      .as_ref()\n      .map(|m| id.eq(m.menu.id()))\n      .unwrap_or(false)\n  }\n\n  /// Returns this window menu .\n  pub fn menu(&self) -> Option<Menu<R>> {\n    self.menu_lock().as_ref().map(|m| m.menu.clone())\n  }\n\n  /// Sets the window menu and returns the previous one.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS:** Unsupported. The menu on macOS is app-wide and not specific to one\n  ///   window, if you need to set it, use [`AppHandle::set_menu`] instead.\n  #[cfg_attr(target_os = \"macos\", allow(unused_variables))]\n  pub fn set_menu(&self, menu: Menu<R>) -> crate::Result<Option<Menu<R>>> {\n    let prev_menu = self.remove_menu()?;\n\n    self.manager.menu.insert_menu_into_stash(&menu);\n\n    let window = self.clone();\n    let menu_ = menu.clone();\n    self.run_on_main_thread(move || {\n      #[cfg(windows)]\n      if let Ok(hwnd) = window.hwnd() {\n        let theme = window\n          .theme()\n          .map(crate::menu::map_to_menu_theme)\n          .unwrap_or(muda::MenuTheme::Auto);\n\n        let _ = unsafe { menu_.inner().init_for_hwnd_with_theme(hwnd.0 as _, theme) };\n      }\n      #[cfg(any(\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n      ))]\n      if let (Ok(gtk_window), Ok(gtk_box)) = (window.gtk_window(), window.default_vbox()) {\n        let _ = menu_\n          .inner()\n          .init_for_gtk_window(&gtk_window, Some(&gtk_box));\n      }\n    })?;\n\n    self.menu_lock().replace(WindowMenu {\n      is_app_wide: false,\n      menu,\n    });\n\n    Ok(prev_menu)\n  }\n\n  /// Removes the window menu and returns it.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **macOS:** Unsupported. The menu on macOS is app-wide and not specific to one\n  ///   window, if you need to remove it, use [`AppHandle::remove_menu`] instead.\n  pub fn remove_menu(&self) -> crate::Result<Option<Menu<R>>> {\n    let prev_menu = self.menu_lock().take().map(|m| m.menu);\n\n    // remove from the window\n    #[cfg_attr(target_os = \"macos\", allow(unused_variables))]\n    if let Some(menu) = &prev_menu {\n      let window = self.clone();\n      let menu = menu.clone();\n      self.run_on_main_thread(move || {\n        #[cfg(windows)]\n        if let Ok(hwnd) = window.hwnd() {\n          let _ = unsafe { menu.inner().remove_for_hwnd(hwnd.0 as _) };\n        }\n        #[cfg(any(\n          target_os = \"linux\",\n          target_os = \"dragonfly\",\n          target_os = \"freebsd\",\n          target_os = \"netbsd\",\n          target_os = \"openbsd\"\n        ))]\n        if let Ok(gtk_window) = window.gtk_window() {\n          let _ = menu.inner().remove_for_gtk_window(&gtk_window);\n        }\n      })?;\n    }\n\n    self\n      .manager\n      .remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id()));\n\n    Ok(prev_menu)\n  }\n\n  /// Hides the window menu.\n  pub fn hide_menu(&self) -> crate::Result<()> {\n    // remove from the window\n    #[cfg_attr(target_os = \"macos\", allow(unused_variables))]\n    if let Some(window_menu) = &*self.menu_lock() {\n      let window = self.clone();\n      let menu_ = window_menu.menu.clone();\n      self.run_on_main_thread(move || {\n        #[cfg(windows)]\n        if let Ok(hwnd) = window.hwnd() {\n          let _ = unsafe { menu_.inner().hide_for_hwnd(hwnd.0 as _) };\n        }\n        #[cfg(any(\n          target_os = \"linux\",\n          target_os = \"dragonfly\",\n          target_os = \"freebsd\",\n          target_os = \"netbsd\",\n          target_os = \"openbsd\"\n        ))]\n        if let Ok(gtk_window) = window.gtk_window() {\n          let _ = menu_.inner().hide_for_gtk_window(&gtk_window);\n        }\n      })?;\n    }\n\n    Ok(())\n  }\n\n  /// Shows the window menu.\n  pub fn show_menu(&self) -> crate::Result<()> {\n    // remove from the window\n    #[cfg_attr(target_os = \"macos\", allow(unused_variables))]\n    if let Some(window_menu) = &*self.menu_lock() {\n      let window = self.clone();\n      let menu_ = window_menu.menu.clone();\n      self.run_on_main_thread(move || {\n        #[cfg(windows)]\n        if let Ok(hwnd) = window.hwnd() {\n          let _ = unsafe { menu_.inner().show_for_hwnd(hwnd.0 as _) };\n        }\n        #[cfg(any(\n          target_os = \"linux\",\n          target_os = \"dragonfly\",\n          target_os = \"freebsd\",\n          target_os = \"netbsd\",\n          target_os = \"openbsd\"\n        ))]\n        if let Ok(gtk_window) = window.gtk_window() {\n          let _ = menu_.inner().show_for_gtk_window(&gtk_window);\n        }\n      })?;\n    }\n\n    Ok(())\n  }\n\n  /// Shows the window menu.\n  pub fn is_menu_visible(&self) -> crate::Result<bool> {\n    // remove from the window\n    #[cfg_attr(target_os = \"macos\", allow(unused_variables))]\n    if let Some(window_menu) = &*self.menu_lock() {\n      let (tx, rx) = std::sync::mpsc::channel();\n      let window = self.clone();\n      let menu_ = window_menu.menu.clone();\n      self.run_on_main_thread(move || {\n        #[cfg(windows)]\n        if let Ok(hwnd) = window.hwnd() {\n          let _ = tx.send(unsafe { menu_.inner().is_visible_on_hwnd(hwnd.0 as _) });\n        }\n        #[cfg(any(\n          target_os = \"linux\",\n          target_os = \"dragonfly\",\n          target_os = \"freebsd\",\n          target_os = \"netbsd\",\n          target_os = \"openbsd\"\n        ))]\n        if let Ok(gtk_window) = window.gtk_window() {\n          let _ = tx.send(menu_.inner().is_visible_on_gtk_window(&gtk_window));\n        }\n      })?;\n\n      return Ok(rx.recv().unwrap_or(false));\n    }\n\n    Ok(false)\n  }\n\n  /// Shows the specified menu as a context menu at the cursor position.\n  pub fn popup_menu<M: ContextMenu>(&self, menu: &M) -> crate::Result<()> {\n    menu.popup(self.clone())\n  }\n\n  /// Shows the specified menu as a context menu at the specified position.\n  ///\n  /// The position is relative to the window's top-left corner.\n  pub fn popup_menu_at<M: ContextMenu, P: Into<Position>>(\n    &self,\n    menu: &M,\n    position: P,\n  ) -> crate::Result<()> {\n    menu.popup_at(self.clone(), position)\n  }\n}\n\n/// Window getters.\nimpl<R: Runtime> Window<R> {\n  /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.\n  pub fn scale_factor(&self) -> crate::Result<f64> {\n    self.window.dispatcher.scale_factor().map_err(Into::into)\n  }\n\n  /// Returns the position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop.\n  pub fn inner_position(&self) -> crate::Result<PhysicalPosition<i32>> {\n    self.window.dispatcher.inner_position().map_err(Into::into)\n  }\n\n  /// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop.\n  pub fn outer_position(&self) -> crate::Result<PhysicalPosition<i32>> {\n    self.window.dispatcher.outer_position().map_err(Into::into)\n  }\n\n  /// Returns the physical size of the window's client area.\n  ///\n  /// The client area is the content of the window, excluding the title bar and borders.\n  pub fn inner_size(&self) -> crate::Result<PhysicalSize<u32>> {\n    self.window.dispatcher.inner_size().map_err(Into::into)\n  }\n\n  /// Returns the physical size of the entire window.\n  ///\n  /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead.\n  pub fn outer_size(&self) -> crate::Result<PhysicalSize<u32>> {\n    self.window.dispatcher.outer_size().map_err(Into::into)\n  }\n\n  /// Gets the window's current fullscreen state.\n  pub fn is_fullscreen(&self) -> crate::Result<bool> {\n    self.window.dispatcher.is_fullscreen().map_err(Into::into)\n  }\n\n  /// Gets the window's current minimized state.\n  pub fn is_minimized(&self) -> crate::Result<bool> {\n    self.window.dispatcher.is_minimized().map_err(Into::into)\n  }\n\n  /// Gets the window's current maximized state.\n  pub fn is_maximized(&self) -> crate::Result<bool> {\n    self.window.dispatcher.is_maximized().map_err(Into::into)\n  }\n\n  /// Gets the window's current focus state.\n  pub fn is_focused(&self) -> crate::Result<bool> {\n    self.window.dispatcher.is_focused().map_err(Into::into)\n  }\n\n  /// Gets the window's current decoration state.\n  pub fn is_decorated(&self) -> crate::Result<bool> {\n    self.window.dispatcher.is_decorated().map_err(Into::into)\n  }\n\n  /// Gets the window's current resizable state.\n  pub fn is_resizable(&self) -> crate::Result<bool> {\n    self.window.dispatcher.is_resizable().map_err(Into::into)\n  }\n\n  /// Whether the window is enabled or disabled.\n  pub fn is_enabled(&self) -> crate::Result<bool> {\n    self.window.dispatcher.is_enabled().map_err(Into::into)\n  }\n\n  /// Determines if this window should always be on top of other windows.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  pub fn is_always_on_top(&self) -> crate::Result<bool> {\n    self\n      .window\n      .dispatcher\n      .is_always_on_top()\n      .map_err(Into::into)\n  }\n\n  /// Gets the window's native maximize button state\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  pub fn is_maximizable(&self) -> crate::Result<bool> {\n    self.window.dispatcher.is_maximizable().map_err(Into::into)\n  }\n\n  /// Gets the window's native minimize button state\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  pub fn is_minimizable(&self) -> crate::Result<bool> {\n    self.window.dispatcher.is_minimizable().map_err(Into::into)\n  }\n\n  /// Gets the window's native close button state\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  pub fn is_closable(&self) -> crate::Result<bool> {\n    self.window.dispatcher.is_closable().map_err(Into::into)\n  }\n\n  /// Gets the window's current visibility state.\n  pub fn is_visible(&self) -> crate::Result<bool> {\n    self.window.dispatcher.is_visible().map_err(Into::into)\n  }\n\n  /// Gets the window's current title.\n  pub fn title(&self) -> crate::Result<String> {\n    self.window.dispatcher.title().map_err(Into::into)\n  }\n\n  /// Returns the monitor on which the window currently resides.\n  ///\n  /// Returns None if current monitor can't be detected.\n  pub fn current_monitor(&self) -> crate::Result<Option<Monitor>> {\n    self\n      .window\n      .dispatcher\n      .current_monitor()\n      .map(|m| m.map(Into::into))\n      .map_err(Into::into)\n  }\n\n  /// Returns the monitor that contains the given point.\n  pub fn monitor_from_point(&self, x: f64, y: f64) -> crate::Result<Option<Monitor>> {\n    self\n      .window\n      .dispatcher\n      .monitor_from_point(x, y)\n      .map(|m| m.map(Into::into))\n      .map_err(Into::into)\n  }\n\n  /// Returns the primary monitor of the system.\n  ///\n  /// Returns None if it can't identify any monitor as a primary one.\n  pub fn primary_monitor(&self) -> crate::Result<Option<Monitor>> {\n    self\n      .window\n      .dispatcher\n      .primary_monitor()\n      .map(|m| m.map(Into::into))\n      .map_err(Into::into)\n  }\n\n  /// Returns the list of all the monitors available on the system.\n  pub fn available_monitors(&self) -> crate::Result<Vec<Monitor>> {\n    self\n      .window\n      .dispatcher\n      .available_monitors()\n      .map(|m| m.into_iter().map(Into::into).collect())\n      .map_err(Into::into)\n  }\n\n  /// Returns the native handle that is used by this window.\n  #[cfg(target_os = \"macos\")]\n  pub fn ns_window(&self) -> crate::Result<*mut std::ffi::c_void> {\n    self\n      .window\n      .dispatcher\n      .window_handle()\n      .map_err(Into::into)\n      .and_then(|handle| {\n        if let raw_window_handle::RawWindowHandle::AppKit(h) = handle.as_raw() {\n          let view: &objc2_app_kit::NSView = unsafe { h.ns_view.cast().as_ref() };\n          let ns_window = view.window().expect(\"view to be installed in window\");\n          Ok(objc2::rc::Retained::autorelease_ptr(ns_window).cast())\n        } else {\n          Err(crate::Error::InvalidWindowHandle)\n        }\n      })\n  }\n\n  /// Returns the pointer to the content view of this window.\n  #[cfg(target_os = \"macos\")]\n  pub fn ns_view(&self) -> crate::Result<*mut std::ffi::c_void> {\n    self\n      .window\n      .dispatcher\n      .window_handle()\n      .map_err(Into::into)\n      .and_then(|handle| {\n        if let raw_window_handle::RawWindowHandle::AppKit(h) = handle.as_raw() {\n          Ok(h.ns_view.as_ptr())\n        } else {\n          Err(crate::Error::InvalidWindowHandle)\n        }\n      })\n  }\n\n  /// Returns the native handle that is used by this window.\n  #[cfg(windows)]\n  pub fn hwnd(&self) -> crate::Result<HWND> {\n    self\n      .window\n      .dispatcher\n      .window_handle()\n      .map_err(Into::into)\n      .and_then(|handle| {\n        if let raw_window_handle::RawWindowHandle::Win32(h) = handle.as_raw() {\n          Ok(HWND(h.hwnd.get() as _))\n        } else {\n          Err(crate::Error::InvalidWindowHandle)\n        }\n      })\n  }\n\n  /// Returns the `ApplicationWindow` from gtk crate that is used by this window.\n  ///\n  /// Note that this type can only be used on the main thread.\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  pub fn gtk_window(&self) -> crate::Result<gtk::ApplicationWindow> {\n    self.window.dispatcher.gtk_window().map_err(Into::into)\n  }\n\n  /// Returns the vertical [`gtk::Box`] that is added by default as the sole child of this window.\n  ///\n  /// Note that this type can only be used on the main thread.\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  pub fn default_vbox(&self) -> crate::Result<gtk::Box> {\n    self.window.dispatcher.default_vbox().map_err(Into::into)\n  }\n\n  /// Returns the current window theme.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS**: Only supported on macOS 10.14+.\n  pub fn theme(&self) -> crate::Result<Theme> {\n    self.window.dispatcher.theme().map_err(Into::into)\n  }\n}\n\n/// Desktop window getters.\n#[cfg(desktop)]\nimpl<R: Runtime> Window<R> {\n  /// Get the cursor position relative to the top-left hand corner of the desktop.\n  ///\n  /// Note that the top-left hand corner of the desktop is not necessarily the same as the screen.\n  /// If the user uses a desktop with multiple monitors,\n  /// the top-left hand corner of the desktop is the top-left hand corner of the main monitor on Windows and macOS\n  /// or the top-left of the leftmost monitor on X11.\n  ///\n  /// The coordinates can be negative if the top-left hand corner of the window is outside of the visible screen region.\n  pub fn cursor_position(&self) -> crate::Result<PhysicalPosition<f64>> {\n    self.app_handle.cursor_position()\n  }\n}\n\n/// Desktop window setters and actions.\n#[cfg(desktop)]\nimpl<R: Runtime> Window<R> {\n  /// Centers the window.\n  pub fn center(&self) -> crate::Result<()> {\n    self.window.dispatcher.center().map_err(Into::into)\n  }\n\n  /// Requests user attention to the window, this has no effect if the application\n  /// is already focused. How requesting for user attention manifests is platform dependent,\n  /// see `UserAttentionType` for details.\n  ///\n  /// Providing `None` will unset the request for user attention. Unsetting the request for\n  /// user attention might not be done automatically by the WM when the window receives input.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** `None` has no effect.\n  /// - **Linux:** Urgency levels have the same effect.\n  pub fn request_user_attention(\n    &self,\n    request_type: Option<UserAttentionType>,\n  ) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .request_user_attention(request_type)\n      .map_err(Into::into)\n  }\n\n  /// Determines if this window should be resizable.\n  /// When resizable is set to false, native window's maximize button is automatically disabled.\n  pub fn set_resizable(&self, resizable: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_resizable(resizable)\n      .map_err(Into::into)\n  }\n\n  /// Determines if this window's native maximize button should be enabled.\n  /// If resizable is set to false, this setting is ignored.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Disables the \"zoom\" button in the window titlebar, which is also used to enter fullscreen mode.\n  /// - **Linux / iOS / Android:** Unsupported.\n  pub fn set_maximizable(&self, maximizable: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_maximizable(maximizable)\n      .map_err(Into::into)\n  }\n\n  /// Determines if this window's native minimize button should be enabled.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  pub fn set_minimizable(&self, minimizable: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_minimizable(minimizable)\n      .map_err(Into::into)\n  }\n\n  /// Determines if this window's native close button should be enabled.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** \"GTK+ will do its best to convince the window manager not to show a close button.\n  ///   Depending on the system, this function may not have any effect when called on a window that is already visible\"\n  /// - **iOS / Android:** Unsupported.\n  pub fn set_closable(&self, closable: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_closable(closable)\n      .map_err(Into::into)\n  }\n\n  /// Set this window's title.\n  pub fn set_title(&self, title: &str) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_title(title.to_string())\n      .map_err(Into::into)\n  }\n\n  /// Enable or disable the window.\n  pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_enabled(enabled)\n      .map_err(Into::into)\n  }\n\n  /// Maximizes this window.\n  pub fn maximize(&self) -> crate::Result<()> {\n    self.window.dispatcher.maximize().map_err(Into::into)\n  }\n\n  /// Un-maximizes this window.\n  pub fn unmaximize(&self) -> crate::Result<()> {\n    self.window.dispatcher.unmaximize().map_err(Into::into)\n  }\n\n  /// Minimizes this window.\n  pub fn minimize(&self) -> crate::Result<()> {\n    self.window.dispatcher.minimize().map_err(Into::into)\n  }\n\n  /// Un-minimizes this window.\n  pub fn unminimize(&self) -> crate::Result<()> {\n    self.window.dispatcher.unminimize().map_err(Into::into)\n  }\n\n  /// Show this window.\n  pub fn show(&self) -> crate::Result<()> {\n    self.window.dispatcher.show().map_err(Into::into)\n  }\n\n  /// Hide this window.\n  pub fn hide(&self) -> crate::Result<()> {\n    self.window.dispatcher.hide().map_err(Into::into)\n  }\n\n  /// Closes this window. It emits [`crate::RunEvent::CloseRequested`] first like a user-initiated close request so you can intercept it.\n  pub fn close(&self) -> crate::Result<()> {\n    self.window.dispatcher.close().map_err(Into::into)\n  }\n\n  /// Destroys this window. Similar to [`Self::close`] but does not emit any events and force close the window instead.\n  pub fn destroy(&self) -> crate::Result<()> {\n    self.window.dispatcher.destroy().map_err(Into::into)\n  }\n\n  /// Determines if this window should be [decorated].\n  ///\n  /// [decorated]: https://en.wikipedia.org/wiki/Window_(computing)#Window_decoration\n  pub fn set_decorations(&self, decorations: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_decorations(decorations)\n      .map_err(Into::into)\n  }\n\n  /// Determines if this window should have shadow.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:**\n  ///   - `false` has no effect on decorated window, shadow are always ON.\n  ///   - `true` will make undecorated window have a 1px white border,\n  ///     and on Windows 11, it will have a rounded corners.\n  /// - **Linux:** Unsupported.\n  pub fn set_shadow(&self, enable: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_shadow(enable)\n      .map_err(Into::into)\n  }\n\n  /// Sets window effects, pass [`None`] to clear any effects applied if possible.\n  ///\n  /// Requires the window to be transparent.\n  ///\n  /// See [`EffectsBuilder`] for a convenient builder for [`WindowEffectsConfig`].\n  ///\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```rust,no_run\nuse tauri::{Manager, window::{Color, Effect, EffectState, EffectsBuilder}};\ntauri::Builder::default()\n  .setup(|app| {\n    let window = app.get_window(\"main\").unwrap();\n    window.set_effects(\n      EffectsBuilder::new()\n        .effect(Effect::Popover)\n        .state(EffectState::Active)\n        .radius(5.)\n        .color(Color(0, 0, 0, 255))\n        .build(),\n    )?;\n    Ok(())\n  });\n```\n  \"####\n  )]\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>\n  /// - **Linux**: Unsupported\n  pub fn set_effects<E: Into<Option<WindowEffectsConfig>>>(&self, effects: E) -> crate::Result<()> {\n    let effects = effects.into();\n    let window = self.clone();\n    self.run_on_main_thread(move || {\n      let _ = crate::vibrancy::set_window_effects(&window, effects);\n    })\n  }\n\n  /// Determines if this window should always be below other windows.\n  pub fn set_always_on_bottom(&self, always_on_bottom: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_always_on_bottom(always_on_bottom)\n      .map_err(Into::into)\n  }\n\n  /// Determines if this window should always be on top of other windows.\n  pub fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_always_on_top(always_on_top)\n      .map_err(Into::into)\n  }\n\n  /// Sets whether the window should be visible on all workspaces or virtual desktops.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows / iOS / Android:** Unsupported.\n  pub fn set_visible_on_all_workspaces(\n    &self,\n    visible_on_all_workspaces: bool,\n  ) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_visible_on_all_workspaces(visible_on_all_workspaces)\n      .map_err(Into::into)\n  }\n\n  /// Sets the window background color.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows:** alpha channel is ignored.\n  /// - **iOS / Android:** Unsupported.\n  pub fn set_background_color(&self, color: Option<Color>) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_background_color(color)\n      .map_err(Into::into)\n  }\n\n  /// Prevents the window contents from being captured by other apps.\n  pub fn set_content_protected(&self, protected: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_content_protected(protected)\n      .map_err(Into::into)\n  }\n\n  /// Resizes this window.\n  pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_size(size.into())\n      .map_err(Into::into)\n  }\n\n  /// Sets this window's minimum inner size.\n  pub fn set_min_size<S: Into<Size>>(&self, size: Option<S>) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_min_size(size.map(|s| s.into()))\n      .map_err(Into::into)\n  }\n\n  /// Sets this window's maximum inner size.\n  pub fn set_max_size<S: Into<Size>>(&self, size: Option<S>) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_max_size(size.map(|s| s.into()))\n      .map_err(Into::into)\n  }\n\n  /// Sets this window's minimum inner width.\n  pub fn set_size_constraints(\n    &self,\n    constraints: tauri_runtime::window::WindowSizeConstraints,\n  ) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_size_constraints(constraints)\n      .map_err(Into::into)\n  }\n\n  /// Sets this window's position.\n  pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_position(position.into())\n      .map_err(Into::into)\n  }\n\n  /// Determines if this window should be fullscreen.\n  pub fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_fullscreen(fullscreen)\n      .map_err(Into::into)\n  }\n\n  /// Toggles a fullscreen mode that doesn't require a new macOS space.\n  /// Returns a boolean indicating whether the transition was successful (this won't work if the window was already in the native fullscreen).\n  ///\n  /// This is how fullscreen used to work on macOS in versions before Lion.\n  /// And allows the user to have a fullscreen window without using another space or taking control over the entire monitor.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Uses native simple fullscreen mode.\n  /// - **Other platforms:** Falls back to [`Self::set_fullscreen`].\n  pub fn set_simple_fullscreen(&self, enable: bool) -> crate::Result<()> {\n    #[cfg(target_os = \"macos\")]\n    {\n      self\n        .window\n        .dispatcher\n        .set_simple_fullscreen(enable)\n        .map_err(Into::into)\n    }\n    #[cfg(not(target_os = \"macos\"))]\n    self.set_fullscreen(enable)\n  }\n\n  /// Bring the window to front and focus.\n  pub fn set_focus(&self) -> crate::Result<()> {\n    self.window.dispatcher.set_focus().map_err(Into::into)\n  }\n\n  /// Sets whether the window can be focused.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS**: If the window is already focused, it is not possible to unfocus it after calling `set_focusable(false)`.\n  ///   In this case, you might consider calling [`Window::set_focus`] but it will move the window to the back i.e. at the bottom in terms of z-order.\n  pub fn set_focusable(&self, focusable: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_focusable(focusable)\n      .map_err(Into::into)\n  }\n\n  /// Sets this window' icon.\n  pub fn set_icon(&self, icon: Image<'_>) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_icon(icon.into())\n      .map_err(Into::into)\n  }\n\n  /// Whether to hide the window icon from the taskbar or not.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Unsupported.\n  pub fn set_skip_taskbar(&self, skip: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_skip_taskbar(skip)\n      .map_err(Into::into)\n  }\n\n  /// Grabs the cursor, preventing it from leaving the window.\n  ///\n  /// There's no guarantee that the cursor will be hidden. You should\n  /// hide it by yourself if you want so.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** Unsupported.\n  /// - **macOS:** This locks the cursor in a fixed location, which looks visually awkward.\n  pub fn set_cursor_grab(&self, grab: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_cursor_grab(grab)\n      .map_err(Into::into)\n  }\n\n  /// Modifies the cursor's visibility.\n  ///\n  /// If `false`, this will hide the cursor. If `true`, this will show the cursor.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:** The cursor is only hidden within the confines of the window.\n  /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is\n  ///   outside of the window.\n  pub fn set_cursor_visible(&self, visible: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_cursor_visible(visible)\n      .map_err(Into::into)\n  }\n\n  /// Modifies the cursor icon of the window.\n  pub fn set_cursor_icon(&self, icon: CursorIcon) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_cursor_icon(icon)\n      .map_err(Into::into)\n  }\n\n  /// Changes the position of the cursor in window coordinates.\n  pub fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_cursor_position(position)\n      .map_err(Into::into)\n  }\n\n  /// Ignores the window cursor events.\n  pub fn set_ignore_cursor_events(&self, ignore: bool) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_ignore_cursor_events(ignore)\n      .map_err(Into::into)\n  }\n\n  /// Starts dragging the window.\n  pub fn start_dragging(&self) -> crate::Result<()> {\n    self.window.dispatcher.start_dragging().map_err(Into::into)\n  }\n\n  /// Starts resize-dragging the window.\n  pub fn start_resize_dragging(\n    &self,\n    direction: tauri_runtime::ResizeDirection,\n  ) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .start_resize_dragging(direction)\n      .map_err(Into::into)\n  }\n\n  /// Sets the overlay icon on the taskbar **Windows only**. Using `None` to remove the overlay icon\n  ///\n  /// The overlay icon can be unique for each window.\n  #[cfg(target_os = \"windows\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"windows\")))]\n  pub fn set_overlay_icon(&self, icon: Option<Image<'_>>) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_overlay_icon(icon.map(|x| x.into()))\n      .map_err(Into::into)\n  }\n\n  /// Sets the taskbar badge count. Using `0` or `None` will remove the badge\n  ///\n  /// ## Platform-specific\n  /// - **Windows:** Unsupported, use [`Window::set_overlay_icon`] instead.\n  /// - **iOS:** iOS expects i32, the value will be clamped to i32::MIN, i32::MAX.\n  /// - **Android:** Unsupported.\n  pub fn set_badge_count(&self, count: Option<i64>) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_badge_count(count, Some(format!(\"{}.desktop\", self.package_info().name)))\n      .map_err(Into::into)\n  }\n\n  /// Sets the taskbar badge label **macOS only**. Using `None` will remove the badge\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  pub fn set_badge_label(&self, label: Option<String>) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_badge_label(label)\n      .map_err(Into::into)\n  }\n\n  /// Sets the taskbar progress state.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / macOS**: Progress bar is app-wide and not specific to this window.\n  /// - **Linux**: Only supported desktop environments with `libunity` (e.g. GNOME).\n  /// - **iOS / Android:** Unsupported.\n  pub fn set_progress_bar(&self, progress_state: ProgressBarState) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_progress_bar(crate::runtime::ProgressBarState {\n        status: progress_state.status,\n        progress: progress_state.progress,\n        desktop_filename: Some(format!(\"{}.desktop\", self.package_info().name)),\n      })\n      .map_err(Into::into)\n  }\n\n  /// Sets the title bar style. **macOS only**.\n  pub fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_title_bar_style(style)\n      .map_err(Into::into)\n  }\n\n  /// Sets the theme for this window.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / macOS**: Theme is app-wide and not specific to this window.\n  /// - **iOS / Android:** Unsupported.\n  pub fn set_theme(&self, theme: Option<Theme>) -> crate::Result<()> {\n    self\n      .window\n      .dispatcher\n      .set_theme(theme)\n      .map_err(Into::<crate::Error>::into)?;\n    #[cfg(windows)]\n    if let (Some(menu), Ok(hwnd)) = (self.menu(), self.hwnd()) {\n      let raw_hwnd = hwnd.0 as isize;\n      self.run_on_main_thread(move || {\n        let _ = unsafe {\n          menu.inner().set_theme_for_hwnd(\n            raw_hwnd,\n            theme\n              .map(crate::menu::map_to_menu_theme)\n              .unwrap_or(muda::MenuTheme::Auto),\n          )\n        };\n      })?;\n    };\n    Ok(())\n  }\n}\n\n/// Progress bar state.\n#[cfg(desktop)]\n#[cfg_attr(\n  docsrs,\n  doc(cfg(any(target_os = \"macos\", target_os = \"linux\", windows)))\n)]\n#[derive(serde::Deserialize, Debug)]\npub struct ProgressBarState {\n  /// The progress bar status.\n  pub status: Option<ProgressBarStatus>,\n  /// The progress bar progress. This can be a value ranging from `0` to `100`\n  pub progress: Option<u64>,\n}\n\nimpl<R: Runtime> Listener<R> for Window<R> {\n  /// Listen to an event on this window.\n  ///\n  /// # Examples\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\nuse tauri::{Manager, Listener};\n\ntauri::Builder::default()\n  .setup(|app| {\n    let window = app.get_window(\"main\").unwrap();\n    window.listen(\"component-loaded\", move |event| {\n      println!(\"window just loaded a component\");\n    });\n\n    Ok(())\n  });\n```\n  \"####\n  )]\n  fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId\n  where\n    F: Fn(Event) + Send + 'static,\n  {\n    let event = EventName::new(event.into()).unwrap();\n    self.manager.listen(\n      event,\n      EventTarget::Window {\n        label: self.label().to_string(),\n      },\n      handler,\n    )\n  }\n\n  /// Listen to an event on this window only once.\n  ///\n  /// See [`Self::listen`] for more information.\n  fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId\n  where\n    F: FnOnce(Event) + Send + 'static,\n  {\n    let event = EventName::new(event.into()).unwrap();\n    self.manager.once(\n      event,\n      EventTarget::Window {\n        label: self.label().to_string(),\n      },\n      handler,\n    )\n  }\n\n  /// Unlisten to an event on this window.\n  ///\n  /// # Examples\n  #[cfg_attr(\n    feature = \"unstable\",\n    doc = r####\"\n```\nuse tauri::{Manager, Listener};\n\ntauri::Builder::default()\n  .setup(|app| {\n    let window = app.get_window(\"main\").unwrap();\n    let window_ = window.clone();\n    let handler = window.listen(\"component-loaded\", move |event| {\n      println!(\"window just loaded a component\");\n\n      // we no longer need to listen to the event\n      // we also could have used `window.once` instead\n      window_.unlisten(event.id());\n    });\n\n    // stop listening to the event when you do not need it anymore\n    window.unlisten(handler);\n\n    Ok(())\n  });\n```\n  \"####\n  )]\n  fn unlisten(&self, id: EventId) {\n    self.manager.unlisten(id)\n  }\n}\n\nimpl<R: Runtime> Emitter<R> for Window<R> {}\n\n/// The [`WindowEffectsConfig`] object builder\n#[derive(Default)]\npub struct EffectsBuilder(WindowEffectsConfig);\nimpl EffectsBuilder {\n  /// Create a new [`WindowEffectsConfig`] builder\n  pub fn new() -> Self {\n    Self(WindowEffectsConfig::default())\n  }\n\n  /// Adds effect to the [`WindowEffectsConfig`] `effects` field\n  pub fn effect(mut self, effect: Effect) -> Self {\n    self.0.effects.push(effect);\n    self\n  }\n\n  /// Adds effects to the [`WindowEffectsConfig`] `effects` field\n  pub fn effects<I: IntoIterator<Item = Effect>>(mut self, effects: I) -> Self {\n    self.0.effects.extend(effects);\n    self\n  }\n\n  /// Clears the [`WindowEffectsConfig`] `effects` field\n  pub fn clear_effects(mut self) -> Self {\n    self.0.effects.clear();\n    self\n  }\n\n  /// Sets `state` field for the [`WindowEffectsConfig`] **macOS Only**\n  pub fn state(mut self, state: EffectState) -> Self {\n    self.0.state = Some(state);\n    self\n  }\n  /// Sets `radius` field fo the [`WindowEffectsConfig`] **macOS Only**\n  pub fn radius(mut self, radius: f64) -> Self {\n    self.0.radius = Some(radius);\n    self\n  }\n  /// Sets `color` field fo the [`WindowEffectsConfig`] **Windows Only**\n  pub fn color(mut self, color: Color) -> Self {\n    self.0.color = Some(color);\n    self\n  }\n\n  /// Builds a [`WindowEffectsConfig`]\n  pub fn build(self) -> WindowEffectsConfig {\n    self.0\n  }\n}\n\nimpl From<WindowEffectsConfig> for EffectsBuilder {\n  fn from(value: WindowEffectsConfig) -> Self {\n    Self(value)\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  #[test]\n  fn window_is_send_sync() {\n    crate::test_utils::assert_send::<super::Window>();\n    crate::test_utils::assert_sync::<super::Window>();\n  }\n}\n"
  },
  {
    "path": "crates/tauri/src/window/plugin.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The tauri plugin to create and manipulate windows from JS.\n\nuse crate::{\n  plugin::{Builder, TauriPlugin},\n  Runtime,\n};\n\n#[cfg(desktop)]\nmod desktop_commands {\n  use tauri_runtime::{window::WindowSizeConstraints, ResizeDirection};\n  use tauri_utils::TitleBarStyle;\n\n  use super::*;\n  use crate::{\n    command,\n    sealed::ManagerBase,\n    utils::config::{WindowConfig, WindowEffectsConfig},\n    window::Color,\n    window::{ProgressBarState, WindowBuilder},\n    AppHandle, CursorIcon, Manager, Monitor, PhysicalPosition, PhysicalSize, Position, Size, Theme,\n    UserAttentionType, Webview, Window,\n  };\n\n  #[command(root = \"crate\")]\n  pub async fn get_all_windows<R: Runtime>(app: AppHandle<R>) -> Vec<String> {\n    app.manager().windows().keys().cloned().collect()\n  }\n\n  #[command(root = \"crate\")]\n  pub async fn create<R: Runtime>(app: AppHandle<R>, options: WindowConfig) -> crate::Result<()> {\n    WindowBuilder::from_config(&app, &options)?.build()?;\n    Ok(())\n  }\n\n  fn get_window<R: Runtime>(window: Window<R>, label: Option<String>) -> crate::Result<Window<R>> {\n    match label {\n      Some(l) if !l.is_empty() => window\n        .manager()\n        .get_window(&l)\n        .ok_or(crate::Error::WindowNotFound),\n      _ => Ok(window),\n    }\n  }\n\n  macro_rules! getter {\n    ($cmd: ident, $ret: ty) => {\n      #[command(root = \"crate\")]\n      pub async fn $cmd<R: Runtime>(\n        window: Window<R>,\n        label: Option<String>,\n      ) -> crate::Result<$ret> {\n        get_window(window, label)?.$cmd().map_err(Into::into)\n      }\n    };\n  }\n\n  macro_rules! setter {\n    ($cmd: ident) => {\n      #[command(root = \"crate\")]\n      pub async fn $cmd<R: Runtime>(window: Window<R>, label: Option<String>) -> crate::Result<()> {\n        get_window(window, label)?.$cmd().map_err(Into::into)\n      }\n    };\n\n    ($cmd: ident, $input: ty) => {\n      #[command(root = \"crate\")]\n      pub async fn $cmd<R: Runtime>(\n        window: Window<R>,\n        label: Option<String>,\n        value: $input,\n      ) -> crate::Result<()> {\n        get_window(window, label)?.$cmd(value).map_err(Into::into)\n      }\n    };\n  }\n\n  getter!(scale_factor, f64);\n  getter!(inner_position, PhysicalPosition<i32>);\n  getter!(outer_position, PhysicalPosition<i32>);\n  getter!(inner_size, PhysicalSize<u32>);\n  getter!(outer_size, PhysicalSize<u32>);\n  getter!(is_fullscreen, bool);\n  getter!(is_minimized, bool);\n  getter!(is_maximized, bool);\n  getter!(is_focused, bool);\n  getter!(is_decorated, bool);\n  getter!(is_resizable, bool);\n  getter!(is_maximizable, bool);\n  getter!(is_minimizable, bool);\n  getter!(is_closable, bool);\n  getter!(is_visible, bool);\n  getter!(is_enabled, bool);\n  getter!(title, String);\n  getter!(current_monitor, Option<Monitor>);\n  getter!(primary_monitor, Option<Monitor>);\n  getter!(available_monitors, Vec<Monitor>);\n  getter!(cursor_position, PhysicalPosition<f64>);\n  getter!(theme, Theme);\n  getter!(is_always_on_top, bool);\n\n  setter!(center);\n  setter!(request_user_attention, Option<UserAttentionType>);\n  setter!(set_resizable, bool);\n  setter!(set_maximizable, bool);\n  setter!(set_minimizable, bool);\n  setter!(set_closable, bool);\n  setter!(set_title, &str);\n  setter!(maximize);\n  setter!(unmaximize);\n  setter!(minimize);\n  setter!(unminimize);\n  setter!(show);\n  setter!(hide);\n  setter!(close);\n  setter!(destroy);\n  setter!(set_decorations, bool);\n  setter!(set_shadow, bool);\n  setter!(set_effects, Option<WindowEffectsConfig>);\n  setter!(set_always_on_top, bool);\n  setter!(set_always_on_bottom, bool);\n  setter!(set_content_protected, bool);\n  setter!(set_size, Size);\n  setter!(set_min_size, Option<Size>);\n  setter!(set_max_size, Option<Size>);\n  setter!(set_position, Position);\n  setter!(set_fullscreen, bool);\n  setter!(set_simple_fullscreen, bool);\n  setter!(set_focus);\n  setter!(set_focusable, bool);\n  setter!(set_skip_taskbar, bool);\n  setter!(set_cursor_grab, bool);\n  setter!(set_cursor_visible, bool);\n  setter!(set_background_color, Option<Color>);\n  setter!(set_cursor_icon, CursorIcon);\n  setter!(set_cursor_position, Position);\n  setter!(set_ignore_cursor_events, bool);\n  setter!(start_dragging);\n  setter!(start_resize_dragging, ResizeDirection);\n  setter!(set_progress_bar, ProgressBarState);\n  setter!(set_badge_count, Option<i64>);\n  #[cfg(target_os = \"macos\")]\n  setter!(set_badge_label, Option<String>);\n  setter!(set_visible_on_all_workspaces, bool);\n  setter!(set_title_bar_style, TitleBarStyle);\n  setter!(set_size_constraints, WindowSizeConstraints);\n  setter!(set_theme, Option<Theme>);\n  setter!(set_enabled, bool);\n\n  #[command(root = \"crate\")]\n  #[cfg(target_os = \"windows\")]\n  pub async fn set_overlay_icon<R: Runtime>(\n    webview: Webview<R>,\n    window: Window<R>,\n    label: Option<String>,\n    value: Option<crate::image::JsImage>,\n  ) -> crate::Result<()> {\n    let window = get_window(window, label)?;\n    let resources_table = webview.resources_table();\n\n    let value = match value {\n      Some(value) => Some(value.into_img(&resources_table)?.as_ref().clone()),\n      None => None,\n    };\n\n    window.set_overlay_icon(value)\n  }\n\n  #[command(root = \"crate\")]\n  pub async fn set_icon<R: Runtime>(\n    webview: Webview<R>,\n    window: Window<R>,\n    label: Option<String>,\n    value: crate::image::JsImage,\n  ) -> crate::Result<()> {\n    let window = get_window(window, label)?;\n    let resources_table = webview.resources_table();\n    window.set_icon(value.into_img(&resources_table)?.as_ref().clone())\n  }\n\n  #[command(root = \"crate\")]\n  pub async fn toggle_maximize<R: Runtime>(\n    window: Window<R>,\n    label: Option<String>,\n  ) -> crate::Result<()> {\n    let window = get_window(window, label)?;\n    match window.is_maximized()? {\n      true => window.unmaximize()?,\n      false => window.maximize()?,\n    };\n    Ok(())\n  }\n\n  #[command(root = \"crate\")]\n  pub async fn internal_toggle_maximize<R: Runtime>(\n    window: Window<R>,\n    label: Option<String>,\n  ) -> crate::Result<()> {\n    let window = get_window(window, label)?;\n    if window.is_resizable()? {\n      match window.is_maximized()? {\n        true => window.unmaximize()?,\n        false => window.maximize()?,\n      };\n    }\n    Ok(())\n  }\n\n  #[command(root = \"crate\")]\n  pub async fn monitor_from_point<R: Runtime>(\n    window: Window<R>,\n    label: Option<String>,\n    x: f64,\n    y: f64,\n  ) -> crate::Result<Option<Monitor>> {\n    let window = get_window(window, label)?;\n    window.monitor_from_point(x, y)\n  }\n}\n\n/// Initializes the plugin.\npub fn init<R: Runtime>() -> TauriPlugin<R> {\n  use serialize_to_javascript::{default_template, DefaultTemplate, Template};\n\n  let mut init_script = String::new();\n\n  #[derive(Template)]\n  #[default_template(\"./scripts/drag.js\")]\n  struct Drag<'a> {\n    os_name: &'a str,\n  }\n\n  init_script.push_str(\n    &Drag {\n      os_name: std::env::consts::OS,\n    }\n    .render_default(&Default::default())\n    .unwrap()\n    .into_string(),\n  );\n\n  Builder::new(\"window\")\n    .js_init_script(init_script)\n    .invoke_handler(\n      #[cfg(desktop)]\n      crate::generate_handler![\n        #![plugin(window)]\n        desktop_commands::create,\n        // getters\n        desktop_commands::get_all_windows,\n        desktop_commands::scale_factor,\n        desktop_commands::inner_position,\n        desktop_commands::outer_position,\n        desktop_commands::inner_size,\n        desktop_commands::outer_size,\n        desktop_commands::is_fullscreen,\n        desktop_commands::is_minimized,\n        desktop_commands::is_maximized,\n        desktop_commands::is_focused,\n        desktop_commands::is_decorated,\n        desktop_commands::is_resizable,\n        desktop_commands::is_maximizable,\n        desktop_commands::is_minimizable,\n        desktop_commands::is_closable,\n        desktop_commands::is_visible,\n        desktop_commands::is_enabled,\n        desktop_commands::title,\n        desktop_commands::current_monitor,\n        desktop_commands::primary_monitor,\n        desktop_commands::monitor_from_point,\n        desktop_commands::available_monitors,\n        desktop_commands::cursor_position,\n        desktop_commands::theme,\n        desktop_commands::is_always_on_top,\n        // setters\n        desktop_commands::center,\n        desktop_commands::request_user_attention,\n        desktop_commands::set_resizable,\n        desktop_commands::set_maximizable,\n        desktop_commands::set_minimizable,\n        desktop_commands::set_closable,\n        desktop_commands::set_title,\n        desktop_commands::maximize,\n        desktop_commands::unmaximize,\n        desktop_commands::minimize,\n        desktop_commands::unminimize,\n        desktop_commands::show,\n        desktop_commands::hide,\n        desktop_commands::close,\n        desktop_commands::destroy,\n        desktop_commands::set_decorations,\n        desktop_commands::set_shadow,\n        desktop_commands::set_effects,\n        desktop_commands::set_always_on_top,\n        desktop_commands::set_always_on_bottom,\n        desktop_commands::set_content_protected,\n        desktop_commands::set_size,\n        desktop_commands::set_min_size,\n        desktop_commands::set_max_size,\n        desktop_commands::set_size_constraints,\n        desktop_commands::set_position,\n        desktop_commands::set_fullscreen,\n        desktop_commands::set_simple_fullscreen,\n        desktop_commands::set_focus,\n        desktop_commands::set_focusable,\n        desktop_commands::set_enabled,\n        desktop_commands::set_skip_taskbar,\n        desktop_commands::set_cursor_grab,\n        desktop_commands::set_cursor_visible,\n        desktop_commands::set_cursor_icon,\n        desktop_commands::set_cursor_position,\n        desktop_commands::set_ignore_cursor_events,\n        desktop_commands::start_dragging,\n        desktop_commands::start_resize_dragging,\n        desktop_commands::set_badge_count,\n        #[cfg(target_os = \"macos\")]\n        desktop_commands::set_badge_label,\n        desktop_commands::set_progress_bar,\n        #[cfg(target_os = \"windows\")]\n        desktop_commands::set_overlay_icon,\n        desktop_commands::set_icon,\n        desktop_commands::set_visible_on_all_workspaces,\n        desktop_commands::set_background_color,\n        desktop_commands::set_title_bar_style,\n        desktop_commands::set_theme,\n        desktop_commands::toggle_maximize,\n        desktop_commands::internal_toggle_maximize,\n      ],\n      #[cfg(mobile)]\n      |invoke| {\n        invoke.resolver.reject(\"Window API not available on mobile\");\n        true\n      },\n    )\n    .build()\n}\n"
  },
  {
    "path": "crates/tauri/src/window/scripts/drag.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n;(function () {\n  //-----------------------//\n  // drag on mousedown and maximize on double click on Windows and Linux\n  // while macOS maximization should be on mouseup and if the mouse\n  // moves after the double click, it should be cancelled (see https://github.com/tauri-apps/tauri/issues/8306)\n  //-----------------------//\n  const TAURI_DRAG_REGION_ATTR = 'data-tauri-drag-region'\n\n  function isClickableElement(el) {\n    const tag = el.tagName && el.tagName.toLowerCase()\n\n    return (\n      tag === 'a'\n      || tag === 'button'\n      || tag === 'input'\n      || tag === 'select'\n      || tag === 'textarea'\n      || tag === 'label'\n      || tag === 'summary'\n      || (el.hasAttribute('contenteditable')\n        && el.getAttribute('contenteditable') !== 'false')\n      || (el.hasAttribute('tabindex') && el.getAttribute('tabindex') !== '-1')\n    )\n  }\n\n  // Walk the composed path from target upward. If a clickable element or a\n  // data-tauri-drag-region=\"false\" element is encountered, return false (don't drag).\n  // Otherwise return true.\n  //\n  // Supported values for data-tauri-drag-region:\n  //   (bare / no value) -> self: only direct clicks on this element trigger drag\n  //   \"deep\"            -> deep: clicks anywhere in the subtree trigger drag\n  //   \"false\"           -> disabled: drag is blocked here (and for ancestors)\n  function isDragRegion(composedPath) {\n    for (const el of composedPath) {\n      if (!(el instanceof HTMLElement)) continue\n\n      // if we hit a clickable element or a disabled drag region, don't drag\n      if (\n        isClickableElement(el)\n        || el.getAttribute(TAURI_DRAG_REGION_ATTR) === 'false'\n      ) {\n        return false\n      }\n\n      const attr = el.getAttribute(TAURI_DRAG_REGION_ATTR)\n      if (attr !== null) {\n        // deep: the whole subtree is a drag region\n        if (attr === 'deep') return true\n        // bare (or any unrecognized value): self-only\n        if (el === composedPath[0]) return true\n        // click was on a child of a self-only region - stop walking, don't drag\n        return false\n      }\n    }\n\n    return false\n  }\n\n  const osName = __TEMPLATE_os_name__\n\n  // initial mousedown position for macOS\n  let initialX = 0\n  let initialY = 0\n\n  document.addEventListener('mousedown', (e) => {\n    if (\n      // was left mouse button\n      e.button === 0\n      // and was normal click to drag or double click to maximize\n      && (e.detail === 1 || e.detail === 2)\n      // and is drag region\n      && isDragRegion(e.composedPath())\n    ) {\n      // macOS maximization happens on `mouseup`,\n      // so we save needed state and early return\n      if (osName === 'macos' && e.detail === 2) {\n        initialX = e.clientX\n        initialY = e.clientY\n        return\n      }\n\n      // prevents text cursor\n      e.preventDefault()\n\n      // fix #2549: double click on drag region edge causes content to maximize without window sizing change\n      // https://github.com/tauri-apps/tauri/issues/2549#issuecomment-1250036908\n      e.stopImmediatePropagation()\n\n      // start dragging if the element has a `tauri-drag-region` data attribute and maximize on double-clicking it\n      const cmd = e.detail === 2 ? 'internal_toggle_maximize' : 'start_dragging'\n      window.__TAURI_INTERNALS__.invoke('plugin:window|' + cmd)\n    }\n  })\n\n  // on macOS we maximize on mouseup instead, to match the system behavior where maximization can be canceled\n  // if the mouse moves outside the data-tauri-drag-region\n  if (osName === 'macos') {\n    document.addEventListener('mouseup', (e) => {\n      if (\n        // was left mouse button\n        e.button === 0\n        // and was double click\n        && e.detail === 2\n        // and the cursor hasn't moved from initial mousedown\n        && e.clientX === initialX\n        && e.clientY === initialY\n        // and the event path contains a drag region (with no clickable element in between)\n        && isDragRegion(e.composedPath())\n      ) {\n        window.__TAURI_INTERNALS__.invoke(\n          'plugin:window|internal_toggle_maximize'\n        )\n      }\n    })\n  }\n})()\n"
  },
  {
    "path": "crates/tauri/test/api/test.txt",
    "content": "This is a test doc!"
  },
  {
    "path": "crates/tauri/test/fixture/config.json",
    "content": "{ \"devUrl\": \"http://localhost\" }\n"
  },
  {
    "path": "crates/tauri/test/fixture/dist/index.html",
    "content": "<html>\n  <head></head>\n  <body>\n    <iframe id=\"mainframe\"></iframe>\n  </body>\n</html>\n"
  },
  {
    "path": "crates/tauri/test/fixture/isolation/dist/index.html",
    "content": "<html>\n  <head></head>\n  <body>\n    <iframe id=\"mainframe\"></iframe>\n  </body>\n</html>\n"
  },
  {
    "path": "crates/tauri/test/fixture/isolation/isolation-dist/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Isolation Secure Script</title>\n  </head>\n  <body>\n    <script src=\"index.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "crates/tauri/test/fixture/isolation/isolation-dist/index.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\n  console.log('hook', payload, options)\n  return payload\n}\n"
  },
  {
    "path": "crates/tauri/test/fixture/isolation/src-tauri/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../../../../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"identifier\": \"isolation.tauri.example\",\n  \"build\": {\n    \"frontendDist\": \"../dist\",\n    \"devUrl\": \"http://localhost:4000\"\n  },\n  \"app\": {\n    \"windows\": [\n      {\n        \"title\": \"Isolation Tauri App\"\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; connect-src ipc: http://ipc.localhost\",\n      \"pattern\": {\n        \"use\": \"isolation\",\n        \"options\": {\n          \"dir\": \"../isolation-dist\"\n        }\n      }\n    }\n  },\n  \"bundle\": {\n    \"active\": true\n  }\n}\n"
  },
  {
    "path": "crates/tauri/test/fixture/src-tauri/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../../../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"identifier\": \"studio.tauri.example\",\n  \"build\": {\n    \"frontendDist\": \"../dist\",\n    \"devUrl\": \"http://localhost:4000\"\n  },\n  \"app\": {\n    \"windows\": [\n      {\n        \"title\": \"Tauri App\"\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; connect-src ipc: http://ipc.localhost\",\n      \"headers\": null\n    }\n  },\n  \"bundle\": {\n    \"active\": true\n  }\n}\n"
  },
  {
    "path": "crates/tauri/test/fixture/test.txt",
    "content": ""
  },
  {
    "path": "crates/tauri/test/updater/fixture/archives/archive.linux.tar.gz.sig",
    "content": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldSWTRKaFRZQmJER2NJZ21zb1RObVMwZmdkQUZ2OFFxM2dYdVhLRXpSNFp1VW03Zml2THlDRldpSmJPRkYyQlQ4QzltdkYxTG5MbzdWODZwdFN4aUNoWXY1V0lMem55T3djPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNjI5OTE2MjIwCWZpbGU6YXJjaGl2ZS5saW51eC50YXIuZ3oKZXExb3FCVGlxVkc3YThBVklQeVdhS0psOWROT3p4aVcwaE1ZaE9SRWVFU2lBdnRTVFd1SnJ4QjlTWW90d045UXFZZ1VROE1mUFJhUWxaUjVSaXAwREE9PQo="
  },
  {
    "path": "crates/tauri/test/updater/fixture/archives/archive.macos.tar.gz.sig",
    "content": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldSWTRKaFRZQmJER1VrQzlhaWFLOExCM3VoV1gxTW1IU0ZQQTZKTXN6U0MwMDEzcThyc3R3a0pmVkJrdWFhN1JCOTNpaEg5c1JhSGY0QWxROENlbTBxbFhpVTNWUWViWVFBPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNjE1Mzk5ODg3CWZpbGU6Li4vLi4vdGF1cmktdXBkYXRlci90ZXN0L2ZpeHR1cmUvYXJjaGl2ZXMvYXJjaGl2ZS50YXIuZ3oKczI0cUxjM2YvMGdpMDI1ejE3ZnREQTNtdlBwR2xydW5rVFBvcUxVcUt2dVpxZkpPd1ZLT1Z1K0hZS0hjRVk3Ylg4UVQxWEtWMHJ4ZUwwSXcvZjlaQmc9PQo="
  },
  {
    "path": "crates/tauri/test/updater/fixture/archives/archive.windows.zip.sig",
    "content": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVVZN3dSRm5KTmZPMlovQjAxdGtCN3YxYWJybFM2RzlxRm1rMnNkc20rbWRrS2d4ZlNJT0F4RUdrTFVGYWlUQVMxUE1VRk1uNW5IS2tucHNYNnRLZGdFPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkxODg2NjIzCWZpbGU6L1VzZXJzL2RhdmlkL2Rldi90YXVyaV91cGRhdGVyL3RhdXJpLXVwZGF0ZXIvdGVzdC9maXh0dXJlL2FyY2hpdmVzL2FyY2hpdmUuemlwCkNiNkJBKzNkR2M3WFhGTDgwSnB6NnpSYUl0VUFKVFNyRTJGVEdFbVkrNXZPTHBOQU1UeWIxbTB5QVpLRTBoM1NHeGs0RGxvTFRKY0FoWmVIS2psNkJBPT0K"
  },
  {
    "path": "crates/tauri/test/updater/fixture/bad_signature/update.key",
    "content": "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5dGlHbTEvRFhRRis2STdlTzF3eWhOVk9LNjdGRENJMnFSREE3R2V3b3Rwb0FBQkFBQUFBQUFBQUFBQUlBQUFBQWFNZEJTNXFuVjk0bmdJMENRRXVYNG5QVzBDd1NMOWN4Q2RKRXZxRDZNakw3Y241Vkt3aTg2WGtoajJGS1owV0ZuSmo4ZXJ0ZCtyaWF0RWJObFpnd1EveDB4NzBTU2RweG9ZaUpuc3hnQ3BYVG9HNnBXUW5SZ2Q3b3dvZ3Y2UnhQZ1BQZDU3bXl6d3M9Cg=="
  },
  {
    "path": "crates/tauri/test/updater/fixture/bad_signature/update.key.pub",
    "content": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEY1OTgwQzc0UjVGNjM0Q0IKUldUTE5QWWxkQnlZOWFBK21kekU4OGgzdStleEtkeStHaFR5NjEyRHovRnlUdzAwWGJxWEU2aGYK"
  },
  {
    "path": "crates/tauri/test/updater/fixture/good_signature/update.key",
    "content": "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg=="
  },
  {
    "path": "crates/tauri/test/updater/fixture/good_signature/update.key.pub",
    "content": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK"
  },
  {
    "path": "crates/tauri-build/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.5.6]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.3`\n- Upgraded to `tauri-codegen@2.5.5`\n\n## \\[2.5.5]\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.5.4`\n\n## \\[2.5.4]\n\n### Enhancements\n\n- [`2d28e3143`](https://www.github.com/tauri-apps/tauri/commit/2d28e3143ee3d97d7570ea03877aa00a0d6e47d0) ([#14632](https://www.github.com/tauri-apps/tauri/pull/14632) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Small code refactors for improved code readability. No user facing changes.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.2`\n- Upgraded to `tauri-codegen@2.5.3`\n\n## \\[2.5.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.1`\n- Upgraded to `tauri-codegen@2.5.2`\n\n## \\[2.5.2]\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.5.1`\n\n## \\[2.5.1]\n\n### Bug Fixes\n\n- [`4b6b8690a`](https://www.github.com/tauri-apps/tauri/commit/4b6b8690ab886ebdf1307951cffbe03e31280baa) ([#14347](https://www.github.com/tauri-apps/tauri/pull/14347) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused docs.rs builds to fail. No user facing changes.\n\n## \\[2.5.0]\n\n### New Features\n\n- [`3b4fac201`](https://www.github.com/tauri-apps/tauri/commit/3b4fac2017832d426dd07c5e24e26684eda57f7b) ([#14194](https://www.github.com/tauri-apps/tauri/pull/14194)) Add `tauri.conf.json > bundle > android > autoIncrementVersionCode` config option to automatically increment the Android version code.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.0`\n- Upgraded to `tauri-codegen@2.5.0`\n\n## \\[2.4.1]\n\n### Enhancements\n\n- [`c23bec62d`](https://www.github.com/tauri-apps/tauri/commit/c23bec62d6d5724798869681aa1534423aae28e2) ([#14083](https://www.github.com/tauri-apps/tauri/pull/14083) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Tauri now ignores `macOS.minimumSystemVersion` in `tauri dev` to prevent forced rebuilds of macOS specific dependencies when using something like `rust-analyzer` at the same time as `tauri dev`.\n\n## \\[2.4.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.7.0`\n- Upgraded to `tauri-codegen@2.4.0`\n\n## \\[2.3.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.6.0`\n- Upgraded to `tauri-codegen@2.3.1`\n\n## \\[2.2.1]\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.3.0`\n- Upgraded to `tauri-utils@2.5.0`\n\n## \\[2.2.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.4.0`\n- Upgraded to `tauri-codegen@2.2.0`\n- [`48b12b440`](https://www.github.com/tauri-apps/tauri/commit/48b12b440478937c46fdfef9f9d95194be117020) Update to `tauri-utils@2.4.0`\n\n## \\[2.1.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.1`\n- Upgraded to `tauri-codegen@2.1.1`\n\n## \\[2.1.0]\n\n### New Features\n\n- [`013f8f652`](https://www.github.com/tauri-apps/tauri/commit/013f8f652302f2d49c5ec0a075582033d8b074fb) ([#12890](https://www.github.com/tauri-apps/tauri/pull/12890) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Added `build > removeUnusedCommands` to trigger the build scripts and macros to remove unused commands based on the capabilities you defined. Note this won't be accounting for dynamically added ACLs so make sure to check it when using this.\n\n### Performance Improvements\n\n- [`1cd8f55ee`](https://www.github.com/tauri-apps/tauri/commit/1cd8f55eed326d61860fee62ba2d2f4464bdcfcc) ([#13033](https://www.github.com/tauri-apps/tauri/pull/13033) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Don't ship global `bundle.global.js` if `app > withGlobalTauri` is set to false\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.0`\n- Upgraded to `tauri-codegen@2.1.0`\n\n## \\[2.0.6]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.2.0`\n- Upgraded to `tauri-codegen@2.0.5`\n\n## \\[2.0.5]\n\n### Bug Fixes\n\n- [`848d0e060`](https://www.github.com/tauri-apps/tauri/commit/848d0e060e6eb3c8e9e8175adc7896587b5a947d) ([#12270](https://www.github.com/tauri-apps/tauri/pull/12270) by [@aurelj](https://www.github.com/tauri-apps/tauri/../../aurelj)) Update `cargo_toml` to `0.21.0`. This adds compatibility with Rust's 2024 Edition.\n- [`cd1d026f9`](https://www.github.com/tauri-apps/tauri/commit/cd1d026f9799c26b04acb64f49e7ee0a8b193049) ([#11961](https://www.github.com/tauri-apps/tauri/pull/11961) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix tauri fails to build if the project path contains glob characters\n\n## \\[2.0.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.1`\n- Upgraded to `tauri-codegen@2.0.4`\n\n## \\[2.0.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.0`\n- Upgraded to `tauri-codegen@2.0.3`\n\n## \\[2.0.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.2`\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.1`\n- Upgraded to `tauri-codegen@2.0.1`\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78.\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0`\n- Upgraded to `tauri-codegen@2.0.0`\n\n## \\[2.0.0-rc.13]\n\n### Bug Fixes\n\n- [`1efa5e718`](https://www.github.com/tauri-apps/tauri/commit/1efa5e7184009537b688a395596c696173ae0231) ([#11099](https://www.github.com/tauri-apps/tauri/pull/11099) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Rerun build script if the platform-specific configuration file changes.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.13`\n- Upgraded to `tauri-codegen@2.0.0-rc.13`\n\n## \\[2.0.0-rc.12]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.12`\n- Upgraded to `tauri-codegen@2.0.0-rc.12`\n\n## \\[2.0.0-rc.11]\n\n### Bug Fixes\n\n- [`9d468774a`](https://www.github.com/tauri-apps/tauri/commit/9d468774a94b5f5210a3012db2e58dbfab4755f4) ([#10975](https://www.github.com/tauri-apps/tauri/pull/10975) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The executable and NSIS installer on Windows will now use the `productName` config for the `FileDescription` property instead of `shortDescription`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.11`\n- Upgraded to `tauri-codegen@2.0.0-rc.11`\n\n## \\[2.0.0-rc.10]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.10`\n- Upgraded to `tauri-codegen@2.0.0-rc.10`\n\n## \\[2.0.0-rc.9]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.9`\n- Upgraded to `tauri-codegen@2.0.0-rc.9`\n\n## \\[2.0.0-rc.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.8`\n- Upgraded to `tauri-codegen@2.0.0-rc.8`\n\n## \\[2.0.0-rc.7]\n\n### New Features\n\n- [`ad83d41cb`](https://www.github.com/tauri-apps/tauri/commit/ad83d41cb5bc3bc3611a3dbaf0e355df3021dac0) ([#10743](https://www.github.com/tauri-apps/tauri/pull/10743) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WindowsAttributes::new_without_app_manifest` to create `WindowsAttributes` without the default manifest.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.7`\n- Upgraded to `tauri-codegen@2.0.0-rc.7`\n\n## \\[2.0.0-rc.6]\n\n### Bug Fixes\n\n- [`793ee0531`](https://www.github.com/tauri-apps/tauri/commit/793ee0531730597e6008c9c0dedabbab7a2bef53) ([#10700](https://www.github.com/tauri-apps/tauri/pull/10700) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow hyphens and underscores on app identifiers.\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.6`\n- Upgraded to `tauri-codegen@2.0.0-rc.6`\n\n## \\[2.0.0-rc.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.5`\n- Upgraded to `tauri-codegen@2.0.0-rc.5`\n\n## \\[2.0.0-rc.4]\n\n### Bug Fixes\n\n- [`5c335ae9a`](https://www.github.com/tauri-apps/tauri/commit/5c335ae9ad88e46c2135a557390f6e808c9a6088) ([#10648](https://www.github.com/tauri-apps/tauri/pull/10648) by [@Flakebi](https://www.github.com/tauri-apps/tauri/../../Flakebi)) Prevent build script from rerunning unnecessarily by only writing files when the content changes.\n- [`77844529f`](https://www.github.com/tauri-apps/tauri/commit/77844529f323434919ad6581d54cb2d97500cf4d) ([#10678](https://www.github.com/tauri-apps/tauri/pull/10678) by [@Norbiros](https://www.github.com/tauri-apps/tauri/../../Norbiros)) Correctly export `DefaultPermissionsRule`\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.4`\n- Upgraded to `tauri-codegen@2.0.0-rc.4`\n\n## \\[2.0.0-rc.3]\n\n### New Features\n\n- [`0bb7b0f35`](https://www.github.com/tauri-apps/tauri/commit/0bb7b0f352960fb5111a40157c0953d19e76f1fd) ([#10559](https://www.github.com/tauri-apps/tauri/pull/10559) by [@Norbiros](https://www.github.com/tauri-apps/tauri/../../Norbiros)) Added `InlinedPlugin::default_permission` to autogenerate the default permission of an inlined plugin.\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.0.0-rc.3`\n- Upgraded to `tauri-utils@2.0.0-rc.3`\n\n## \\[2.0.0-rc.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.2`\n- Upgraded to `tauri-codegen@2.0.0-rc.2`\n\n## \\[2.0.0-rc.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.1`\n- Upgraded to `tauri-codegen@2.0.0-rc.1`\n\n## \\[2.0.0-rc.0]\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.0.0-rc.0`\n- Upgraded to `tauri-utils@2.0.0-rc.0`\n\n## \\[2.0.0-beta.19]\n\n### Bug Fixes\n\n- [`69dcfdfe0`](https://www.github.com/tauri-apps/tauri/commit/69dcfdfe0f3b0570fcf5997267a7200087d5341b) ([#10267](https://www.github.com/tauri-apps/tauri/pull/10267) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix build script rerun-if-changed instruction if Info.plist do not exist next to tauri.conf.json.\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.0.0-beta.19`\n- Upgraded to `tauri-utils@2.0.0-beta.19`\n\n## \\[2.0.0-beta.18]\n\n### Enhancements\n\n- [`35110dba2`](https://www.github.com/tauri-apps/tauri/commit/35110dba21d7db0f155c45da58b41c9ca4d5853c) ([#10106](https://www.github.com/tauri-apps/tauri/pull/10106)) Fix delete app data button gone on higher scaling (>= 1.5)\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.18`\n- Upgraded to `tauri-codegen@2.0.0-beta.18`\n- [`f955f7b49`](https://www.github.com/tauri-apps/tauri/commit/f955f7b4903bcea376c0a8b430736f66c8cebf56) ([#9929](https://www.github.com/tauri-apps/tauri/pull/9929)) Switch from `dirs_next` to `dirs` as `dirs_next` is now unmaintained while `dirs` is\n\n## \\[2.0.0-beta.17]\n\n### Enhancements\n\n- [`adac2185a`](https://www.github.com/tauri-apps/tauri/commit/adac2185a3e2e65a89a3c392363c50ddde4acff2)([#9898](https://www.github.com/tauri-apps/tauri/pull/9898)) Check for Android version code before building the package in release mode.\n\n### Bug Fixes\n\n- [`19b696b61`](https://www.github.com/tauri-apps/tauri/commit/19b696b61c95ced0f07dd7471565ad329a0badcf)([#9710](https://www.github.com/tauri-apps/tauri/pull/9710)) Avoid copying resources if the target path is the same as source.\n\n### What's Changed\n\n- [`9ac930380`](https://www.github.com/tauri-apps/tauri/commit/9ac930380a5df3fe700e68e75df8684d261ca292)([#9850](https://www.github.com/tauri-apps/tauri/pull/9850)) Emit `cargo:rustc-check-cfg` instruction so Cargo validates custom cfg attributes on Rust 1.80 (or nightly-2024-05-05).\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.17`\n- Upgraded to `tauri-codegen@2.0.0-beta.17`\n\n### Breaking Changes\n\n- [`1df5cdeb0`](https://www.github.com/tauri-apps/tauri/commit/1df5cdeb06f5464e0eec4055e21b7b7bc8739eed)([#9858](https://www.github.com/tauri-apps/tauri/pull/9858)) Use `tauri.conf.json > identifier` to set the `PackageName` in Android and `BundleId` in iOS.\n- [`aaecb6a72`](https://www.github.com/tauri-apps/tauri/commit/aaecb6a72e5d1462967cc910c2628999997742d0)([#9890](https://www.github.com/tauri-apps/tauri/pull/9890)) Renamed `dev` function to `is_dev`\n\n## \\[2.0.0-beta.16]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.16`\n- Upgraded to `tauri-codegen@2.0.0-beta.16`\n\n## \\[2.0.0-beta.15]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.15`\n- Upgraded to `tauri-codegen@2.0.0-beta.15`\n\n## \\[2.0.0-beta.14]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.14`\n- Upgraded to `tauri-codegen@2.0.0-beta.14`\n\n## \\[2.0.0-beta.13]\n\n### Bug Fixes\n\n- [`88c0ad9cf`](https://www.github.com/tauri-apps/tauri/commit/88c0ad9cf5d2f9ed65285540c26b54fb18b10137)([#9471](https://www.github.com/tauri-apps/tauri/pull/9471)) Fix tauri always rebuilding even if source code didn't change.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.13`\n- Upgraded to `tauri-codegen@2.0.0-beta.13`\n\n## \\[2.0.0-beta.12]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.12`\n- Upgraded to `tauri-codegen@2.0.0-beta.12`\n\n## \\[2.0.0-beta.11]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.11`\n- Upgraded to `tauri-codegen@2.0.0-beta.11`\n\n## \\[2.0.0-beta.10]\n\n### New Features\n\n- [`e227fe02f`](https://www.github.com/tauri-apps/tauri/commit/e227fe02f986e145c0731a64693e1c830a9eb5b0)([#9156](https://www.github.com/tauri-apps/tauri/pull/9156)) Allow plugins to define (at compile time) JavaScript that are initialized when `withGlobalTauri` is true.\n\n### Enhancements\n\n- [`7213b9e47`](https://www.github.com/tauri-apps/tauri/commit/7213b9e47242bef814aa7257e0bf84631bf5fe7e)([#9124](https://www.github.com/tauri-apps/tauri/pull/9124)) Fallback to an empty permission set if the plugin did not define its `default` permissions.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.10`\n- Upgraded to `tauri-codegen@2.0.0-beta.10`\n\n## \\[2.0.0-beta.9]\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.0.0-beta.9`\n- Upgraded to `tauri-utils@2.0.0-beta.9`\n\n## \\[2.0.0-beta.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.8`\n- Upgraded to `tauri-codegen@2.0.0-beta.8`\n\n## \\[2.0.0-beta.7]\n\n### Bug Fixes\n\n- [`bb23511ea`](https://www.github.com/tauri-apps/tauri/commit/bb23511ea80bcaffbdebf057301e463fff268c90)([#9079](https://www.github.com/tauri-apps/tauri/pull/9079)) Fixed generation of capability schema for permissions field which previously disallowed mixed (strings and objects) permission definition.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.7`\n- Upgraded to `tauri-codegen@2.0.0-beta.7`\n\n## \\[2.0.0-beta.6]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.6`\n- Upgraded to `tauri-codegen@2.0.0-beta.6`\n\n### Breaking Changes\n\n- [`3657ad82`](https://www.github.com/tauri-apps/tauri/commit/3657ad82f88ce528551d032d521c52eed3f396b4)([#9008](https://www.github.com/tauri-apps/tauri/pull/9008)) Allow defining permissions for the application commands via `tauri_build::Attributes::app_manifest`.\n\n## \\[2.0.0-beta.5]\n\n### Breaking Changes\n\n- [`b9e6a018`](https://www.github.com/tauri-apps/tauri/commit/b9e6a01879d9233040f3d3fab11c59e70563da7e)([#8937](https://www.github.com/tauri-apps/tauri/pull/8937)) The `custom-protocol` Cargo feature is no longer required on your application and is now ignored. To check if running on production, use `#[cfg(not(dev))]` instead of `#[cfg(feature = \"custom-protocol\")]`.\n- [`b9e6a018`](https://www.github.com/tauri-apps/tauri/commit/b9e6a01879d9233040f3d3fab11c59e70563da7e)([#8937](https://www.github.com/tauri-apps/tauri/pull/8937)) Removed `tauri_build::CodegenContext::dev()` and added `tauri_build::dev()`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.5`\n- Upgraded to `tauri-codegen@2.0.0-beta.5`\n\n## \\[2.0.0-beta.4]\n\n### Enhancements\n\n- [`b5eb6472`](https://www.github.com/tauri-apps/tauri/commit/b5eb64728aeb410d3f3068608a94762655c4690f)([#8940](https://www.github.com/tauri-apps/tauri/pull/8940)) Enable Hight DPI awareness for NSIS installer so it is not blurry on some systems.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.4`\n- Upgraded to `tauri-codegen@2.0.0-beta.4`\n\n## \\[2.0.0-beta.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.3`\n- Upgraded to `tauri-codegen@2.0.0-beta.3`\n\n## \\[2.0.0-beta.2]\n\n### Enhancements\n\n- [`83a68deb`](https://www.github.com/tauri-apps/tauri/commit/83a68deb5676d39cd4728d2e140f6b46d5f787ed)([#8797](https://www.github.com/tauri-apps/tauri/pull/8797)) Added a new configuration option `tauri.conf.json > app > security > capabilities` to reference existing capabilities and inline new ones. If it is empty, all capabilities are still included preserving the current behavior.\n- [`edb11c13`](https://www.github.com/tauri-apps/tauri/commit/edb11c138def2e317099db432479e3ca5dbf803f)([#8781](https://www.github.com/tauri-apps/tauri/pull/8781)) Added `Attributes::plugin()` to register a plugin that is inlined in the application crate.\n- [`8d16a80d`](https://www.github.com/tauri-apps/tauri/commit/8d16a80d2fb2468667e7987d0cc99dbc7e3b9d0a)([#8802](https://www.github.com/tauri-apps/tauri/pull/8802)) Added `CodegenContext::capability` to include a capability file dynamically.\n\n### Bug Fixes\n\n- [`0e8e9cd0`](https://www.github.com/tauri-apps/tauri/commit/0e8e9cd064627e734adf8f62e571dc5f4e8f4d9f)([#8906](https://www.github.com/tauri-apps/tauri/pull/8906)) Fixes the capability schema not resolving inner definitions.\n- [`19fb5f0b`](https://www.github.com/tauri-apps/tauri/commit/19fb5f0b20479885bf8bc4fdd8c431052420191d)([#8782](https://www.github.com/tauri-apps/tauri/pull/8782)) Fix generating invalid schema files.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.2`\n- Upgraded to `tauri-codegen@2.0.0-beta.2`\n\n## \\[2.0.0-beta.1]\n\n### Enhancements\n\n- [`4e101f80`](https://www.github.com/tauri-apps/tauri/commit/4e101f801657e7d01ce8c22f9c6468067d0caab2)([#8756](https://www.github.com/tauri-apps/tauri/pull/8756)) Moved the capability JSON schema to the `src-tauri/gen` folder so it's easier to track changes on the `capabilities` folder.\n\n### Bug Fixes\n\n- [`4e101f80`](https://www.github.com/tauri-apps/tauri/commit/4e101f801657e7d01ce8c22f9c6468067d0caab2)([#8756](https://www.github.com/tauri-apps/tauri/pull/8756)) Do not trigger build script to rerun if the frontendDist directory does not exist.\n- [`0f2789cd`](https://www.github.com/tauri-apps/tauri/commit/0f2789cd6767e2eadbc4f7dfe32e2173e972b9a0)([#8757](https://www.github.com/tauri-apps/tauri/pull/8757)) Do not rewrite capability JSON schema if it did not change.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.1`\n- Upgraded to `tauri-codegen@2.0.0-beta.1`\n\n## \\[2.0.0-beta.0]\n\n### New Features\n\n- [`74a2a603`](https://www.github.com/tauri-apps/tauri/commit/74a2a6036a5e57462f161d728cbd8a6f121028ca)([#8661](https://www.github.com/tauri-apps/tauri/pull/8661)) Implement access control list for IPC usage.\n\n### Enhancements\n\n- [`e8d3793c`](https://www.github.com/tauri-apps/tauri/commit/e8d3793c3c34715569312a91633fde4d58d7621c)([#8732](https://www.github.com/tauri-apps/tauri/pull/8732)) Add `config-json` cargo feature flag (enabled by default) to. Disabling this feature flag will stop cargo from rebuilding when `tauri.conf.json` changes, see [#8721](https://github.com/tauri-apps/tauri/issues/8721) for more info.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.0`\n- Upgraded to `tauri-codegen@2.0.0-beta.0`\n\n### Breaking Changes\n\n- [`8de308d1`](https://www.github.com/tauri-apps/tauri/commit/8de308d1bf6a855d7a26af58bd0e744938ba47d8)([#8723](https://www.github.com/tauri-apps/tauri/pull/8723)) Restructured Tauri config per [RFC#5](https://github.com/tauri-apps/rfcs/blob/f3e82a6b0c5390401e855850d47dc7b7d9afd684/texts/0005-tauri-config-restructure.md):\n\n  - Moved `package.productName`, `package.version` and `tauri.bundle.identifier` fields to the top-level.\n  - Removed `package` object.\n  - Renamed `tauri` object to `app`.\n  - Moved `tauri.bundle` object to the top-level.\n  - Renamed `build.distDir` field to `frontendDist`.\n  - Renamed `build.devPath` field to `devUrl` and will no longer accepts paths, it will only accept URLs.\n  - Moved `tauri.pattern` to `app.security.pattern`.\n  - Removed `tauri.bundle.updater` object, and its fields have been moved to the updater plugin under `plugins.updater` object.\n  - Moved `build.withGlobalTauri` to `app.withGlobalTauri`.\n  - Moved `tauri.bundle.dmg` object to `bundle.macOS.dmg`.\n  - Moved `tauri.bundle.deb` object to `bundle.linux.deb`.\n  - Moved `tauri.bundle.appimage` object to `bundle.linux.appimage`.\n  - Removed all license fields from each bundle configuration object and instead added `bundle.license` and `bundle.licenseFile`.\n  - Renamed `AppUrl` to `FrontendDist` and refactored its variants to be more explicit.\n- [`0cdfda28`](https://www.github.com/tauri-apps/tauri/commit/0cdfda28767701369cd774e2b20d943c6ddc9f05)([#8737](https://www.github.com/tauri-apps/tauri/pull/8737)) Moved `mobile::PluginBuilder`, `mobile::update_entitlements`, `config::plugin_config` and `mobile::update_android_manifest` to the new `tauri-plugin` crate.\n- [`74a2a603`](https://www.github.com/tauri-apps/tauri/commit/74a2a6036a5e57462f161d728cbd8a6f121028ca)([#8661](https://www.github.com/tauri-apps/tauri/pull/8661)) `CodegenContext::build` and `CodegenContext::try_build` have been removed, use `tauri_build::try_build(tauri_build::Attributes::new().codegen(codegen))` instead.\n\n## \\[2.0.0-alpha.14]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.13`\n- Upgraded to `tauri-codegen@2.0.0-alpha.13`\n\n## \\[2.0.0-alpha.13]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.12`\n- Upgraded to `tauri-codegen@2.0.0-alpha.12`\n\n## \\[2.0.0-alpha.12]\n\n### Bug Fixes\n\n- [`a5479712`](https://www.github.com/tauri-apps/tauri/commit/a5479712095c224e2cb147d5c271acbc2fc97e79)([#8168](https://www.github.com/tauri-apps/tauri/pull/8168)) Fixed an issue that caused the resource compiler to not run on Windows when `package.version` was not set in `tauri.conf.json` preventing the app from starting.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.11`\n- Upgraded to `tauri-codegen@2.0.0-alpha.11`\n\n## \\[2.0.0-alpha.11]\n\n### Enhancements\n\n- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release.\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.0.0-alpha.10`\n- Upgraded to `tauri-utils@2.0.0-alpha.10`\n\n## \\[2.0.0-alpha.10]\n\n### New Features\n\n- [`880266a7`](https://www.github.com/tauri-apps/tauri/commit/880266a7f697e1fe58d685de3bb6836ce5251e92)([#8031](https://www.github.com/tauri-apps/tauri/pull/8031)) Bump the MSRV to 1.70.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.9`\n- Upgraded to `tauri-codegen@2.0.0-alpha.9`\n\n### Breaking Changes\n\n- [`ebcc21e4`](https://www.github.com/tauri-apps/tauri/commit/ebcc21e4b95f4e8c27639fb1bca545b432f52d5e)([#8057](https://www.github.com/tauri-apps/tauri/pull/8057)) Renamed the beforeDevCommand, beforeBuildCommand and beforeBundleCommand hooks environment variables from `TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG` to `TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG` to differentiate the prefix with other CLI environment variables.\n\n## \\[2.0.0-alpha.9]\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.0.0-alpha.8`\n- Upgraded to `tauri-utils@2.0.0-alpha.8`\n\n## \\[2.0.0-alpha.8]\n\n### Bug Fixes\n\n- [`560b34dd`](https://www.github.com/tauri-apps/tauri/commit/560b34dd2a194ad62db09b3e9e41a2cfff4e5710)([#7610](https://www.github.com/tauri-apps/tauri/pull/7610)) Skip validation of the `tray-icon` feature flag.\n\n## \\[2.0.0-alpha.7]\n\n### New Features\n\n- [`522de0e7`](https://www.github.com/tauri-apps/tauri/commit/522de0e78891d0bdf6387a5118985fc41a11baeb)([#7447](https://www.github.com/tauri-apps/tauri/pull/7447)) Added the `config::plugin_config` function to read the plugin configuration set from the CLI.\n- [`1e1d839e`](https://www.github.com/tauri-apps/tauri/commit/1e1d839e7e3d9496f71b6bc1336ced01f2965541)([#7450](https://www.github.com/tauri-apps/tauri/pull/7450)) Added the `mobile::update_android_manifest` function.\n- [`aba04fa8`](https://www.github.com/tauri-apps/tauri/commit/aba04fa823d70ff8df9bd22f8e6a25184689c3cb)([#7448](https://www.github.com/tauri-apps/tauri/pull/7448)) Added the `mobile::update_entitlements` function for iOS.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.7`\n- Upgraded to `tauri-codegen@2.0.0-alpha.7`\n\n## \\[2.0.0-alpha.6]\n\n### Bug Fixes\n\n- [`3256a372`](https://www.github.com/tauri-apps/tauri/commit/3256a37263f60eafdf5a8321458b868bff26c1b8)([#7016](https://www.github.com/tauri-apps/tauri/pull/7016)) Fixes injection of the proguard rules on the Android project.\n\n## \\[2.0.0-alpha.5]\n\n- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65.\n- [`2969d1cb`](https://www.github.com/tauri-apps/tauri/commit/2969d1cbba39301f9cc611d9f7d7051d80eef846)([#6773](https://www.github.com/tauri-apps/tauri/pull/6773)) Use absolute path to each Android plugin project instead of copying the files to enhance developer experience.\n- [`cdad6e08`](https://www.github.com/tauri-apps/tauri/commit/cdad6e083728ea61bd6fc734ef93f6306056ea2e)([#6774](https://www.github.com/tauri-apps/tauri/pull/6774)) Changed how the `tauri-android` dependency is injected. This requires the `gen/android` project to be recreated.\n- [`5a768d5c`](https://www.github.com/tauri-apps/tauri/commit/5a768d5ce69d6c9011c41f38a43481087c8d4921)([#6886](https://www.github.com/tauri-apps/tauri/pull/6886)) Remove `WindowsAttributes::sdk_dir`.\n\n## \\[2.0.0-alpha.4]\n\n- Added `android` configuration object under `tauri > bundle`.\n  - Bumped due to a bump in tauri-utils.\n  - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07\n\n## \\[2.0.0-alpha.3]\n\n- Read the `IPHONEOS_DEPLOYMENT_TARGET` environment variable to set the Swift iOS target version, defaults to 13.\n  - [4c3b9ecf](https://www.github.com/tauri-apps/tauri/commit/4c3b9ecfdcd1a4489b1e466727f11045ef34d67a) fix(build): iOS deployment target env var is IPHONEOS_DEPLOYMENT_TARGET ([#6602](https://www.github.com/tauri-apps/tauri/pull/6602)) on 2023-03-31\n\n## \\[2.0.0-alpha.2]\n\n- Add `mobile::PluginBuilder` for running build tasks related to Tauri plugins.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n\n## \\[2.0.0-alpha.1]\n\n- Refactor mobile environment variables.\n  - [dee9460f](https://www.github.com/tauri-apps/tauri/commit/dee9460f9c9bc92e9c638e7691e616849ac2085b) feat: keep CLI alive when iOS app exits, show logs, closes [#5855](https://www.github.com/tauri-apps/tauri/pull/5855) ([#5902](https://www.github.com/tauri-apps/tauri/pull/5902)) on 2022-12-27\n- Bump the MSRV to 1.64.\n  - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30\n- Removed mobile logging initialization, which will be handled by `tauri-plugin-log`.\n  - [](https://www.github.com/tauri-apps/tauri/commit/undefined)  on undefined\n\n## \\[2.0.0-alpha.0]\n\n- Set environment variables used by `tauri::mobile_entry_point`.\n  - [98904863](https://www.github.com/tauri-apps/tauri/commit/9890486321c9c79ccfb7c547fafee85b5c3ffa71) feat(core): add `mobile_entry_point` macro ([#4983](https://www.github.com/tauri-apps/tauri/pull/4983)) on 2022-08-21\n- First mobile alpha release!\n  - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08\n\n## \\[1.5.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.2`\n- Upgraded to `tauri-codegen@1.4.2`\n\n## \\[1.5.0]\n\n### What's Changed\n\n- [`d1e09da0`](https://www.github.com/tauri-apps/tauri/commit/d1e09da084b849b9e384fc27ed250dd17e72c7a3)([#7918](https://www.github.com/tauri-apps/tauri/pull/7918)) Bump to 1.5 due to tauri-utils dependency bump.\n\n## \\[1.4.1]\n\n### Bug Fixes\n\n- [`5ecb46b3`](https://www.github.com/tauri-apps/tauri/commit/5ecb46b3410afd1b5c82494c1e0a91d5a358c41a)([#7773](https://www.github.com/tauri-apps/tauri/pull/7773)) Automatically set rpath on macOS if frameworks are bundled and copy frameworks to `src-tauri/target/Frameworks` for usage in development.\n- [`290e366a`](https://www.github.com/tauri-apps/tauri/commit/290e366ae98e9a52b1b43bfd3e285150427ebffa)([#7419](https://www.github.com/tauri-apps/tauri/pull/7419)) Correctly copy the WebView2 runtime in development when `webviewInstallMode` is used instead of `webviewFixedRuntimePath`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.0`\n- Upgraded to `tauri-codegen@1.4.1`\n\n## \\[1.4.0]\n\n### Enhancements\n\n- [`52474e47`](https://www.github.com/tauri-apps/tauri/commit/52474e479d695865299d8c8d868fb98b99731020)([#7141](https://www.github.com/tauri-apps/tauri/pull/7141)) Enhance Cargo features check.\n- [`af937290`](https://www.github.com/tauri-apps/tauri/commit/af93729031565a69d1fde6cf16bea3b9b6e43a65)([#6676](https://www.github.com/tauri-apps/tauri/pull/6676)) On Windows, set `LegalCopyright` and `FileDescription` file properties on the executable from `tauri.bundle.copyright` and `tauri.bundle.shortDescription`,\n- [`d2710e9d`](https://www.github.com/tauri-apps/tauri/commit/d2710e9d2e8fd93975ef6494512370faa8cb3b7e)([#6944](https://www.github.com/tauri-apps/tauri/pull/6944)) Unpin `time`, `ignore`, and `winnow` crate versions. Developers now have to pin crates if needed themselves. A list of crates that need pinning to adhere to Tauri's MSRV will be visible in Tauri's GitHub workflow: <https://github.com/tauri-apps/tauri/blob/dev/.github/workflows/test-core.yml#L85>.\n\n## \\[1.3.0]\n\n- Bump minimum supported Rust version to 1.60.\n  - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31\n- Add initial support for building `nsis` bundles on non-Windows platforms.\n  - [60e6f6c3](https://www.github.com/tauri-apps/tauri/commit/60e6f6c3f1605f3064b5bb177992530ff788ccf0) feat(bundler): Add support for creating NSIS bundles on unix hosts ([#5788](https://www.github.com/tauri-apps/tauri/pull/5788)) on 2023-01-19\n- Add `WindowsAttributes::app_manifest` to specify the application manifest on Windows.\n  - [bca09f7f](https://www.github.com/tauri-apps/tauri/commit/bca09f7f5ff1c9c5a4b51da043bdd5da668a179b) feat(tauri-build): add option to specify Windows manifest, closes [#5584](https://www.github.com/tauri-apps/tauri/pull/5584) ([#5730](https://www.github.com/tauri-apps/tauri/pull/5730)) on 2022-12-14\n- Added support for Cargo's workspace inheritance for package information. The cli now also detects inherited `tauri` and `tauri-build` dependencies and disables manifest rewrites accordingly.\n  - [cd8c074a](https://www.github.com/tauri-apps/tauri/commit/cd8c074ae6592303d3f6844a4fb6d262eae913b2) feat(cli): add support for Cargo's workspace inheritance for the package version, closes [#5070](https://www.github.com/tauri-apps/tauri/pull/5070) ([#5775](https://www.github.com/tauri-apps/tauri/pull/5775)) on 2022-12-14\n  - [d20a7288](https://www.github.com/tauri-apps/tauri/commit/d20a728892eee1858ab525ab6216cd721f473ab5) feat: Further improve workspace inheritance, closes [#6122](https://www.github.com/tauri-apps/tauri/pull/6122), [#5070](https://www.github.com/tauri-apps/tauri/pull/5070) ([#6144](https://www.github.com/tauri-apps/tauri/pull/6144)) on 2023-01-26\n- Pin `winnow` crate to 0.4.1 to keep the 1.60 MSRV.\n\n## \\[1.2.1]\n\n- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`.\n  - Bumped due to a bump in tauri-utils.\n  - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18\n\n## \\[1.2.0]\n\n- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28\n\n## \\[1.1.1]\n\n- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs.\n  - Bumped due to a bump in tauri-utils.\n  - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16\n\n## \\[1.1.0]\n\n- Rerun codegen if assets or icons change.\n  - [ff8fd761](https://www.github.com/tauri-apps/tauri/commit/ff8fd7619ae894b70f149b192d8635d842827141) fix(tauri-build): rerun if assets or icons change ([#4910](https://www.github.com/tauri-apps/tauri/pull/4910)) on 2022-08-10\n- Create the `desktop` and `mobile` cfg aliases.\n  - [c04d0340](https://www.github.com/tauri-apps/tauri/commit/c04d0340e2f163483f3556c7aabe35322ee71e6a) feat(core): prepare build for mobile targets ([#4830](https://www.github.com/tauri-apps/tauri/pull/4830)) on 2022-08-02\n- Added support to configuration files in TOML format (Tauri.toml file).\n  - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02\n- Enhance the dialog style on Windows via the manifest dependency `Microsoft.Windows.Common-Controls v6.0.0.0`.\n  - [5c5c42ed](https://www.github.com/tauri-apps/tauri/commit/5c5c42edb64adf4250af6891d7d226fda7d4d783) feat(build): use modern dialog styles on Windows, closes [#4709](https://www.github.com/tauri-apps/tauri/pull/4709) ([#4840](https://www.github.com/tauri-apps/tauri/pull/4840)) on 2022-08-02\n- Fix root of codegen output when using the `CodegenContext` API.\n  - [ed581950](https://www.github.com/tauri-apps/tauri/commit/ed581950ea6fd0afee47aa73fb63083d2483429f) fix(tauri-build): use `::tauri` as root for the CodegenContext ([#4894](https://www.github.com/tauri-apps/tauri/pull/4894)) on 2022-08-08\n- Return an error if a sidecar is configured with the same file name as the application.\n  - [5cc1fd0f](https://www.github.com/tauri-apps/tauri/commit/5cc1fd0f7b01b257a461d4e867ddc8cd5072ff15) feat(tauri-build): validate sidecar name, closes [#4780](https://www.github.com/tauri-apps/tauri/pull/4780) closes [#4823](https://www.github.com/tauri-apps/tauri/pull/4823) ([#4814](https://www.github.com/tauri-apps/tauri/pull/4814)) on 2022-08-02\n- Only rewrite temporary icon files when the content change, avoid needless rebuilds.\n  - [f957cbb5](https://www.github.com/tauri-apps/tauri/commit/f957cbb56ccbd8d1c047a978b4579946252a6fd2) fix(codegen): write output file when contents change ([#4889](https://www.github.com/tauri-apps/tauri/pull/4889)) on 2022-08-09\n\n## \\[1.0.4]\n\n- Reduce the amount of allocations when converting cases.\n  - [bc370e32](https://www.github.com/tauri-apps/tauri/commit/bc370e326810446e15b1f50fb962b980114ba16b) feat: reduce the amount of `heck`-related allocations ([#4634](https://www.github.com/tauri-apps/tauri/pull/4634)) on 2022-07-11\n\n## \\[1.0.3]\n\n- Improve configuration deserialization error messages.\n  - [9170c920](https://www.github.com/tauri-apps/tauri/commit/9170c9207044fa561535f624916dfdbaa41ff79d) feat(core): improve config deserialization error messages ([#4607](https://www.github.com/tauri-apps/tauri/pull/4607)) on 2022-07-06\n- The `TAURI_CONFIG` environment variable now represents the configuration to be merged instead of the entire JSON.\n  - [fa028ebf](https://www.github.com/tauri-apps/tauri/commit/fa028ebf3c8ca7b43a70d283a01dbea86217594f) refactor: do not pass entire config from CLI to core, send patch instead ([#4598](https://www.github.com/tauri-apps/tauri/pull/4598)) on 2022-07-06\n\n## \\[1.0.2]\n\n- Expose `platform::windows_version` function.\n  - Bumped due to a bump in tauri-utils.\n  - [bf764e83](https://www.github.com/tauri-apps/tauri/commit/bf764e83e01e7443e6cc54572001e1c98c357465) feat(utils): expose `windows_version` function ([#4534](https://www.github.com/tauri-apps/tauri/pull/4534)) on 2022-06-30\n\n## \\[1.0.1]\n\n- Changed the `BundleConfig::targets` to a `BundleTarget` enum to enhance generated documentation.\n  - Bumped due to a bump in tauri-utils.\n  - [31c15cd2](https://www.github.com/tauri-apps/tauri/commit/31c15cd2bd94dbe39fb94982a15cbe02ac5d8925) docs(config): enhance documentation for bundle targets, closes [#3251](https://www.github.com/tauri-apps/tauri/pull/3251) ([#4418](https://www.github.com/tauri-apps/tauri/pull/4418)) on 2022-06-21\n- Added `platform::is_windows_7`.\n  - Bumped due to a bump in tauri-utils.\n  - [57039fb2](https://www.github.com/tauri-apps/tauri/commit/57039fb2166571de85271b014a8711a29f06be1a) fix(core): add windows 7 notification support ([#4491](https://www.github.com/tauri-apps/tauri/pull/4491)) on 2022-06-28\n- Suppress unused variable warning in release builds.\n  - Bumped due to a bump in tauri-utils.\n  - [45981851](https://www.github.com/tauri-apps/tauri/commit/45981851e35119266c1a079e1ff27a39f1fdfaed) chore(lint): unused variable warnings for release builds ([#4411](https://www.github.com/tauri-apps/tauri/pull/4411)) on 2022-06-22\n- Added webview install mode options.\n  - Bumped due to a bump in tauri-utils.\n  - [2ca762d2](https://www.github.com/tauri-apps/tauri/commit/2ca762d207030a892a6d128b519e150e2d733468) feat(bundler): extend webview2 installation options, closes [#2882](https://www.github.com/tauri-apps/tauri/pull/2882) [#2452](https://www.github.com/tauri-apps/tauri/pull/2452) ([#4466](https://www.github.com/tauri-apps/tauri/pull/4466)) on 2022-06-26\n\n## \\[1.0.0]\n\n- Upgrade to `stable`!\n  - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15\n\n## \\[1.0.0-rc.15]\n\n- Read the tray icon path relatively to the config directory.\n  - Bumped due to a bump in tauri-codegen.\n  - [562e8ca2](https://www.github.com/tauri-apps/tauri/commit/562e8ca23facf1a8e5fa6c8cdf872357d3523a78) fix(codegen): tray icon path is relative to the config directory on 2022-06-15\n\n## \\[1.0.0-rc.14]\n\n- Do not copy the tray icon to the output directory on Linux since it is embedded in the binary.\n  - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14\n\n## \\[1.0.0-rc.13]\n\n- Copy `tauri.conf.json > tauri.bundle.windows.webview_fixed_runtime_path` as a resource to the target directory to fix development usage of a fixed Webview2 runtime path.\n  - [8a634895](https://www.github.com/tauri-apps/tauri/commit/8a63489567b9fa86e404ad42b5b30c64361efe85) fix(build): fixed Webview2 runtime path in development, closes [#4308](https://www.github.com/tauri-apps/tauri/pull/4308) on 2022-06-10\n- Improve usage of the GNU toolchain on Windows by copying the Webview2Loader.dll file to the target directory.\n  - [58a6879b](https://www.github.com/tauri-apps/tauri/commit/58a6879b82e3a82027604cdd0913caacaaab5c76) feat(tauri-build): improve Windows GNU toolchain usage, closes [#4319](https://www.github.com/tauri-apps/tauri/pull/4319) ([#4323](https://www.github.com/tauri-apps/tauri/pull/4323)) on 2022-06-12\n- Only statically link the VC runtime when the `STATIC_VCRUNTIME` environment variable is set to `true` (automatically done by the Tauri CLI).\n  - [d703d27a](https://www.github.com/tauri-apps/tauri/commit/d703d27a707edc028f13b35603205da1133fcc2b) fix(build): statically link VC runtime only on `tauri build` ([#4292](https://www.github.com/tauri-apps/tauri/pull/4292)) on 2022-06-07\n\n## \\[1.0.0-rc.12]\n\n- Statically link the Visual C++ runtime instead of using a merge module on the installer.\n  - [bb061509](https://www.github.com/tauri-apps/tauri/commit/bb061509fb674bef86ecbc1de3aa8f3e367a9907) refactor(core): statically link vcruntime, closes [#4122](https://www.github.com/tauri-apps/tauri/pull/4122) ([#4227](https://www.github.com/tauri-apps/tauri/pull/4227)) on 2022-05-27\n\n## \\[1.0.0-rc.11]\n\n- Create `dev` cfg alias.\n  - [9cdcf9b3](https://www.github.com/tauri-apps/tauri/commit/9cdcf9b3a8fa27612b3156c1702a4e776627e869) feat(build): create `dev` alias ([#4212](https://www.github.com/tauri-apps/tauri/pull/4212)) on 2022-05-25\n\n## \\[1.0.0-rc.10]\n\n- Delete existing sidecar before copying new one.\n  - [a737f25c](https://www.github.com/tauri-apps/tauri/commit/a737f25c1078083e0b0f7f338f5c958b86914323) fix(tauri-build): delete existing sidecar file, closes [#4134](https://www.github.com/tauri-apps/tauri/pull/4134) ([#4167](https://www.github.com/tauri-apps/tauri/pull/4167)) on 2022-05-18\n\n## \\[1.0.0-rc.9]\n\n- Search `tauri.conf.json > tauri > bundle > icons` for a `.ico` file for the window icon instead of simple default `icons/icon.ico` when `WindowsAttributes::window_icon_path` is not set.\n  - [bad85a1f](https://www.github.com/tauri-apps/tauri/commit/bad85a1f11da03421401080531275ba201480cd1) feat(build): find .ico in config instead of default `icons/icon.ico` ([#4115](https://www.github.com/tauri-apps/tauri/pull/4115)) on 2022-05-13\n\n## \\[1.0.0-rc.8]\n\n- Properly set file version information for the Windows executable.\n  - [1ca2dd67](https://www.github.com/tauri-apps/tauri/commit/1ca2dd677d50b4c724c55b37060c3ba64b81c54a) fix(tauri-build): properly set executable version info on Windows ([#4045](https://www.github.com/tauri-apps/tauri/pull/4045)) on 2022-05-03\n\n## \\[1.0.0-rc.7]\n\n- Rerun build script if `TAURI_CONFIG` environment variable change.\n  - [7ae9e252](https://www.github.com/tauri-apps/tauri/commit/7ae9e25262376529637cd3b47b1cf84809efaec9) fix(tauri-build): rerun if `TAURI_CONFIG` env var changes on 2022-04-26\n\n## \\[1.0.0-rc.6]\n\n- Copy system tray icon resource to the target directory on Linux.\n  - [f2a30d8b](https://www.github.com/tauri-apps/tauri/commit/f2a30d8bc54fc3ba49e16f69a413eca5f61a9b1f) refactor(core): use ayatana appindicator by default, keep option to use gtk ([#3916](https://www.github.com/tauri-apps/tauri/pull/3916)) on 2022-04-19\n\n## \\[1.0.0-rc.5]\n\n- Print error context on the `build` panic.\n  - [49546c52](https://www.github.com/tauri-apps/tauri/commit/49546c5269080f38d57365788eb2592bff8f6d10) feat(build): print error context ([#3644](https://www.github.com/tauri-apps/tauri/pull/3644)) on 2022-03-09\n\n## \\[1.0.0-rc.4]\n\n- Parse window icons at compile time.\n  - Bumped due to a bump in tauri-codegen.\n  - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05\n\n## \\[1.0.0-rc.3]\n\n- Automatically emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET` with the value set on `tauri.conf.json > tauri > bundle > macos > minimumSystemVersion`.\n  - [4bacea5b](https://www.github.com/tauri-apps/tauri/commit/4bacea5bf48ea5ca6c9bdd10e28e85e67a0c6ef9) feat(core): set `MACOSX_DEPLOYMENT_TARGET` environment variable, closes [#2732](https://www.github.com/tauri-apps/tauri/pull/2732) ([#3496](https://www.github.com/tauri-apps/tauri/pull/3496)) on 2022-02-17\n\n## \\[1.0.0-rc.2]\n\n- Rerun if sidecar or resource change.\n  - [afcc3ec5](https://www.github.com/tauri-apps/tauri/commit/afcc3ec50148074293350aaa26a05812207716be) fix(build): rerun if resource or sidecar change ([#3460](https://www.github.com/tauri-apps/tauri/pull/3460)) on 2022-02-14\n\n## \\[1.0.0-rc.1]\n\n- Remove `cargo:rerun-if-changed` check for non-existent file that caused projects to *always* rebuild.\n  - [65287cd6](https://www.github.com/tauri-apps/tauri/commit/65287cd6148feeba91df86217b261770fed34608) remove non-existent cargo rerun check ([#3412](https://www.github.com/tauri-apps/tauri/pull/3412)) on 2022-02-11\n\n## \\[1.0.0-rc.0]\n\n- Allow user to specify windows sdk path in build.rs.\n  - [59b6ee87](https://www.github.com/tauri-apps/tauri/commit/59b6ee87932d341433032befe3babd897ed8f7d0) fix(tauri-build): allow user to specify win sdk path (fix: [#2871](https://www.github.com/tauri-apps/tauri/pull/2871)) ([#2893](https://www.github.com/tauri-apps/tauri/pull/2893)) on 2021-11-16\n- Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension.\n\nHere is the logic flow that determines if JSON or JSON5 will be used to parse the config:\n\n1. Check if `tauri.conf.json` exists\n   a. Parse it with `serde_json`\n   b. Parse it with `json5` if `serde_json` fails\n   c. Return original `serde_json` error if all above steps failed\n2. Check if `tauri.conf.json5` exists\n   a. Parse it with `json5`\n   b. Return error if all above steps failed\n3. Return error if all above steps failed\n\n- [995de57a](https://www.github.com/tauri-apps/tauri/commit/995de57a76cf51215277673e526d7ec32b86b564) Add seamless support for using JSON5 in the config file ([#47](https://www.github.com/tauri-apps/tauri/pull/47)) on 2022-02-03\n- Move the copying of resources and sidecars from `cli.rs` to `tauri-build` so using the Cargo CLI directly processes the files for the application execution in development.\n  - [5eb72c24](https://www.github.com/tauri-apps/tauri/commit/5eb72c24deddf5a01093bea96b90c0d8806afc3f) refactor: copy resources and sidecars on the Cargo build script ([#3357](https://www.github.com/tauri-apps/tauri/pull/3357)) on 2022-02-08\n- The minimum Rust version is now `1.56`.\n  - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22\n- Validate `tauri` dependency `features` under `Cargo.toml` matching `tauri.conf.json`'s `allowlist`.\n  - [4de285c3](https://www.github.com/tauri-apps/tauri/commit/4de285c3967d32250d73acdd5d171a6fd332d2b3) feat(core): validate Cargo features matching allowlist \\[TRI-023] on 2022-01-09\n\n## \\[1.0.0-beta.4]\n\n- Implement `Debug` on public API structs and enums.\n  - [fa9341ba](https://www.github.com/tauri-apps/tauri/commit/fa9341ba18ba227735341530900714dba0f27291) feat(core): implement `Debug` on public API structs/enums, closes [#2292](https://www.github.com/tauri-apps/tauri/pull/2292) ([#2387](https://www.github.com/tauri-apps/tauri/pull/2387)) on 2021-08-11\n\n## \\[1.0.0-beta.3]\n\n- Improve ESM detection with regexes.\n  - Bumped due to a bump in tauri-codegen.\n  - [4b0ec018](https://www.github.com/tauri-apps/tauri/commit/4b0ec0188078a8fefd4119fe5e19ebc30191f802) fix(core): improve JS ESM detection ([#2139](https://www.github.com/tauri-apps/tauri/pull/2139)) on 2021-07-02\n- Inject invoke key on `script` tags with `type=\"module\"`.\n  - Bumped due to a bump in tauri-codegen.\n  - [f03eea9c](https://www.github.com/tauri-apps/tauri/commit/f03eea9c9b964709532afbc4d1dd343b3fd96081) feat(core): inject invoke key on `<script type=\"module\">` ([#2120](https://www.github.com/tauri-apps/tauri/pull/2120)) on 2021-06-29\n\n## \\[1.0.0-beta.2]\n\n- Detect ESM scripts and inject the invoke key directly instead of using an IIFE.\n  - Bumped due to a bump in tauri-codegen.\n  - [7765c7fa](https://www.github.com/tauri-apps/tauri/commit/7765c7fa281853ddfb26b6b17534df95eaede804) fix(core): invoke key injection on ES module, improve performance ([#2094](https://www.github.com/tauri-apps/tauri/pull/2094)) on 2021-06-27\n- Improve invoke key code injection performance time rewriting code at compile time.\n  - Bumped due to a bump in tauri-codegen.\n  - [7765c7fa](https://www.github.com/tauri-apps/tauri/commit/7765c7fa281853ddfb26b6b17534df95eaede804) fix(core): invoke key injection on ES module, improve performance ([#2094](https://www.github.com/tauri-apps/tauri/pull/2094)) on 2021-06-27\n\n## \\[1.0.0-beta.1]\n\n- Pull Windows resource information (`FileVersion`, `ProductVersion`, `ProductName` and `FileDescription`) from `tauri.conf.json > package` configuration.\n  - [dc6b0d85](https://www.github.com/tauri-apps/tauri/commit/dc6b0d8522ca9f0962aa7c6fe446743889470b8c) feat(core): set .rc values from tauri.conf.json, closes [#1849](https://www.github.com/tauri-apps/tauri/pull/1849) ([#1951](https://www.github.com/tauri-apps/tauri/pull/1951)) on 2021-06-05\n\n## \\[1.0.0-beta.0]\n\n- The `try_build` method now has a `Attributes` argument to allow specifying the window icon path used on Windows.\n  - [c91105f](https://www.github.com/tauri-apps/tauri/commit/c91105ff965cceb2dfb402004c12799bf3b1c2f6) refactor(build): allow setting window icon path on `try_build` ([#1686](https://www.github.com/tauri-apps/tauri/pull/1686)) on 2021-05-03\n\n## \\[1.0.0-beta-rc.1]\n\n- The package info APIs now checks the `package` object on `tauri.conf.json`.\n  - Bumped due to a bump in tauri-codegen.\n  - [8fd1baf](https://www.github.com/tauri-apps/tauri/commit/8fd1baf69b14bb81d7be9d31605ed7f02058b392) fix(core): pull package info from tauri.conf.json if set ([#1581](https://www.github.com/tauri-apps/tauri/pull/1581)) on 2021-04-22\n  - [f575aaa](https://www.github.com/tauri-apps/tauri/commit/f575aaad71f23d44b2f89cf9ee5d84817dc3bb7a) fix: change files not referencing core packages ([#1619](https://www.github.com/tauri-apps/tauri/pull/1619)) on 2021-04-25\n\n## \\[1.0.0-beta-rc.0]\n\n- Update all code files to have our license header.\n  - [bf82136](https://www.github.com/tauri-apps/tauri/commit/bf8213646689175f8a158b956911f3a43e360690) feat(license): SPDX Headers ([#1449](https://www.github.com/tauri-apps/tauri/pull/1449)) on 2021-04-11\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n"
  },
  {
    "path": "crates/tauri-build/Cargo.toml",
    "content": "[package]\nname = \"tauri-build\"\nversion = \"2.5.6\"\ndescription = \"build time code to pair with https://crates.io/crates/tauri\"\nexclude = [\"CHANGELOG.md\", \"/target\"]\nreadme = \"README.md\"\nauthors.workspace = true\nhomepage.workspace = true\nrepository.workspace = true\ncategories.workspace = true\nlicense.workspace = true\nedition.workspace = true\nrust-version.workspace = true\n\n[package.metadata.docs.rs]\nall-features = true\ndefault-target = \"x86_64-unknown-linux-gnu\"\ntargets = [\n  \"x86_64-pc-windows-msvc\",\n  \"x86_64-unknown-linux-gnu\",\n  \"x86_64-apple-darwin\",\n  \"x86_64-linux-android\",\n  \"x86_64-apple-ios\",\n]\n\n[dependencies]\nanyhow = \"1\"\nquote = { version = \"1\", optional = true }\ntauri-codegen = { version = \"2.5.5\", path = \"../tauri-codegen\", optional = true }\ntauri-utils = { version = \"2.8.3\", path = \"../tauri-utils\", features = [\n  \"build\",\n  \"resources\",\n] }\ncargo_toml = \"0.22\"\nserde = \"1\"\nserde_json = \"1\"\nheck = \"0.5\"\njson-patch = \"3\"\nwalkdir = \"2\"\ntauri-winres = \"0.3\"\nsemver = \"1\"\ndirs = \"6\"\nglob = \"0.3\"\n# Our code requires at least 0.8.21 so don't simplify this to 0.8\nschemars = { version = \"0.8.21\", features = [\"preserve_order\"] }\n\n[features]\ndefault = [\"config-json\"]\ncodegen = [\"tauri-codegen\", \"quote\"]\nisolation = [\"tauri-codegen/isolation\", \"tauri-utils/isolation\"]\nconfig-json = []\nconfig-json5 = [\"tauri-utils/config-json5\"]\nconfig-toml = [\"tauri-utils/config-toml\"]\n"
  },
  {
    "path": "crates/tauri-build/LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/tauri-build/LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2017 - Present Tauri Apps Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "crates/tauri-build/README.md",
    "content": "# tauri-build\n\n <img align=\"right\" src=\"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\" height=\"128\" width=\"128\">\n\n[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev)\n[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri)\n[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield)\n[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S)\n[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app)\n[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)\n[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri)\n\n| Component   | Version                                                                                                    |\n| ----------- | ---------------------------------------------------------------------------------------------------------- |\n| tauri-build | [![](https://img.shields.io/crates/v/tauri-build?style=flat-square)](https://crates.io/crates/tauri-build) |\n\n## About Tauri\n\nTauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.\n\nTauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.\n\n## This module\n\nThis applies the macros at build-time in order to rig some special features needed by `cargo`.\n\nTo learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.\n\n## Semver\n\n**tauri** is following [Semantic Versioning 2.0](https://semver.org/).\n\n## Licenses\n\nCode: (c) 2021 - The Tauri Programme within The Commons Conservancy.\n\nMIT or MIT/Apache 2.0 where applicable.\n\nLogo: CC-BY-NC-ND\n\n- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)\n"
  },
  {
    "path": "crates/tauri-build/src/acl.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  collections::{BTreeMap, HashMap},\n  env, fs,\n  path::{Path, PathBuf},\n};\n\nuse anyhow::{Context, Result};\nuse tauri_utils::{\n  acl::{\n    capability::Capability,\n    manifest::{Manifest, PermissionFile},\n    schema::CAPABILITIES_SCHEMA_FOLDER_PATH,\n    ACL_MANIFESTS_FILE_NAME, APP_ACL_KEY, CAPABILITIES_FILE_NAME,\n  },\n  platform::Target,\n  write_if_changed,\n};\n\nuse crate::Attributes;\n\n/// Definition of a plugin that is part of the Tauri application instead of having its own crate.\n///\n/// By default it generates a plugin manifest that parses permissions from the `permissions/$plugin-name` directory.\n/// To change the glob pattern that is used to find permissions, use [`Self::permissions_path_pattern`].\n///\n/// To autogenerate permissions for each of the plugin commands, see [`Self::commands`].\n#[derive(Debug, Default, Clone)]\npub struct InlinedPlugin {\n  commands: &'static [&'static str],\n  permissions_path_pattern: Option<&'static str>,\n  default: Option<DefaultPermissionRule>,\n}\n\n/// Variants of a generated default permission that can be used on an [`InlinedPlugin`].\n#[derive(Debug, Clone)]\npub enum DefaultPermissionRule {\n  /// Allow all commands from [`InlinedPlugin::commands`].\n  AllowAllCommands,\n  /// Allow the given list of permissions.\n  ///\n  /// Note that the list refers to permissions instead of command names,\n  /// so for example a command called `execute` would need to be allowed as `allow-execute`.\n  Allow(Vec<String>),\n}\n\nimpl InlinedPlugin {\n  pub fn new() -> Self {\n    Self::default()\n  }\n\n  /// Define a list of commands that gets permissions autogenerated in the format of `allow-$command` and `deny-$command`\n  /// where $command is the command name in snake_case.\n  pub fn commands(mut self, commands: &'static [&'static str]) -> Self {\n    self.commands = commands;\n    self\n  }\n\n  /// Sets a glob pattern that is used to find the permissions of this inlined plugin.\n  ///\n  /// **Note:** You must emit [rerun-if-changed] instructions for the plugin permissions directory.\n  ///\n  /// By default it is `./permissions/$plugin-name/**/*`\n  pub fn permissions_path_pattern(mut self, pattern: &'static str) -> Self {\n    self.permissions_path_pattern.replace(pattern);\n    self\n  }\n\n  /// Creates a default permission for the plugin using the given rule.\n  ///\n  /// Alternatively you can pull a permission in the filesystem in the permissions directory, see [`Self::permissions_path_pattern`].\n  pub fn default_permission(mut self, default: DefaultPermissionRule) -> Self {\n    self.default.replace(default);\n    self\n  }\n}\n\n/// Tauri application permission manifest.\n///\n/// By default it generates a manifest that parses permissions from the `permissions` directory.\n/// To change the glob pattern that is used to find permissions, use [`Self::permissions_path_pattern`].\n///\n/// To autogenerate permissions for each of the app commands, see [`Self::commands`].\n#[derive(Debug, Default, Clone, Copy)]\npub struct AppManifest {\n  commands: &'static [&'static str],\n  permissions_path_pattern: Option<&'static str>,\n}\n\nimpl AppManifest {\n  pub fn new() -> Self {\n    Self::default()\n  }\n\n  /// Define a list of commands that gets permissions autogenerated in the format of `allow-$command` and `deny-$command`\n  /// where $command is the command name in snake_case.\n  pub fn commands(mut self, commands: &'static [&'static str]) -> Self {\n    self.commands = commands;\n    self\n  }\n\n  /// Sets a glob pattern that is used to find the permissions of the app.\n  ///\n  /// **Note:** You must emit [rerun-if-changed] instructions for the permissions directory.\n  ///\n  /// By default it is `./permissions/**/*` ignoring any [`InlinedPlugin`].\n  pub fn permissions_path_pattern(mut self, pattern: &'static str) -> Self {\n    self.permissions_path_pattern.replace(pattern);\n    self\n  }\n}\n\n/// Saves capabilities in a file inside the project, mainly to be read by tauri-cli.\nfn save_capabilities(capabilities: &BTreeMap<String, Capability>) -> Result<PathBuf> {\n  let dir = Path::new(CAPABILITIES_SCHEMA_FOLDER_PATH);\n  fs::create_dir_all(dir)?;\n\n  let path = dir.join(CAPABILITIES_FILE_NAME);\n  let json = serde_json::to_string(&capabilities)?;\n  write_if_changed(&path, json)?;\n\n  Ok(path)\n}\n\n/// Saves ACL manifests in a file inside the project, mainly to be read by tauri-cli.\nfn save_acl_manifests(acl_manifests: &BTreeMap<String, Manifest>) -> Result<PathBuf> {\n  let dir = Path::new(CAPABILITIES_SCHEMA_FOLDER_PATH);\n  fs::create_dir_all(dir)?;\n\n  let path = dir.join(ACL_MANIFESTS_FILE_NAME);\n  let json = serde_json::to_string(&acl_manifests)?;\n  write_if_changed(&path, json)?;\n\n  Ok(path)\n}\n\n/// Read plugin permissions and scope schema from env vars\nfn read_plugins_manifests() -> Result<BTreeMap<String, Manifest>> {\n  use tauri_utils::acl;\n\n  let permission_map =\n    acl::build::read_permissions().context(\"failed to read plugin permissions\")?;\n  let mut global_scope_map =\n    acl::build::read_global_scope_schemas().context(\"failed to read global scope schemas\")?;\n\n  let mut manifests = BTreeMap::new();\n\n  for (plugin_name, permission_files) in permission_map {\n    let global_scope_schema = global_scope_map.remove(&plugin_name);\n    let manifest = Manifest::new(permission_files, global_scope_schema);\n    manifests.insert(plugin_name, manifest);\n  }\n\n  Ok(manifests)\n}\n\nstruct InlinedPluginsAcl {\n  manifests: BTreeMap<String, Manifest>,\n  permission_files: BTreeMap<String, Vec<PermissionFile>>,\n}\n\nfn inline_plugins(\n  out_dir: &Path,\n  inlined_plugins: HashMap<&'static str, InlinedPlugin>,\n) -> Result<InlinedPluginsAcl> {\n  let mut acl_manifests = BTreeMap::new();\n  let mut permission_files_map = BTreeMap::new();\n\n  for (name, plugin) in inlined_plugins {\n    let plugin_out_dir = out_dir.join(\"plugins\").join(name);\n    fs::create_dir_all(&plugin_out_dir)?;\n\n    let mut permission_files = if plugin.commands.is_empty() {\n      Vec::new()\n    } else {\n      let autogenerated = tauri_utils::acl::build::autogenerate_command_permissions(\n        &plugin_out_dir,\n        plugin.commands,\n        \"\",\n        false,\n      );\n\n      let default_permissions = plugin.default.map(|default| match default {\n        DefaultPermissionRule::AllowAllCommands => autogenerated.allowed,\n        DefaultPermissionRule::Allow(permissions) => permissions,\n      });\n      if let Some(default_permissions) = default_permissions {\n        let default_permissions = default_permissions\n          .iter()\n          .map(|p| format!(\"\\\"{p}\\\"\"))\n          .collect::<Vec<String>>()\n          .join(\",\");\n        let default_permission = format!(\n          r###\"# Automatically generated - DO NOT EDIT!\n[default]\npermissions = [{default_permissions}]\n\"###\n        );\n\n        let default_permission_path = plugin_out_dir.join(\"default.toml\");\n\n        write_if_changed(&default_permission_path, default_permission)\n          .unwrap_or_else(|_| panic!(\"unable to autogenerate {default_permission_path:?}\"));\n      }\n\n      tauri_utils::acl::build::define_permissions(\n        &PathBuf::from(glob::Pattern::escape(&plugin_out_dir.to_string_lossy()))\n          .join(\"*\")\n          .to_string_lossy(),\n        name,\n        &plugin_out_dir,\n        |_| true,\n      )?\n    };\n\n    if let Some(pattern) = plugin.permissions_path_pattern {\n      permission_files.extend(tauri_utils::acl::build::define_permissions(\n        pattern,\n        name,\n        &plugin_out_dir,\n        |_| true,\n      )?);\n    } else {\n      let default_permissions_path = Path::new(\"permissions\").join(name);\n      if default_permissions_path.exists() {\n        println!(\n          \"cargo:rerun-if-changed={}\",\n          default_permissions_path.display()\n        );\n      }\n      permission_files.extend(tauri_utils::acl::build::define_permissions(\n        &PathBuf::from(glob::Pattern::escape(\n          &default_permissions_path.to_string_lossy(),\n        ))\n        .join(\"**\")\n        .join(\"*\")\n        .to_string_lossy(),\n        name,\n        &plugin_out_dir,\n        |_| true,\n      )?);\n    }\n\n    permission_files_map.insert(name.into(), permission_files.clone());\n\n    let manifest = tauri_utils::acl::manifest::Manifest::new(permission_files, None);\n    acl_manifests.insert(name.into(), manifest);\n  }\n\n  Ok(InlinedPluginsAcl {\n    manifests: acl_manifests,\n    permission_files: permission_files_map,\n  })\n}\n\n#[derive(Debug)]\nstruct AppManifestAcl {\n  manifest: Manifest,\n  permission_files: Vec<PermissionFile>,\n}\n\nfn app_manifest_permissions(\n  out_dir: &Path,\n  manifest: AppManifest,\n  inlined_plugins: &HashMap<&'static str, InlinedPlugin>,\n) -> Result<AppManifestAcl> {\n  let app_out_dir = out_dir.join(\"app-manifest\");\n  fs::create_dir_all(&app_out_dir)?;\n  let pkg_name = \"__app__\";\n\n  let mut permission_files = if manifest.commands.is_empty() {\n    Vec::new()\n  } else {\n    let autogenerated_path = Path::new(\"./permissions/autogenerated\");\n    tauri_utils::acl::build::autogenerate_command_permissions(\n      autogenerated_path,\n      manifest.commands,\n      \"\",\n      false,\n    );\n    tauri_utils::acl::build::define_permissions(\n      &autogenerated_path.join(\"*\").to_string_lossy(),\n      pkg_name,\n      &app_out_dir,\n      |_| true,\n    )?\n  };\n\n  if let Some(pattern) = manifest.permissions_path_pattern {\n    permission_files.extend(tauri_utils::acl::build::define_permissions(\n      pattern,\n      pkg_name,\n      &app_out_dir,\n      |_| true,\n    )?);\n  } else {\n    let default_permissions_path = Path::new(\"permissions\");\n    if default_permissions_path.exists() {\n      println!(\n        \"cargo:rerun-if-changed={}\",\n        default_permissions_path.display()\n      );\n    }\n\n    let permissions_root = env::current_dir()?.join(\"permissions\");\n    let inlined_plugins_permissions: Vec<_> = inlined_plugins\n      .keys()\n      .map(|name| permissions_root.join(name))\n      .flat_map(|p| p.canonicalize())\n      .collect();\n\n    permission_files.extend(tauri_utils::acl::build::define_permissions(\n      &default_permissions_path\n        .join(\"**\")\n        .join(\"*\")\n        .to_string_lossy(),\n      pkg_name,\n      &app_out_dir,\n      // filter out directories containing inlined plugins\n      |p| {\n        !inlined_plugins_permissions\n          .iter()\n          .any(|inlined_path| p.starts_with(inlined_path))\n      },\n    )?);\n  }\n\n  Ok(AppManifestAcl {\n    permission_files: permission_files.clone(),\n    manifest: tauri_utils::acl::manifest::Manifest::new(permission_files, None),\n  })\n}\n\nfn validate_capabilities(\n  acl_manifests: &BTreeMap<String, Manifest>,\n  capabilities: &BTreeMap<String, Capability>,\n) -> Result<()> {\n  let target = tauri_utils::platform::Target::from_triple(&std::env::var(\"TARGET\").unwrap());\n\n  for capability in capabilities.values() {\n    if !capability\n      .platforms\n      .as_ref()\n      .map(|platforms| platforms.contains(&target))\n      .unwrap_or(true)\n    {\n      continue;\n    }\n\n    for permission_entry in &capability.permissions {\n      let permission_id = permission_entry.identifier();\n\n      let key = permission_id.get_prefix().unwrap_or(APP_ACL_KEY);\n      let permission_name = permission_id.get_base();\n\n      let permission_exists = acl_manifests\n        .get(key)\n        .map(|manifest| {\n          // the default permission is always treated as valid, the CLI automatically adds it on the `tauri add` command\n          permission_name == \"default\"\n            || manifest.permissions.contains_key(permission_name)\n            || manifest.permission_sets.contains_key(permission_name)\n        })\n        .unwrap_or(false);\n\n      if !permission_exists {\n        let mut available_permissions = Vec::new();\n        for (key, manifest) in acl_manifests {\n          let prefix = if key == APP_ACL_KEY {\n            \"\".to_string()\n          } else {\n            format!(\"{key}:\")\n          };\n          if manifest.default_permission.is_some() {\n            available_permissions.push(format!(\"{prefix}default\"));\n          }\n          for p in manifest.permissions.keys() {\n            available_permissions.push(format!(\"{prefix}{p}\"));\n          }\n          for p in manifest.permission_sets.keys() {\n            available_permissions.push(format!(\"{prefix}{p}\"));\n          }\n        }\n\n        anyhow::bail!(\n          \"Permission {} not found, expected one of {}\",\n          permission_id.get(),\n          available_permissions.join(\", \")\n        );\n      }\n    }\n  }\n\n  Ok(())\n}\n\npub fn build(out_dir: &Path, target: Target, attributes: &Attributes) -> super::Result<()> {\n  let mut acl_manifests = read_plugins_manifests()?;\n\n  let app_acl = app_manifest_permissions(\n    out_dir,\n    attributes.app_manifest,\n    &attributes.inlined_plugins,\n  )?;\n  let has_app_manifest = app_acl.manifest.default_permission.is_some()\n    || !app_acl.manifest.permission_sets.is_empty()\n    || !app_acl.manifest.permissions.is_empty();\n  if has_app_manifest {\n    acl_manifests.insert(APP_ACL_KEY.into(), app_acl.manifest);\n  }\n\n  let inline_plugins_acl = inline_plugins(out_dir, attributes.inlined_plugins.clone())?;\n\n  acl_manifests.extend(inline_plugins_acl.manifests);\n\n  let acl_manifests_path = save_acl_manifests(&acl_manifests)?;\n  fs::copy(acl_manifests_path, out_dir.join(ACL_MANIFESTS_FILE_NAME))?;\n\n  tauri_utils::acl::schema::generate_capability_schema(&acl_manifests, target)?;\n\n  let capabilities = if let Some(pattern) = attributes.capabilities_path_pattern {\n    tauri_utils::acl::build::parse_capabilities(pattern)?\n  } else {\n    println!(\"cargo:rerun-if-changed=capabilities\");\n    tauri_utils::acl::build::parse_capabilities(\"./capabilities/**/*\")?\n  };\n  validate_capabilities(&acl_manifests, &capabilities)?;\n\n  let capabilities_path = save_capabilities(&capabilities)?;\n  fs::copy(capabilities_path, out_dir.join(CAPABILITIES_FILE_NAME))?;\n\n  let mut permissions_map = inline_plugins_acl.permission_files;\n  if has_app_manifest {\n    permissions_map.insert(APP_ACL_KEY.to_string(), app_acl.permission_files);\n  }\n\n  tauri_utils::acl::build::generate_allowed_commands(out_dir, Some(capabilities), permissions_map)?;\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-build/src/codegen/context.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse anyhow::{Context, Result};\nuse std::{\n  env::var,\n  fs::{create_dir_all, File},\n  io::{BufWriter, Write},\n  path::{Path, PathBuf},\n};\nuse tauri_codegen::{context_codegen, ContextData};\nuse tauri_utils::config::FrontendDist;\n\n// TODO docs\n/// A builder for generating a Tauri application context during compile time.\n#[cfg_attr(docsrs, doc(cfg(feature = \"codegen\")))]\n#[derive(Debug)]\npub struct CodegenContext {\n  config_path: PathBuf,\n  out_file: PathBuf,\n  capabilities: Option<Vec<PathBuf>>,\n}\n\nimpl Default for CodegenContext {\n  fn default() -> Self {\n    Self {\n      config_path: PathBuf::from(\"tauri.conf.json\"),\n      out_file: PathBuf::from(\"tauri-build-context.rs\"),\n      capabilities: None,\n    }\n  }\n}\n\nimpl CodegenContext {\n  /// Create a new [`CodegenContext`] builder that is already filled with the default options.\n  pub fn new() -> Self {\n    Self::default()\n  }\n\n  /// Set the path to the `tauri.conf.json` (relative to the package's directory).\n  ///\n  /// This defaults to a file called `tauri.conf.json` inside of the current working directory of\n  /// the package compiling; does not need to be set manually if that config file is in the same\n  /// directory as your `Cargo.toml`.\n  #[must_use]\n  pub fn config_path(mut self, config_path: impl Into<PathBuf>) -> Self {\n    self.config_path = config_path.into();\n    self\n  }\n\n  /// Sets the output file's path.\n  ///\n  /// **Note:** This path should be relative to the `OUT_DIR`.\n  ///\n  /// Don't set this if you are using [`tauri::tauri_build_context!`] as that helper macro\n  /// expects the default value. This option can be useful if you are not using the helper and\n  /// instead using [`std::include!`] on the generated code yourself.\n  ///\n  /// Defaults to `tauri-build-context.rs`.\n  ///\n  /// [`tauri::tauri_build_context!`]: https://docs.rs/tauri/latest/tauri/macro.tauri_build_context.html\n  #[must_use]\n  pub fn out_file(mut self, filename: PathBuf) -> Self {\n    self.out_file = filename;\n    self\n  }\n\n  /// Adds a capability file to the generated context.\n  #[must_use]\n  pub fn capability<P: AsRef<Path>>(mut self, path: P) -> Self {\n    self\n      .capabilities\n      .get_or_insert_with(Default::default)\n      .push(path.as_ref().to_path_buf());\n    self\n  }\n\n  /// Generate the code and write it to the output file - returning the path it was saved to.\n  ///\n  /// Unless you are doing something special with this builder, you don't need to do anything with\n  /// the returned output path.\n  pub(crate) fn try_build(self) -> Result<PathBuf> {\n    let (config, config_parent) = tauri_codegen::get_config(&self.config_path)?;\n\n    // rerun if changed\n    match &config.build.frontend_dist {\n      Some(FrontendDist::Directory(p)) => {\n        let dist_path = config_parent.join(p);\n        if dist_path.exists() {\n          println!(\"cargo:rerun-if-changed={}\", dist_path.display());\n        }\n      }\n      Some(FrontendDist::Files(files)) => {\n        for path in files {\n          println!(\n            \"cargo:rerun-if-changed={}\",\n            config_parent.join(path).display()\n          );\n        }\n      }\n      _ => (),\n    }\n    for icon in &config.bundle.icon {\n      println!(\n        \"cargo:rerun-if-changed={}\",\n        config_parent.join(icon).display()\n      );\n    }\n    if let Some(tray_icon) = config.app.tray_icon.as_ref().map(|t| &t.icon_path) {\n      println!(\n        \"cargo:rerun-if-changed={}\",\n        config_parent.join(tray_icon).display()\n      );\n    }\n\n    #[cfg(target_os = \"macos\")]\n    {\n      let info_plist_path = config_parent.join(\"Info.plist\");\n      if info_plist_path.exists() {\n        println!(\"cargo:rerun-if-changed={}\", info_plist_path.display());\n      }\n\n      if let Some(plist_path) = &config.bundle.macos.info_plist {\n        let info_plist_path = config_parent.join(plist_path);\n        if info_plist_path.exists() {\n          println!(\"cargo:rerun-if-changed={}\", info_plist_path.display());\n        }\n      }\n    }\n\n    let code = context_codegen(ContextData {\n      dev: crate::is_dev(),\n      config,\n      config_parent,\n      // it's very hard to have a build script for unit tests, so assume this is always called from\n      // outside the tauri crate, making the ::tauri root valid.\n      root: quote::quote!(::tauri),\n      capabilities: self.capabilities,\n      assets: None,\n      test: false,\n    })?;\n\n    // get the full output file path\n    let out = var(\"OUT_DIR\")\n      .map(PathBuf::from)\n      .map(|path| path.join(&self.out_file))\n      .with_context(|| \"unable to find OUT_DIR during tauri-build\")?;\n\n    // make sure any nested directories in OUT_DIR are created\n    let parent = out.parent().with_context(|| {\n      \"`Codegen` could not find the parent to `out_file` while creating the file\"\n    })?;\n    create_dir_all(parent)?;\n\n    let mut file = File::create(&out).map(BufWriter::new).with_context(|| {\n      format!(\n        \"Unable to create output file during tauri-build {}\",\n        out.display()\n      )\n    })?;\n\n    writeln!(file, \"{code}\").with_context(|| {\n      format!(\n        \"Unable to write tokenstream to out file during tauri-build {}\",\n        out.display()\n      )\n    })?;\n\n    Ok(out)\n  }\n}\n"
  },
  {
    "path": "crates/tauri-build/src/codegen/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npub(crate) mod context;\n"
  },
  {
    "path": "crates/tauri-build/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! This applies the macros at build-time in order to rig some special features needed by `cargo`.\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n#![cfg_attr(docsrs, feature(doc_cfg))]\n\nuse anyhow::Context;\npub use anyhow::Result;\nuse cargo_toml::Manifest;\n\nuse tauri_utils::{\n  config::{BundleResources, Config, WebviewInstallMode},\n  resources::{external_binaries, ResourcePaths},\n};\n\nuse std::{\n  collections::HashMap,\n  env, fs,\n  path::{Path, PathBuf},\n};\n\nmod acl;\n#[cfg(feature = \"codegen\")]\nmod codegen;\nmod manifest;\nmod mobile;\nmod static_vcruntime;\n\n#[cfg(feature = \"codegen\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"codegen\")))]\npub use codegen::context::CodegenContext;\n\npub use acl::{AppManifest, DefaultPermissionRule, InlinedPlugin};\n\nfn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {\n  let from = from.as_ref();\n  let to = to.as_ref();\n  if !from.exists() {\n    return Err(anyhow::anyhow!(\"{:?} does not exist\", from));\n  }\n  if !from.is_file() {\n    return Err(anyhow::anyhow!(\"{:?} is not a file\", from));\n  }\n  let dest_dir = to.parent().expect(\"No data in parent\");\n  fs::create_dir_all(dest_dir)?;\n  fs::copy(from, to)?;\n  Ok(())\n}\n\nfn copy_binaries(\n  binaries: ResourcePaths,\n  target_triple: &str,\n  path: &Path,\n  package_name: Option<&str>,\n) -> Result<()> {\n  for src in binaries {\n    let src = src?;\n    println!(\"cargo:rerun-if-changed={}\", src.display());\n    let file_name = src\n      .file_name()\n      .expect(\"failed to extract external binary filename\")\n      .to_string_lossy()\n      .replace(&format!(\"-{target_triple}\"), \"\");\n\n    if package_name == Some(&file_name) {\n      return Err(anyhow::anyhow!(\n        \"Cannot define a sidecar with the same name as the Cargo package name `{}`. Please change the sidecar name in the filesystem and the Tauri configuration.\",\n        file_name\n      ));\n    }\n\n    let dest = path.join(file_name);\n    if dest.exists() {\n      fs::remove_file(&dest).unwrap();\n    }\n    copy_file(&src, &dest)?;\n  }\n  Ok(())\n}\n\n/// Copies resources to a path.\nfn copy_resources(resources: ResourcePaths<'_>, path: &Path) -> Result<()> {\n  let path = path.canonicalize()?;\n  for resource in resources.iter() {\n    let resource = resource?;\n\n    println!(\"cargo:rerun-if-changed={}\", resource.path().display());\n\n    // avoid copying the resource if target is the same as source\n    let src = resource.path().canonicalize()?;\n    let target = path.join(resource.target());\n    if src != target {\n      copy_file(src, target)?;\n    }\n  }\n  Ok(())\n}\n\n#[cfg(unix)]\nfn symlink_dir(src: &Path, dst: &Path) -> std::io::Result<()> {\n  std::os::unix::fs::symlink(src, dst)\n}\n\n/// Makes a symbolic link to a directory.\n#[cfg(windows)]\nfn symlink_dir(src: &Path, dst: &Path) -> std::io::Result<()> {\n  std::os::windows::fs::symlink_dir(src, dst)\n}\n\n/// Makes a symbolic link to a file.\n#[cfg(unix)]\nfn symlink_file(src: &Path, dst: &Path) -> std::io::Result<()> {\n  std::os::unix::fs::symlink(src, dst)\n}\n\n/// Makes a symbolic link to a file.\n#[cfg(windows)]\nfn symlink_file(src: &Path, dst: &Path) -> std::io::Result<()> {\n  std::os::windows::fs::symlink_file(src, dst)\n}\n\nfn copy_dir(from: &Path, to: &Path) -> Result<()> {\n  for entry in walkdir::WalkDir::new(from) {\n    let entry = entry?;\n    debug_assert!(entry.path().starts_with(from));\n    let rel_path = entry.path().strip_prefix(from)?;\n    let dest_path = to.join(rel_path);\n    if entry.file_type().is_symlink() {\n      let target = fs::read_link(entry.path())?;\n      if entry.path().is_dir() {\n        symlink_dir(&target, &dest_path)?;\n      } else {\n        symlink_file(&target, &dest_path)?;\n      }\n    } else if entry.file_type().is_dir() {\n      fs::create_dir(dest_path)?;\n    } else {\n      fs::copy(entry.path(), dest_path)?;\n    }\n  }\n  Ok(())\n}\n\n// Copies the framework under `{src_dir}/{framework}.framework` to `{dest_dir}/{framework}.framework`.\nfn copy_framework_from(src_dir: &Path, framework: &str, dest_dir: &Path) -> Result<bool> {\n  let src_name = format!(\"{framework}.framework\");\n  let src_path = src_dir.join(&src_name);\n  if src_path.exists() {\n    copy_dir(&src_path, &dest_dir.join(&src_name))?;\n    Ok(true)\n  } else {\n    Ok(false)\n  }\n}\n\n// Copies the macOS application bundle frameworks to the target folder\nfn copy_frameworks(dest_dir: &Path, frameworks: &[String]) -> Result<()> {\n  fs::create_dir_all(dest_dir)\n    .with_context(|| format!(\"Failed to create frameworks output directory at {dest_dir:?}\"))?;\n  for framework in frameworks.iter() {\n    if framework.ends_with(\".framework\") {\n      let src_path = Path::new(framework);\n      let src_name = src_path\n        .file_name()\n        .expect(\"Couldn't get framework filename\");\n      let dest_path = dest_dir.join(src_name);\n      copy_dir(src_path, &dest_path)?;\n      continue;\n    } else if framework.ends_with(\".dylib\") {\n      let src_path = Path::new(framework);\n      if !src_path.exists() {\n        return Err(anyhow::anyhow!(\"Library not found: {}\", framework));\n      }\n      let src_name = src_path.file_name().expect(\"Couldn't get library filename\");\n      let dest_path = dest_dir.join(src_name);\n      copy_file(src_path, &dest_path)?;\n      continue;\n    } else if framework.contains('/') {\n      return Err(anyhow::anyhow!(\n        \"Framework path should have .framework extension: {}\",\n        framework\n      ));\n    }\n    if let Some(home_dir) = dirs::home_dir() {\n      if copy_framework_from(&home_dir.join(\"Library/Frameworks/\"), framework, dest_dir)? {\n        continue;\n      }\n    }\n    if copy_framework_from(\"/Library/Frameworks/\".as_ref(), framework, dest_dir)?\n      || copy_framework_from(\"/Network/Library/Frameworks/\".as_ref(), framework, dest_dir)?\n    {\n      continue;\n    }\n  }\n  Ok(())\n}\n\n// creates a cfg alias if `has_feature` is true.\n// `alias` must be a snake case string.\nfn cfg_alias(alias: &str, has_feature: bool) {\n  println!(\"cargo:rustc-check-cfg=cfg({alias})\");\n  if has_feature {\n    println!(\"cargo:rustc-cfg={alias}\");\n  }\n}\n\n/// Attributes used on Windows.\n#[allow(dead_code)]\n#[derive(Debug)]\npub struct WindowsAttributes {\n  window_icon_path: Option<PathBuf>,\n  /// A string containing an [application manifest] to be included with the application on Windows.\n  ///\n  /// Defaults to:\n  /// ```text\n  #[doc = include_str!(\"windows-app-manifest.xml\")]\n  /// ```\n  ///\n  /// ## Warning\n  ///\n  /// if you are using tauri's dialog APIs, you need to specify a dependency on Common Control v6 by adding the following to your custom manifest:\n  /// ```text\n  ///  <dependency>\n  ///    <dependentAssembly>\n  ///      <assemblyIdentity\n  ///        type=\"win32\"\n  ///        name=\"Microsoft.Windows.Common-Controls\"\n  ///        version=\"6.0.0.0\"\n  ///        processorArchitecture=\"*\"\n  ///        publicKeyToken=\"6595b64144ccf1df\"\n  ///        language=\"*\"\n  ///      />\n  ///    </dependentAssembly>\n  ///  </dependency>\n  /// ```\n  ///\n  /// [application manifest]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests\n  app_manifest: Option<String>,\n}\n\nimpl Default for WindowsAttributes {\n  fn default() -> Self {\n    Self::new()\n  }\n}\n\nimpl WindowsAttributes {\n  /// Creates the default attribute set.\n  pub fn new() -> Self {\n    Self {\n      window_icon_path: Default::default(),\n      app_manifest: Some(include_str!(\"windows-app-manifest.xml\").into()),\n    }\n  }\n\n  /// Creates the default attribute set without the default app manifest.\n  #[must_use]\n  pub fn new_without_app_manifest() -> Self {\n    Self {\n      app_manifest: None,\n      window_icon_path: Default::default(),\n    }\n  }\n\n  /// Sets the icon to use on the window. Currently only used on Windows.\n  /// It must be in `ico` format. Defaults to `icons/icon.ico`.\n  #[must_use]\n  pub fn window_icon_path<P: AsRef<Path>>(mut self, window_icon_path: P) -> Self {\n    self\n      .window_icon_path\n      .replace(window_icon_path.as_ref().into());\n    self\n  }\n\n  /// Sets the [application manifest] to be included with the application on Windows.\n  ///\n  /// Defaults to:\n  /// ```text\n  #[doc = include_str!(\"windows-app-manifest.xml\")]\n  /// ```\n  ///\n  /// ## Warning\n  ///\n  /// if you are using tauri's dialog APIs, you need to specify a dependency on Common Control v6 by adding the following to your custom manifest:\n  /// ```text\n  ///  <dependency>\n  ///    <dependentAssembly>\n  ///      <assemblyIdentity\n  ///        type=\"win32\"\n  ///        name=\"Microsoft.Windows.Common-Controls\"\n  ///        version=\"6.0.0.0\"\n  ///        processorArchitecture=\"*\"\n  ///        publicKeyToken=\"6595b64144ccf1df\"\n  ///        language=\"*\"\n  ///      />\n  ///    </dependentAssembly>\n  ///  </dependency>\n  /// ```\n  ///\n  /// # Example\n  ///\n  /// The following manifest will brand the exe as requesting administrator privileges.\n  /// Thus, every time it is executed, a Windows UAC dialog will appear.\n  ///\n  /// ```rust,no_run\n  /// let mut windows = tauri_build::WindowsAttributes::new();\n  /// windows = windows.app_manifest(r#\"\n  /// <assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n  ///   <trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n  ///       <security>\n  ///           <requestedPrivileges>\n  ///               <requestedExecutionLevel level=\"requireAdministrator\" uiAccess=\"false\" />\n  ///           </requestedPrivileges>\n  ///       </security>\n  ///   </trustInfo>\n  /// </assembly>\n  /// \"#);\n  /// let attrs =  tauri_build::Attributes::new().windows_attributes(windows);\n  /// tauri_build::try_build(attrs).expect(\"failed to run build script\");\n  /// ```\n  ///\n  /// Note that you can move the manifest contents to a separate file and use `include_str!(\"manifest.xml\")`\n  /// instead of the inline string.\n  ///\n  /// [manifest]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests\n  #[must_use]\n  pub fn app_manifest<S: AsRef<str>>(mut self, manifest: S) -> Self {\n    self.app_manifest = Some(manifest.as_ref().to_string());\n    self\n  }\n}\n\n/// The attributes used on the build.\n#[derive(Debug, Default)]\npub struct Attributes {\n  #[allow(dead_code)]\n  windows_attributes: WindowsAttributes,\n  capabilities_path_pattern: Option<&'static str>,\n  #[cfg(feature = \"codegen\")]\n  codegen: Option<codegen::context::CodegenContext>,\n  inlined_plugins: HashMap<&'static str, InlinedPlugin>,\n  app_manifest: AppManifest,\n}\n\nimpl Attributes {\n  /// Creates the default attribute set.\n  pub fn new() -> Self {\n    Self::default()\n  }\n\n  /// Sets the icon to use on the window. Currently only used on Windows.\n  #[must_use]\n  pub fn windows_attributes(mut self, windows_attributes: WindowsAttributes) -> Self {\n    self.windows_attributes = windows_attributes;\n    self\n  }\n\n  /// Set the glob pattern to be used to find the capabilities.\n  ///\n  /// **WARNING:** The `removeUnusedCommands` option does not work with a custom capabilities path.\n  ///\n  /// **Note:** You must emit [rerun-if-changed] instructions for your capabilities directory.\n  ///\n  /// [rerun-if-changed]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed\n  #[must_use]\n  pub fn capabilities_path_pattern(mut self, pattern: &'static str) -> Self {\n    self.capabilities_path_pattern.replace(pattern);\n    self\n  }\n\n  /// Adds the given plugin to the list of inlined plugins (a plugin that is part of your application).\n  ///\n  /// See [`InlinedPlugin`] for more information.\n  pub fn plugin(mut self, name: &'static str, plugin: InlinedPlugin) -> Self {\n    self.inlined_plugins.insert(name, plugin);\n    self\n  }\n\n  /// Adds the given list of plugins to the list of inlined plugins (a plugin that is part of your application).\n  ///\n  /// See [`InlinedPlugin`] for more information.\n  pub fn plugins<I>(mut self, plugins: I) -> Self\n  where\n    I: IntoIterator<Item = (&'static str, InlinedPlugin)>,\n  {\n    self.inlined_plugins.extend(plugins);\n    self\n  }\n\n  /// Sets the application manifest for the Access Control List.\n  ///\n  /// See [`AppManifest`] for more information.\n  pub fn app_manifest(mut self, manifest: AppManifest) -> Self {\n    self.app_manifest = manifest;\n    self\n  }\n\n  #[cfg(feature = \"codegen\")]\n  #[cfg_attr(docsrs, doc(cfg(feature = \"codegen\")))]\n  #[must_use]\n  pub fn codegen(mut self, codegen: codegen::context::CodegenContext) -> Self {\n    self.codegen.replace(codegen);\n    self\n  }\n}\n\npub fn is_dev() -> bool {\n  env::var(\"DEP_TAURI_DEV\").expect(\"missing `cargo:dev` instruction, please update tauri to latest\")\n    == \"true\"\n}\n\n/// Run all build time helpers for your Tauri Application.\n///\n/// To provide extra configuration, such as [`AppManifest::commands`]\n/// for fine-grained control over command permissions, see [`try_build`].\n/// See [`Attributes`] for the complete list of configuration options.\n///\n/// # Platforms\n///\n/// [`build()`] should be called inside of `build.rs` regardless of the platform, so **DO NOT** use a [conditional compilation]\n/// check that prevents it from running on any of your targets.\n///\n/// Platform specific code is handled by the helpers automatically.\n///\n/// A build script is required in order to activate some cargo environmental variables that are\n/// used when generating code and embedding assets.\n///\n/// # Panics\n///\n/// If any of the build time helpers fail, they will [`std::panic!`] with the related error message.\n/// This is typically desirable when running inside a build script; see [`try_build`] for no panics.\n///\n/// [conditional compilation]: https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/conditional-compilation.html\npub fn build() {\n  if let Err(error) = try_build(Attributes::default()) {\n    let error = format!(\"{error:#}\");\n    println!(\"{error}\");\n    if error.starts_with(\"unknown field\") {\n      print!(\"found an unknown configuration field. This usually means that you are using a CLI version that is newer than `tauri-build` and is incompatible. \");\n      println!(\n        \"Please try updating the Rust crates by running `cargo update` in the Tauri app folder.\"\n      );\n    }\n    std::process::exit(1);\n  }\n}\n\n/// Same as [`build()`], but takes an extra configuration argument, and does not panic.\n#[allow(unused_variables)]\npub fn try_build(attributes: Attributes) -> Result<()> {\n  use anyhow::anyhow;\n\n  println!(\"cargo:rerun-if-env-changed=TAURI_CONFIG\");\n\n  let target_os = env::var(\"CARGO_CFG_TARGET_OS\").unwrap();\n  let mobile = target_os == \"ios\" || target_os == \"android\";\n  cfg_alias(\"desktop\", !mobile);\n  cfg_alias(\"mobile\", mobile);\n\n  let target_triple = env::var(\"TARGET\").unwrap();\n  let target = tauri_utils::platform::Target::from_triple(&target_triple);\n\n  let (mut config, config_paths) =\n    tauri_utils::config::parse::read_from(target, &env::current_dir().unwrap())?;\n  for config_file_path in config_paths {\n    println!(\"cargo:rerun-if-changed={}\", config_file_path.display());\n  }\n  if let Ok(env) = env::var(\"TAURI_CONFIG\") {\n    let merge_config: serde_json::Value = serde_json::from_str(&env)?;\n    json_patch::merge(&mut config, &merge_config);\n  }\n  let config: Config = serde_json::from_value(config)?;\n\n  let s = config.identifier.split('.');\n  let last = s.clone().count() - 1;\n  let mut android_package_prefix = String::new();\n  for (i, w) in s.enumerate() {\n    if i == last {\n      println!(\n        \"cargo:rustc-env=TAURI_ANDROID_PACKAGE_NAME_APP_NAME={}\",\n        w.replace('-', \"_\")\n      );\n    } else {\n      android_package_prefix.push_str(&w.replace(['_', '-'], \"_1\"));\n      android_package_prefix.push('_');\n    }\n  }\n  android_package_prefix.pop();\n  println!(\"cargo:rustc-env=TAURI_ANDROID_PACKAGE_NAME_PREFIX={android_package_prefix}\");\n\n  if let Some(project_dir) = env::var_os(\"TAURI_ANDROID_PROJECT_PATH\").map(PathBuf::from) {\n    mobile::generate_gradle_files(project_dir)?;\n  }\n\n  cfg_alias(\"dev\", is_dev());\n\n  let cargo_toml_path = Path::new(\"Cargo.toml\").canonicalize()?;\n  let mut manifest = Manifest::<cargo_toml::Value>::from_path_with_metadata(cargo_toml_path)?;\n\n  let out_dir = PathBuf::from(env::var(\"OUT_DIR\").unwrap());\n\n  manifest::check(&config, &mut manifest)?;\n\n  acl::build(&out_dir, target, &attributes)?;\n\n  tauri_utils::plugin::save_global_api_scripts_paths(&out_dir, None);\n\n  println!(\"cargo:rustc-env=TAURI_ENV_TARGET_TRIPLE={target_triple}\");\n  // when running codegen in this build script, we need to access the env var directly\n  env::set_var(\"TAURI_ENV_TARGET_TRIPLE\", &target_triple);\n\n  // TODO: far from ideal, but there's no other way to get the target dir, see <https://github.com/rust-lang/cargo/issues/5457>\n  let target_dir = out_dir\n    .parent()\n    .unwrap()\n    .parent()\n    .unwrap()\n    .parent()\n    .unwrap();\n\n  if let Some(paths) = &config.bundle.external_bin {\n    copy_binaries(\n      ResourcePaths::new(&external_binaries(paths, &target_triple, &target), true),\n      &target_triple,\n      target_dir,\n      manifest.package.as_ref().map(|p| p.name.as_ref()),\n    )?;\n  }\n\n  #[allow(unused_mut, clippy::redundant_clone)]\n  let mut resources = config\n    .bundle\n    .resources\n    .clone()\n    .unwrap_or_else(|| BundleResources::List(Vec::new()));\n  if target_triple.contains(\"windows\") {\n    if let Some(fixed_webview2_runtime_path) = match &config.bundle.windows.webview_install_mode {\n      WebviewInstallMode::FixedRuntime { path } => Some(path),\n      _ => None,\n    } {\n      resources.push(fixed_webview2_runtime_path.display().to_string());\n    }\n  }\n  match resources {\n    BundleResources::List(res) => {\n      copy_resources(ResourcePaths::new(res.as_slice(), true), target_dir)?\n    }\n    BundleResources::Map(map) => copy_resources(ResourcePaths::from_map(&map, true), target_dir)?,\n  }\n\n  if target_triple.contains(\"darwin\") {\n    if let Some(frameworks) = &config.bundle.macos.frameworks {\n      if !frameworks.is_empty() {\n        let frameworks_dir = target_dir.parent().unwrap().join(\"Frameworks\");\n        let _ = fs::remove_dir_all(&frameworks_dir);\n        // copy frameworks to the root `target` folder (instead of `target/debug` for instance)\n        // because the rpath is set to `@executable_path/../Frameworks`.\n        copy_frameworks(&frameworks_dir, frameworks)?;\n\n        // If we have frameworks, we need to set the @rpath\n        // https://github.com/tauri-apps/tauri/issues/7710\n        println!(\"cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../Frameworks\");\n      }\n    }\n\n    if !is_dev() {\n      if let Some(version) = &config.bundle.macos.minimum_system_version {\n        println!(\"cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET={version}\");\n      }\n    }\n  }\n\n  if target_triple.contains(\"ios\") {\n    println!(\n      \"cargo:rustc-env=IPHONEOS_DEPLOYMENT_TARGET={}\",\n      config.bundle.ios.minimum_system_version\n    );\n  }\n\n  if target_triple.contains(\"windows\") {\n    use semver::Version;\n    use tauri_winres::{VersionInfo, WindowsResource};\n\n    let window_icon_path = attributes\n      .windows_attributes\n      .window_icon_path\n      .unwrap_or_else(|| {\n        config\n          .bundle\n          .icon\n          .iter()\n          .find(|i| i.ends_with(\".ico\"))\n          .map(AsRef::as_ref)\n          .unwrap_or(\"icons/icon.ico\")\n          .into()\n      });\n\n    let mut res = WindowsResource::new();\n\n    if let Some(manifest) = attributes.windows_attributes.app_manifest {\n      res.set_manifest(&manifest);\n    }\n\n    if let Some(version_str) = &config.version {\n      if let Ok(v) = Version::parse(version_str) {\n        let version = (v.major << 48) | (v.minor << 32) | (v.patch << 16);\n        res.set_version_info(VersionInfo::FILEVERSION, version);\n        res.set_version_info(VersionInfo::PRODUCTVERSION, version);\n      }\n    }\n\n    if let Some(product_name) = &config.product_name {\n      res.set(\"ProductName\", product_name);\n    }\n\n    let company_name = config.bundle.publisher.unwrap_or_else(|| {\n      config\n        .identifier\n        .split('.')\n        .nth(1)\n        .unwrap_or(&config.identifier)\n        .to_string()\n    });\n\n    res.set(\"CompanyName\", &company_name);\n\n    let file_description = config\n      .product_name\n      .or_else(|| manifest.package.as_ref().map(|p| p.name.clone()))\n      .or_else(|| std::env::var(\"CARGO_PKG_NAME\").ok());\n\n    res.set(\"FileDescription\", &file_description.unwrap());\n\n    if let Some(copyright) = &config.bundle.copyright {\n      res.set(\"LegalCopyright\", copyright);\n    }\n\n    if window_icon_path.exists() {\n      res.set_icon_with_id(&window_icon_path.display().to_string(), \"32512\");\n    } else {\n      return Err(anyhow!(format!(\n        \"`{}` not found; required for generating a Windows Resource file during tauri-build\",\n        window_icon_path.display()\n      )));\n    }\n\n    res.compile().with_context(|| {\n      format!(\n        \"failed to compile `{}` into a Windows Resource file during tauri-build\",\n        window_icon_path.display()\n      )\n    })?;\n\n    let target_env = env::var(\"CARGO_CFG_TARGET_ENV\").unwrap();\n    match target_env.as_str() {\n      \"gnu\" => {\n        let target_arch = match env::var(\"CARGO_CFG_TARGET_ARCH\").unwrap().as_str() {\n          \"x86_64\" => Some(\"x64\"),\n          \"x86\" => Some(\"x86\"),\n          \"aarch64\" => Some(\"arm64\"),\n          arch => None,\n        };\n        if let Some(target_arch) = target_arch {\n          for entry in fs::read_dir(target_dir.join(\"build\"))? {\n            let path = entry?.path();\n            let webview2_loader_path = path\n              .join(\"out\")\n              .join(target_arch)\n              .join(\"WebView2Loader.dll\");\n            if path.to_string_lossy().contains(\"webview2-com-sys\") && webview2_loader_path.exists()\n            {\n              fs::copy(webview2_loader_path, target_dir.join(\"WebView2Loader.dll\"))?;\n              break;\n            }\n          }\n        }\n      }\n      \"msvc\" => {\n        if env::var(\"STATIC_VCRUNTIME\").is_ok_and(|v| v == \"true\") {\n          static_vcruntime::build();\n        }\n      }\n      _ => (),\n    }\n  }\n\n  #[cfg(feature = \"codegen\")]\n  if let Some(codegen) = attributes.codegen {\n    codegen.try_build()?;\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-build/src/manifest.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse anyhow::{anyhow, Result};\nuse cargo_toml::{Dependency, Manifest};\nuse tauri_utils::config::{AppConfig, Config, PatternKind};\n\n#[derive(Debug, Default, PartialEq, Eq)]\nstruct Diff {\n  remove: Vec<String>,\n  add: Vec<String>,\n}\n\n#[derive(Debug, Clone, Copy)]\nenum DependencyKind {\n  Build,\n  Normal,\n}\n\n#[derive(Debug)]\nstruct AllowlistedDependency {\n  name: String,\n  alias: Option<String>,\n  kind: DependencyKind,\n  all_cli_managed_features: Option<Vec<&'static str>>,\n  expected_features: Vec<String>,\n}\n\npub fn check(config: &Config, manifest: &mut Manifest) -> Result<()> {\n  let dependencies = vec![\n    AllowlistedDependency {\n      name: \"tauri-build\".into(),\n      alias: None,\n      kind: DependencyKind::Build,\n      all_cli_managed_features: Some(vec![\"isolation\"]),\n      expected_features: match config.app.security.pattern {\n        PatternKind::Isolation { .. } => vec![\"isolation\".to_string()],\n        _ => vec![],\n      },\n    },\n    AllowlistedDependency {\n      name: \"tauri\".into(),\n      alias: None,\n      kind: DependencyKind::Normal,\n      all_cli_managed_features: Some(\n        AppConfig::all_features()\n          .into_iter()\n          .filter(|f| f != &\"tray-icon\")\n          .collect(),\n      ),\n      expected_features: config\n        .app\n        .features()\n        .into_iter()\n        .filter(|f| f != &\"tray-icon\")\n        .map(|f| f.to_string())\n        .collect::<Vec<String>>(),\n    },\n  ];\n\n  for metadata in dependencies {\n    let mut name = metadata.name.clone();\n    let mut deps = find_dependency(manifest, &metadata.name, metadata.kind);\n    if deps.is_empty() {\n      if let Some(alias) = &metadata.alias {\n        deps = find_dependency(manifest, alias, metadata.kind);\n        name.clone_from(alias);\n      }\n    }\n\n    for dep in deps {\n      if let Err(error) = check_features(dep, &metadata) {\n        return Err(anyhow!(\"\n      The `{}` dependency features on the `Cargo.toml` file does not match the allowlist defined under `tauri.conf.json`.\n      Please run `tauri dev` or `tauri build` or {}.\n    \", name, error));\n      }\n    }\n  }\n\n  Ok(())\n}\n\nfn find_dependency(manifest: &mut Manifest, name: &str, kind: DependencyKind) -> Vec<Dependency> {\n  let dep = match kind {\n    DependencyKind::Build => manifest.build_dependencies.remove(name),\n    DependencyKind::Normal => manifest.dependencies.remove(name),\n  };\n\n  if let Some(dep) = dep {\n    vec![dep]\n  } else {\n    let mut deps = Vec::new();\n    for target in manifest.target.values_mut() {\n      if let Some(dep) = match kind {\n        DependencyKind::Build => target.build_dependencies.remove(name),\n        DependencyKind::Normal => target.dependencies.remove(name),\n      } {\n        deps.push(dep);\n      }\n    }\n    deps\n  }\n}\n\nfn features_diff(current: &[String], expected: &[String]) -> Diff {\n  let mut remove = Vec::new();\n  let mut add = Vec::new();\n  for feature in current {\n    if !expected.contains(feature) {\n      remove.push(feature.clone());\n    }\n  }\n\n  for feature in expected {\n    if !current.contains(feature) {\n      add.push(feature.clone());\n    }\n  }\n\n  Diff { remove, add }\n}\n\nfn check_features(dependency: Dependency, metadata: &AllowlistedDependency) -> Result<(), String> {\n  let features = match dependency {\n    Dependency::Simple(_) => Vec::new(),\n    Dependency::Detailed(dep) => dep.features,\n    Dependency::Inherited(dep) => dep.features,\n  };\n\n  let diff = if let Some(all_cli_managed_features) = &metadata.all_cli_managed_features {\n    features_diff(\n      &features\n        .into_iter()\n        .filter(|f| all_cli_managed_features.contains(&f.as_str()))\n        .collect::<Vec<String>>(),\n      &metadata.expected_features,\n    )\n  } else {\n    features_diff(\n      &features\n        .into_iter()\n        .filter(|f| f.starts_with(\"allow-\"))\n        .collect::<Vec<String>>(),\n      &metadata.expected_features,\n    )\n  };\n\n  let mut error_message = String::new();\n  if !diff.remove.is_empty() {\n    error_message.push_str(\"remove the `\");\n    error_message.push_str(&diff.remove.join(\", \"));\n    error_message.push_str(if diff.remove.len() == 1 {\n      \"` feature\"\n    } else {\n      \"` features\"\n    });\n    if !diff.add.is_empty() {\n      error_message.push_str(\" and \");\n    }\n  }\n  if !diff.add.is_empty() {\n    error_message.push_str(\"add the `\");\n    error_message.push_str(&diff.add.join(\", \"));\n    error_message.push_str(if diff.add.len() == 1 {\n      \"` feature\"\n    } else {\n      \"` features\"\n    });\n  }\n\n  if error_message.is_empty() {\n    Ok(())\n  } else {\n    Err(error_message)\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use super::Diff;\n\n  #[test]\n  fn array_diff() {\n    for (current, expected, result) in [\n      (vec![], vec![], Default::default()),\n      (\n        vec![\"a\".into()],\n        vec![],\n        Diff {\n          remove: vec![\"a\".into()],\n          add: vec![],\n        },\n      ),\n      (vec![\"a\".into()], vec![\"a\".into()], Default::default()),\n      (\n        vec![\"a\".into(), \"b\".into()],\n        vec![\"a\".into()],\n        Diff {\n          remove: vec![\"b\".into()],\n          add: vec![],\n        },\n      ),\n      (\n        vec![\"a\".into(), \"b\".into()],\n        vec![\"a\".into(), \"c\".into()],\n        Diff {\n          remove: vec![\"b\".into()],\n          add: vec![\"c\".into()],\n        },\n      ),\n    ] {\n      assert_eq!(crate::manifest::features_diff(&current, &expected), result);\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-build/src/mobile.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::PathBuf;\n\nuse anyhow::{Context, Result};\nuse tauri_utils::write_if_changed;\n\npub fn generate_gradle_files(project_dir: PathBuf) -> Result<()> {\n  let gradle_settings_path = project_dir.join(\"tauri.settings.gradle\");\n  let app_build_gradle_path = project_dir.join(\"app\").join(\"tauri.build.gradle.kts\");\n\n  let mut gradle_settings =\n    \"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\\n\".to_string();\n  let mut app_build_gradle = \"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\nval implementation by configurations\ndependencies {\"\n    .to_string();\n\n  for (env, value) in std::env::vars_os() {\n    let env = env.to_string_lossy();\n    if env.starts_with(\"DEP_\") && env.ends_with(\"_ANDROID_LIBRARY_PATH\") {\n      let name_len = env.len() - \"DEP_\".len() - \"_ANDROID_LIBRARY_PATH\".len();\n      let mut plugin_name = env\n        .chars()\n        .skip(\"DEP_\".len())\n        .take(name_len)\n        .collect::<String>()\n        .to_lowercase()\n        .replace('_', \"-\");\n      if plugin_name == \"tauri\" {\n        plugin_name = \"tauri-android\".into();\n      }\n      let plugin_path = PathBuf::from(value);\n\n      gradle_settings.push_str(&format!(\"include ':{plugin_name}'\"));\n      gradle_settings.push('\\n');\n      gradle_settings.push_str(&format!(\n        \"project(':{plugin_name}').projectDir = new File({:?})\",\n        tauri_utils::display_path(plugin_path)\n      ));\n      gradle_settings.push('\\n');\n\n      app_build_gradle.push('\\n');\n      app_build_gradle.push_str(&format!(r#\"  implementation(project(\":{plugin_name}\"))\"#));\n    }\n  }\n\n  app_build_gradle.push_str(\"\\n}\");\n\n  // Overwrite only if changed to not trigger rebuilds\n  write_if_changed(&gradle_settings_path, gradle_settings)\n    .context(\"failed to write tauri.settings.gradle\")?;\n\n  write_if_changed(&app_build_gradle_path, app_build_gradle)\n    .context(\"failed to write tauri.build.gradle.kts\")?;\n\n  println!(\"cargo:rerun-if-changed={}\", gradle_settings_path.display());\n  println!(\"cargo:rerun-if-changed={}\", app_build_gradle_path.display());\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-build/src/static_vcruntime.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// taken from <https://github.com/ChrisDenton/static_vcruntime/>\n// we're not using static_vcruntime directly because we want this for debug builds too\n\nuse std::{env, fs, io::Write, path::Path};\n\npub fn build() {\n  override_msvcrt_lib();\n\n  // Disable conflicting libraries that aren't hard coded by Rust\n  println!(\"cargo:rustc-link-arg=/NODEFAULTLIB:libvcruntimed.lib\");\n  println!(\"cargo:rustc-link-arg=/NODEFAULTLIB:vcruntime.lib\");\n  println!(\"cargo:rustc-link-arg=/NODEFAULTLIB:vcruntimed.lib\");\n  println!(\"cargo:rustc-link-arg=/NODEFAULTLIB:libcmtd.lib\");\n  println!(\"cargo:rustc-link-arg=/NODEFAULTLIB:msvcrt.lib\");\n  println!(\"cargo:rustc-link-arg=/NODEFAULTLIB:msvcrtd.lib\");\n  println!(\"cargo:rustc-link-arg=/NODEFAULTLIB:libucrt.lib\");\n  println!(\"cargo:rustc-link-arg=/NODEFAULTLIB:libucrtd.lib\");\n  // Set the libraries we want.\n  println!(\"cargo:rustc-link-arg=/DEFAULTLIB:libcmt.lib\");\n  println!(\"cargo:rustc-link-arg=/DEFAULTLIB:libvcruntime.lib\");\n  println!(\"cargo:rustc-link-arg=/DEFAULTLIB:ucrt.lib\");\n}\n\n/// Override the hard-coded msvcrt.lib by replacing it with a (mostly) empty object file.\nfn override_msvcrt_lib() {\n  // Get the right machine type for the empty library.\n  let arch = std::env::var(\"CARGO_CFG_TARGET_ARCH\");\n  let machine: &[u8] = if arch.as_deref() == Ok(\"x86_64\") {\n    &[0x64, 0x86]\n  } else if arch.as_deref() == Ok(\"x86\") {\n    &[0x4C, 0x01]\n  } else {\n    return;\n  };\n  let bytes: &[u8] = &[\n    1, 0, 94, 3, 96, 98, 60, 0, 0, 0, 1, 0, 0, 0, 0, 0, 132, 1, 46, 100, 114, 101, 99, 116, 118,\n    101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    10, 16, 0, 46, 100, 114, 101, 99, 116, 118, 101, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 4, 0, 0, 0,\n  ];\n\n  // Write the empty \"msvcrt.lib\" to the output directory.\n  let out_dir = env::var(\"OUT_DIR\").unwrap();\n  let path = Path::new(&out_dir).join(\"msvcrt.lib\");\n  let f = fs::OpenOptions::new()\n    .write(true)\n    .create_new(true)\n    .open(path);\n  if let Ok(mut f) = f {\n    f.write_all(machine).unwrap();\n    f.write_all(bytes).unwrap();\n  }\n  // Add the output directory to the native library path.\n  println!(\"cargo:rustc-link-search=native={out_dir}\");\n}\n"
  },
  {
    "path": "crates/tauri-build/src/windows-app-manifest.xml",
    "content": "<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n  <dependency>\n    <dependentAssembly>\n      <assemblyIdentity\n        type=\"win32\"\n        name=\"Microsoft.Windows.Common-Controls\"\n        version=\"6.0.0.0\"\n        processorArchitecture=\"*\"\n        publicKeyToken=\"6595b64144ccf1df\"\n        language=\"*\"\n      />\n    </dependentAssembly>\n  </dependency>\n</assembly>\n"
  },
  {
    "path": "crates/tauri-bundler/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.8.1]\n\n### Bug Fixes\n\n- [`6252432f0`](https://www.github.com/tauri-apps/tauri/commit/6252432f0757d896d7a61819bbff127efac5a156) ([#14945](https://www.github.com/tauri-apps/tauri/pull/14945) by [@veeceey](https://www.github.com/tauri-apps/tauri/../../veeceey)) Fix WIX installer registry search order so that the named `InstallDir` key takes priority over the NSIS default key, preventing install location from changing on updates.\n\n### What's Changed\n\n- [`7be58a1c6`](https://www.github.com/tauri-apps/tauri/commit/7be58a1c643a7ed6d0f484a7e1134022618eb2b2) ([#14894](https://www.github.com/tauri-apps/tauri/pull/14894) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Log patching bundle type information again\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.3`\n\n## \\[2.8.0]\n\n### Enhancements\n\n- [`c769f211f`](https://www.github.com/tauri-apps/tauri/commit/c769f211fcaa543884c9d0f87ebd2ee106c01382) ([#14824](https://www.github.com/tauri-apps/tauri/pull/14824) by [@Kf637](https://www.github.com/tauri-apps/tauri/../../Kf637)) feat(nsis): add Norwegian language support for installer.\n\n### Bug Fixes\n\n- [`7fca58230`](https://www.github.com/tauri-apps/tauri/commit/7fca58230f97c3e6834134419514a0c7dbbe784b) ([#14830](https://www.github.com/tauri-apps/tauri/pull/14830) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Updated `nsis_tauri_utils` to 0.5.3:\n\n  - Use an alternative method `CreateProcessWithTokenW` to run programs as user, this fixed a problem that the program launched with the previous method can't query its own handle\n\n### What's Changed\n\n- [`0575dd287`](https://www.github.com/tauri-apps/tauri/commit/0575dd287e021b61d2aedf64d62ae84a2c925fb4) ([#14521](https://www.github.com/tauri-apps/tauri/pull/14521) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Change the way bundle type information is added to binary files. Instead of looking up the value of a variable we simply look for the default value.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.2`\n- Upgraded to `tauri-macos-sign@2.3.3`\n\n## \\[2.7.5]\n\n### Enhancements\n\n- [`4176f93ae`](https://www.github.com/tauri-apps/tauri/commit/4176f93ae43ef66714c4934feb3df19df3a3e28a) ([#14570](https://www.github.com/tauri-apps/tauri/pull/14570) by [@chfaft](https://www.github.com/tauri-apps/tauri/../../chfaft)) Consider extensions that are defined in the wxs template.\n\n### Bug Fixes\n\n- [`018b4db22`](https://www.github.com/tauri-apps/tauri/commit/018b4db22e167fa67b37b0933e192a0f3556d3e5) ([#14625](https://www.github.com/tauri-apps/tauri/pull/14625) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Skip signing for NSIS uninstaller when using `--no-sign` flag\n- [`91becd9e4`](https://www.github.com/tauri-apps/tauri/commit/91becd9e4fa2db089ddc6b21dadc06133e939e08) ([#14627](https://www.github.com/tauri-apps/tauri/pull/14627) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix NSIS plugins not being signed due to wrong path handlings\n\n### Dependencies\n\n- Upgraded to `tauri-macos-sign@2.3.2`\n\n## \\[2.7.4]\n\n### Bug Fixes\n\n- [`1496145f8`](https://www.github.com/tauri-apps/tauri/commit/1496145f8222649efeff22b819a96208670bbea1) ([#14585](https://www.github.com/tauri-apps/tauri/pull/14585) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the AppImage bundler to fail with 404 errors for 32-bit builds.\n\n### Performance Improvements\n\n- [`ce98d87ce`](https://www.github.com/tauri-apps/tauri/commit/ce98d87ce0aaa907285852eb80691197424e03c3) ([#14474](https://www.github.com/tauri-apps/tauri/pull/14474) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) refactor: remove needless collect. No user facing changes.\n- [`ee3cc4a91`](https://www.github.com/tauri-apps/tauri/commit/ee3cc4a91bf1315ecaefe90f423ffd55ef6c40db) ([#14475](https://www.github.com/tauri-apps/tauri/pull/14475) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) perf: remove needless clones in various files for improved performance. No user facing changes.\n\n### Dependencies\n\n- Upgraded to `tauri-macos-sign@2.3.1`\n- Upgraded to `tauri-utils@2.8.1`\n- [`b5ef603d8`](https://www.github.com/tauri-apps/tauri/commit/b5ef603d84bd8044625e50dcfdabb099b2e9fdd9) ([#14478](https://www.github.com/tauri-apps/tauri/pull/14478) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Updated NSIS from 3.8 to 3.11\n\n## \\[2.7.3]\n\n### Enhancements\n\n- [`22edc65aa`](https://www.github.com/tauri-apps/tauri/commit/22edc65aad0b3e45515008e8e0866112da70c8a1) ([#14408](https://www.github.com/tauri-apps/tauri/pull/14408) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Set user-agent in bundler and cli http requests when fetching build tools.\n\n### Bug Fixes\n\n- [`9a1922636`](https://www.github.com/tauri-apps/tauri/commit/9a192263693d71123a9953e2a6ee60fad07500b4) ([#14410](https://www.github.com/tauri-apps/tauri/pull/14410) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix uninstall fails if you close the app manually during the 'Click Ok to kill it' dialog\n\n## \\[2.7.2]\n\n### Enhancements\n\n- [`7f710b8f3`](https://www.github.com/tauri-apps/tauri/commit/7f710b8f3b509ed327d76761926511cf56e66b2d) ([#14390](https://www.github.com/tauri-apps/tauri/pull/14390) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Inline linuxdeploy plugins which were previously downloaded from `https://raw.githubusercontent.com` which lately blocks many users with a 429 error.\n- [`fc017ee25`](https://www.github.com/tauri-apps/tauri/commit/fc017ee2577f48615367ea519386d3f37837e2c1) ([#14368](https://www.github.com/tauri-apps/tauri/pull/14368) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Mention symbol stripping on Linux in binary patch failed warning message\n\n## \\[2.7.1]\n\n### Dependencies\n\n- Upgraded to `tauri-macos-sign@2.3.0`\n\n## \\[2.7.0]\n\n### New Features\n\n- [`2a06d1006`](https://www.github.com/tauri-apps/tauri/commit/2a06d10066a806e392efe8bfb16d943ee0b0b61d) ([#14052](https://www.github.com/tauri-apps/tauri/pull/14052)) Add a `--no-sign` flag to the `tauri build` and `tauri bundle` commands to skip the code signing step, improving the developer experience for local testing and development without requiring code signing keys.\n- [`cc8c0b531`](https://www.github.com/tauri-apps/tauri/commit/cc8c0b53171173dbd1d01781a50de1a3ea159031) ([#14031](https://www.github.com/tauri-apps/tauri/pull/14031)) Support providing `plist::Value` as macOS entitlements.\n\n### Enhancements\n\n- [`b06b3bd09`](https://www.github.com/tauri-apps/tauri/commit/b06b3bd091b0fed26cdcfb23cacb0462a7a9cc2d) ([#14126](https://www.github.com/tauri-apps/tauri/pull/14126)) Improve error messages with more context.\n\n### Bug Fixes\n\n- [`06d4a4ed6`](https://www.github.com/tauri-apps/tauri/commit/06d4a4ed6c146d6c7782016cf90037b56b944445) ([#14241](https://www.github.com/tauri-apps/tauri/pull/14241)) Set `APPIMAGE_EXTRACT_AND_RUN` on top of using the `--appimage-extra-and-run` cli arg for linuxdeploy.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.0`\n\n### Breaking Changes\n\n- [`ed7c9a410`](https://www.github.com/tauri-apps/tauri/commit/ed7c9a4100e08c002212265549d12130d021ad1e) ([#14108](https://www.github.com/tauri-apps/tauri/pull/14108)) Changed `MacOsSettings::info_plist_path` to `MacOsSettings::info_plist`.\n\n## \\[2.6.1]\n\n### Bug Fixes\n\n- [`f3df96fb3`](https://www.github.com/tauri-apps/tauri/commit/f3df96fb38e2f27ce6bf232fe87f35bcfec50ce4) ([#14065](https://www.github.com/tauri-apps/tauri/pull/14065) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix binary patching updater type fails on 32 bit Windows builds\n\n## \\[2.6.0]\n\n### New Features\n\n- [`a9ec12843`](https://www.github.com/tauri-apps/tauri/commit/a9ec12843aa7d0eb774bd3a53e2e63da12cfa77b) ([#13521](https://www.github.com/tauri-apps/tauri/pull/13521) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added a `--skip-stapling` option to make `tauri build|bundle` *not* wait for notarization to finish on macOS.\n\n### Enhancements\n\n- [`8b465a12b`](https://www.github.com/tauri-apps/tauri/commit/8b465a12ba73e94d7a3995defd9cc362d15eeebe) ([#13913](https://www.github.com/tauri-apps/tauri/pull/13913) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler now pulls the latest AppImage linuxdeploy plugin instead of using the built-in one. This should remove the libfuse requirement.\n- [`4475e93e1`](https://www.github.com/tauri-apps/tauri/commit/4475e93e136e9e2bd5f3c7817fa2040924f630f6) ([#13824](https://www.github.com/tauri-apps/tauri/pull/13824) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler and cli will now read TLS Certificates installed on the system when downloading tools and checking versions.\n\n### Bug Fixes\n\n- [`a8f1569b0`](https://www.github.com/tauri-apps/tauri/commit/a8f1569b04edf7b54a19e19ad37b421b0808f512) ([#13921](https://www.github.com/tauri-apps/tauri/pull/13921) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) The bundler will no longer try to sign non-binary and already signed binary files on Windows\n- [`bc6b125b2`](https://www.github.com/tauri-apps/tauri/commit/bc6b125b24589ffc412a4f17d899a387a0fc0bb2) ([#13909](https://www.github.com/tauri-apps/tauri/pull/13909) by [@Andrew15-5](https://www.github.com/tauri-apps/tauri/../../Andrew15-5)) The bundler now falls back to `1` for the release in case an empty string was provided instead of using `-.` in the file name.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.7.0`\n- Upgraded to `tauri-macos-sign@2.2.0`\n\n## \\[2.5.2]\n\n### Bug Fixes\n\n- [`af95fb601`](https://www.github.com/tauri-apps/tauri/commit/af95fb6014ea54a2636bfd299095608f6cd93221) ([#13870](https://www.github.com/tauri-apps/tauri/pull/13870) by [@kittuov](https://www.github.com/tauri-apps/tauri/../../kittuov)) The bundler now signs the main binary after patching it for every package type on windows\n\n## \\[2.5.1]\n\n### Bug Fixes\n\n- [`f94af9035`](https://www.github.com/tauri-apps/tauri/commit/f94af90359ec8b01138ae542391caa704ec18ca8) ([#13786](https://www.github.com/tauri-apps/tauri/pull/13786) by [@catalinsh](https://www.github.com/tauri-apps/tauri/../../catalinsh)) Fix NSIS per-machine installer not requesting elevation when run by non-admin users.\n- [`f2dbe7309`](https://www.github.com/tauri-apps/tauri/commit/f2dbe730979d570be3ee3ecac9621204c4ceb788) ([#13772](https://www.github.com/tauri-apps/tauri/pull/13772) by [@catalinsh](https://www.github.com/tauri-apps/tauri/../../catalinsh)) Fix incorrect expected file path for `nsis_tauri_utils.dll` resulting in tauri-cli re-downloading the file on every build.\n- [`7a6fd5b75`](https://www.github.com/tauri-apps/tauri/commit/7a6fd5b75d61071e2771f6277c0376ec206d302a) ([#13863](https://www.github.com/tauri-apps/tauri/pull/13863) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The AppImage bundler now pulls the AppRun binaries from our GitHub mirror, fixing 404 errors.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.6.0`\n\n## \\[2.5.0]\n\n### New Features\n\n- [`414619c36`](https://www.github.com/tauri-apps/tauri/commit/414619c36e94e21939534dd72c0438b93da75546) ([#13536](https://www.github.com/tauri-apps/tauri/pull/13536) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) Added support for the `bundleName` property in the macOS bundler configuration. This allows specifying the `CFBundleName` value for generated macOS bundles.\n- [`7322f0579`](https://www.github.com/tauri-apps/tauri/commit/7322f057923aaec88960ad5556776774b745762f) ([#13502](https://www.github.com/tauri-apps/tauri/pull/13502) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Allow using `CheckIfAppIsRunning` macro inside NSIS hooks, for example `!insertmacro CheckIfAppIsRunning \"another-executable.exe\" \"Another Executable\"`.\n\n### Bug Fixes\n\n- [`479cee3d3`](https://www.github.com/tauri-apps/tauri/commit/479cee3d3680f9020005bdfb380d3a9482e286a1) ([#13260](https://www.github.com/tauri-apps/tauri/pull/13260) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler now sets the `ARCH` env var to the current build target to prevent potential issues with `appimagetool`'s auto-detection.\n- [`e045fe32c`](https://www.github.com/tauri-apps/tauri/commit/e045fe32c9b0bed954916dc42528e28ee19f75b8) ([#13334](https://www.github.com/tauri-apps/tauri/pull/13334) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix custom Windows sign command failing to sign app uninstaller if it references relative paths.\n- [`bd8a7cf39`](https://www.github.com/tauri-apps/tauri/commit/bd8a7cf39df316bf27c73a303d5e650301af0104) ([#13581](https://www.github.com/tauri-apps/tauri/pull/13581) by [@martpie](https://www.github.com/tauri-apps/tauri/../../martpie)) Fixes app icon not being displayed on Gnome dock and grid view when using Wayland.\n- [`b52da29d5`](https://www.github.com/tauri-apps/tauri/commit/b52da29d5dbdb675ddba438a335e6a59f620e536) ([#13429](https://www.github.com/tauri-apps/tauri/pull/13429) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `mainBinaryName` doesn't work when there's `.` in it\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.5.0`\n\n## \\[2.4.0]\n\n### New Features\n\n- [`b0babb6df`](https://www.github.com/tauri-apps/tauri/commit/b0babb6df12dafe45c21a2c9c424fd86ffd75ca7) ([#12938](https://www.github.com/tauri-apps/tauri/pull/12938)) Added hebrew translation for the custom Tauri messages in the NSIS bundle.\n- [`0aa48fb9e`](https://www.github.com/tauri-apps/tauri/commit/0aa48fb9e4b9d7b5bf3522000a76ebc1836394ed) ([#13030](https://www.github.com/tauri-apps/tauri/pull/13030)) Added `bundleVersion` to iOS and macOS configuration to support specifying a `CFBundleVersion`.\n\n### Enhancements\n\n- [`8d994f60f`](https://www.github.com/tauri-apps/tauri/commit/8d994f60fe05ec0f45cbe926506bbe10b0d36e3c) ([#11676](https://www.github.com/tauri-apps/tauri/pull/11676)) Sign NSIS and WiX DLLs when bundling\n- [`8d994f60f`](https://www.github.com/tauri-apps/tauri/commit/8d994f60fe05ec0f45cbe926506bbe10b0d36e3c) ([#11676](https://www.github.com/tauri-apps/tauri/pull/11676)) Sign DLLs from resources.\n\n### Bug Fixes\n\n- [`9ea76503d`](https://www.github.com/tauri-apps/tauri/commit/9ea76503dcf8da11fab65550f4ab8d3565a424ef) ([#13186](https://www.github.com/tauri-apps/tauri/pull/13186)) Fix NSIS bundler can't include resources and sidecars with `$` in the path\n- [`2dccfab53`](https://www.github.com/tauri-apps/tauri/commit/2dccfab5321fef55d45f3a4c674b6151b1c4424a) ([#13236](https://www.github.com/tauri-apps/tauri/pull/13236)) Fix `fileAssociations` missing `LSHandlerRank` on macOS.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.4.0`\n\n## \\[2.3.1]\n\n### Bug Fixes\n\n- [`2138bbc21`](https://www.github.com/tauri-apps/tauri/commit/2138bbc21294785df5f4144670104387289f79c1) ([#13087](https://www.github.com/tauri-apps/tauri/pull/13087) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix NSIS installer displaying in wrong language if `SpanishInternational` is included\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.1`\n\n## \\[2.3.0]\n\n### Enhancements\n\n- [`f981a5ee8`](https://www.github.com/tauri-apps/tauri/commit/f981a5ee8b292b9ea09329f60cecc7f688dda734) ([#12602](https://www.github.com/tauri-apps/tauri/pull/12602) by [@kxxt](https://www.github.com/tauri-apps/tauri/../../kxxt)) Add basic support for linux riscv64 platform.\n\n### Bug Fixes\n\n- [`3626b7a92`](https://www.github.com/tauri-apps/tauri/commit/3626b7a92be2890a82e8d5bd00d13887e199ea4a) ([#12759](https://www.github.com/tauri-apps/tauri/pull/12759) by [@ninjadev64](https://www.github.com/tauri-apps/tauri/../../ninjadev64)) Fix resources being bundled to the wrong path during RPM bundling when resources are specified as a map.\n- [`2b960dfd9`](https://www.github.com/tauri-apps/tauri/commit/2b960dfd9fdc995bd6474958c05783ff53b64b7e) ([#12643](https://www.github.com/tauri-apps/tauri/pull/12643) by [@animeshchaudhri](https://www.github.com/tauri-apps/tauri/../../animeshchaudhri)) Remove the autostart plugin registry entry when the app is uninstalled (NSIS only).\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.0`\n\n## \\[2.2.4]\n\n### Enhancements\n\n- [`5eba0785c`](https://www.github.com/tauri-apps/tauri/commit/5eba0785c461a0d0bec47653eaf6ccdf5f05d347) ([#12605](https://www.github.com/tauri-apps/tauri/pull/12605) by [@niusia-ua](https://www.github.com/tauri-apps/tauri/../../niusia-ua)) Added Ukrainian translation for the custom tauri messages in the nsis bundle\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.2.0`\n- Upgraded to `tauri-macos-sign@2.1.0`\n\n## \\[2.2.3]\n\n### Bug Fixes\n\n- [`de8600b4d`](https://www.github.com/tauri-apps/tauri/commit/de8600b4d9a04e809e078c8aea61825d1328201f) ([#12471](https://www.github.com/tauri-apps/tauri/pull/12471) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) Bumped `nsis-tauri-utils` to `0.4.2` which fixes the following bugs:\n\n  - Fixed launch on start checkbox in nsis installer does not work well with applications that require elevated permissions\n  - Fixed nsis installer may fail to install if launched by updater plugin\n- [`fbe7c9ead`](https://www.github.com/tauri-apps/tauri/commit/fbe7c9ead76e71ca258c6f48bbb62185fcc37b1c) ([#12466](https://www.github.com/tauri-apps/tauri/pull/12466) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the compiled AppImage to miss webkitgtk's internal `libwebkit2gtkinjectedbundle.so` file.\n- [`f5a59b93b`](https://www.github.com/tauri-apps/tauri/commit/f5a59b93bfefb43ff131a7870b3c5d5e48c1ca1e) ([#12136](https://www.github.com/tauri-apps/tauri/pull/12136) by [@unknovvn](https://www.github.com/tauri-apps/tauri/../../unknovvn)) The NSIS bundler will now replace non-numeric build metadata with `0` instead of returning an error.\n- [`9dac2863a`](https://www.github.com/tauri-apps/tauri/commit/9dac2863afa70fb0bcddf859b284afba917f28ae) ([#12323](https://www.github.com/tauri-apps/tauri/pull/12323) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Skip signing the .dmg if self signing via `\"signingIdentity\": \"-\"` is used.\n- [`b8eb28877`](https://www.github.com/tauri-apps/tauri/commit/b8eb28877fe822dbe17999fc8af98ed7d0983679) ([#12427](https://www.github.com/tauri-apps/tauri/pull/12427) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Clean up `Software\\${MANUFACTURER}\\${PRODUCTNAME}` registry key in the NSIS uninstaller if \"Delete application data\" option is checked when uninstalling.\n\n## \\[2.2.2]\n\n### Bug Fixes\n\n- [`72748cc45`](https://www.github.com/tauri-apps/tauri/commit/72748cc45cf670dd03c86c8deceb5942598f5ad9) ([#12365](https://www.github.com/tauri-apps/tauri/pull/12365) by [@don41382](https://www.github.com/tauri-apps/tauri/../../don41382)) Fixed an issue that caused the `.msi` installer not to lookup the `INSTALLDIR` set in the `nsis` installer.\n- [`cf771bf69`](https://www.github.com/tauri-apps/tauri/commit/cf771bf69aa26b62d11a54a69131c631505d8c55) ([#12402](https://www.github.com/tauri-apps/tauri/pull/12402) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the .msi installer to not contain root resources when there were .dll files present in the target directory.\n- [`07ccdc499`](https://www.github.com/tauri-apps/tauri/commit/07ccdc499c3240e7240be3abf95ef2d7d00b2dc7) ([#12324](https://www.github.com/tauri-apps/tauri/pull/12324) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue leading to NSIS based installers to not contain the `WebView2Loader.dll` file when targetting `windows-gnu`.\n\n## \\[2.2.1]\n\n### Bug Fixes\n\n- [`cd1d026f9`](https://www.github.com/tauri-apps/tauri/commit/cd1d026f9799c26b04acb64f49e7ee0a8b193049) ([#11961](https://www.github.com/tauri-apps/tauri/pull/11961) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix tauri fails to build if the project path contains glob characters\n\n## \\[2.2.0]\n\n### New Features\n\n- [`cccb308c7`](https://www.github.com/tauri-apps/tauri/commit/cccb308c7b559b0838138d6cea280665f060c925) ([#11562](https://www.github.com/tauri-apps/tauri/pull/11562) by [@jLynx](https://www.github.com/tauri-apps/tauri/../../jLynx)) Generate signature for `.deb` packages when `createUpdaterArtifacts` option is enabled.\n\n### Enhancements\n\n- [`93a3a043d`](https://www.github.com/tauri-apps/tauri/commit/93a3a043d39cc96515d51d98beeb14261d3a246b) ([#11727](https://www.github.com/tauri-apps/tauri/pull/11727) by [@Kiyozz](https://www.github.com/tauri-apps/tauri/../../Kiyozz)) Add support for `Portuguese` language for NSIS windows installer.\n- [`53f808674`](https://www.github.com/tauri-apps/tauri/commit/53f808674b2c0012bc44a41ced90e742afbb41e8) ([#11799](https://www.github.com/tauri-apps/tauri/pull/11799) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler now reads the `TAURI_BUNDLER_DMG_IGNORE_CI` env var to decide whether to check for `CI: true` when building DMG files.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.1`\n\n## \\[2.1.0]\n\n### New Features\n\n- [`1b6b2cfaa`](https://www.github.com/tauri-apps/tauri/commit/1b6b2cfaa14ab1d418c676cedbf942a812377a30) ([#11521](https://www.github.com/tauri-apps/tauri/pull/11521) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Process `bundle > windows > wix > fragmentPaths` with Handlebars to interpolate expressions within it.\n- [`6dea12a06`](https://www.github.com/tauri-apps/tauri/commit/6dea12a0677a905cb1f14969fe05c53e7cd717c6) ([#11402](https://www.github.com/tauri-apps/tauri/pull/11402) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > deb > recommends` and `bundle > linux > rpm > recommends` fields to declare a strong, but not absolute, dependency for your `.deb` and `.rpm` packages.\n- [`058c0db72`](https://www.github.com/tauri-apps/tauri/commit/058c0db72f43fbe1574d0db654560e693755cd7e) ([#11584](https://www.github.com/tauri-apps/tauri/pull/11584) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > rpm > compression` config option to control RPM bundle compression type and level.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.0`\n\n## \\[2.0.4]\n\n### New Features\n\n- [`c8f55b615`](https://www.github.com/tauri-apps/tauri/commit/c8f55b615d2d98ade5c0f1896139dc283382a176) ([#11388](https://www.github.com/tauri-apps/tauri/pull/11388) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundler > windows > wix > version` to manually specify a wix-compatible version.\n\n## \\[2.0.3]\n\n### New Features\n\n- [`eda5713ea`](https://www.github.com/tauri-apps/tauri/commit/eda5713eab78d28182071ea25ceca5f1994f37ea) ([#11242](https://www.github.com/tauri-apps/tauri/pull/11242) by [@alex-sandri](https://www.github.com/tauri-apps/tauri/../../alex-sandri)) Add `Italian` to supported NSIS installer languages\n\n### Enhancements\n\n- [`504bb8ec8`](https://www.github.com/tauri-apps/tauri/commit/504bb8ec8cb294c5067357e18328580dd2b950c9) ([#11287](https://www.github.com/tauri-apps/tauri/pull/11287) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Pull upstream changes for the DMG creation script.\n\n### Bug Fixes\n\n- [`069c05e44`](https://www.github.com/tauri-apps/tauri/commit/069c05e44fd6f30083fdc00dd6c0001278898592) ([#11315](https://www.github.com/tauri-apps/tauri/pull/11315) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix CLI crashing and failing to find a `.ico` file when `bundle > icon` option is using globs and doesn't have a string that ends with `.ico`.\n\n## \\[2.0.2]\n\n### Bug Fixes\n\n- [`858b3516a`](https://www.github.com/tauri-apps/tauri/commit/858b3516a008ae5e6f2af489805896e2c142be10) ([#11217](https://www.github.com/tauri-apps/tauri/pull/11217) by [@kittuov](https://www.github.com/tauri-apps/tauri/../../kittuov)) On Windows, fixed command arguments for `bundle -> windows -> msi -> elevatedUpdateTask`. to work with spaces in `productName`\n- [`a49a19ffa`](https://www.github.com/tauri-apps/tauri/commit/a49a19ffa304f031fb1a04d31a567cc7f42a380a) ([#11218](https://www.github.com/tauri-apps/tauri/pull/11218)) Fix bundling `appimage`, `deb` and `rpm` bundles failing to open when using `mainBinaryName` with spaces.\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.1`\n- Upgraded to `tauri-macos-sign@2.0.1`\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78.\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0`\n- Upgraded to `tauri-macos-sign@2.0.0`\n\n## \\[2.0.1-rc.15]\n\n### Bug Fixes\n\n- [`e10fdb786`](https://www.github.com/tauri-apps/tauri/commit/e10fdb786cc3e23b957238835881c41fa6acf8d4) ([#11182](https://www.github.com/tauri-apps/tauri/pull/11182) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Revert recent change that generated MSI installers with the same product code which prevented updates to happen, it is now ranomized like it previously was.\n\n### Dependencies\n\n- Upgraded to `tauri-macos-sign@0.1.1-rc.1`\n\n## \\[2.0.1-rc.14]\n\n### New Features\n\n- [`06718b456`](https://www.github.com/tauri-apps/tauri/commit/06718b4569577a004daed9b8455e852a50b4b80f) ([#11096](https://www.github.com/tauri-apps/tauri/pull/11096) by [@thep0y](https://www.github.com/tauri-apps/tauri/../../thep0y)) Add the `TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE` environment variable to specify a more accessible mirror template, facilitating companies, organizations, or individuals who cannot access GitHub to download the necessary files through their own mirror servers.\n- [`f57a729cd`](https://www.github.com/tauri-apps/tauri/commit/f57a729cd8f7e10d8daf0b9d5b85f9c7ad530496) ([#11039](https://www.github.com/tauri-apps/tauri/pull/11039) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `upgradeCode` in `wix` configuration to set an upgrade code for your MSI installer. This is recommended to be set if you plan to change your `productName`.\n\n### Bug Fixes\n\n- [`dfba0ede6`](https://www.github.com/tauri-apps/tauri/commit/dfba0ede683656f265681f68cd61c8511ea49847) ([#11084](https://www.github.com/tauri-apps/tauri/pull/11084) by [@olivierlemasle](https://www.github.com/tauri-apps/tauri/../../olivierlemasle)) Detect ARM gnueabi as soft-float (armel) instead of hard-float (armhf). Also change the signature of `tauri_bundler::bundle::Settings::binary_arch` to return an enum instead of a `&str`.\n- [`544328d5a`](https://www.github.com/tauri-apps/tauri/commit/544328d5a304979b92e542e6bacdb539e9e8fbdc) ([#11139](https://www.github.com/tauri-apps/tauri/pull/11139) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix NSIS installer failing to determine whether webview installer downloaded correctly or not.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.13`\n\n## \\[2.0.1-rc.13]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.12`\n\n## \\[2.0.1-rc.12]\n\n### Bug Fixes\n\n- [`94e9d476e`](https://www.github.com/tauri-apps/tauri/commit/94e9d476ef506b1b8c09f55b81620c7839f98086) ([#11011](https://www.github.com/tauri-apps/tauri/pull/11011) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `main_binary_name` in custom wix and nsis templates including `.exe`\n\n## \\[2.0.1-rc.11]\n\n### New Features\n\n- [`656618225`](https://www.github.com/tauri-apps/tauri/commit/65661822580c31eb10a44be45842e259c598374c) ([#10866](https://www.github.com/tauri-apps/tauri/pull/10866) by [@thep0y](https://www.github.com/tauri-apps/tauri/../../thep0y)) Add `TAURI_BUNDLER_TOOLS_GITHUB_MIRROR` environment variable to specify a GitHub mirror to download files and tools used by tauri bundler. This is designed for areas like Mainland China where GitHub access can be unreliable.\n- [`35bd9dd3d`](https://www.github.com/tauri-apps/tauri/commit/35bd9dd3dc3d8972bbc4aa5f4a6c6fa14354e9bf) ([#10977](https://www.github.com/tauri-apps/tauri/pull/10977) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `mainBinaryName` config option to set the file name for the main binary.\n- [`b13cb208a`](https://www.github.com/tauri-apps/tauri/commit/b13cb208a3b8973a0d8e14bbdc587550982ec1f0) ([#10962](https://www.github.com/tauri-apps/tauri/pull/10962) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Store main binary name in registry for NSIS installer and delete old main binary on updates if the name changes.\n\n### Enhancements\n\n- [`a1e88d2b5`](https://www.github.com/tauri-apps/tauri/commit/a1e88d2b57219406332599d8e964e28b9b774068) ([#10969](https://www.github.com/tauri-apps/tauri/pull/10969) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Generate a consistent Product code for MSI installer derived from `identifier` instead of generating random one each build.\n\n### Bug Fixes\n\n- [`44d54a071`](https://www.github.com/tauri-apps/tauri/commit/44d54a07107edd38f31be15b49ddca0cacc335e5) ([#11005](https://www.github.com/tauri-apps/tauri/pull/11005) by [@goenning](https://www.github.com/tauri-apps/tauri/../../goenning)) Use appimage files instead of debian files when building appimage\n- [`9d468774a`](https://www.github.com/tauri-apps/tauri/commit/9d468774a94b5f5210a3012db2e58dbfab4755f4) ([#10975](https://www.github.com/tauri-apps/tauri/pull/10975) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The executable and NSIS installer on Windows will now use the `productName` config for the `FileDescription` property instead of `shortDescription`.\n- [`7eb1171e3`](https://www.github.com/tauri-apps/tauri/commit/7eb1171e3a2eb7bc5085ff28fc8610e5af82bdd5) ([#10967](https://www.github.com/tauri-apps/tauri/pull/10967) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix generated `UpgradeCode` for MSI not matching MSI installers created with tauri-bundler@v1.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.11`\n\n### Breaking Changes\n\n- [`35bd9dd3d`](https://www.github.com/tauri-apps/tauri/commit/35bd9dd3dc3d8972bbc4aa5f4a6c6fa14354e9bf) ([#10977](https://www.github.com/tauri-apps/tauri/pull/10977) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Changed changelog file location in `deb` to `usr/share/doc/<product_name>/changelog.gz` instead of `usr/share/doc/<main_binary_name>/changelog.gz`. For tauri v1 users, the path is unchanged as `product_name` and `main_binary_name` used the same value.\n- [`35bd9dd3d`](https://www.github.com/tauri-apps/tauri/commit/35bd9dd3dc3d8972bbc4aa5f4a6c6fa14354e9bf) ([#10977](https://www.github.com/tauri-apps/tauri/pull/10977) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Changed resources directory location in `deb` and `rpm` to `/usr/lib/<product_name>` instead of `/usr/lib/<main_binary_name>`. For tauri v1 users, the path is unchanged as `product_name` and `main_binary_name` used the same value.\n\n## \\[2.0.1-rc.10]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.10`\n\n## \\[2.0.1-rc.9]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.9`\n\n## \\[2.0.1-rc.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.8`\n\n## \\[2.0.1-rc.7]\n\n### Enhancements\n\n- [`5ec74456b`](https://www.github.com/tauri-apps/tauri/commit/5ec74456b9ae45eaa63e225856de1b7eb83abe6f) ([#10825](https://www.github.com/tauri-apps/tauri/pull/10825) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) The debian `Maintainer` field now defaults to the Cargo.toml authors, but fallbacks to the `publisher` config value and the second part of the bundle identifier.\n\n## \\[2.0.1-rc.6]\n\n### New Features\n\n- [`58dda44a5`](https://www.github.com/tauri-apps/tauri/commit/58dda44a59b915f091602cdfc53385a148469793) ([#10339](https://www.github.com/tauri-apps/tauri/pull/10339) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Add a new option `minimumWebview2Version` for Windows NSIS installer to trigger a webview2 update if the user's webview2 is older than this version\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.7`\n\n### Breaking Changes\n\n- [`073bb4f45`](https://www.github.com/tauri-apps/tauri/commit/073bb4f459a923541b94970dfa7e087bccaa2cfd) ([#10772](https://www.github.com/tauri-apps/tauri/pull/10772) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Removed the deprecated `webview_fixed_runtime_path` config option, use the `webview_install_mode` instead.\n\n## \\[2.0.1-rc.5]\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.6`\n\n## \\[2.0.1-rc.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.5`\n\n## \\[2.0.1-rc.3]\n\n### New Features\n\n- [`8d148a9e2`](https://www.github.com/tauri-apps/tauri/commit/8d148a9e2566edebfea2d75f32df7c9396d765a4) ([#10634](https://www.github.com/tauri-apps/tauri/pull/10634) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) Custom sign command with object notation for whitespaces in the command path and arguments.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.4`\n\n## \\[2.0.1-rc.2]\n\n### New Features\n\n- [`f8d658ea1`](https://www.github.com/tauri-apps/tauri/commit/f8d658ea1b99236e00cd5d3010e9ef7b427e400f) ([#10588](https://www.github.com/tauri-apps/tauri/pull/10588) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) `TAURI_WINDOWS_SIGNTOOL_PATH` environment variable for specifying the path to signtool.exe.\n\n### Enhancements\n\n- [`8deb1966a`](https://www.github.com/tauri-apps/tauri/commit/8deb1966ace93d1350f271d525a878ba4b0879ce) ([#10652](https://www.github.com/tauri-apps/tauri/pull/10652) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Infer macOS codesign identity from the `APPLE_CERTIFICATE` environment variable when provided, meaning the identity no longer needs to be provided when signing on CI using that option. If the imported certificate name does not match a provided signingIdentity configuration, an error is returned.\n\n### Bug Fixes\n\n- [`521d1d5cd`](https://www.github.com/tauri-apps/tauri/commit/521d1d5cdb052df554c022659f527ca7914dc65f) ([#10619](https://www.github.com/tauri-apps/tauri/pull/10619) by [@Broken-Deer](https://www.github.com/tauri-apps/tauri/../../Broken-Deer)) Fixed an issue that caused the bundler to not be able to download the AppImage tooling when building for ARM 32bit.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.3`\n- Upgraded to `tauri-macos-sign@0.1.1-rc.0`\n\n## \\[2.0.1-rc.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.2`\n\n## \\[2.0.1-rc.0]\n\n### Bug Fixes\n\n- [`a440a3f9d`](https://www.github.com/tauri-apps/tauri/commit/a440a3f9d85376d994f2ba904b1ae0828c5a0fbb) ([#10498](https://www.github.com/tauri-apps/tauri/pull/10498) by [@catalinsh](https://www.github.com/tauri-apps/tauri/../../catalinsh)) Correct nsis pre-uninstall hook to post-uninstall\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.1`\n\n## \\[2.0.0-rc.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.0`\n\n## \\[2.0.1-beta.19]\n\n### Bug Fixes\n\n- [`d1df6be70`](https://www.github.com/tauri-apps/tauri/commit/d1df6be701fc1cd64fd227d68041a1096386d3b5) ([#10270](https://www.github.com/tauri-apps/tauri/pull/10270) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix bundler warns about no updater-enabled targets were built for self contained updaters like app image, nsis, msi\n\n### What's Changed\n\n- [`9f0a5fcea`](https://www.github.com/tauri-apps/tauri/commit/9f0a5fceaced7862c8a57beba6616c21ff3b17f8) ([#10271](https://www.github.com/tauri-apps/tauri/pull/10271) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Make `NSIS_HOOK_PREINSTALL` and `NSIS_HOOK_PREUNINSTALL` run before `CheckIfAppIsRunning` (which checks if the app is running and asks the user if they want to kill the app)\n\n## \\[2.0.1-beta.18]\n\n### New Features\n\n- [`c734b9e3c`](https://www.github.com/tauri-apps/tauri/commit/c734b9e3cd6e5a22dfd84ec8a779c2ee9591751b) ([#10072](https://www.github.com/tauri-apps/tauri/pull/10072) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Upgraded the WiX version to 3.14 which fixes vulnerability issues and adds support for Arm targets.\n\n### Bug Fixes\n\n- [`96f65fef3`](https://www.github.com/tauri-apps/tauri/commit/96f65fef3659e83bbee4426e94da4472c962e391) ([#10188](https://www.github.com/tauri-apps/tauri/pull/10188) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) On macOS, the bundler will now correctly print a warning when the updater is enabled while the `.app` bundle is disabled.\n- [`5998a90f3`](https://www.github.com/tauri-apps/tauri/commit/5998a90f3f0dda124eff1ce3c59e96d329e8b435) ([#10184](https://www.github.com/tauri-apps/tauri/pull/10184) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix NSIS installer failing to launch apps that contain spaces after installation.\n\n### Dependencies\n\n- Upgraded to `tauri-macos-sign@0.1.0-beta.0`\n- Upgraded to `tauri-utils@2.0.0-beta.19`\n\n### Breaking Changes\n\n- [`11aa7743e`](https://www.github.com/tauri-apps/tauri/commit/11aa7743e7a277ed9ec3e92040dc484afe77d261) ([#10177](https://www.github.com/tauri-apps/tauri/pull/10177) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Changed NSIS installer hooks from `!define` to `!macro`\n\n## \\[2.0.1-beta.17]\n\n### New Features\n\n- [`fafc238f7`](https://www.github.com/tauri-apps/tauri/commit/fafc238f7288548975ca7d3e5207b925c0295c91) ([#9977](https://www.github.com/tauri-apps/tauri/pull/9977)) Add `bundle > homepage` option, if unset, it will fallback to `homepage` defined in `Cargo.toml`.\n- [`656a64974`](https://www.github.com/tauri-apps/tauri/commit/656a64974468bc207bf39537e02ae179bdee9b83) ([#9318](https://www.github.com/tauri-apps/tauri/pull/9318)) Added a configuration option to disable hardened runtime on macOS codesign.\n- [`3ab170917`](https://www.github.com/tauri-apps/tauri/commit/3ab170917ed535fc9013f0a9255631fb34493e18) ([#9932](https://www.github.com/tauri-apps/tauri/pull/9932)) Add an option to disable NSIS compression `bundle > nsis > compression: \"none\"`\n- [`f21029b1b`](https://www.github.com/tauri-apps/tauri/commit/f21029b1bc25f5cb987e1a25de94c2d364e3e462) ([#9994](https://www.github.com/tauri-apps/tauri/pull/9994)) Add `bundle > nsis > startMenuFolder` option to customize start menu folder for NSIS installer\n\n### Enhancements\n\n- [`61bbd8373`](https://www.github.com/tauri-apps/tauri/commit/61bbd8373fc5fc7ada835aecc6d92610d214174a) ([#10117](https://www.github.com/tauri-apps/tauri/pull/10117)) Added a public property to the msi to tell the installer to launch the app after installation. This was added for the updater plugin.\n- [`ea78bf555`](https://www.github.com/tauri-apps/tauri/commit/ea78bf55587e5a8891e918bb9b560b3b2d75f445) ([#9915](https://www.github.com/tauri-apps/tauri/pull/9915)) For NSIS installer, migrate old shortcuts by setting the path only instead of re-creating a new one\n\n### Bug Fixes\n\n- [`b9e11a8b9`](https://www.github.com/tauri-apps/tauri/commit/b9e11a8b971f149d9b2a4262eed41670716c910c) ([#10036](https://www.github.com/tauri-apps/tauri/pull/10036)) Fixed an issue that caused the AppImage to segfault on start due to an incorrect .desktop file.\n- [`3fd84cb3c`](https://www.github.com/tauri-apps/tauri/commit/3fd84cb3c9aca7560ad6db8dbebda564308a3bfa) ([#10049](https://www.github.com/tauri-apps/tauri/pull/10049)) Fix encoding of NSIS license page when using a license file without a BOM.\n- [`de7da04a6`](https://www.github.com/tauri-apps/tauri/commit/de7da04a62dfd247633a03563afbc6f387cc0b82) ([#9974](https://www.github.com/tauri-apps/tauri/pull/9974)) Use the `productName` for `rpm` package name instead of main binary name, to be consistent with other bundle types.\n- [`faf282ca6`](https://www.github.com/tauri-apps/tauri/commit/faf282ca6ccf76c706db46f85aa207018dcf1ced) ([#10103](https://www.github.com/tauri-apps/tauri/pull/10103)) Fix NSIS uninstaller failing to clean up deep links\n- [`58821fc0e`](https://www.github.com/tauri-apps/tauri/commit/58821fc0e5bcca01f3702f4d31edf91d696f323d) ([#10086](https://www.github.com/tauri-apps/tauri/pull/10086)) Fix NSIS esitmated size unit being in kB (1000 bytes) not KB (1024 bytes)\n- [`6f469534b`](https://www.github.com/tauri-apps/tauri/commit/6f469534b075d901d978c940873f9480f0d10ee0) ([#9944](https://www.github.com/tauri-apps/tauri/pull/9944)) Fix NSIS installer runs the app as admin when using `perMachine` install mode\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.18`\n- [`f955f7b49`](https://www.github.com/tauri-apps/tauri/commit/f955f7b4903bcea376c0a8b430736f66c8cebf56) ([#9929](https://www.github.com/tauri-apps/tauri/pull/9929)) Switch from `dirs_next` to `dirs` as `dirs_next` is now unmaintained while `dirs` is\n\n### Breaking Changes\n\n- [`f21029b1b`](https://www.github.com/tauri-apps/tauri/commit/f21029b1bc25f5cb987e1a25de94c2d364e3e462) ([#9994](https://www.github.com/tauri-apps/tauri/pull/9994)) Changed NSIS start menu shortcut to be placed directly inside `%AppData%\\Microsoft\\Windows\\Start Menu\\Programs` without an additional folder. You can get the old behavior by setting `bundle > nsis > startMenuFolder` to the same value as your `productName`\n- [`911242f09`](https://www.github.com/tauri-apps/tauri/commit/911242f0928e0a2add3595fa9de27850fb875fa6) ([#9883](https://www.github.com/tauri-apps/tauri/pull/9883)) Move updater target from `bundle > targets` to a separate field `bundle > createUpdaterArtifacts`\n\n## \\[2.0.1-beta.16]\n\n### Bug Fixes\n\n- [`38df6ea1c`](https://www.github.com/tauri-apps/tauri/commit/38df6ea1c184307d294ee467c4b1c6a488b6e289)([#9909](https://www.github.com/tauri-apps/tauri/pull/9909)) Fix regression in NSIS where it created shortcuts that point to non-existent files.\n\n## \\[2.0.1-beta.15]\n\n### New Features\n\n- [`5462e5cad`](https://www.github.com/tauri-apps/tauri/commit/5462e5cadc73c1b9083d852061d7c7f982cfbe53)([#9731](https://www.github.com/tauri-apps/tauri/pull/9731)) Add support for NSIS installer hooks providing a path to a `.nsh` file in `bundle > windows > nsis > installer_hooks` key in `tauri.conf.json`.\n- [`d6d3efbd1`](https://www.github.com/tauri-apps/tauri/commit/d6d3efbd125489cb46642b6d013cdc1eb7fc1a66)([#9865](https://www.github.com/tauri-apps/tauri/pull/9865)) On Windows, add option to specify a custom signing command to be used. This opens an endless possibilities, for example use `osslsigncode` on non-Windows or use hardware tokens and HSM or even using Azure Trusted Signing.\n\n### Enhancements\n\n- [`418d72d72`](https://www.github.com/tauri-apps/tauri/commit/418d72d72ded0e8238ae433ac60e5c6df19d947c)([#9559](https://www.github.com/tauri-apps/tauri/pull/9559)) Added `/UPDATE` flag for NSIS installer which will make the installer avoid deleting app data and re-creating shortcuts.\n\n### Bug Fixes\n\n- [`4754786aa`](https://www.github.com/tauri-apps/tauri/commit/4754786aa278a9e61f3973ce736f248b075e6bdc)([#9885](https://www.github.com/tauri-apps/tauri/pull/9885)) Fixed an issue causing the deep link feature to create invalid `Info.plist` values on macOS.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.17`\n\n### Breaking Changes\n\n- [`fc1543c65`](https://www.github.com/tauri-apps/tauri/commit/fc1543c65e736622bed93543dcc6504c43e200bb)([#9864](https://www.github.com/tauri-apps/tauri/pull/9864)) Removed `skip_webview_install` (`skipWebviewInstall`) option from config, which has been deprecated for a while now and planned to be removed in v2. Use `webview_install_mode` (`webviewInstallMode`) instead.\n\n## \\[2.0.1-beta.14]\n\n### Enhancements\n\n- [`781d74799`](https://www.github.com/tauri-apps/tauri/commit/781d74799a543def205a255fa6fced19967c9872)([#9840](https://www.github.com/tauri-apps/tauri/pull/9840)) Reduced the compression level for rpm bundles from 9 (max) to 6. This has almost no effect on file size but should reduce build time by roughly 25%.\n\n### Bug Fixes\n\n- [`d0d974fa5`](https://www.github.com/tauri-apps/tauri/commit/d0d974fa5ef250b2a9219e4c6a2ca175f495e88f)([#9833](https://www.github.com/tauri-apps/tauri/pull/9833)) Fix NSIS installer deep links registration.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.16`\n\n## \\[2.0.1-beta.13]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.15`\n\n## \\[2.0.1-beta.12]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.14`\n\n## \\[2.0.1-beta.11]\n\n### Enhancements\n\n- [`128c58000`](https://www.github.com/tauri-apps/tauri/commit/128c5800091b7fda54bf7d157b785066281e0c74)([#9604](https://www.github.com/tauri-apps/tauri/pull/9604)) Update `nsis_tauri_utils` plugin to `0.3` and use the built-in NSIS download plugin, which reduces the NSIS installer size by 775kb.\n- [`68c39b8c0`](https://www.github.com/tauri-apps/tauri/commit/68c39b8c0ca79e2fa9e250fccdf966136af18c0e)([#9527](https://www.github.com/tauri-apps/tauri/pull/9527)) Use nsis's built-in COM plugin instead of `ApplicationID` plugin, this reduces the installer size by 100 KB, and also fixes pinned shortcut not getting cleaned up on uninstall.\n\n## \\[2.0.1-beta.10]\n\n### New Features\n\n- [`05088b067`](https://www.github.com/tauri-apps/tauri/commit/05088b0679912ab352e54bfed02e0b97dd3f0f08)([#9494](https://www.github.com/tauri-apps/tauri/pull/9494)) Expose `{{long_description}}` variable for custom templates.\n\n### Enhancements\n\n- [`de7bcf3cc`](https://www.github.com/tauri-apps/tauri/commit/de7bcf3cc5cea6754491a9a4a8657ef3321c8398)([#9478](https://www.github.com/tauri-apps/tauri/pull/9478)) Append product name automatically when choosing a new install path using browse for nsis installer\n\n### Bug Fixes\n\n- [`e64b8f1dc`](https://www.github.com/tauri-apps/tauri/commit/e64b8f1dcedad3222f46755bf6f30392a7ec2f90)([#9479](https://www.github.com/tauri-apps/tauri/pull/9479)) The NSIS uninstaller now won't mindlessly try to remove the whole installation folder when the \"Remove application data\" checkbox was ticked. This prevents data loss when the app was installed in a folder which contained other files.\n- [`e64b8f1dc`](https://www.github.com/tauri-apps/tauri/commit/e64b8f1dcedad3222f46755bf6f30392a7ec2f90)([#9479](https://www.github.com/tauri-apps/tauri/pull/9479)) Fixed an issue causing the NSIS bundler to install resources incorrectly when the installer was built on a non-Windows system.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.13`\n\n## \\[2.0.1-beta.9]\n\n### New Features\n\n- [`36b4c1249`](https://www.github.com/tauri-apps/tauri/commit/36b4c12497fbe636066f4848c6877b3ab6cc892e)([#9331](https://www.github.com/tauri-apps/tauri/pull/9331)) Added support for `provides`, `conflicts` and `replaces` (`obsoletes` for RPM) options for `bundler > deb` and `bundler > rpm` configs.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.12`\n\n## \\[2.0.1-beta.8]\n\n### New Features\n\n- [`259d84529`](https://www.github.com/tauri-apps/tauri/commit/259d845290dde40639537258b2810567910f47f3)([#9209](https://www.github.com/tauri-apps/tauri/pull/9209)) Add suport for include `preinstall`, `postinstall`, `preremove` and `postremove` scripts into Debian and RPM packages.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.11`\n\n## \\[2.0.1-beta.7]\n\n### Bug Fixes\n\n- [`a799f24f9`](https://www.github.com/tauri-apps/tauri/commit/a799f24f97e12c3f3fcdd1864fdd7c6559263fb7)([#9185](https://www.github.com/tauri-apps/tauri/pull/9185)) Fixed an issue that caused the msi bundler to crash when deep link schemes were configured.\n\n## \\[2.0.1-beta.6]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.10`\n\n## \\[2.0.1-beta.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.9`\n\n## \\[2.0.1-beta.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.8`\n\n## \\[2.0.1-beta.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.7`\n\n## \\[2.0.1-beta.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.6`\n\n## \\[2.0.1-beta.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.5`\n\n## \\[2.0.1-beta.0]\n\n### Bug Fixes\n\n- [`84c783f6`](https://www.github.com/tauri-apps/tauri/commit/84c783f6bc46827032666b0cba100ed37560240c)([#8948](https://www.github.com/tauri-apps/tauri/pull/8948)) Fix NSIS installer always containing a license page even though `licenseFile` option is not set in the config.\n- [`84c783f6`](https://www.github.com/tauri-apps/tauri/commit/84c783f6bc46827032666b0cba100ed37560240c)([#8948](https://www.github.com/tauri-apps/tauri/pull/8948)) Don't fallback to `licenseFile` and use only `license` field when building RPM.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.4`\n\n## \\[2.0.0-beta.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.3`\n\n## \\[2.0.0-beta.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.2`\n\n## \\[2.0.0-beta.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.1`\n\n## \\[2.0.0-beta.0]\n\n### Enhancements\n\n- [`d6c7568c`](https://www.github.com/tauri-apps/tauri/commit/d6c7568c27445653edf570f3969163bc358ba2ba)([#8720](https://www.github.com/tauri-apps/tauri/pull/8720)) Add `files` option to the AppImage Configuration.\n- [`30be0e30`](https://www.github.com/tauri-apps/tauri/commit/30be0e305773edbd07d3834d2cad979ac67a4d23)([#8303](https://www.github.com/tauri-apps/tauri/pull/8303)) Added Russian language support to the NSIS bundler.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.0`\n\n### Breaking Changes\n\n- [`8de308d1`](https://www.github.com/tauri-apps/tauri/commit/8de308d1bf6a855d7a26af58bd0e744938ba47d8)([#8723](https://www.github.com/tauri-apps/tauri/pull/8723)) -   Removed all license fields from `WixSettings`, `NsisSettings` and `MacOsSettings` and replaced with `license` and `license_file` fields in `BundlerSettings`.\n\n## \\[2.0.0-alpha.14]\n\n### New Features\n\n- [`27bad32d`](https://www.github.com/tauri-apps/tauri/commit/27bad32d4d4acca8155b20225d529d540fb9aaf4)([#7798](https://www.github.com/tauri-apps/tauri/pull/7798)) Add `files` object on the `tauri > bundle > macOS` configuration option.\n- [`27bad32d`](https://www.github.com/tauri-apps/tauri/commit/27bad32d4d4acca8155b20225d529d540fb9aaf4)([#7798](https://www.github.com/tauri-apps/tauri/pull/7798)) Add `files` map on the `MacOsSettings` struct to add custom files to the `.app` bundle.\n\n### Enhancements\n\n- [`091100ac`](https://www.github.com/tauri-apps/tauri/commit/091100acbb507b51de39fb1446f685926f888fd2)([#5202](https://www.github.com/tauri-apps/tauri/pull/5202)) Add RPM packaging\n- [`8032b22f`](https://www.github.com/tauri-apps/tauri/commit/8032b22f2a5b866e4be8b9bd2165eedd09309e15)([#8596](https://www.github.com/tauri-apps/tauri/pull/8596)) Support using socks proxy from environment when downloading files.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.13`\n\n## \\[2.0.0-alpha.13]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.12`\n\n## \\[2.0.0-alpha.12]\n\n### Bug Fixes\n\n- [`34196e25`](https://www.github.com/tauri-apps/tauri/commit/34196e25c4ca2362bdfe1cf4598082aca71fe0a0)([#8182](https://www.github.com/tauri-apps/tauri/pull/8182)) Use original version string on WiX output file name.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.11`\n\n## \\[2.0.0-alpha.11]\n\n### Enhancements\n\n- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release.\n- [`cfe6fa6c`](https://www.github.com/tauri-apps/tauri/commit/cfe6fa6c91a8cc177d4665ba04dad32ba545159d)([#8061](https://www.github.com/tauri-apps/tauri/pull/8061)) Added German language support to the NSIS bundler.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.10`\n\n## \\[2.0.0-alpha.10]\n\n### New Features\n\n- [`880266a7`](https://www.github.com/tauri-apps/tauri/commit/880266a7f697e1fe58d685de3bb6836ce5251e92)([#8031](https://www.github.com/tauri-apps/tauri/pull/8031)) Bump the MSRV to 1.70.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.9`\n\n## \\[2.0.0-alpha.9]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.8`\n\n## \\[2.0.0-alpha.8]\n\n### Enhancements\n\n- [`04949d16`](https://www.github.com/tauri-apps/tauri/commit/04949d16586acddab97a3c083a61c81b18d6933e)([#7624](https://www.github.com/tauri-apps/tauri/pull/7624)) Added Bulgarian language support to the NSIS bundler.\n\n## \\[2.0.0-alpha.7]\n\n### Bug Fixes\n\n- [`3065c8ae`](https://www.github.com/tauri-apps/tauri/commit/3065c8aea375535763e1532951c4057a426fce80)([#7296](https://www.github.com/tauri-apps/tauri/pull/7296)) Enable `zip`'s `deflate` feature flag to fix issues when downloading nsis and wix tools.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.7`\n\n## \\[2.0.0-alpha.6]\n\n### Dependencies\n\n- Updated to latest `tauri-utils`\n\n## \\[2.0.0-alpha.5]\n\n- [`2d5378bf`](https://www.github.com/tauri-apps/tauri/commit/2d5378bfc1ba817ee2f331b41738a90e5997e5e8)([#6717](https://www.github.com/tauri-apps/tauri/pull/6717)) Removed the `UpdaterSettings::dialog` field.\n- [`6a6b1388`](https://www.github.com/tauri-apps/tauri/commit/6a6b1388ea5787aea4c0e0b0701a4772087bbc0f)([#6853](https://www.github.com/tauri-apps/tauri/pull/6853)) Correctly escape XML for resource files in WiX bundler.\n- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65.\n- [`422b4817`](https://www.github.com/tauri-apps/tauri/commit/422b48179856504e980a156500afa8e22c44d357)([#6871](https://www.github.com/tauri-apps/tauri/pull/6871)) Added the following languages to the NSIS bundler:\n\n  - `Spanish`\n  - `SpanishInternational`\n- [`2915bd06`](https://www.github.com/tauri-apps/tauri/commit/2915bd068ed40dc01a363b69212c6b6f2d3ec01e)([#6854](https://www.github.com/tauri-apps/tauri/pull/6854)) Correctly escape arguments in NSIS script to fix bundling apps that use non-default WebView2 install modes.\n\n## \\[2.0.0-alpha.4]\n\n- Added `android` configuration object under `tauri > bundle`.\n  - Bumped due to a bump in tauri-utils.\n  - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07\n\n## \\[2.0.0-alpha.3]\n\n- Pull changes from Tauri 1.3 release.\n  - [](https://www.github.com/tauri-apps/tauri/commit/undefined)  on undefined\n\n## \\[2.0.0-alpha.2]\n\n- Added the `shadow` option to the window configuration and `set_shadow` option to the `window` allow list.\n  - Bumped due to a bump in tauri-utils.\n  - [a81750d7](https://www.github.com/tauri-apps/tauri/commit/a81750d779bc72f0fdb7de90b7fbddfd8049b328) feat(core): add shadow APIs ([#6206](https://www.github.com/tauri-apps/tauri/pull/6206)) on 2023-02-08\n\n## \\[2.0.0-alpha.1]\n\n- Bump the MSRV to 1.64.\n  - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30\n\n## \\[2.0.0-alpha.0]\n\n- First mobile alpha release!\n  - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08\n\n## \\[1.5.1]\n\n### Bug Fixes\n\n- [`516386158`](https://www.github.com/tauri-apps/tauri/commit/5163861588b229fe2e13e61bf65fbf5b88743bb3)([#9040](https://www.github.com/tauri-apps/tauri/pull/9040)) On Windows, fix building WiX installer when resources contains an XML charcter that should be escaped.\n- [`97a05145f`](https://www.github.com/tauri-apps/tauri/commit/97a05145fbb24533526eba6589594f03046e11df)([#9119](https://www.github.com/tauri-apps/tauri/pull/9119)) Fix compilation error due to dependency on unstable features of `log` crate.\n\n## \\[1.5.0]\n\n### New Features\n\n- [`7aa30dec`](https://www.github.com/tauri-apps/tauri/commit/7aa30dec85a17c3d3faaf3841b93e10991b991b0)([#8620](https://www.github.com/tauri-apps/tauri/pull/8620)) Add `priority`, `section` and `changelog` options in Debian config.\n- [`89911296`](https://www.github.com/tauri-apps/tauri/commit/89911296e475d5c36f3486b9b75232505846e767)([#8259](https://www.github.com/tauri-apps/tauri/pull/8259)) On macOS, support for signing nested .dylib, .app, .xpc and .framework under predefined directories inside the bundled frameworks (\"MacOS\", \"Frameworks\", \"Plugins\", \"Helpers\", \"XPCServices\" and \"Libraries\").\n- [`8ce51cec`](https://www.github.com/tauri-apps/tauri/commit/8ce51cec3baf4ed88d80c59bf3bbe96fd369c7a0)([#7718](https://www.github.com/tauri-apps/tauri/pull/7718)) On Windows, NSIS installer now supports `/ARGS` flag to pass arguments to be used when launching the app after installation, only works if `/R` is used.\n\n### Enhancements\n\n- [`06890c70`](https://www.github.com/tauri-apps/tauri/commit/06890c70c643516b4e8037af87c8ee9103b977fa)([#8611](https://www.github.com/tauri-apps/tauri/pull/8611)) Support using socks proxy from environment when downloading files.\n\n### Bug Fixes\n\n- [`6bdba1f3`](https://www.github.com/tauri-apps/tauri/commit/6bdba1f330bedb5cdeda49eca1e295f281eb82eb)([#8585](https://www.github.com/tauri-apps/tauri/pull/8585)) Fix the `non-standard-file-perm` and `non-standard-dir-perm` issue in Debian packages\n\n### Dependencies\n\n- [`49266487`](https://www.github.com/tauri-apps/tauri/commit/4926648751ddbf764b8ffc46f3adc218afb2d472)([#8618](https://www.github.com/tauri-apps/tauri/pull/8618)) Replace `libflate` with `flate2` , this will help to provide additional functionalities and features.\n\n## \\[1.4.8]\n\n### Enhancements\n\n- [`b44e9c0f`](https://www.github.com/tauri-apps/tauri/commit/b44e9c0fcbb3f6994e38b8ef1ae18515db18ba7d)([#8431](https://www.github.com/tauri-apps/tauri/pull/8431)) Check if required files/tools for bundling are outdated or mis-hashed and redownload them.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.2`\n\n## \\[1.4.7]\n\n### Bug Fixes\n\n- [`777ddf43`](https://www.github.com/tauri-apps/tauri/commit/777ddf434a966966dc8918322c1ec9ee3f822ee2)([#8376](https://www.github.com/tauri-apps/tauri/pull/8376)) Unset `NSISDIR` and `NSISCONFDIR` when running `makensis.exe` so it doesn't conflict with NSIS installed by the user.\n- [`5ff9d459`](https://www.github.com/tauri-apps/tauri/commit/5ff9d4592a6dd8fc93165012ef367d78ea06e4ce)([#8390](https://www.github.com/tauri-apps/tauri/pull/8390)) NSIS perUser installers will now only check if the app is running on the current user.\n\n## \\[1.4.6]\n\n### Bug Fixes\n\n- [`1d5aa38a`](https://www.github.com/tauri-apps/tauri/commit/1d5aa38ae418ea31f593590b6d32cf04d3bfd8c1)([#8162](https://www.github.com/tauri-apps/tauri/pull/8162)) Fixes errors on command output, occuring when the output stream contains an invalid UTF-8 character, or ends with a multi-bytes UTF-8 character.\n- [`977a39f4`](https://www.github.com/tauri-apps/tauri/commit/977a39f4f7fb5e47492b51df931643b1af4f92b0)([#8292](https://www.github.com/tauri-apps/tauri/pull/8292)) Migrate the WebView2 offline installer to use shorturl provided by Microsoft.\n- [`f26d9f08`](https://www.github.com/tauri-apps/tauri/commit/f26d9f0884f63f61b9f4d4fac15e6b251163793e)([#8263](https://www.github.com/tauri-apps/tauri/pull/8263)) Fixes an issue in the NSIS installer which caused the uninstallation to leave empty folders on the system if the `resources` feature was used.\n- [`92bc7d0e`](https://www.github.com/tauri-apps/tauri/commit/92bc7d0e16157434330a1bcf1eefda6f0f1e5f85)([#8233](https://www.github.com/tauri-apps/tauri/pull/8233)) Fixes an issue in the NSIS installer which caused the installation to take much longer than expected when many `resources` were added to the bundle.\n\n## \\[1.4.5]\n\n### Enhancements\n\n- [`cfe6fa6c`](https://www.github.com/tauri-apps/tauri/commit/cfe6fa6c91a8cc177d4665ba04dad32ba545159d)([#8061](https://www.github.com/tauri-apps/tauri/pull/8061)) Added German language support to the NSIS bundler.\n\n## \\[1.4.4]\n\n### Enhancements\n\n- [`3880b42d`](https://www.github.com/tauri-apps/tauri/commit/3880b42d18f218b89998bb5ead1aacfbed9d6202)([#7974](https://www.github.com/tauri-apps/tauri/pull/7974)) Include notarytool log output on error message in case notarization fails.\n\n### Bug Fixes\n\n- [`be8e5aa3`](https://www.github.com/tauri-apps/tauri/commit/be8e5aa3071d9bc5d0bd24647e8168f312d11c8d)([#8042](https://www.github.com/tauri-apps/tauri/pull/8042)) Fixes duplicated newlines on command outputs.\n\n## \\[1.4.3]\n\n### Bug Fixes\n\n- [`d0ae6750`](https://www.github.com/tauri-apps/tauri/commit/d0ae67503cdb2aeaadcea27af67285eea1cf3756)([#8012](https://www.github.com/tauri-apps/tauri/pull/8012)) Read `HTTP_PROXY` env var when downloading bundling resources on Windows.\n- [`113bcd7b`](https://www.github.com/tauri-apps/tauri/commit/113bcd7b684a72eb0f421c663c6aa874197252bb)([#7980](https://www.github.com/tauri-apps/tauri/pull/7980)) In Debian packages, set `root` the owner of control files and package files.\n\n## \\[1.4.2]\n\n### Bug Fixes\n\n- [`f552c179`](https://www.github.com/tauri-apps/tauri/commit/f552c1796a61a5cfd51fad6d616bea3164b48a21)([#7998](https://www.github.com/tauri-apps/tauri/pull/7998)) Update the WebView2 offline installer GUIDs to resolve the 404 HTTP response status codes.\n\n## \\[1.4.1]\n\n### Bug Fixes\n\n- [`40d34002`](https://www.github.com/tauri-apps/tauri/commit/40d340021c0eab65aa1713807f7564e0698a321e)([#7972](https://www.github.com/tauri-apps/tauri/pull/7972)) The `APPLE_TEAM_ID` environment variable is now required for notarization authentication via Apple ID and app-specific password.\n- [`cdd5516f`](https://www.github.com/tauri-apps/tauri/commit/cdd5516f339ad4345623a1e785c6e2c3a77477f8)([#7956](https://www.github.com/tauri-apps/tauri/pull/7956)) Fixes an app crash on app updates when the product name contained spaces.\n\n## \\[1.4.0]\n\n### New Features\n\n- [`4dd4893d`](https://www.github.com/tauri-apps/tauri/commit/4dd4893d7d166ac3a3b6dc2e3bd2540326352a78)([#5950](https://www.github.com/tauri-apps/tauri/pull/5950)) Allow using a resource map instead of a simple array in `BundleSettings::resources_map`.\n\n### Enhancements\n\n- [`764968ab`](https://www.github.com/tauri-apps/tauri/commit/764968ab383ec639e061986bc2411dd44e71b612)([#7398](https://www.github.com/tauri-apps/tauri/pull/7398)) Sign NSIS uninstaller as well.\n- [`2f8881c0`](https://www.github.com/tauri-apps/tauri/commit/2f8881c010fa3493c092ddf3a343df08d7a79fc9)([#7775](https://www.github.com/tauri-apps/tauri/pull/7775)) Read the `APPLE_TEAM_ID` environment variable for macOS notarization arguments.\n- [`cb1d4164`](https://www.github.com/tauri-apps/tauri/commit/cb1d4164e71e29f071b8438d02a7ec86a9fac67b)([#7487](https://www.github.com/tauri-apps/tauri/pull/7487)) On Windows, code sign the application binaries before trying to create the WiX and NSIS bundles to always sign the executables even if no bundle types are enabled.\n\n  On Windows, code sign the sidecar binaries if they are not signed already.\n- [`57f73f1b`](https://www.github.com/tauri-apps/tauri/commit/57f73f1b6a07e690d122d7534a0b3531c8c12c03)([#7486](https://www.github.com/tauri-apps/tauri/pull/7486)) On Windows, NSIS installer will write webview2 installer file to the well-known temp dir instead of the install dir, so we don't pollute the install dir.\n- [`a7777ff4`](https://www.github.com/tauri-apps/tauri/commit/a7777ff485b725f177d08bbc00af607cd8ee8d6d)([#7626](https://www.github.com/tauri-apps/tauri/pull/7626)) Added Bulgarian language support to the NSIS bundler.\n- [`e3bfb014`](https://www.github.com/tauri-apps/tauri/commit/e3bfb01411c3cc5e602c8f961f6cb5c9dd9524e1)([#7776](https://www.github.com/tauri-apps/tauri/pull/7776)) Add `compression` configuration option under `tauri > bundle > windows > nsis`.\n\n### Bug Fixes\n\n- [`46df2c9b`](https://www.github.com/tauri-apps/tauri/commit/46df2c9b917096388695f72ca4c56791fe652ef6)([#7360](https://www.github.com/tauri-apps/tauri/pull/7360)) Fix bundler skipping updater artifacts if `updater` target shows before other updater-enabled targets in the list, see [#7349](https://github.com/tauri-apps/tauri/issues/7349).\n- [`2d35f937`](https://www.github.com/tauri-apps/tauri/commit/2d35f937de59272d26556310155c0b15d849953c)([#7481](https://www.github.com/tauri-apps/tauri/pull/7481)) Fix bundler skipping updater artifacts if only a macOS DMG bundle target is specified.\n- [`dcdbe3eb`](https://www.github.com/tauri-apps/tauri/commit/dcdbe3eb6cc7d8a43caef98dfce71a11a4597644)([#7774](https://www.github.com/tauri-apps/tauri/pull/7774)) Remove extended attributes on the macOS app bundle using `xattr -cr $PATH`.\n- [`dcdbe3eb`](https://www.github.com/tauri-apps/tauri/commit/dcdbe3eb6cc7d8a43caef98dfce71a11a4597644)([#7774](https://www.github.com/tauri-apps/tauri/pull/7774)) Code sign sidecars and frameworks on macOS.\n- [`eba8e131`](https://www.github.com/tauri-apps/tauri/commit/eba8e1315ed7078eb9a9479f9e0072b061067341)([#7386](https://www.github.com/tauri-apps/tauri/pull/7386)) On Windows, fix installation packages not showing correct copyright information.\n- [`32218a6f`](https://www.github.com/tauri-apps/tauri/commit/32218a6f8c1d90c2503e7cbc4523e4ab464ba032)([#7326](https://www.github.com/tauri-apps/tauri/pull/7326)) On Windows, fix NSIS installer identifying a previous NSIS-installed app as WiX-installed app and then fails to uninstall it.\n- [`ca977f4b`](https://www.github.com/tauri-apps/tauri/commit/ca977f4b87c66808b4eac31a6d1925842b4c1570)([#7591](https://www.github.com/tauri-apps/tauri/pull/7591)) On Windows, Fix NSIS uninstaller deleting the wrong application data if the delete the application data checkbox is checked.\n- [`0ae53f41`](https://www.github.com/tauri-apps/tauri/commit/0ae53f413948c7b955e595aa9c6c9e777caa8666)([#7361](https://www.github.com/tauri-apps/tauri/pull/7361)) On Windows, fix NSIS installer showing an error dialog even when the previous version was uninstalled sucessfully.\n- [`09f7f57e`](https://www.github.com/tauri-apps/tauri/commit/09f7f57eeadbf94d8e9e14f3ab2b115a4c4aa473)([#7711](https://www.github.com/tauri-apps/tauri/pull/7711)) On Windows, fix NSIS installer trying to kill itself if the installer file name and the app `productName` are the same.\n- [`6e36ebbf`](https://www.github.com/tauri-apps/tauri/commit/6e36ebbf84dee11a98d8df916c316c7d6f67b2a8)([#7342](https://www.github.com/tauri-apps/tauri/pull/7342)) On Windows, fix NSIS uninstaller failing to remove Start Menu shortcut if `perMachine` mode is used.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.0`\n- [`a2be88a2`](https://www.github.com/tauri-apps/tauri/commit/a2be88a21db76e9fa063c527031f3849f066eecd)([#7405](https://www.github.com/tauri-apps/tauri/pull/7405)) Removed the `bitness` dependency to speed up compile time.\n\n### Breaking Changes\n\n- [`964d81ff`](https://www.github.com/tauri-apps/tauri/commit/964d81ff01a076516d323546c169b2ba8156e55a)([#7616](https://www.github.com/tauri-apps/tauri/pull/7616)) The macOS notarization now uses `notarytool` as `altool` will be discontinued on November 2023. When authenticating with an API key, the key `.p8` file path must be provided in the `APPLE_API_KEY_PATH` environment variable. To prevent a breaking change, we will try to find the key path in the `altool` default search paths.\n\n## \\[1.3.0]\n\n### New Features\n\n- [`35cd751a`](https://www.github.com/tauri-apps/tauri/commit/35cd751adc6fef1f792696fa0cfb471b0bf99374)([#5176](https://www.github.com/tauri-apps/tauri/pull/5176)) Added `desktop_template` option on `DebianSettings`.\n- [`29488205`](https://www.github.com/tauri-apps/tauri/commit/2948820579d20dfaa0861c2f0a58bd7737a7ffd1)([#6867](https://www.github.com/tauri-apps/tauri/pull/6867)) Allow specifying custom language files of Tauri's custom messages for the NSIS installer\n- [`e092f799`](https://www.github.com/tauri-apps/tauri/commit/e092f799469ff32c7d1595d0f07d06fd2dab5c29)([#6887](https://www.github.com/tauri-apps/tauri/pull/6887)) Add `nsis > template` option to specify custom NSIS installer template.\n- [`df89ccc1`](https://www.github.com/tauri-apps/tauri/commit/df89ccc1912db6b81d43d56c9e6d66980ece2e8d)([#6955](https://www.github.com/tauri-apps/tauri/pull/6955)) For NSIS, Add support for `/P` to install or uninstall in passive mode, `/R` to (re)start the app and `/NS` to disable creating shortcuts in `silent` and `passive` modes.\n\n### Enhancements\n\n- [`3327dd64`](https://www.github.com/tauri-apps/tauri/commit/3327dd641d929ff7457a37cd7e557b45314b9755)([#7081](https://www.github.com/tauri-apps/tauri/pull/7081)) Remove macOS app bundles from the output if they are not requested by the user.\n- [`fc7f9eba`](https://www.github.com/tauri-apps/tauri/commit/fc7f9ebada959e555f4cff617286f52ce463f29e)([#7001](https://www.github.com/tauri-apps/tauri/pull/7001)) Added Copyright field as BrandingText to the NSIS bundler.\n- [`540ddd4e`](https://www.github.com/tauri-apps/tauri/commit/540ddd4e6a4926c26507a8d06fbe72a5895f8509)([#6906](https://www.github.com/tauri-apps/tauri/pull/6906)) Added Dutch language support to the NSIS bundler.\n- [`b257bebf`](https://www.github.com/tauri-apps/tauri/commit/b257bebf9e478d616690ba1c357dc7f071ea7b31)([#6906](https://www.github.com/tauri-apps/tauri/pull/6906)) Added Japanese language support to the NSIS bundler.\n- [`61e3ad89`](https://www.github.com/tauri-apps/tauri/commit/61e3ad89e93b060236d94459fb2d15938c7ce092)([#7010](https://www.github.com/tauri-apps/tauri/pull/7010)) Added Korean language support to the NSIS bundler.\n- [`21d5eb84`](https://www.github.com/tauri-apps/tauri/commit/21d5eb84ab2372466a3254e656e2ec9b346a2db7)([#6965](https://www.github.com/tauri-apps/tauri/pull/6965)) Added Persian language support to the NSIS bundler.\n- [`df89ccc1`](https://www.github.com/tauri-apps/tauri/commit/df89ccc1912db6b81d43d56c9e6d66980ece2e8d)([#6955](https://www.github.com/tauri-apps/tauri/pull/6955)) NSIS `silent` and `passive` installer/updater will auto-kill the app if its running.\n- [`43858a31`](https://www.github.com/tauri-apps/tauri/commit/43858a319725d696a298770982cc04e6dd0befb3)([#7038](https://www.github.com/tauri-apps/tauri/pull/7038)) Added Swedish language support to the NSIS bundler.\n- [`ac183948`](https://www.github.com/tauri-apps/tauri/commit/ac183948d610a01ac602846cddd80563c86c45af)([#7018](https://www.github.com/tauri-apps/tauri/pull/7018)) Added Turkish language support to the NSIS bundler.\n- [`60334f9e`](https://www.github.com/tauri-apps/tauri/commit/60334f9e02f6fdf1510e234aacc8c23382a05554)([#6859](https://www.github.com/tauri-apps/tauri/pull/6859)) NSIS installer will now check if a previous WiX `.msi` installation exist and will prompt users to uninstall it.\n- [`db7c5fbf`](https://www.github.com/tauri-apps/tauri/commit/db7c5fbf2e86f3694720f65834eb2c258b7c1291)([#7143](https://www.github.com/tauri-apps/tauri/pull/7143)) Remove `attohttpc` in favor of `ureq`.\n\n### Bug Fixes\n\n- [`0302138f`](https://www.github.com/tauri-apps/tauri/commit/0302138f2fa8c869888f07006eda333a8dd2ba7f)([#6992](https://www.github.com/tauri-apps/tauri/pull/6992)) -   Updated the AppImage bundler to follow symlinks for `/usr/lib*`.\n  - Fixes AppImage bundling for Void Linux, which was failing to bundle webkit2gtk because the `/usr/lib64` is a symlink to `/usr/lib`.\n- [`1b8001b8`](https://www.github.com/tauri-apps/tauri/commit/1b8001b8b8757b6914e9b61a71ad1e092a5fa754)([#7056](https://www.github.com/tauri-apps/tauri/pull/7056)) Fix incorrect estimated app size for NSIS bundler when installed to a non-empty directory.\n- [`df89ccc1`](https://www.github.com/tauri-apps/tauri/commit/df89ccc1912db6b81d43d56c9e6d66980ece2e8d)([#6955](https://www.github.com/tauri-apps/tauri/pull/6955)) Fix NSIS installer disabling `do not uninstall` button and silent installer aborting, if `allowDowngrades` was disabled even when we are not downgrading.\n- [`17da87d3`](https://www.github.com/tauri-apps/tauri/commit/17da87d3cd7922da18e8e2e41fcb8519f5c45f50)([#7036](https://www.github.com/tauri-apps/tauri/pull/7036)) Fix NSIS bundler failing to build when `productName` contained chinsese characters.\n- [`4d4b72ba`](https://www.github.com/tauri-apps/tauri/commit/4d4b72ba38c5cea5cade501e383d946208e21deb)([#7086](https://www.github.com/tauri-apps/tauri/pull/7086)) Fix missing quote in Japanese NSIS language file.\n- [`3cc295e9`](https://www.github.com/tauri-apps/tauri/commit/3cc295e99762d39a4a118db9b1f811f0503fe9e1)([#6928](https://www.github.com/tauri-apps/tauri/pull/6928)) Fix NSIS installer not using the old installation path as a default when using `perMachine` or `currentUser` install modes. Also fixes NSIS not respecting the `/D` flag which used to set the installation directory from command line.\n- [`df89ccc1`](https://www.github.com/tauri-apps/tauri/commit/df89ccc1912db6b81d43d56c9e6d66980ece2e8d)([#6955](https://www.github.com/tauri-apps/tauri/pull/6955)) Fix NSIS silent installer not creating Desktop and StartMenu shortcuts. Pass `/NS` to disable creating them.\n\n## \\[1.2.1]\n\n- Correctly escape XML for resource files in WiX bundler.\n  - [6a6b1388](https://www.github.com/tauri-apps/tauri/commit/6a6b1388ea5787aea4c0e0b0701a4772087bbc0f) fix(bundler): correctly escape resource xml, fixes [#6853](https://www.github.com/tauri-apps/tauri/pull/6853) ([#6855](https://www.github.com/tauri-apps/tauri/pull/6855)) on 2023-05-04\n\n- Added the following languages to the NSIS bundler:\n\n- `Spanish`\n\n- `SpanishInternational`\n\n- [422b4817](https://www.github.com/tauri-apps/tauri/commit/422b48179856504e980a156500afa8e22c44d357) Add Spanish and SpanishInternational languages ([#6871](https://www.github.com/tauri-apps/tauri/pull/6871)) on 2023-05-06\n\n- Correctly escape arguments in NSIS script to fix bundling apps that use non-default WebView2 install modes.\n  - [2915bd06](https://www.github.com/tauri-apps/tauri/commit/2915bd068ed40dc01a363b69212c6b6f2d3ec01e) fix(bundler): Fix webview install modes in NSIS bundler ([#6854](https://www.github.com/tauri-apps/tauri/pull/6854)) on 2023-05-04\n\n## \\[1.2.0]\n\n- Add dylib support to `tauri.bundle.macOS.frameworks`.\n  - [ce76d95a](https://www.github.com/tauri-apps/tauri/commit/ce76d95ab186f4556166a2a22360067eab000fc8) feat(tauri-cli): add dylib support to `tauri.bundle.macOS.frameworks`, closes [#4615](https://www.github.com/tauri-apps/tauri/pull/4615) ([#5732](https://www.github.com/tauri-apps/tauri/pull/5732)) on 2022-12-31\n- Added support for pre-release identifiers and build numbers for the `.msi` bundle target. Only one of each can be used and it must be numeric only. The version must still be semver compatible according to https://semver.org/.\n  - [20ff1f45](https://www.github.com/tauri-apps/tauri/commit/20ff1f45968f1cfd30d093bb58255b348fbdfb98) feat(bundler): Add support for numeric-only build numbers in msi version ([#6096](https://www.github.com/tauri-apps/tauri/pull/6096)) on 2023-01-19\n- On Windows, printing consistent paths on Windows with backslashs only.\n  - [9da99607](https://www.github.com/tauri-apps/tauri/commit/9da996073ff07d4b59668a5315d40e9bc578e340) fix(cli): fix printing paths on Windows ([#6137](https://www.github.com/tauri-apps/tauri/pull/6137)) on 2023-01-26\n- Fixed error during bundling process for the appimage target on subsequent bundling attempts.\n  - [2f70d8da](https://www.github.com/tauri-apps/tauri/commit/2f70d8da2bc079400bb49e6793f755306049aab2) fix: symlink issue bundling for linux [#5781](https://www.github.com/tauri-apps/tauri/pull/5781) ([#6391](https://www.github.com/tauri-apps/tauri/pull/6391)) on 2023-03-17\n- Fixes DMG bundling not finding bundle to set icon position.\n  - [7489f966](https://www.github.com/tauri-apps/tauri/commit/7489f9669734a48be063907696b0f200a293ccb6) fix(bundler): fix problem of macOS bunder while i18n is set, closes [#6614](https://www.github.com/tauri-apps/tauri/pull/6614) ([#6615](https://www.github.com/tauri-apps/tauri/pull/6615)) on 2023-04-03\n- Use escaping on Handlebars templates.\n  - [6d6b6e65](https://www.github.com/tauri-apps/tauri/commit/6d6b6e653ea70fc02794f723092cdc860995c259) feat: configure escaping on handlebars templates ([#6678](https://www.github.com/tauri-apps/tauri/pull/6678)) on 2023-05-02\n- Bump minimum supported Rust version to 1.60.\n  - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31\n- Add initial support for building `nsis` bundles on non-Windows platforms.\n  - [60e6f6c3](https://www.github.com/tauri-apps/tauri/commit/60e6f6c3f1605f3064b5bb177992530ff788ccf0) feat(bundler): Add support for creating NSIS bundles on unix hosts ([#5788](https://www.github.com/tauri-apps/tauri/pull/5788)) on 2023-01-19\n- Add `nsis` bundle target\n  - [c94e1326](https://www.github.com/tauri-apps/tauri/commit/c94e1326a7c0767a13128a8b1d327a00156ece12) feat(bundler): add `nsis`, closes [#4450](https://www.github.com/tauri-apps/tauri/pull/4450), closes [#2319](https://www.github.com/tauri-apps/tauri/pull/2319) ([#4674](https://www.github.com/tauri-apps/tauri/pull/4674)) on 2023-01-03\n- On Windows, the `msi` installer's `Launch App` checkbox will be checked by default.\n  - [89602cdc](https://www.github.com/tauri-apps/tauri/commit/89602cdce34f3ea5b4a9b8921dc04a197f2d7de8) feat(bundler): check `Launch app` by default for WiX, closes [#5859](https://www.github.com/tauri-apps/tauri/pull/5859) ([#5871](https://www.github.com/tauri-apps/tauri/pull/5871)) on 2022-12-26\n\n## \\[1.1.2]\n\n- Fixes blank taskbar icon on WiX updates.\n  - [9093ef33](https://www.github.com/tauri-apps/tauri/commit/9093ef3314c27d2295b4266893fd2290c1bdfb6a) fix(bundler): blank taskbar icon on WiX update, closes [#5631](https://www.github.com/tauri-apps/tauri/pull/5631) ([#5779](https://www.github.com/tauri-apps/tauri/pull/5779)) on 2022-12-08\n\n## \\[1.1.1]\n\n- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`.\n  - Bumped due to a bump in tauri-utils.\n  - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18\n\n## \\[1.1.0]\n\n- Use correct code `ja-JP` for japanese instead of `jp-JP`.\n  - [d4cac202](https://www.github.com/tauri-apps/tauri/commit/d4cac202923fc34962721413f7051bca50002809) fix(bundler): fix japanese lang code, closes [#5342](https://www.github.com/tauri-apps/tauri/pull/5342) ([#5346](https://www.github.com/tauri-apps/tauri/pull/5346)) on 2022-10-04\n- Fix WiX DLL load on Windows Server.\n  - [7aaf27ce](https://www.github.com/tauri-apps/tauri/commit/7aaf27ce5f026547ed490731e37cfc917458bbd6) fix(bundler): load WiX DLLs on Github Actions ([#5552](https://www.github.com/tauri-apps/tauri/pull/5552)) on 2022-11-04\n- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28\n- Add `tauri.conf.json > bundle > publisher` field to specify the app publisher.\n  - [628285c1](https://www.github.com/tauri-apps/tauri/commit/628285c1cf43f03ed62378f3b6cc0c991317526f) feat(bundler): add `publisher` field, closes [#5273](https://www.github.com/tauri-apps/tauri/pull/5273) ([#5283](https://www.github.com/tauri-apps/tauri/pull/5283)) on 2022-09-28\n- Clear environment variables on the WiX light.exe and candle.exe commands to avoid \"Windows Installer Service could not be accessed\" error. Variables prefixed with `TAURI` are propagated.\n  - [7c0fa1f3](https://www.github.com/tauri-apps/tauri/commit/7c0fa1f3f93943b87a0042b5ba3bd6bb4099304a) fix(bundler): clear env before calling wix, closes [#4791](https://www.github.com/tauri-apps/tauri/pull/4791) ([#4819](https://www.github.com/tauri-apps/tauri/pull/4819)) on 2022-10-03\n\n## \\[1.0.7]\n\n- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs.\n  - Bumped due to a bump in tauri-utils.\n  - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16\n\n## \\[1.0.6]\n\n- Avoid re-downloading AppImage build tools on every build.\n  - [02462052](https://www.github.com/tauri-apps/tauri/commit/024620529ed7c6cc601501db45abb7257f0b58f4) fix(bundler): cache appimage bundle tools ([#4790](https://www.github.com/tauri-apps/tauri/pull/4790)) on 2022-07-30\n- Add `fips_compliant` configuration option for WiX.\n  - [d88b9de7](https://www.github.com/tauri-apps/tauri/commit/d88b9de7aaeaaa2e42e4795dbc2b8642b5ae7a50) feat(core): add `fips_compliant` wix config option, closes [#4541](https://www.github.com/tauri-apps/tauri/pull/4541) ([#4843](https://www.github.com/tauri-apps/tauri/pull/4843)) on 2022-08-04\n\n## \\[1.0.5]\n\n- Correctly fill the architecture when building Debian packages targeting ARM64 (aarch64).\n  - [635f23b8](https://www.github.com/tauri-apps/tauri/commit/635f23b88adbb8726d628f67840709cd870836dc) fix(bundler): correctly set debian architecture for aarch64 ([#4700](https://www.github.com/tauri-apps/tauri/pull/4700)) on 2022-07-17\n\n## \\[1.0.4]\n\n- Reduce the amount of allocations when converting cases.\n  - [bc370e32](https://www.github.com/tauri-apps/tauri/commit/bc370e326810446e15b1f50fb962b980114ba16b) feat: reduce the amount of `heck`-related allocations ([#4634](https://www.github.com/tauri-apps/tauri/pull/4634)) on 2022-07-11\n- Automatically load WiX extensions referenced in fragments.\n  - [261d1bc9](https://www.github.com/tauri-apps/tauri/commit/261d1bc9d4a0ecf4dda16a47a1652efeabffc378) feat(bundler): load WiX extensions used on fragments, closes [#4546](https://www.github.com/tauri-apps/tauri/pull/4546) ([#4656](https://www.github.com/tauri-apps/tauri/pull/4656)) on 2022-07-12\n- Fix AppImage builds by pinning the linuxdeploy version.\n  - [89cb2526](https://www.github.com/tauri-apps/tauri/commit/89cb2526409d3b88e0aa15b93e4d26b09d9c0373) fix(bundler): pin linuxdeploy version on 2022-07-14\n- Use `Bin_${sidecarFilename}` as the `Id` of sidecar file on WiX so you can reference it in your WiX fragments.\n  - [597c9820](https://www.github.com/tauri-apps/tauri/commit/597c98203cad9b45949816659f5f59976328585a) feat(bundler): use known Id for the sidecar files on WiX, ref [#4546](https://www.github.com/tauri-apps/tauri/pull/4546) ([#4658](https://www.github.com/tauri-apps/tauri/pull/4658)) on 2022-07-12\n\n## \\[1.0.3]\n\n- Build AppImages inside the `src-tauri/target` folder rather than `~/.cache/tauri`. Making it easier to clean and rebuild from scratch.\n  - [8dd03e69](https://www.github.com/tauri-apps/tauri/commit/8dd03e69b0766eaef0b9f9fdcfe2ccbc9d0a10d1) fix(bundler): Build AppImages inside the target folder ([#4521](https://www.github.com/tauri-apps/tauri/pull/4521)) on 2022-07-03\n- Ensure the notarization `RequestUUID` and `Status` parser works on macOS 10.13.6+.\n  - [23d3d847](https://www.github.com/tauri-apps/tauri/commit/23d3d847d1b2d0c394d729a84fbeae2936111116) fix(bundler): ensure RequestUUID and Status parser adds a \\n, closes [#4549](https://www.github.com/tauri-apps/tauri/pull/4549) ([#4559](https://www.github.com/tauri-apps/tauri/pull/4559)) on 2022-07-03\n  - [f7c59ecf](https://www.github.com/tauri-apps/tauri/commit/f7c59ecfc85a90a0ff5c22da1a8b0e93d3663c86) fix(bundler): support macOS 10.13.6+ on notarization, closes [#4549](https://www.github.com/tauri-apps/tauri/pull/4549) ([#4593](https://www.github.com/tauri-apps/tauri/pull/4593)) on 2022-07-05\n\n## \\[1.0.2]\n\n- Enhance the `DownloadedBootstrapper` Webview2 install mode compatibility with Windows 8.\n  - [3df6c8c6](https://www.github.com/tauri-apps/tauri/commit/3df6c8c6454a052047b9f766691048860b50ea70) feat(bundler): enable TLS 1.2 before downloading webview2 bootstrapper ([#4543](https://www.github.com/tauri-apps/tauri/pull/4543)) on 2022-06-30\n\n## \\[1.0.1]\n\n- Fix AppImage bundling when appimagelauncher is enabled.\n  - [b0133083](https://www.github.com/tauri-apps/tauri/commit/b0133083dd4d22b0b7fdee02000ef8ecab26694b) Fix appimage creation in container when host has appimagelauncher enabled ([#4457](https://www.github.com/tauri-apps/tauri/pull/4457)) on 2022-06-27\n- Fixes AppImage bundler crashes when the file path contains whitespace.\n  - [82eb6e79](https://www.github.com/tauri-apps/tauri/commit/82eb6e79e8098bccd2b3d3581056b5350beb46c6) fix(bundler): Fix appimage bundler crashing if path has spaces ([#4471](https://www.github.com/tauri-apps/tauri/pull/4471)) on 2022-06-26\n- Ensure `usr/lib` is a directory in the AppImage bundle.\n  - [aa0336d6](https://www.github.com/tauri-apps/tauri/commit/aa0336d6c5764f1357d845f2bf3763a89a3771a1) fix(bundler): ensure AppImage usr/lib is a dir ([#4419](https://www.github.com/tauri-apps/tauri/pull/4419)) on 2022-06-21\n- AppImage bundling will now prefer bundling correctly named appindicator library (including `.1` version suffix). With a symlink for compatibility with the old naming.\n  - [bf45ca1d](https://www.github.com/tauri-apps/tauri/commit/bf45ca1df6691c05bdf72c5716cc01e89a7791d4) fix(cli,bundler): prefer AppImage libraries with ABI version ([#4505](https://www.github.com/tauri-apps/tauri/pull/4505)) on 2022-06-29\n- Fix language code for korean (ko-KR).\n  - [08a73acd](https://www.github.com/tauri-apps/tauri/commit/08a73acde877453ca5b45ea7548cdd3d407366a2) fix(bundler): fix language code. closes [#4437](https://www.github.com/tauri-apps/tauri/pull/4437) ([#4444](https://www.github.com/tauri-apps/tauri/pull/4444)) on 2022-06-24\n- Use the plist crate instead of the `PlistBuddy` binary to merge user Info.plist file.\n  - [45076b3e](https://www.github.com/tauri-apps/tauri/commit/45076b3ede4c5a3c14ffc0e4277c2c87639690cb) refactor(bundler): use the `plist` crate to create and merge Info.plist ([#4412](https://www.github.com/tauri-apps/tauri/pull/4412)) on 2022-06-21\n- Validate app version before bundling WiX.\n  - [672174b8](https://www.github.com/tauri-apps/tauri/commit/672174b822fcd2dff4a4aeeab370be3748e13843) feat(bundler): validate version before bundling with WiX ([#4429](https://www.github.com/tauri-apps/tauri/pull/4429)) on 2022-06-21\n- Check if `$HOME\\AppData\\Local\\tauri\\WixTools` directory has all the required files and redownload WiX if something is missing.\n  - [956af4f3](https://www.github.com/tauri-apps/tauri/commit/956af4f30f665a1d059aad15d070b4bab9ca49b3) feat(bundler): validate wix toolset files, ref [#4474](https://www.github.com/tauri-apps/tauri/pull/4474) ([#4475](https://www.github.com/tauri-apps/tauri/pull/4475)) on 2022-06-26\n- Added webview install mode options.\n  - [2ca762d2](https://www.github.com/tauri-apps/tauri/commit/2ca762d207030a892a6d128b519e150e2d733468) feat(bundler): extend webview2 installation options, closes [#2882](https://www.github.com/tauri-apps/tauri/pull/2882) [#2452](https://www.github.com/tauri-apps/tauri/pull/2452) ([#4466](https://www.github.com/tauri-apps/tauri/pull/4466)) on 2022-06-26\n\n## \\[1.0.0]\n\n- Upgrade to `stable`!\n  - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15\n\n## \\[1.0.0-rc.10]\n\n- Bundle the tray library file (`.so` extension) in the AppImage if the `TRAY_LIBRARY_PATH` environment variable is set.\n  - [34552444](https://www.github.com/tauri-apps/tauri/commit/3455244436578003a5fbb447b039e5c8971152ec) feat(cli): bundle appindicator library in the AppImage, closes [#3859](https://www.github.com/tauri-apps/tauri/pull/3859) ([#4267](https://www.github.com/tauri-apps/tauri/pull/4267)) on 2022-06-07\n- Bundle additional gstreamer files needed for audio and video playback if the `APPIMAGE_BUNDLE_GSTREAMER` environment variable is set.\n  - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10\n- Cache bundling tools in a directory shared by all tauri projects. Usually in `$XDG_HOME/.cache/tauri` on Linux and `$HOME\\AppData\\Local\\tauri` on Windows.\n  - [f48b1b0b](https://www.github.com/tauri-apps/tauri/commit/f48b1b0b3bec6a2a85c7dac67a128fa578f7ee15) feat(bundler): cache bundling tools in a common dir for all projects ([#4305](https://www.github.com/tauri-apps/tauri/pull/4305)) on 2022-06-09\n- Pull correct linuxdeploy AppImage when building for 32-bit targets.\n  - [53ae13d9](https://www.github.com/tauri-apps/tauri/commit/53ae13d99a06b786fb4fbeb1a10e934f7be3008c) fix(bundler): Pull correct 32bit linuxdeploy appimage, closes [#4260](https://www.github.com/tauri-apps/tauri/pull/4260) ([#4269](https://www.github.com/tauri-apps/tauri/pull/4269)) on 2022-06-04\n- Copy the `/usr/bin/xdg-open` binary if it exists and the `APPIMAGE_BUNDLE_XDG_OPEN` environment variable is set.\n  - [2322ac11](https://www.github.com/tauri-apps/tauri/commit/2322ac11cf6290c6bf65413048a049c8072f863b) fix(bundler): bundle `/usr/bin/xdg-open` in appimage if open API enabled ([#4265](https://www.github.com/tauri-apps/tauri/pull/4265)) on 2022-06-04\n- On Linux, high-dpi icons are now placed in the correct directory\n  and should be recognized by the desktop environment.\n  - [c2b7c775](https://www.github.com/tauri-apps/tauri/commit/c2b7c7751764d0963362a4b271cd921d6424e5e9) fix: put linux high dpi icons in the correct dir ([#4281](https://www.github.com/tauri-apps/tauri/pull/4281)) on 2022-06-10\n- Only png files from tauri.conf.json > tauri.bundle.icon are used for app icons for linux targets.\n  Previously, all sizes from all source files (10 files using tauricon defaults) were used.\n  This also prevents unexpectedly mixed icons in cases where platform-specific icons are used.\n  - [a6f45d52](https://www.github.com/tauri-apps/tauri/commit/a6f45d5248ec0f4d3876afd8f106998306ea931e) Debian icon no fallback, fixes [#4280](https://www.github.com/tauri-apps/tauri/pull/4280) ([#4282](https://www.github.com/tauri-apps/tauri/pull/4282)) on 2022-06-09\n- Log command output in real time instead of waiting for it to finish.\n  - [76d1eaae](https://www.github.com/tauri-apps/tauri/commit/76d1eaaebda5c8f6b0d41bf6587945e98cd441f3) feat(cli): debug command output in real time ([#4318](https://www.github.com/tauri-apps/tauri/pull/4318)) on 2022-06-12\n\n## \\[1.0.0-rc.9]\n\n- Statically link the Visual C++ runtime instead of using a merge module on the installer.\n  - [bb061509](https://www.github.com/tauri-apps/tauri/commit/bb061509fb674bef86ecbc1de3aa8f3e367a9907) refactor(core): statically link vcruntime, closes [#4122](https://www.github.com/tauri-apps/tauri/pull/4122) ([#4227](https://www.github.com/tauri-apps/tauri/pull/4227)) on 2022-05-27\n\n## \\[1.0.0-rc.8]\n\n- Use binary arch instead of `x86_64` on the AppImage bundle script.\n  - [6830a739](https://www.github.com/tauri-apps/tauri/commit/6830a739535e920f1857a1946fb69750a6d7b92f) fix(bundler): use binary arch on appimage bundle script ([#4194](https://www.github.com/tauri-apps/tauri/pull/4194)) on 2022-05-23\n- Fixes lost files on upgrade due to wrong implementation to keep shortcuts.\n  - [8539e02f](https://www.github.com/tauri-apps/tauri/commit/8539e02f7fd56cc47b25ed45c8403d66abe262ac) fix(bundler): wix upgrade do not installing new files, closes [#4182](https://www.github.com/tauri-apps/tauri/pull/4182) on 2022-05-21\n\n## \\[1.0.0-rc.7]\n\n- Change `tsp` value from `Option<bool>` to `bool`.\n  - [29d8e768](https://www.github.com/tauri-apps/tauri/commit/29d8e768aa0b52e1997c0f5c9e447b80eff47b93) feat(config): adjust schema for documentation website, closes [#4139](https://www.github.com/tauri-apps/tauri/pull/4139) ([#4142](https://www.github.com/tauri-apps/tauri/pull/4142)) on 2022-05-17\n- Fixes processing of resources with glob patterns when there are nested directories on Windows.\n  - [3e702cf8](https://www.github.com/tauri-apps/tauri/commit/3e702cf8b15762cdca43c8d7ff6f6e8ee9670244) fix(bundler): ignore duplicated files in resource iter, closes [#4126](https://www.github.com/tauri-apps/tauri/pull/4126) ([#4129](https://www.github.com/tauri-apps/tauri/pull/4129)) on 2022-05-15\n- Fixes resource bundling on Windows when the resource path includes root or parent directory components.\n  - [787ea09a](https://www.github.com/tauri-apps/tauri/commit/787ea09adc40644b89926e2b629261065141d16c) fix: generate windows resource directories using resource_relpath, closes [#4087](https://www.github.com/tauri-apps/tauri/pull/4087). ([#4111](https://www.github.com/tauri-apps/tauri/pull/4111)) on 2022-05-13\n- Set the application name when signing the Windows MSI.\n  - [8e1daad1](https://www.github.com/tauri-apps/tauri/commit/8e1daad1537e93c6a969c03328b8502b92bf5b89) fix(bundler): set app name when signing MSI, closes [#3945](https://www.github.com/tauri-apps/tauri/pull/3945) ([#3950](https://www.github.com/tauri-apps/tauri/pull/3950)) on 2022-05-17\n- Change WiX MajorUpgrade element's `Schedule` to `afterInstallExecute` to prevent removal of existing configuration such as the executable's pin to taskbar.\n  - [d965b921](https://www.github.com/tauri-apps/tauri/commit/d965b92174a1e6a01fc1a080254402d52145af1e) fix(bundler): prevent removal of `pin to taskbar` on Windows ([#4144](https://www.github.com/tauri-apps/tauri/pull/4144)) on 2022-05-17\n- Change the MSI reinstall mode so it only reinstall missing or different version files.\n  - [1948ae53](https://www.github.com/tauri-apps/tauri/commit/1948ae53fdcd0ef99ef302066792d779a62c5065) fix(bundler): only reinstall missing or != version files, closes [#4122](https://www.github.com/tauri-apps/tauri/pull/4122) ([#4125](https://www.github.com/tauri-apps/tauri/pull/4125)) on 2022-05-15\n- Allow configuring the display options for the MSI execution allowing quieter updates.\n  - [9f2c3413](https://www.github.com/tauri-apps/tauri/commit/9f2c34131952ea83c3f8e383bc3cec7e1450429f) feat(core): configure msiexec display options, closes [#3951](https://www.github.com/tauri-apps/tauri/pull/3951) ([#4061](https://www.github.com/tauri-apps/tauri/pull/4061)) on 2022-05-15\n\n## \\[1.0.0-rc.6]\n\n- Remove `Settings::verbose` option. You may now bring your own `log` frontend to receive logging output from the bundler while remaining in control of verbosity and formatting.\n  - [35f21471](https://www.github.com/tauri-apps/tauri/commit/35f2147161e6697cbd2824681eeaf870b5a991c2) feat(cli): Improve CLI logging ([#4060](https://www.github.com/tauri-apps/tauri/pull/4060)) on 2022-05-07\n- Ignore errors when loading `icns` files in the `.deb` package generation.\n  - [de444b15](https://www.github.com/tauri-apps/tauri/commit/de444b15d222a65861b099a7536318bad000110e) fix(bundler): debian failing to load icns icon, closes [#3062](https://www.github.com/tauri-apps/tauri/pull/3062) ([#4009](https://www.github.com/tauri-apps/tauri/pull/4009)) on 2022-04-30\n- Fix app downgrades when using the Windows installer.\n  - [72e577dc](https://www.github.com/tauri-apps/tauri/commit/72e577dcc6a6733182059ab51b28a03c6077edc1) fix(bundler): properly reinstall files on MSI downgrades, closes [#3868](https://www.github.com/tauri-apps/tauri/pull/3868) ([#4044](https://www.github.com/tauri-apps/tauri/pull/4044)) on 2022-05-04\n\n## \\[1.0.0-rc.5]\n\n- Set the Debian control file `Priority` field to `optional`.\n  - [3bd3d923](https://www.github.com/tauri-apps/tauri/commit/3bd3d923d32144b4e28f9f597edf74ee422a9b54) fix: add priority field in debian/control ([#3865](https://www.github.com/tauri-apps/tauri/pull/3865)) on 2022-04-20\n- Fixes DLL resource usage on Windows.\n  - [f66bc3c2](https://www.github.com/tauri-apps/tauri/commit/f66bc3c2b8360b8b685dcadeb373852abe43d9e5) fix(bundler): DLL resources, closes [#3948](https://www.github.com/tauri-apps/tauri/pull/3948) ([#3949](https://www.github.com/tauri-apps/tauri/pull/3949)) on 2022-04-23\n- **Breaking change:** Removed the `useBootstrapper` option. Use https://github.com/tauri-apps/fix-path-env-rs instead.\n  - [6a5ff08c](https://www.github.com/tauri-apps/tauri/commit/6a5ff08ce9052b656aa40accedfd4315825164a3) refactor: remove bootstrapper, closes [#3786](https://www.github.com/tauri-apps/tauri/pull/3786) ([#3832](https://www.github.com/tauri-apps/tauri/pull/3832)) on 2022-03-31\n\n## \\[1.0.0-rc.4]\n\n- Fixes DMG bundling on macOS 12.3.\n  - [348a1ab5](https://www.github.com/tauri-apps/tauri/commit/348a1ab59d2697478a594016016f1fccbf1ac054) fix(bundler): DMG bundling on macOS 12.3 cannot use bless, closes [#3719](https://www.github.com/tauri-apps/tauri/pull/3719) ([#3721](https://www.github.com/tauri-apps/tauri/pull/3721)) on 2022-03-18\n\n## \\[1.0.0-rc.3]\n\n- Added `tsp` config option under `tauri > bundle > windows`, which enables Time-Stamp Protocol (RFC 3161) for the timestamping\n  server under code signing on Windows if set to `true`.\n  - [bdd5f7c2](https://www.github.com/tauri-apps/tauri/commit/bdd5f7c2f03af4af8b60a9527e55bb18525d989b) fix: add support for Time-Stamping Protocol for Windows codesigning (fix [#3563](https://www.github.com/tauri-apps/tauri/pull/3563)) ([#3570](https://www.github.com/tauri-apps/tauri/pull/3570)) on 2022-03-07\n- Properly create the updater bundle for all generated Microsoft Installer files.\n  - [6a6f1e7b](https://www.github.com/tauri-apps/tauri/commit/6a6f1e7bf922bc6fa56db2e8e40affbb0849731d) fix(bundler): build updater bundle for all .msi files ([#3520](https://www.github.com/tauri-apps/tauri/pull/3520)) on 2022-02-24\n- Fixes the Microsoft Installer launch path.\n  - [8d699283](https://www.github.com/tauri-apps/tauri/commit/8d699283a4741c83b476fb079dc0333c7bf4f919) fix(bundler): Auto-launch app from install location, closes [#3547](https://www.github.com/tauri-apps/tauri/pull/3547) ([#3553](https://www.github.com/tauri-apps/tauri/pull/3553)) on 2022-02-24\n\n## \\[1.0.0-rc.2]\n\n- Fixes sidecar bundling on Windows.\n  - [2ecbed8d](https://www.github.com/tauri-apps/tauri/commit/2ecbed8d59d9e1788170aa87b90ed9556a473425) fix(bundler): sidecar on Windows, closes [#3446](https://www.github.com/tauri-apps/tauri/pull/3446) ([#3482](https://www.github.com/tauri-apps/tauri/pull/3482)) on 2022-02-16\n\n## \\[1.0.0-rc.1]\n\n- Change default value for the `freezePrototype` configuration to `false`.\n  - Bumped due to a bump in tauri-utils.\n  - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12\n\n## \\[1.0.0-rc.0]\n\n- Provide a provider short name for macOS app notarization if your Apple developer account is connected to more than one team.\n  - [8ab8d529](https://www.github.com/tauri-apps/tauri/commit/8ab8d529426b1ed7926daced16a45b077033bfe2) Fix [#3288](https://www.github.com/tauri-apps/tauri/pull/3288): Add provider_short_name for macOS ([#3289](https://www.github.com/tauri-apps/tauri/pull/3289)) on 2022-01-27\n\n- Allow building AppImages on systems without FUSE setup.\n  - [28dd9adb](https://www.github.com/tauri-apps/tauri/commit/28dd9adb266b97db0bf7179268269f8614ce78e8) feat(bundler): support building AppImage without FUSE ([#3259](https://www.github.com/tauri-apps/tauri/pull/3259)) on 2022-01-21\n\n- Fixes AppImage crashes caused by missing WebKit runtime files.\n  - [bec7833a](https://www.github.com/tauri-apps/tauri/commit/bec7833a6c29ed9d1a5ab53e1c2cdd80e66cd272) fix(bundler): bundle additional webkit files. patch absolute paths in libwebkit\\*.so files. fixes [#2805](https://www.github.com/tauri-apps/tauri/pull/2805),[#2689](https://www.github.com/tauri-apps/tauri/pull/2689) ([#2940](https://www.github.com/tauri-apps/tauri/pull/2940)) on 2021-12-09\n\n- Initialize the preselected installation path with the location of the previous installation.\n  - [ac1dfd8c](https://www.github.com/tauri-apps/tauri/commit/ac1dfd8c3039d87bef1fa2d815876903d5cc07ae) feat(bundler): initialize msi install path with previous location ([#3158](https://www.github.com/tauri-apps/tauri/pull/3158)) on 2022-01-07\n\n- Replaces usage of the nightly command `RUSTC_BOOTSTRAP=1 rustc -Z unstable-options --print target-spec-json` with the stable command `rustc --print cfg`, improving target triple detection.\n  - [839daec7](https://www.github.com/tauri-apps/tauri/commit/839daec7ab79c3ff2f552dd7579069bc64e83625) fix(bundler): Use `arch` instead of `llvm_target`. fix [#3285](https://www.github.com/tauri-apps/tauri/pull/3285) ([#3286](https://www.github.com/tauri-apps/tauri/pull/3286)) on 2022-02-05\n\n- Fixes a deadlock on the `ResourcePaths` iterator.\n  - [4c1be451](https://www.github.com/tauri-apps/tauri/commit/4c1be451066612862363bc481910bd6c3da1e185) fix(bundler): deadlock on `ResourcePaths` iterator, closes [#3146](https://www.github.com/tauri-apps/tauri/pull/3146) ([#3152](https://www.github.com/tauri-apps/tauri/pull/3152)) on 2022-01-02\n\n- Move the copying of resources and sidecars from `cli.rs` to `tauri-build` so using the Cargo CLI directly processes the files for the application execution in development.\n  - [5eb72c24](https://www.github.com/tauri-apps/tauri/commit/5eb72c24deddf5a01093bea96b90c0d8806afc3f) refactor: copy resources and sidecars on the Cargo build script ([#3357](https://www.github.com/tauri-apps/tauri/pull/3357)) on 2022-02-08\n\n- The minimum Rust version is now `1.56`.\n  - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22\n\n- **Breaking change:** The sidecar's target triple suffix is now removed at build time.\n  - [3035e458](https://www.github.com/tauri-apps/tauri/commit/3035e4581c161ec7f0bd6d9b42e9015cf1dd1d77) Remove target triple from sidecar bin paths, closes [#3355](https://www.github.com/tauri-apps/tauri/pull/3355) ([#3356](https://www.github.com/tauri-apps/tauri/pull/3356)) on 2022-02-07\n\n- When building Universal macOS Binaries through the virtual target `universal-apple-darwin`:\n\n- Expect a universal binary to be created by the user\n\n- Ensure that binary is bundled and accessed correctly at runtime\n\n- [3035e458](https://www.github.com/tauri-apps/tauri/commit/3035e4581c161ec7f0bd6d9b42e9015cf1dd1d77) Remove target triple from sidecar bin paths, closes [#3355](https://www.github.com/tauri-apps/tauri/pull/3355) ([#3356](https://www.github.com/tauri-apps/tauri/pull/3356)) on 2022-02-07\n\n- Allow setting the localization file for WiX.\n  - [af329f27](https://www.github.com/tauri-apps/tauri/commit/af329f2722d6194c6d70e976fc970dc2c9e4de2b) feat(bundler): wix localization, closes [#3174](https://www.github.com/tauri-apps/tauri/pull/3174) ([#3179](https://www.github.com/tauri-apps/tauri/pull/3179)) on 2022-02-05\n\n- Fix registry keys on the WiX template.\n  - [2be1abd1](https://www.github.com/tauri-apps/tauri/commit/2be1abd112cc3d927c235b6d00a508e6d35be49e) fix(bundler) wix template escape character ([#2608](https://www.github.com/tauri-apps/tauri/pull/2608)) on 2021-09-21\n\n- Configure WiX to add an option to launch the application after finishing setup.\n  - [feb3a8f8](https://www.github.com/tauri-apps/tauri/commit/feb3a8f896802ff274333012c3b399beb5c86f41) feat(bundler): configure WiX to add launch option, closes [#3015](https://www.github.com/tauri-apps/tauri/pull/3015) ([#3043](https://www.github.com/tauri-apps/tauri/pull/3043)) on 2021-12-09\n\n- Sign WiX installer in addition to the executable file.\n  - [d801cc89](https://www.github.com/tauri-apps/tauri/commit/d801cc89b8bfa9beba347ebcd48cfccf890ff5bb) wix installer is also signed ([#3266](https://www.github.com/tauri-apps/tauri/pull/3266)) on 2022-01-23\n\n## \\[1.0.0-beta.4]\n\n- Merge Tauri-generated Info.plist with the contents of `src-tauri/Info.plist` if it exists.\n  - [537ab1b6](https://www.github.com/tauri-apps/tauri/commit/537ab1b6d5a792c550a535619965c9e4126292e6) feat(core): inject src-tauri/Info.plist file on dev and merge on bundle, closes [#1570](https://www.github.com/tauri-apps/tauri/pull/1570) [#2338](https://www.github.com/tauri-apps/tauri/pull/2338) ([#2444](https://www.github.com/tauri-apps/tauri/pull/2444)) on 2021-08-15\n- Change the WiX config to allow upgrading installation with same version instead of duplicating the application.\n  - [dd5e1ede](https://www.github.com/tauri-apps/tauri/commit/dd5e1ede32ab8c10849fe6583d93ef493dd6f184) fix(bundler): `AllowSameVersionUpgrades` on WiX, closes [#2211](https://www.github.com/tauri-apps/tauri/pull/2211) ([#2428](https://www.github.com/tauri-apps/tauri/pull/2428)) on 2021-08-14\n- Check target architecture at runtime running `$ RUSTC_BOOTSTRAP=1 rustc -Z unstable-options --print target-spec-json` and parsing the `llvm-target` field, fixing macOS M1 sidecar check until we can compile the CLI with M1 target on GitHub Actions.\n  - [5ebf389f](https://www.github.com/tauri-apps/tauri/commit/5ebf389f6c6805ccd2b15d81fe12416770e39222) feat(bundler): check target arch at runtime, closes [#2286](https://www.github.com/tauri-apps/tauri/pull/2286) ([#2422](https://www.github.com/tauri-apps/tauri/pull/2422)) on 2021-08-13\n- Added `banner_path` field to the `WixSettings` struct.\n  - [13003ec7](https://www.github.com/tauri-apps/tauri/commit/13003ec761b1530705d6129519dc4e226eb992c7) feat(bundler): add config for WiX banner path, closes [#2175](https://www.github.com/tauri-apps/tauri/pull/2175) ([#2448](https://www.github.com/tauri-apps/tauri/pull/2448)) on 2021-08-16\n- Added `dialog_image_path` field to the `WixSettings` struct.\n  - [9bfdeb42](https://www.github.com/tauri-apps/tauri/commit/9bfdeb42effeeec27aa15bbc5b05040eadfda5ba) feat(bundler): add config for WiX dialog image path ([#2449](https://www.github.com/tauri-apps/tauri/pull/2449)) on 2021-08-16\n- Only convert package name and binary name to kebab-case, keeping the `.desktop` `Name` field with the original configured value.\n  - [3f039cb8](https://www.github.com/tauri-apps/tauri/commit/3f039cb8a308b0f18deaa37d7cfb1cc50d308d0e) fix: keep original `productName` for .desktop `Name` field, closes [#2295](https://www.github.com/tauri-apps/tauri/pull/2295) ([#2384](https://www.github.com/tauri-apps/tauri/pull/2384)) on 2021-08-10\n- Use `linuxdeploy` instead of `appimagetool` for `AppImage` bundling.\n  - [397710b2](https://www.github.com/tauri-apps/tauri/commit/397710b2c5699dab78118f58760dda07e276d4c2) refactor(bundler): use linuxdeploy instead of appimagetool, closes [#1986](https://www.github.com/tauri-apps/tauri/pull/1986) ([#2437](https://www.github.com/tauri-apps/tauri/pull/2437)) on 2021-08-15\n\n## \\[1.0.0-beta.3]\n\n- Fix WIX uninstaller by using unique `GUID` shortcut.\n  - [caa8fcc9](https://www.github.com/tauri-apps/tauri/commit/caa8fcc93e5b56dacf042b9e7c6e7c56a1609310) fix(windows): use random `Guid` for uninstaller (wix) ([#2208](https://www.github.com/tauri-apps/tauri/pull/2208)) on 2021-07-14\n- Run powershell commands with `-NoProfile` flag\n  - [3e6f3416](https://www.github.com/tauri-apps/tauri/commit/3e6f34160deab4f774d90aba28122e5b6b6f9db2) fix(cli.rs): run powershell kill command without profile ([#2130](https://www.github.com/tauri-apps/tauri/pull/2130)) on 2021-06-30\n\n## \\[1.0.0-beta.2]\n\n- Properly detect target platform's architecture.\n  - [628a53eb](https://www.github.com/tauri-apps/tauri/commit/628a53eb6176f811d22d7730f08a99e5c370dbf4) fix(cli): properly detect target architecture, closes [#2040](https://www.github.com/tauri-apps/tauri/pull/2040) ([#2102](https://www.github.com/tauri-apps/tauri/pull/2102)) on 2021-06-28\n- Properly bundle resources with nested folder structure.\n  - [a6157212](https://www.github.com/tauri-apps/tauri/commit/a61572127df839ed23e34e9b49b2bada5f18f7fb) fix(bundler): resources bundling on Windows with nested folder structure ([#2081](https://www.github.com/tauri-apps/tauri/pull/2081)) on 2021-06-25\n\n## \\[1.0.0-beta.1]\n\n- The process of copying binaries and resources to `project_out_directory` was moved to the Tauri CLI.\n  - [8f29a260](https://www.github.com/tauri-apps/tauri/commit/8f29a260e67aa111f6aeb262bd846a46d2858ce9) fix(cli.rs): copy resources and binaries on dev, closes [#1298](https://www.github.com/tauri-apps/tauri/pull/1298) ([#1946](https://www.github.com/tauri-apps/tauri/pull/1946)) on 2021-06-04\n- Allow setting a path to a license file for the Windows Installer (`tauri.conf.json > bundle > windows > wix > license`).\n  - [b769c7f7](https://www.github.com/tauri-apps/tauri/commit/b769c7f7da4064b6133bf39a82127863d0d35531) feat(bundler): windows installer license, closes [#2009](https://www.github.com/tauri-apps/tauri/pull/2009) ([#2027](https://www.github.com/tauri-apps/tauri/pull/2027)) on 2021-06-21\n- Configure app shortcut on the Windows Installer.\n  - [f0603fcc](https://www.github.com/tauri-apps/tauri/commit/f0603fccb389620e105a5927a9e4b84b5e6853f4) feat(bundler): desktop shortcut on Windows ([#2052](https://www.github.com/tauri-apps/tauri/pull/2052)) on 2021-06-23\n- Allow setting the Windows installer language and using project names that contains non-Unicode characters.\n  - [47919619](https://www.github.com/tauri-apps/tauri/commit/47919619815900fc3af47ec5873e31afb778b0ad) feat(bundler): allow setting wix language, closes [#1976](https://www.github.com/tauri-apps/tauri/pull/1976) ([#1988](https://www.github.com/tauri-apps/tauri/pull/1988)) on 2021-06-15\n- Fixes resource bundling on Windows when there is nested resource folders.\n  - [35a20527](https://www.github.com/tauri-apps/tauri/commit/35a2052771fc0897064ed146d9557527a0a76453) fix(bundler): windows resources bundling with nested folders ([#1878](https://www.github.com/tauri-apps/tauri/pull/1878)) on 2021-05-21\n\n## \\[1.0.0-beta.0]\n\n- Fixes the `Installed-Size` value on the debian package.\n  - [8e0d4f6](https://www.github.com/tauri-apps/tauri/commit/8e0d4f666c2fbcc3452db9edf87aa726c9ebe4b8) fix(bundler): debian package `Installed-Size` value ([#1735](https://www.github.com/tauri-apps/tauri/pull/1735)) on 2021-05-07\n- Use `armhf` as Debian package architecture on `arm` CPUs.\n  - [894643c](https://www.github.com/tauri-apps/tauri/commit/894643cdcd7446f63c4a0ab157be3cb8c242952a) feat(bundler): use `armhf` as Debian package architecture on arm CPUs ([#1663](https://www.github.com/tauri-apps/tauri/pull/1663)) on 2021-04-30\n- Adds basic library documentation.\n  - [fcee4c2](https://www.github.com/tauri-apps/tauri/commit/fcee4c25fc2e83a587e096b26d9f7c374c3db057) refactor(bundler): finish initial documentation, reorganize modules ([#1662](https://www.github.com/tauri-apps/tauri/pull/1662)) on 2021-04-30\n- The `PackageTypes` enum now includes all options, including Windows packages.\n  - [fcee4c2](https://www.github.com/tauri-apps/tauri/commit/fcee4c25fc2e83a587e096b26d9f7c374c3db057) refactor(bundler): finish initial documentation, reorganize modules ([#1662](https://www.github.com/tauri-apps/tauri/pull/1662)) on 2021-04-30\n- Adds `icon_path` field to the `WindowsSettings` struct (defaults to `icons/icon.ico`).\n  - [314936e](https://www.github.com/tauri-apps/tauri/commit/314936efbeb3ecaece244da5a1a4a59341d4f76f) feat(bundler): add icon path config instead of enforcing icons/icon.ico ([#1698](https://www.github.com/tauri-apps/tauri/pull/1698)) on 2021-05-03\n- Pull latest changes from `create-dmg`, fixing unmount issue.\n  - [f1aa120](https://www.github.com/tauri-apps/tauri/commit/f1aa12075f9f649ff6baebc0f8e7a10f1e616cc6) fix(bundler): update create-dmg, fixes [#1571](https://www.github.com/tauri-apps/tauri/pull/1571) ([#1729](https://www.github.com/tauri-apps/tauri/pull/1729)) on 2021-05-06\n- Fixes DMG volume icon.\n  - [e37e187](https://www.github.com/tauri-apps/tauri/commit/e37e187d4a3c7568463c2546099d03dd5a314f40) fix(bundler): dmg volume icon ([#1730](https://www.github.com/tauri-apps/tauri/pull/1730)) on 2021-05-06\n- Added the \\`#\\[non_exhaustive] attribute where appropriate.\n  - [e087f0f](https://www.github.com/tauri-apps/tauri/commit/e087f0f9374355ac4b4a48f94727ef8b26b1c4cf) feat: add `#[non_exhaustive]` attribute ([#1725](https://www.github.com/tauri-apps/tauri/pull/1725)) on 2021-05-05\n\n## \\[1.0.0-beta-rc.1]\n\n- Find best available icon for AppImage, follow `.DirIcon` spec.\n  - [fbf73f3](https://www.github.com/tauri-apps/tauri/commit/fbf73f3ab53387e68c8cbf9e788820bea0f2f111) fix(bundler): find icon for AppImage, define `.DirIcon`, closes [#749](https://www.github.com/tauri-apps/tauri/pull/749) ([#1594](https://www.github.com/tauri-apps/tauri/pull/1594)) on 2021-04-23\n- Allow including custom files on the debian package.\n  - [9e87fe6](https://www.github.com/tauri-apps/tauri/commit/9e87fe6a69a8f74c8e61221e36e15b7eb1d19432) feat(bundler): allow including custom files on debian package, fix [#1428](https://www.github.com/tauri-apps/tauri/pull/1428) ([#1613](https://www.github.com/tauri-apps/tauri/pull/1613)) on 2021-04-25\n- Adds support to custom WiX template.\n  - [ebe755a](https://www.github.com/tauri-apps/tauri/commit/ebe755ac5c37025bae0cf8860e9b04b507f71949) feat: [#1528](https://www.github.com/tauri-apps/tauri/pull/1528) wix supports custom templates ([#1529](https://www.github.com/tauri-apps/tauri/pull/1529)) on 2021-04-25\n- Adds support to `wix` fragments for custom .msi installer functionality.\n  - [69ea51e](https://www.github.com/tauri-apps/tauri/commit/69ea51ec93a6d4fa90f3482a51f0c6d20c97fa29) feat(bundler): implement wix fragments, closes [#1528](https://www.github.com/tauri-apps/tauri/pull/1528) ([#1601](https://www.github.com/tauri-apps/tauri/pull/1601)) on 2021-04-23\n- Adds `skip_webview_install` config under `windows > wix` to disable Webview2 runtime installation after the app install.\n  - [d13afec](https://www.github.com/tauri-apps/tauri/commit/d13afec20402b8ddbbf3ceb4349edb1956ed79bc) feat(bundler): add option to skip webview2 runtime installation, closes [#1606](https://www.github.com/tauri-apps/tauri/pull/1606) ([#1612](https://www.github.com/tauri-apps/tauri/pull/1612)) on 2021-04-24\n\n## \\[1.0.0-beta-rc.0]\n\n- Append app version and OS architecture on AppImage output filename.\n  - [ae76c60](https://www.github.com/tauri-apps/tauri/commit/ae76c60a615602fcb8c1dd824a6ad9fa8f48fe69) fix(bundler): appimage paths and filename ([#1227](https://www.github.com/tauri-apps/tauri/pull/1227)) on 2021-02-13\n- The Tauri bundler is now a general purpose library instead of a Cargo custom subcommand.\n  - [b1e6b74](https://www.github.com/tauri-apps/tauri/commit/b1e6b74a4f624b623a840686fb1abe1d23593867) refactor(cli): decouple bundler from cargo ([#1269](https://www.github.com/tauri-apps/tauri/pull/1269)) on 2021-02-21\n- Rename macOS bundle settings from `osx` to `macOS`.\n  - [080f639](https://www.github.com/tauri-apps/tauri/commit/080f6391bac4fd59e9e71b9785d7a2f835703805) refactor(bundler): specific settings on dedicated structs, update README ([#1380](https://www.github.com/tauri-apps/tauri/pull/1380)) on 2021-03-25\n- The `dev` and `build` pipeline is now written in Rust.\n  - [3e8abe3](https://www.github.com/tauri-apps/tauri/commit/3e8abe376407bb0ca8893602590ed9edf7aa71a1) feat(cli) rewrite the core CLI in Rust ([#851](https://www.github.com/tauri-apps/tauri/pull/851)) on 2021-01-30\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Update all code files to have our license header.\n  - [bf82136](https://www.github.com/tauri-apps/tauri/commit/bf8213646689175f8a158b956911f3a43e360690) feat(license): SPDX Headers ([#1449](https://www.github.com/tauri-apps/tauri/pull/1449)) on 2021-04-11\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Alpha version of tauri-updater. Please refer to the `README` for more details.\n  - [6d70c8e](https://www.github.com/tauri-apps/tauri/commit/6d70c8e1e256fe839c4a947375bb529d7b4f7301) feat(updater): Alpha version ([#643](https://www.github.com/tauri-apps/tauri/pull/643)) on 2021-04-05\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Bundle Visual C++ redistributable files with VC142\\_CRT merge modules.\n  - [3047a18](https://www.github.com/tauri-apps/tauri/commit/3047a18975db07abdf49985f531c3925f68a0db9) feat(bundler): add visual c++ redistributable files with MSM ([#1368](https://www.github.com/tauri-apps/tauri/pull/1368)) on 2021-03-22\n- Automatically install Webview2 runtime alongside app.\n  - [8e9752b](https://www.github.com/tauri-apps/tauri/commit/8e9752bb8bad5c56b55a3750876e0073efdc6d39) feat(bundler/wix): install webview2 runtime ([#1329](https://www.github.com/tauri-apps/tauri/pull/1329)) on 2021-03-07\n- Fixes the bundler workspace detection.\n  - [e34ee4c](https://www.github.com/tauri-apps/tauri/commit/e34ee4c29c7fde02e09685a3100f0b2ef6380c98) fix(bundler): workspace detection, closes [#1007](https://www.github.com/tauri-apps/tauri/pull/1007) ([#1235](https://www.github.com/tauri-apps/tauri/pull/1235)) on 2021-02-14\n\n## \\[0.9.4]\n\n- `dirs` crate is unmaintained, now using `dirs-next` instead.\n  - [82cda98](https://www.github.com/tauri-apps/tauri/commit/82cda98532975c6d4b1c93bf2f326173f39e0964) chore(tauri) `dirs` crate is unmaintained, use `dirst-next` instead ([#1057](https://www.github.com/tauri-apps/tauri/pull/1057)) on 2020-10-17\n  - [72996be](https://www.github.com/tauri-apps/tauri/commit/72996be1bd3eb878c4cf30bfec79058071c26d7a) apply version updates ([#1024](https://www.github.com/tauri-apps/tauri/pull/1024)) on 2020-10-21\n- Force IPv4 on `wget` so AppImage bundling doesn't hang.\n  - [6f5667b](https://www.github.com/tauri-apps/tauri/commit/6f5667bf72d58972b8d05ee2e42a031c85f95ed4) fix: [#1018](https://www.github.com/tauri-apps/tauri/pull/1018) Force IPv4 on wget requests ([#1019](https://www.github.com/tauri-apps/tauri/pull/1019)) on 2020-10-11\n  - [72996be](https://www.github.com/tauri-apps/tauri/commit/72996be1bd3eb878c4cf30bfec79058071c26d7a) apply version updates ([#1024](https://www.github.com/tauri-apps/tauri/pull/1024)) on 2020-10-21\n- Set the Windows installer (WiX) `WorkingDirectory` field to `INSTALLDIR` so the app can read paths relatively (previously resolving to `C:\\Windows\\System32`).\n  - [5cf3402](https://www.github.com/tauri-apps/tauri/commit/5cf3402735ac2e88fc4aae5fe39fc0a41262b6fa) fix: add working directory to wix's shortcut ([#1021](https://www.github.com/tauri-apps/tauri/pull/1021)) on 2020-09-24\n  - [72996be](https://www.github.com/tauri-apps/tauri/commit/72996be1bd3eb878c4cf30bfec79058071c26d7a) apply version updates ([#1024](https://www.github.com/tauri-apps/tauri/pull/1024)) on 2020-10-21\n\n## \\[0.9.3]\n\n- Improve checking for Xcode command line tools to allow builds on mac\n  - [7a788fd](https://www.github.com/tauri-apps/tauri/commit/7a788fdceebc2bf6b7b46ebe54e98597d4a71529) fix: improve checking for Rez (fix [#994](https://www.github.com/tauri-apps/tauri/pull/994)) ([#995](https://www.github.com/tauri-apps/tauri/pull/995)) on 2020-08-28\n- add a newline after Categories in deb .desktop file generation to fix issues #899 and #925.\n  - [37bcf5f](https://www.github.com/tauri-apps/tauri/commit/37bcf5fea154bdefbca2692b69aafaabba8c23e2) fix(bundler) missing newline in deb desktop file generation (fix: [#899](https://www.github.com/tauri-apps/tauri/pull/899), [#925](https://www.github.com/tauri-apps/tauri/pull/925)) ([#998](https://www.github.com/tauri-apps/tauri/pull/998)) on 2020-08-27\n\n## \\[0.9.2]\n\n- Bump all deps as noted in #975, #976, #977, #978, and #979.\n  - [06dd75b](https://www.github.com/tauri-apps/tauri/commit/06dd75b68a15d388808c51ae2bf50554ae761d9d) chore: bump all js/rust deps ([#983](https://www.github.com/tauri-apps/tauri/pull/983)) on 2020-08-20\n\n## \\[0.9.1]\n\n- Hide external scripts output unless `--verbose` is passed.\n  - [78add1e](https://www.github.com/tauri-apps/tauri/commit/78add1e79ef42ed61e988a0012be87b428439332) feat(bundler): hide output from shell scripts unless --verbose is passed (fixes [#888](https://www.github.com/tauri-apps/tauri/pull/888)) ([#893](https://www.github.com/tauri-apps/tauri/pull/893)) on 2020-07-26\n- Fixes the target directory detection, respecting the `CARGO_TARGET_DIR` and `.cargo/config (build.target-dir)` options to set the Cargo output directory.\n  - [63b9c64](https://www.github.com/tauri-apps/tauri/commit/63b9c6457233d777b698b53cd6661c6cd9a0d67b) fix(bundler) properly detect the target directory ([#895](https://www.github.com/tauri-apps/tauri/pull/895)) on 2020-07-25\n- Bundling every DLL file on the binary directory.\n  - [a00ac02](https://www.github.com/tauri-apps/tauri/commit/a00ac023eef9f7b3a508ca9acd664470382d7d06) fix(bundler) webview dll not being bundled, fixes [#875](https://www.github.com/tauri-apps/tauri/pull/875) ([#889](https://www.github.com/tauri-apps/tauri/pull/889)) on 2020-07-24\n\n## \\[0.9.0]\n\n- Fixes the AppImage bundling on containers.\n  - [53e8dc1](https://www.github.com/tauri-apps/tauri/commit/53e8dc1880b78994e17bf769d60e13f9e13dbf99) fix(bundler) support AppImage bundling on containers [#822](https://www.github.com/tauri-apps/tauri/pull/822) on 2020-07-13\n  - [bd0118f](https://www.github.com/tauri-apps/tauri/commit/bd0118f160360e588180de3f3518ef47a4d86a46) fix(changes) covector status pass on 2020-07-14\n- Bundler output refactor: move Windows artifacts to the `bundle/wix` folder and use a standard output name `${bundleName}_${version}_${arch}.${extension}`.\n  - [9130f1b](https://www.github.com/tauri-apps/tauri/commit/9130f1b1a422121fa9f3afbeeb87e851568e995f) refactor(bundler) standard output names and path ([#823](https://www.github.com/tauri-apps/tauri/pull/823)) on 2020-07-13\n\n## \\[0.8.5]\n\n- Ignoring non UTF-8 characters on the loopback command output.\n  - [f340b29](https://www.github.com/tauri-apps/tauri/commit/f340b2914dc9c3a94ca8606f4663964fa87b95ea) fix(tauri) addition to the previous commit on 2020-07-10\n\n## \\[0.8.4]\n\n- Properly run the loopback command on Windows.\n\n## \\[0.8.3]\n\n- Fixes the unbound variable issue on the DMG bundler script.\n\n## \\[0.8.2]\n\n- Fixes the AppImage bundler script.\n\n## \\[0.8.1]\n\n- Improves the logging of child processes like bundle_appimage.sh and bundle_dmg.sh.\n\n## \\[0.8.0]\n\n- The bundler now bundles all binaries from your project (undefined) and undefined.\n  When multiple binaries are used, make sure to use the undefined config field.\n- Check if mksquashfs is installed before bundling AppImage\n\n## \\[0.7.0]\n\n- Fixes AppImage bundler (appimagetool usage, build script running properly, proper AppRun and .desktop files).\n"
  },
  {
    "path": "crates/tauri-bundler/Cargo.toml",
    "content": "[package]\nname = \"tauri-bundler\"\nversion = \"2.8.1\"\nauthors = [\n  \"George Burton <burtonageo@gmail.com>\",\n  \"Tauri Programme within The Commons Conservancy\",\n]\ncategories = [\"command-line-utilities\", \"development-tools::cargo-plugins\"]\nlicense = \"Apache-2.0 OR MIT\"\nkeywords = [\"bundle\", \"cargo\", \"tauri\"]\nrepository = \"https://github.com/tauri-apps/tauri\"\ndescription = \"Wrap rust executables in OS-specific app bundles for Tauri\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\nexclude = [\"CHANGELOG.md\", \"/target\", \"rustfmt.toml\"]\n\n[dependencies]\ntauri-utils = { version = \"2.8.3\", path = \"../tauri-utils\", features = [\n  \"resources\",\n] }\nimage = \"0.25\"\nflate2 = \"1\"\nthiserror = \"2\"\nanyhow = \"1\"\nserde_json = \"1\"\nserde = { version = \"1\", features = [\"derive\"] }\nstrsim = \"0.11\"\ntar = \"0.4\"\nwalkdir = \"2\"\nhandlebars = \"6\"\ntempfile = \"3\"\nlog = { version = \"0.4.21\", features = [\"kv\"] }\ndirs = \"6\"\nos_pipe = \"1\"\nureq = { version = \"3\", default-features = false, features = [\"socks-proxy\"] }\nnative-tls = { version = \"0.2\", optional = true }\nhex = \"0.4\"\nsemver = \"1\"\nsha1 = \"0.10\"\nsha2 = \"0.10\"\nzip = { version = \"4\", default-features = false, features = [\"deflate\"] }\ndunce = \"1\"\nurl = \"2\"\nuuid = { version = \"1\", features = [\"v4\", \"v5\"] }\nregex = \"1\"\ngoblin = \"0.9\"\nplist = \"1\"\n\n\n[target.\"cfg(target_os = \\\"windows\\\")\".dependencies]\nbitness = \"0.4\"\nwindows-registry = \"0.5\"\nglob = \"0.3\"\n\n[target.\"cfg(target_os = \\\"windows\\\")\".dependencies.windows-sys]\nversion = \"0.60\"\nfeatures = [\"Win32_System_SystemInformation\", \"Win32_System_Diagnostics_Debug\"]\n\n[target.\"cfg(target_os = \\\"macos\\\")\".dependencies]\nicns = { package = \"tauri-icns\", version = \"0.1\" }\ntime = { version = \"0.3\", features = [\"formatting\"] }\ntauri-macos-sign = { version = \"2.3.3\", path = \"../tauri-macos-sign\" }\n\n[target.\"cfg(target_os = \\\"linux\\\")\".dependencies]\nheck = \"0.5\"\nar = \"0.9\"\nmd5 = \"0.8\"\nrpm = { version = \"0.16\", features = [\"bzip2-compression\"] }\n\n[target.\"cfg(unix)\".dependencies]\nwhich = \"8\"\n\n[lib]\nname = \"tauri_bundler\"\npath = \"src/lib.rs\"\n\n[features]\ndefault = [\"rustls\", \"platform-certs\"]\nnative-tls = [\"ureq/native-tls\"]\nnative-tls-vendored = [\"native-tls\", \"native-tls/vendored\"]\nrustls = [\"ureq/rustls\"]\nplatform-certs = [\"ureq/platform-verifier\"]\n"
  },
  {
    "path": "crates/tauri-bundler/License_Apache.md",
    "content": "Copyright 2017 Cargo-Bundle developers\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n---\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1.  Definitions.\n\n    \"License\" shall mean the terms and conditions for use, reproduction,\n    and distribution as defined by Sections 1 through 9 of this document.\n\n    \"Licensor\" shall mean the copyright owner or entity authorized by\n    the copyright owner that is granting the License.\n\n    \"Legal Entity\" shall mean the union of the acting entity and all\n    other entities that control, are controlled by, or are under common\n    control with that entity. For the purposes of this definition,\n    \"control\" means (i) the power, direct or indirect, to cause the\n    direction or management of such entity, whether by contract or\n    otherwise, or (ii) ownership of fifty percent (50%) or more of the\n    outstanding shares, or (iii) beneficial ownership of such entity.\n\n    \"You\" (or \"Your\") shall mean an individual or Legal Entity\n    exercising permissions granted by this License.\n\n    \"Source\" form shall mean the preferred form for making modifications,\n    including but not limited to software source code, documentation\n    source, and configuration files.\n\n    \"Object\" form shall mean any form resulting from mechanical\n    transformation or translation of a Source form, including but\n    not limited to compiled object code, generated documentation,\n    and conversions to other media types.\n\n    \"Work\" shall mean the work of authorship, whether in Source or\n    Object form, made available under the License, as indicated by a\n    copyright notice that is included in or attached to the work\n    (an example is provided in the Appendix below).\n\n    \"Derivative Works\" shall mean any work, whether in Source or Object\n    form, that is based on (or derived from) the Work and for which the\n    editorial revisions, annotations, elaborations, or other modifications\n    represent, as a whole, an original work of authorship. For the purposes\n    of this License, Derivative Works shall not include works that remain\n    separable from, or merely link (or bind by name) to the interfaces of,\n    the Work and Derivative Works thereof.\n\n    \"Contribution\" shall mean any work of authorship, including\n    the original version of the Work and any modifications or additions\n    to that Work or Derivative Works thereof, that is intentionally\n    submitted to Licensor for inclusion in the Work by the copyright owner\n    or by an individual or Legal Entity authorized to submit on behalf of\n    the copyright owner. For the purposes of this definition, \"submitted\"\n    means any form of electronic, verbal, or written communication sent\n    to the Licensor or its representatives, including but not limited to\n    communication on electronic mailing lists, source code control systems,\n    and issue tracking systems that are managed by, or on behalf of, the\n    Licensor for the purpose of discussing and improving the Work, but\n    excluding communication that is conspicuously marked or otherwise\n    designated in writing by the copyright owner as \"Not a Contribution.\"\n\n    \"Contributor\" shall mean Licensor and any individual or Legal Entity\n    on behalf of whom a Contribution has been received by Licensor and\n    subsequently incorporated within the Work.\n\n2.  Grant of Copyright License. Subject to the terms and conditions of\n    this License, each Contributor hereby grants to You a perpetual,\n    worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n    copyright license to reproduce, prepare Derivative Works of,\n    publicly display, publicly perform, sublicense, and distribute the\n    Work and such Derivative Works in Source or Object form.\n\n3.  Grant of Patent License. Subject to the terms and conditions of\n    this License, each Contributor hereby grants to You a perpetual,\n    worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n    (except as stated in this section) patent license to make, have made,\n    use, offer to sell, sell, import, and otherwise transfer the Work,\n    where such license applies only to those patent claims licensable\n    by such Contributor that are necessarily infringed by their\n    Contribution(s) alone or by combination of their Contribution(s)\n    with the Work to which such Contribution(s) was submitted. If You\n    institute patent litigation against any entity (including a\n    cross-claim or counterclaim in a lawsuit) alleging that the Work\n    or a Contribution incorporated within the Work constitutes direct\n    or contributory patent infringement, then any patent licenses\n    granted to You under this License for that Work shall terminate\n    as of the date such litigation is filed.\n\n4.  Redistribution. You may reproduce and distribute copies of the\n    Work or Derivative Works thereof in any medium, with or without\n    modifications, and in Source or Object form, provided that You\n    meet the following conditions:\n\n    (a) You must give any other recipients of the Work or\n    Derivative Works a copy of this License; and\n\n    (b) You must cause any modified files to carry prominent notices\n    stating that You changed the files; and\n\n    (c) You must retain, in the Source form of any Derivative Works\n    that You distribute, all copyright, patent, trademark, and\n    attribution notices from the Source form of the Work,\n    excluding those notices that do not pertain to any part of\n    the Derivative Works; and\n\n    (d) If the Work includes a \"NOTICE\" text file as part of its\n    distribution, then any Derivative Works that You distribute must\n    include a readable copy of the attribution notices contained\n    within such NOTICE file, excluding those notices that do not\n    pertain to any part of the Derivative Works, in at least one\n    of the following places: within a NOTICE text file distributed\n    as part of the Derivative Works; within the Source form or\n    documentation, if provided along with the Derivative Works; or,\n    within a display generated by the Derivative Works, if and\n    wherever such third-party notices normally appear. The contents\n    of the NOTICE file are for informational purposes only and\n    do not modify the License. You may add Your own attribution\n    notices within Derivative Works that You distribute, alongside\n    or as an addendum to the NOTICE text from the Work, provided\n    that such additional attribution notices cannot be construed\n    as modifying the License.\n\n    You may add Your own copyright statement to Your modifications and\n    may provide additional or different license terms and conditions\n    for use, reproduction, or distribution of Your modifications, or\n    for any such Derivative Works as a whole, provided Your use,\n    reproduction, and distribution of the Work otherwise complies with\n    the conditions stated in this License.\n\n5.  Submission of Contributions. Unless You explicitly state otherwise,\n    any Contribution intentionally submitted for inclusion in the Work\n    by You to the Licensor shall be under the terms and conditions of\n    this License, without any additional terms or conditions.\n    Notwithstanding the above, nothing herein shall supersede or modify\n    the terms of any separate license agreement you may have executed\n    with Licensor regarding such Contributions.\n\n6.  Trademarks. This License does not grant permission to use the trade\n    names, trademarks, service marks, or product names of the Licensor,\n    except as required for reasonable and customary use in describing the\n    origin of the Work and reproducing the content of the NOTICE file.\n\n7.  Disclaimer of Warranty. Unless required by applicable law or\n    agreed to in writing, Licensor provides the Work (and each\n    Contributor provides its Contributions) on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n    implied, including, without limitation, any warranties or conditions\n    of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n    PARTICULAR PURPOSE. You are solely responsible for determining the\n    appropriateness of using or redistributing the Work and assume any\n    risks associated with Your exercise of permissions under this License.\n\n8.  Limitation of Liability. In no event and under no legal theory,\n    whether in tort (including negligence), contract, or otherwise,\n    unless required by applicable law (such as deliberate and grossly\n    negligent acts) or agreed to in writing, shall any Contributor be\n    liable to You for damages, including any direct, indirect, special,\n    incidental, or consequential damages of any character arising as a\n    result of this License or out of the use or inability to use the\n    Work (including but not limited to damages for loss of goodwill,\n    work stoppage, computer failure or malfunction, or any and all\n    other commercial damages or losses), even if such Contributor\n    has been advised of the possibility of such damages.\n\n9.  Accepting Warranty or Additional Liability. While redistributing\n    the Work or Derivative Works thereof, You may choose to offer,\n    and charge a fee for, acceptance of support, warranty, indemnity,\n    or other liability obligations and/or rights consistent with this\n    License. However, in accepting such obligations, You may act only\n    on Your own behalf and on Your sole responsibility, not on behalf\n    of any other Contributor, and only if You agree to indemnify,\n    defend, and hold each Contributor harmless for any liability\n    incurred by, or claims asserted against, such Contributor by reason\n    of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/tauri-bundler/License_MIT.md",
    "content": "Copyright 2017 Cargo-Bundle developers\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n\n---\n\nMIT License\n\nCopyright (c) 2017 - Present Tauri Apps Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "crates/tauri-bundler/README.md",
    "content": "# Tauri Bundler\n\nWrap Rust executables in OS-specific app bundles.\n\n## About\n\nThis is a fork of the awesome [cargo-bundle](https://github.com/burtonageo/cargo-bundle), turned into a library used by the [Tauri CLI](../cli).\n\n## Configuration\n\nTauri automatically loads configurations from the `tauri.conf.json > bundle` object, but this library doesn't rely on it and can be used by non-Tauri apps.\n\n### General settings\n\nThese settings apply to bundles for all (or most) OSes.\n\n- `name`: The name of the built application. If this is not present, then it will use the `name` value from\n  your `Cargo.toml` file.\n- `identifier`: [REQUIRED] A string that uniquely identifies your application,\n  in reverse-DNS form (for example, `\"com.example.appname\"` or\n  `\"io.github.username.project\"`). For OS X and iOS, this is used as the\n  bundle's `CFBundleIdentifier` value; for Windows, this is hashed to create\n  an application GUID.\n- `icon`: [OPTIONAL] The icons used for your application. This should be an array of file paths or globs (with images\n  in various sizes/formats); `tauri-bundler` will automatically convert between image formats as necessary for\n  different platforms. Supported formats include ICNS, ICO, PNG, and anything else that can be decoded by the\n  [`image`](https://crates.io/crates/image) crate. Icons intended for high-resolution (e.g. Retina) displays\n  should have a filename with `@2x` just before the extension (see example below).\n- `version`: [OPTIONAL] The version of the application. If this is not present, then it will use the `version`\n  value from your `Cargo.toml` file.\n- `resources`: [OPTIONAL] List of files or directories which will be copied to the resources section of the\n  bundle. Globs are supported.\n- `copyright`: [OPTIONAL] This contains a copyright string associated with your application.\n- `category`: [OPTIONAL] What kind of application this is. This can\n  be a human-readable string (e.g. `\"Puzzle game\"`), or a Mac OS X\n  LSApplicationCategoryType value\n  (e.g. `\"public.app-category.puzzle-games\"`), or a GNOME desktop\n  file category name (e.g. `\"LogicGame\"`), and `tauri-bundler` will\n  automatically convert as needed for different platforms.\n- `short_description`: [OPTIONAL] A short, one-line description of the application. If this is not present, then it\n  will use the `description` value from your `Cargo.toml` file.\n- `long_description`: [OPTIONAL] A longer, multi-line description of the application.\n\n### Debian-specific settings\n\nThese settings are used only when bundling `deb` packages.\n\n- `depends`: A list of strings indicating other packages (e.g. shared\n  libraries) that this package depends on to be installed. If present, this\n  forms the `Depends:` field of the `deb` package control file.\n\n### Mac OS X-specific settings\n\nThese settings are used only when bundling `app` and `dmg` packages.\n\n- `frameworks`: A list of strings indicating any Mac OS X frameworks that\n  need to be bundled with the app. Each string can either be the name of a\n  framework (without the `.framework` extension, e.g. `\"SDL2\"`), in which case\n  `tauri-bundler` will search for that framework in the standard install\n  locations (`~/Library/Frameworks/`, `/Library/Frameworks/`, and\n  `/Network/Library/Frameworks/`), or a path to a specific framework bundle\n  (e.g. `./data/frameworks/SDL2.framework`). Note that this setting just makes\n  `tauri-bundler` copy the specified frameworks into the OS X app bundle (under\n  `Foobar.app/Contents/Frameworks/`); you are still responsible for (1)\n  arranging for the compiled binary to link against those frameworks (e.g. by\n  emitting lines like `cargo:rustc-link-lib=framework=SDL2` from your\n  `build.rs` script), and (2) embedding the correct rpath in your binary\n  (e.g. by running `install_name_tool -add_rpath\n\"@executable_path/../Frameworks\" path/to/binary` after compiling).\n- `minimum_system_version`: A version string indicating the minimum Mac OS\n  X version that the bundled app supports (e.g. `\"10.11\"`). If you are using\n  this config field, you may also want have your `build.rs` script emit\n  `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.11` (or whatever version number\n  you want) to ensure that the compiled binary has the same minimum version.\n- `license`: Path to the license file for the DMG bundle.\n- `exception_domain`: The exception domain to use on the macOS .app bundle. Allows communication to the outside world e.g. a web server you're shipping.\n- `provider_short_name`: If your Apple ID is connected to multiple teams, you have to specify the provider short name of the team you want to use to notarize your app. See [Customizing the notarization workflow](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow) and search for `--list-providers` for more information how to obtain your provider short name.\n\n### Example `tauri.conf.json`:\n\n```json\n{\n  \"productName\": \"Your Awesome App\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.my.app\",\n  \"app\": {},\n  \"bundle\": {\n    \"active\": true,\n    \"shortDescription\": \"\",\n    \"longDescription\": \"\",\n    \"copyright\": \"Copyright (c) You 2021. All rights reserved.\",\n    \"icon\": [\n      \"icons/32x32.png\",\n      \"icons/128x128.png\",\n      \"icons/128x128@2x.png\",\n      \"icons/icon.icns\",\n      \"icons/icon.ico\"\n    ],\n    \"resources\": [\"./assets/**/*.png\"],\n    \"deb\": {\n      \"depends\": [\"debian-dependency1\", \"debian-dependency2\"]\n    },\n    \"macOS\": {\n      \"frameworks\": [],\n      \"minimumSystemVersion\": \"10.11\",\n      \"license\": \"./LICENSE\"\n    },\n    \"externalBin\": [\"./sidecar-app\"]\n  }\n}\n```\n\n## License\n\n(c) 2017 - present, George Burton, Tauri-Apps Organization\n\nThis program is licensed either under the terms of the\n[Apache Software License](http://www.apache.org/licenses/LICENSE-2.0), or the\n[MIT License](https://opensource.org/licenses/MIT).\n\n-> note, for bundle_dmg we have included a BSD 3 licensed binary `seticon`.\nhttps://github.com/sveinbjornt/osxiconutils/blob/master/seticon.m\n`tools/rust/cargo-tauri-bundle/src/bundle/templates/seticon`\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/category.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{fmt, str::FromStr};\n\nconst CONFIDENCE_THRESHOLD: f64 = 0.8;\n\nconst MACOS_APP_CATEGORY_PREFIX: &str = \"public.app-category.\";\n\n// TODO: RIght now, these categories correspond to LSApplicationCategoryType\n// values for OS X.  There are also some additional GNOME registered categories\n// that don't fit these; we should add those here too.\n/// The possible app categories.\n/// Corresponds to `LSApplicationCategoryType` on macOS and the GNOME desktop categories on Debian.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n#[non_exhaustive]\npub enum AppCategory {\n  Business,\n  DeveloperTool,\n  Education,\n  Entertainment,\n  Finance,\n  Game,\n  ActionGame,\n  AdventureGame,\n  ArcadeGame,\n  BoardGame,\n  CardGame,\n  CasinoGame,\n  DiceGame,\n  EducationalGame,\n  FamilyGame,\n  KidsGame,\n  MusicGame,\n  PuzzleGame,\n  RacingGame,\n  RolePlayingGame,\n  SimulationGame,\n  SportsGame,\n  StrategyGame,\n  TriviaGame,\n  WordGame,\n  GraphicsAndDesign,\n  HealthcareAndFitness,\n  Lifestyle,\n  Medical,\n  Music,\n  News,\n  Photography,\n  Productivity,\n  Reference,\n  SocialNetworking,\n  Sports,\n  Travel,\n  Utility,\n  Video,\n  Weather,\n}\n\nimpl FromStr for AppCategory {\n  type Err = Option<&'static str>;\n\n  /// Given a string, returns the `AppCategory` it refers to, or the closest\n  /// string that the user might have intended (if any).\n  fn from_str(input: &str) -> Result<AppCategory, Self::Err> {\n    // Canonicalize input:\n    let mut input = input.to_ascii_lowercase();\n    if input.starts_with(MACOS_APP_CATEGORY_PREFIX) {\n      input = input\n        .split_at(MACOS_APP_CATEGORY_PREFIX.len())\n        .1\n        .to_string();\n    }\n    input = input.replace(' ', \"\");\n    input = input.replace('-', \"\");\n\n    // Find best match:\n    let mut best_confidence = 0.0;\n    let mut best_category: Option<AppCategory> = None;\n    for &(string, category) in CATEGORY_STRINGS.iter() {\n      if input == string {\n        return Ok(category);\n      }\n      let confidence = strsim::jaro_winkler(&input, string);\n      if confidence >= CONFIDENCE_THRESHOLD && confidence > best_confidence {\n        best_confidence = confidence;\n        best_category = Some(category);\n      }\n    }\n    Err(best_category.map(AppCategory::canonical))\n  }\n}\n\nimpl AppCategory {\n  /// Map an AppCategory to the string we recommend to use in Cargo.toml if\n  /// the users misspells the category name.\n  fn canonical(self) -> &'static str {\n    match self {\n      AppCategory::Business => \"Business\",\n      AppCategory::DeveloperTool => \"Developer Tool\",\n      AppCategory::Education => \"Education\",\n      AppCategory::Entertainment => \"Entertainment\",\n      AppCategory::Finance => \"Finance\",\n      AppCategory::Game => \"Game\",\n      AppCategory::ActionGame => \"Action Game\",\n      AppCategory::AdventureGame => \"Adventure Game\",\n      AppCategory::ArcadeGame => \"Arcade Game\",\n      AppCategory::BoardGame => \"Board Game\",\n      AppCategory::CardGame => \"Card Game\",\n      AppCategory::CasinoGame => \"Casino Game\",\n      AppCategory::DiceGame => \"Dice Game\",\n      AppCategory::EducationalGame => \"Educational Game\",\n      AppCategory::FamilyGame => \"Family Game\",\n      AppCategory::KidsGame => \"Kids Game\",\n      AppCategory::MusicGame => \"Music Game\",\n      AppCategory::PuzzleGame => \"Puzzle Game\",\n      AppCategory::RacingGame => \"Racing Game\",\n      AppCategory::RolePlayingGame => \"Role-Playing Game\",\n      AppCategory::SimulationGame => \"Simulation Game\",\n      AppCategory::SportsGame => \"Sports Game\",\n      AppCategory::StrategyGame => \"Strategy Game\",\n      AppCategory::TriviaGame => \"Trivia Game\",\n      AppCategory::WordGame => \"Word Game\",\n      AppCategory::GraphicsAndDesign => \"Graphics and Design\",\n      AppCategory::HealthcareAndFitness => \"Healthcare and Fitness\",\n      AppCategory::Lifestyle => \"Lifestyle\",\n      AppCategory::Medical => \"Medical\",\n      AppCategory::Music => \"Music\",\n      AppCategory::News => \"News\",\n      AppCategory::Photography => \"Photography\",\n      AppCategory::Productivity => \"Productivity\",\n      AppCategory::Reference => \"Reference\",\n      AppCategory::SocialNetworking => \"Social Networking\",\n      AppCategory::Sports => \"Sports\",\n      AppCategory::Travel => \"Travel\",\n      AppCategory::Utility => \"Utility\",\n      AppCategory::Video => \"Video\",\n      AppCategory::Weather => \"Weather\",\n    }\n  }\n\n  /// Map an AppCategory to the closest set of Freedesktop registered\n  /// categories that matches that category.\n  ///\n  /// Cf <https://specifications.freedesktop.org/menu-spec/latest/>\n  pub fn freedesktop_categories(self) -> &'static str {\n    match &self {\n      AppCategory::Business => \"Office;\",\n      AppCategory::DeveloperTool => \"Development;\",\n      AppCategory::Education => \"Education;\",\n      AppCategory::Entertainment => \"Network;\",\n      AppCategory::Finance => \"Office;Finance;\",\n      AppCategory::Game => \"Game;\",\n      AppCategory::ActionGame => \"Game;ActionGame;\",\n      AppCategory::AdventureGame => \"Game;AdventureGame;\",\n      AppCategory::ArcadeGame => \"Game;ArcadeGame;\",\n      AppCategory::BoardGame => \"Game;BoardGame;\",\n      AppCategory::CardGame => \"Game;CardGame;\",\n      AppCategory::CasinoGame => \"Game;\",\n      AppCategory::DiceGame => \"Game;\",\n      AppCategory::EducationalGame => \"Game;Education;\",\n      AppCategory::FamilyGame => \"Game;\",\n      AppCategory::KidsGame => \"Game;KidsGame;\",\n      AppCategory::MusicGame => \"Game;\",\n      AppCategory::PuzzleGame => \"Game;LogicGame;\",\n      AppCategory::RacingGame => \"Game;\",\n      AppCategory::RolePlayingGame => \"Game;RolePlaying;\",\n      AppCategory::SimulationGame => \"Game;Simulation;\",\n      AppCategory::SportsGame => \"Game;SportsGame;\",\n      AppCategory::StrategyGame => \"Game;StrategyGame;\",\n      AppCategory::TriviaGame => \"Game;\",\n      AppCategory::WordGame => \"Game;\",\n      AppCategory::GraphicsAndDesign => \"Graphics;\",\n      AppCategory::HealthcareAndFitness => \"Science;\",\n      AppCategory::Lifestyle => \"Education;\",\n      AppCategory::Medical => \"Science;MedicalSoftware;\",\n      AppCategory::Music => \"AudioVideo;Audio;Music;\",\n      AppCategory::News => \"Network;News;\",\n      AppCategory::Photography => \"Graphics;Photography;\",\n      AppCategory::Productivity => \"Office;\",\n      AppCategory::Reference => \"Education;\",\n      AppCategory::SocialNetworking => \"Network;\",\n      AppCategory::Sports => \"Education;Sports;\",\n      AppCategory::Travel => \"Education;\",\n      AppCategory::Utility => \"Utility;\",\n      AppCategory::Video => \"AudioVideo;Video;\",\n      AppCategory::Weather => \"Science;\",\n    }\n  }\n\n  /// Map an AppCategory to the closest LSApplicationCategoryType value that\n  /// matches that category.\n  pub fn macos_application_category_type(self) -> &'static str {\n    match &self {\n      AppCategory::Business => \"public.app-category.business\",\n      AppCategory::DeveloperTool => \"public.app-category.developer-tools\",\n      AppCategory::Education => \"public.app-category.education\",\n      AppCategory::Entertainment => \"public.app-category.entertainment\",\n      AppCategory::Finance => \"public.app-category.finance\",\n      AppCategory::Game => \"public.app-category.games\",\n      AppCategory::ActionGame => \"public.app-category.action-games\",\n      AppCategory::AdventureGame => \"public.app-category.adventure-games\",\n      AppCategory::ArcadeGame => \"public.app-category.arcade-games\",\n      AppCategory::BoardGame => \"public.app-category.board-games\",\n      AppCategory::CardGame => \"public.app-category.card-games\",\n      AppCategory::CasinoGame => \"public.app-category.casino-games\",\n      AppCategory::DiceGame => \"public.app-category.dice-games\",\n      AppCategory::EducationalGame => \"public.app-category.educational-games\",\n      AppCategory::FamilyGame => \"public.app-category.family-games\",\n      AppCategory::KidsGame => \"public.app-category.kids-games\",\n      AppCategory::MusicGame => \"public.app-category.music-games\",\n      AppCategory::PuzzleGame => \"public.app-category.puzzle-games\",\n      AppCategory::RacingGame => \"public.app-category.racing-games\",\n      AppCategory::RolePlayingGame => \"public.app-category.role-playing-games\",\n      AppCategory::SimulationGame => \"public.app-category.simulation-games\",\n      AppCategory::SportsGame => \"public.app-category.sports-games\",\n      AppCategory::StrategyGame => \"public.app-category.strategy-games\",\n      AppCategory::TriviaGame => \"public.app-category.trivia-games\",\n      AppCategory::WordGame => \"public.app-category.word-games\",\n      AppCategory::GraphicsAndDesign => \"public.app-category.graphics-design\",\n      AppCategory::HealthcareAndFitness => \"public.app-category.healthcare-fitness\",\n      AppCategory::Lifestyle => \"public.app-category.lifestyle\",\n      AppCategory::Medical => \"public.app-category.medical\",\n      AppCategory::Music => \"public.app-category.music\",\n      AppCategory::News => \"public.app-category.news\",\n      AppCategory::Photography => \"public.app-category.photography\",\n      AppCategory::Productivity => \"public.app-category.productivity\",\n      AppCategory::Reference => \"public.app-category.reference\",\n      AppCategory::SocialNetworking => \"public.app-category.social-networking\",\n      AppCategory::Sports => \"public.app-category.sports\",\n      AppCategory::Travel => \"public.app-category.travel\",\n      AppCategory::Utility => \"public.app-category.utilities\",\n      AppCategory::Video => \"public.app-category.video\",\n      AppCategory::Weather => \"public.app-category.weather\",\n    }\n  }\n}\n\nimpl<'d> serde::Deserialize<'d> for AppCategory {\n  fn deserialize<D: serde::Deserializer<'d>>(deserializer: D) -> Result<AppCategory, D::Error> {\n    deserializer.deserialize_str(AppCategoryVisitor { did_you_mean: None })\n  }\n}\n\nstruct AppCategoryVisitor {\n  did_you_mean: Option<&'static str>,\n}\n\nimpl serde::de::Visitor<'_> for AppCategoryVisitor {\n  type Value = AppCategory;\n\n  fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {\n    match self.did_you_mean {\n      Some(string) => write!(\n        formatter,\n        \"a valid app category string (did you mean \\\"{string}\\\"?)\"\n      ),\n      None => write!(formatter, \"a valid app category string\"),\n    }\n  }\n\n  fn visit_str<E: serde::de::Error>(mut self, value: &str) -> Result<AppCategory, E> {\n    match AppCategory::from_str(value) {\n      Ok(category) => Ok(category),\n      Err(did_you_mean) => {\n        self.did_you_mean = did_you_mean;\n        let unexp = serde::de::Unexpected::Str(value);\n        Err(serde::de::Error::invalid_value(unexp, &self))\n      }\n    }\n  }\n}\n\nconst CATEGORY_STRINGS: &[(&str, AppCategory)] = &[\n  (\"actiongame\", AppCategory::ActionGame),\n  (\"actiongames\", AppCategory::ActionGame),\n  (\"adventuregame\", AppCategory::AdventureGame),\n  (\"adventuregames\", AppCategory::AdventureGame),\n  (\"arcadegame\", AppCategory::ArcadeGame),\n  (\"arcadegames\", AppCategory::ArcadeGame),\n  (\"boardgame\", AppCategory::BoardGame),\n  (\"boardgames\", AppCategory::BoardGame),\n  (\"business\", AppCategory::Business),\n  (\"cardgame\", AppCategory::CardGame),\n  (\"cardgames\", AppCategory::CardGame),\n  (\"casinogame\", AppCategory::CasinoGame),\n  (\"casinogames\", AppCategory::CasinoGame),\n  (\"developer\", AppCategory::DeveloperTool),\n  (\"developertool\", AppCategory::DeveloperTool),\n  (\"developertools\", AppCategory::DeveloperTool),\n  (\"development\", AppCategory::DeveloperTool),\n  (\"dicegame\", AppCategory::DiceGame),\n  (\"dicegames\", AppCategory::DiceGame),\n  (\"education\", AppCategory::Education),\n  (\"educationalgame\", AppCategory::EducationalGame),\n  (\"educationalgames\", AppCategory::EducationalGame),\n  (\"entertainment\", AppCategory::Entertainment),\n  (\"familygame\", AppCategory::FamilyGame),\n  (\"familygames\", AppCategory::FamilyGame),\n  (\"finance\", AppCategory::Finance),\n  (\"fitness\", AppCategory::HealthcareAndFitness),\n  (\"game\", AppCategory::Game),\n  (\"games\", AppCategory::Game),\n  (\"graphicdesign\", AppCategory::GraphicsAndDesign),\n  (\"graphicsanddesign\", AppCategory::GraphicsAndDesign),\n  (\"graphicsdesign\", AppCategory::GraphicsAndDesign),\n  (\"healthcareandfitness\", AppCategory::HealthcareAndFitness),\n  (\"healthcarefitness\", AppCategory::HealthcareAndFitness),\n  (\"kidsgame\", AppCategory::KidsGame),\n  (\"kidsgames\", AppCategory::KidsGame),\n  (\"lifestyle\", AppCategory::Lifestyle),\n  (\"logicgame\", AppCategory::PuzzleGame),\n  (\"medical\", AppCategory::Medical),\n  (\"medicalsoftware\", AppCategory::Medical),\n  (\"music\", AppCategory::Music),\n  (\"musicgame\", AppCategory::MusicGame),\n  (\"musicgames\", AppCategory::MusicGame),\n  (\"news\", AppCategory::News),\n  (\"photography\", AppCategory::Photography),\n  (\"productivity\", AppCategory::Productivity),\n  (\"puzzlegame\", AppCategory::PuzzleGame),\n  (\"puzzlegames\", AppCategory::PuzzleGame),\n  (\"racinggame\", AppCategory::RacingGame),\n  (\"racinggames\", AppCategory::RacingGame),\n  (\"reference\", AppCategory::Reference),\n  (\"roleplaying\", AppCategory::RolePlayingGame),\n  (\"roleplayinggame\", AppCategory::RolePlayingGame),\n  (\"roleplayinggames\", AppCategory::RolePlayingGame),\n  (\"rpg\", AppCategory::RolePlayingGame),\n  (\"simulationgame\", AppCategory::SimulationGame),\n  (\"simulationgames\", AppCategory::SimulationGame),\n  (\"socialnetwork\", AppCategory::SocialNetworking),\n  (\"socialnetworking\", AppCategory::SocialNetworking),\n  (\"sports\", AppCategory::Sports),\n  (\"sportsgame\", AppCategory::SportsGame),\n  (\"sportsgames\", AppCategory::SportsGame),\n  (\"strategygame\", AppCategory::StrategyGame),\n  (\"strategygames\", AppCategory::StrategyGame),\n  (\"travel\", AppCategory::Travel),\n  (\"triviagame\", AppCategory::TriviaGame),\n  (\"triviagames\", AppCategory::TriviaGame),\n  (\"utilities\", AppCategory::Utility),\n  (\"utility\", AppCategory::Utility),\n  (\"video\", AppCategory::Video),\n  (\"weather\", AppCategory::Weather),\n  (\"wordgame\", AppCategory::WordGame),\n  (\"wordgames\", AppCategory::WordGame),\n];\n\n#[cfg(test)]\nmod tests {\n  use super::AppCategory;\n  use std::str::FromStr;\n\n  #[test]\n  fn category_from_string_ok() {\n    // Canonical name of category works:\n    assert_eq!(\n      AppCategory::from_str(\"Education\"),\n      Ok(AppCategory::Education)\n    );\n    assert_eq!(\n      AppCategory::from_str(\"Developer Tool\"),\n      Ok(AppCategory::DeveloperTool)\n    );\n    // Lowercase, spaces, and hyphens are fine:\n    assert_eq!(\n      AppCategory::from_str(\" puzzle  game \"),\n      Ok(AppCategory::PuzzleGame)\n    );\n    assert_eq!(\n      AppCategory::from_str(\"Role-playing game\"),\n      Ok(AppCategory::RolePlayingGame)\n    );\n    // Using macOS LSApplicationCategoryType value is fine:\n    assert_eq!(\n      AppCategory::from_str(\"public.app-category.developer-tools\"),\n      Ok(AppCategory::DeveloperTool)\n    );\n    assert_eq!(\n      AppCategory::from_str(\"public.app-category.role-playing-games\"),\n      Ok(AppCategory::RolePlayingGame)\n    );\n    // Using GNOME category name is fine:\n    assert_eq!(\n      AppCategory::from_str(\"Development\"),\n      Ok(AppCategory::DeveloperTool)\n    );\n    assert_eq!(\n      AppCategory::from_str(\"LogicGame\"),\n      Ok(AppCategory::PuzzleGame)\n    );\n    // Using common abbreviations is fine:\n    assert_eq!(\n      AppCategory::from_str(\"RPG\"),\n      Ok(AppCategory::RolePlayingGame)\n    );\n  }\n\n  #[test]\n  fn category_from_string_did_you_mean() {\n    assert_eq!(AppCategory::from_str(\"gaming\"), Err(Some(\"Game\")));\n    assert_eq!(AppCategory::from_str(\"photos\"), Err(Some(\"Photography\")));\n    assert_eq!(\n      AppCategory::from_str(\"strategery\"),\n      Err(Some(\"Strategy Game\"))\n    );\n  }\n\n  #[test]\n  fn category_from_string_totally_wrong() {\n    assert_eq!(AppCategory::from_str(\"fhqwhgads\"), Err(None));\n    assert_eq!(AppCategory::from_str(\"WHARRGARBL\"), Err(None));\n  }\n\n  #[test]\n  fn ls_application_category_type_round_trip() {\n    let values = &[\n      \"public.app-category.business\",\n      \"public.app-category.developer-tools\",\n      \"public.app-category.education\",\n      \"public.app-category.entertainment\",\n      \"public.app-category.finance\",\n      \"public.app-category.games\",\n      \"public.app-category.action-games\",\n      \"public.app-category.adventure-games\",\n      \"public.app-category.arcade-games\",\n      \"public.app-category.board-games\",\n      \"public.app-category.card-games\",\n      \"public.app-category.casino-games\",\n      \"public.app-category.dice-games\",\n      \"public.app-category.educational-games\",\n      \"public.app-category.family-games\",\n      \"public.app-category.kids-games\",\n      \"public.app-category.music-games\",\n      \"public.app-category.puzzle-games\",\n      \"public.app-category.racing-games\",\n      \"public.app-category.role-playing-games\",\n      \"public.app-category.simulation-games\",\n      \"public.app-category.sports-games\",\n      \"public.app-category.strategy-games\",\n      \"public.app-category.trivia-games\",\n      \"public.app-category.word-games\",\n      \"public.app-category.graphics-design\",\n      \"public.app-category.healthcare-fitness\",\n      \"public.app-category.lifestyle\",\n      \"public.app-category.medical\",\n      \"public.app-category.music\",\n      \"public.app-category.news\",\n      \"public.app-category.photography\",\n      \"public.app-category.productivity\",\n      \"public.app-category.reference\",\n      \"public.app-category.social-networking\",\n      \"public.app-category.sports\",\n      \"public.app-category.travel\",\n      \"public.app-category.utilities\",\n      \"public.app-category.video\",\n      \"public.app-category.weather\",\n    ];\n    // Test that if the user uses an LSApplicationCategoryType string as\n    // the category string, they will get back that same string for the\n    // macOS app bundle LSApplicationCategoryType.\n    for &value in values.iter() {\n      let category = AppCategory::from_str(value).expect(value);\n      assert_eq!(category.macos_application_category_type(), value);\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/kmp/mod.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// Knuth–Morris–Pratt algorithm\n// based on https://github.com/howeih/rust_kmp\npub fn index_of(pattern: &[u8], target: &[u8]) -> Option<usize> {\n  let failure_function = find_failure_function(pattern);\n\n  let mut t_i: usize = 0;\n  let mut p_i: usize = 0;\n  let target_len = target.len();\n  let mut result_idx = None;\n  let pattern_len = pattern.len();\n\n  while (t_i < target_len) && (p_i < pattern_len) {\n    if target[t_i] == pattern[p_i] {\n      if result_idx.is_none() {\n        result_idx.replace(t_i);\n      }\n      t_i += 1;\n      p_i += 1;\n      if p_i >= pattern_len {\n        return result_idx;\n      }\n    } else {\n      if p_i == 0 {\n        p_i = 0;\n        t_i += 1;\n      } else {\n        p_i = failure_function[p_i - 1];\n      }\n      result_idx = None;\n    }\n  }\n  None\n}\n\nfn find_failure_function(pattern: &[u8]) -> Vec<usize> {\n  let mut i = 1;\n  let mut j = 0;\n  let pattern_length = pattern.len();\n  let end_i = pattern_length - 1;\n  let mut failure_function = vec![0usize; pattern_length];\n  while i <= end_i {\n    if pattern[i] == pattern[j] {\n      failure_function[i] = j + 1;\n      i += 1;\n      j += 1;\n    } else if j == 0 {\n      failure_function[i] = 0;\n      i += 1;\n    } else {\n      j = failure_function[j - 1];\n    }\n  }\n  failure_function\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/linux/appimage/linuxdeploy-plugin-gstreamer.sh",
    "content": "#! /bin/bash\n\n# abort on all errors\nset -e\n\nif [ \"$DEBUG\" != \"\" ]; then\n    set -x\nfi\n\nscript=$(readlink -f \"$0\")\n\nshow_usage() {\n    echo \"Usage: $script --appdir <path to AppDir>\"\n    echo\n    echo \"Bundles GStreamer plugins into an AppDir\"\n    echo\n    echo \"Required variables:\"\n    echo \"  LINUXDEPLOY=\\\".../linuxdeploy\\\" path to linuxdeploy (e.g., AppImage); set automatically when plugin is run directly by linuxdeploy\"\n    echo\n    echo \"Optional variables:\"\n    echo \"  GSTREAMER_INCLUDE_BAD_PLUGINS=\\\"1\\\" (default: disabled; set to empty string or unset to disable)\"\n    echo \"  GSTREAMER_PLUGINS_DIR=\\\"...\\\" (directory containing GStreamer plugins; default: guessed based on main distro architecture)\"\n    echo \"  GSTREAMER_HELPERS_DIR=\\\"...\\\" (directory containing GStreamer helper tools like gst-plugin-scanner; default: guessed based on main distro architecture)\"\n    echo \"  GSTREAMER_VERSION=\\\"1.0\\\" (default: 1.0)\"\n}\n\nwhile [ \"$1\" != \"\" ]; do\n    case \"$1\" in\n        --plugin-api-version)\n            echo \"0\"\n            exit 0\n            ;;\n        --appdir)\n            APPDIR=\"$2\"\n            shift\n            shift\n            ;;\n        --help)\n            show_usage\n            exit 0\n            ;;\n        *)\n            echo \"Invalid argument: $1\"\n            echo\n            show_usage\n            exit 1\n            ;;\n    esac\ndone\n\nif [ \"$APPDIR\" == \"\" ]; then\n    show_usage\n    exit 1\nfi\n\nif ! which patchelf &>/dev/null && ! type patchelf &>/dev/null; then\n    echo \"Error: patchelf not found\"\n    echo\n    show_usage\n    exit 2\nfi\n\nif [[ \"$LINUXDEPLOY\" == \"\" ]]; then\n    echo \"Error: \\$LINUXDEPLOY not set\"\n    echo\n    show_usage\n    exit 3\nfi\n\nmkdir -p \"$APPDIR\"\n\nexport GSTREAMER_VERSION=\"${GSTREAMER_VERSION:-1.0}\"\n\nplugins_target_dir=\"$APPDIR\"/usr/lib/gstreamer-\"$GSTREAMER_VERSION\"\nhelpers_target_dir=\"$APPDIR\"/usr/lib/gstreamer\"$GSTREAMER_VERSION\"/gstreamer-\"$GSTREAMER_VERSION\"\n\nif [ \"$GSTREAMER_PLUGINS_DIR\" != \"\" ]; then\n    plugins_dir=\"${GSTREAMER_PLUGINS_DIR}\"\nelif [ -d /usr/lib/\"$(uname -m)\"-linux-gnu/gstreamer-\"$GSTREAMER_VERSION\" ]; then\n    plugins_dir=/usr/lib/$(uname -m)-linux-gnu/gstreamer-\"$GSTREAMER_VERSION\"\nelse\n    plugins_dir=/usr/lib/gstreamer-\"$GSTREAMER_VERSION\"\nfi\n\nif [ \"$GSTREAMER_HELPERS_DIR\" != \"\" ]; then\n    helpers_dir=\"${GSTREAMER_HELPERS_DIR}\"\nelse\n    helpers_dir=/usr/lib/$(uname -m)-linux-gnu/gstreamer\"$GSTREAMER_VERSION\"/gstreamer-\"$GSTREAMER_VERSION\"\nfi\n\nif [ ! -d \"$plugins_dir\" ]; then\n    echo \"Error: could not find plugins directory: $plugins_dir\"\n    exit 1\nfi\n\nmkdir -p \"$plugins_target_dir\"\n\necho \"Copying plugins into $plugins_target_dir\"\nfor i in \"$plugins_dir\"/*; do\n    [ -d \"$i\" ] && continue\n    [ ! -f \"$i\" ] && echo \"File does not exist: $i\" && continue\n\n    echo \"Copying plugin: $i\"\n    cp \"$i\" \"$plugins_target_dir\"\ndone\n\n\"$LINUXDEPLOY\" --appdir \"$APPDIR\"\n\nfor i in \"$plugins_target_dir\"/*; do\n    [ -d \"$i\" ] && continue\n    [ ! -f \"$i\" ] && echo \"File does not exist: $i\" && continue\n    (file \"$i\" | grep -v ELF --silent) && echo \"Ignoring non ELF file: $i\" && continue\n\n    echo \"Manually setting rpath for $i\"\n    patchelf --set-rpath '$ORIGIN/..:$ORIGIN' \"$i\"\ndone\n\nmkdir -p \"$helpers_target_dir\"\n\necho \"Copying helpers in $helpers_target_dir\"\nfor i in \"$helpers_dir\"/*; do\n    [ -d \"$i\" ] && continue\n    [ ! -f \"$i\" ] && echo \"File does not exist: $i\" && continue\n\n    echo \"Copying helper: $i\"\n    cp \"$i\" \"$helpers_target_dir\"\ndone\n\nfor i in \"$helpers_target_dir\"/*; do\n    [ -d \"$i\" ] && continue\n    [ ! -f \"$i\" ] && echo \"File does not exist: $i\" && continue\n    (file \"$i\" | grep -v ELF --silent) && echo \"Ignoring non ELF file: $i\" && continue\n\n    echo \"Manually setting rpath for $i\"\n    patchelf --set-rpath '$ORIGIN/../..' \"$i\"\ndone\n\necho \"Installing AppRun hook\"\nmkdir -p \"$APPDIR\"/apprun-hooks\n\nif [ \"$GSTREAMER_VERSION\" == \"1.0\" ]; then\n    cat > \"$APPDIR\"/apprun-hooks/linuxdeploy-plugin-gstreamer.sh <<\\EOF\n#! /bin/bash\n\nexport GST_REGISTRY_REUSE_PLUGIN_SCANNER=\"no\"\nexport GST_PLUGIN_SYSTEM_PATH_1_0=\"${APPDIR}/usr/lib/gstreamer-1.0\"\nexport GST_PLUGIN_PATH_1_0=\"${APPDIR}/usr/lib/gstreamer-1.0\"\n\nexport GST_PLUGIN_SCANNER_1_0=\"${APPDIR}/usr/lib/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner\"\nexport GST_PTP_HELPER_1_0=\"${APPDIR}/usr/lib/gstreamer1.0/gstreamer-1.0/gst-ptp-helper\"\nEOF\nelif [ \"$GSTREAMER_VERSION\" == \"0.10\" ]; then\n    cat > \"$APPDIR\"/apprun-hooks/linuxdeploy-plugin-gstreamer.sh <<\\EOF\n#! /bin/bash\n\nexport GST_REGISTRY_REUSE_PLUGIN_SCANNER=\"no\"\nexport GST_PLUGIN_SYSTEM_PATH_0_10=\"${APPDIR}/usr/lib/gstreamer-1.0\"\n\nexport GST_PLUGIN_SCANNER_0_10=\"${APPDIR}/usr/lib/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner\"\nexport GST_PTP_HELPER_0_10=\"${APPDIR}/usr/lib/gstreamer1.0/gstreamer-1.0/gst-ptp-helper\"\nEOF\nelse\n    echo \"Warning: unknown GStreamer version: $GSTREAMER_VERSION, cannot install AppRun hook\"\nfi\n\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/linux/appimage/linuxdeploy-plugin-gtk.sh",
    "content": "#! /usr/bin/env bash\n\n# GTK3 environment variables: https://docs.gtk.org/gtk3/running.html\n# GTK4 environment variables: https://docs.gtk.org/gtk4/running.html\n\n# abort on all errors\nset -e\n\nif [ \"$DEBUG\" != \"\" ]; then\n    set -x\n    verbose=\"--verbose\"\nfi\n\nscript=$(readlink -f \"$0\")\n\nshow_usage() {\n    echo \"Usage: $script --appdir <path to AppDir>\"\n    echo\n    echo \"Bundles resources for applications that use GTK into an AppDir\"\n    echo\n    echo \"Required variables:\"\n    echo \"  LINUXDEPLOY=\\\".../linuxdeploy\\\" path to linuxdeploy (e.g., AppImage); set automatically when plugin is run directly by linuxdeploy\"\n    #echo\n    #echo \"Optional variables:\"\n    #echo \"  DEPLOY_GTK_VERSION (major version of GTK to deploy, e.g. '2', '3' or '4'; auto-detect by default)\"\n}\n\nvariable_is_true() {\n    local var=\"$1\"\n\n    if [ -n \"$var\" ] && { [ \"$var\" == \"true\" ] || [ \"$var\" -gt 0 ]; } 2> /dev/null; then\n        return 0 # true\n    else\n        return 1 # false\n    fi\n}\n\nget_pkgconf_variable() {\n    local variable=\"$1\"\n    local library=\"$2\"\n    local default_path=\"$3\"\n\n    path=\"$(\"$PKG_CONFIG\" --variable=\"$variable\" \"$library\")\"\n    if [ -n \"$path\" ]; then\n        echo \"$path\"\n    elif [ -n \"$default_path\" ]; then\n        echo \"$default_path\"\n    else\n        echo \"$0: there is no '$variable' variable for '$library' library.\" > /dev/stderr\n        echo \"Please check the '$library.pc' file is present in \\$PKG_CONFIG_PATH (you may need to install the appropriate -dev/-devel package).\" > /dev/stderr\n        exit 1\n    fi\n}\n\ncopy_tree() {\n    local src=(\"${@:1:$#-1}\")\n    local dst=\"${*:$#}\"\n\n    for elem in \"${src[@]}\"; do\n        mkdir -p \"${dst::-1}$elem\"\n        cp \"$elem\" --archive --parents --target-directory=\"$dst\" $verbose\n    done\n}\n\nsearch_tool() {\n    local tool=\"$1\"\n    local directory=\"$2\"\n\n    if command -v \"$tool\"; then\n        return 0\n    fi\n\n    PATH_ARRAY=(\n        \"/usr/lib/$(uname -m)-linux-gnu/$directory/$tool\"\n        \"/usr/lib/$directory/$tool\"\n        \"/usr/bin/$tool\"\n        \"/usr/bin/$tool-64\"\n        \"/usr/bin/$tool-32\"\n    )\n\n    for path in \"${PATH_ARRAY[@]}\"; do\n        if [ -x \"$path\" ]; then\n            echo \"$path\"\n            return 0\n        fi\n    done\n}\n\n#DEPLOY_GTK_VERSION=\"${DEPLOY_GTK_VERSION:-0}\" # When not set by user, this variable use the integer '0' as a sentinel value\nDEPLOY_GTK_VERSION=3 # Force GTK3 for tauri apps\nAPPDIR=\n\nwhile [ \"$1\" != \"\" ]; do\n    case \"$1\" in\n        --plugin-api-version)\n            echo \"0\"\n            exit 0\n            ;;\n        --appdir)\n            APPDIR=\"$2\"\n            shift\n            shift\n            ;;\n        --help)\n            show_usage\n            exit 0\n            ;;\n        *)\n            echo \"Invalid argument: $1\"\n            echo\n            show_usage\n            exit 1\n            ;;\n    esac\ndone\n\nif [ \"$APPDIR\" == \"\" ]; then\n    show_usage\n    exit 1\nfi\n\nmkdir -p \"$APPDIR\"\n# make lib64 writable again.\nchmod +w \"$APPDIR\"/usr/lib64 || true\n\nif command -v pkgconf > /dev/null; then\n    PKG_CONFIG=\"pkgconf\"\nelif command -v pkg-config > /dev/null; then\n    PKG_CONFIG=\"pkg-config\"\nelse\n    echo \"$0: pkg-config/pkgconf not found in PATH, aborting\"\n    exit 1\nfi\n\nif ! command -v find &>/dev/null && ! type find &>/dev/null; then\n    echo -e \"$0: find not found.\\nInstall findutils then re-run the plugin.\"\n    exit 1\nfi\n\nif [ -z \"$LINUXDEPLOY\" ]; then\n    echo -e \"$0: LINUXDEPLOY environment variable is not set.\\nDownload a suitable linuxdeploy AppImage, set the environment variable and re-run the plugin.\"\n    exit 1\nfi\n\ngtk_versions=0 # Count major versions of GTK when auto-detect GTK version\nif [ \"$DEPLOY_GTK_VERSION\" -eq 0 ]; then\n    echo \"Determining which GTK version to deploy\"\n    while IFS= read -r -d '' file; do\n        if [ \"$DEPLOY_GTK_VERSION\" -ne 2 ] && ldd \"$file\" | grep -q \"libgtk-x11-2.0.so\"; then\n            DEPLOY_GTK_VERSION=2\n            gtk_versions=\"$((gtk_versions+1))\"\n        fi\n        if [ \"$DEPLOY_GTK_VERSION\" -ne 3 ] && ldd \"$file\" | grep -q \"libgtk-3.so\"; then\n            DEPLOY_GTK_VERSION=3\n            gtk_versions=\"$((gtk_versions+1))\"\n        fi\n        if [ \"$DEPLOY_GTK_VERSION\" -ne 4 ] && ldd \"$file\" | grep -q \"libgtk-4.so\"; then\n            DEPLOY_GTK_VERSION=4\n            gtk_versions=\"$((gtk_versions+1))\"\n        fi\n    done < <(find \"$APPDIR/usr/bin\" -executable -type f -print0)\nfi\n\nif [ \"$gtk_versions\" -gt 1 ]; then\n    echo \"$0: can not deploy multiple GTK versions at the same time.\"\n    echo \"Please set DEPLOY_GTK_VERSION to {2, 3, 4}.\"\n    exit 1\nelif [ \"$DEPLOY_GTK_VERSION\" -eq 0 ]; then\n    echo \"$0: failed to auto-detect GTK version.\"\n    echo \"Please set DEPLOY_GTK_VERSION to {2, 3, 4}.\"\n    exit 1\nfi\n\necho \"Installing AppRun hook\"\nHOOKSDIR=\"$APPDIR/apprun-hooks\"\nHOOKFILE=\"$HOOKSDIR/linuxdeploy-plugin-gtk.sh\"\nmkdir -p \"$HOOKSDIR\"\ncat > \"$HOOKFILE\" <<\\EOF\n#! /usr/bin/env bash\n\ngsettings get org.gnome.desktop.interface gtk-theme 2> /dev/null | grep -qi \"dark\" && GTK_THEME_VARIANT=\"dark\" || GTK_THEME_VARIANT=\"light\"\nAPPIMAGE_GTK_THEME=\"${APPIMAGE_GTK_THEME:-\"Adwaita:$GTK_THEME_VARIANT\"}\" # Allow user to override theme (discouraged)\n\nexport APPDIR=\"${APPDIR:-\"$(dirname \"$(realpath \"$0\")\")\"}\" # Workaround to run extracted AppImage\nexport GTK_DATA_PREFIX=\"$APPDIR\"\nexport GTK_THEME=\"$APPIMAGE_GTK_THEME\" # Custom themes are broken\nexport GDK_BACKEND=x11 # Crash with Wayland backend on Wayland - We tested it without it and ended up with this: https://github.com/tauri-apps/tauri/issues/8541\nexport XDG_DATA_DIRS=\"$APPDIR/usr/share:/usr/share:$XDG_DATA_DIRS\" # g_get_system_data_dirs() from GLib\nEOF\n\necho \"Installing GLib schemas\"\n# Note: schemasdir is undefined on Ubuntu 16.04\nglib_schemasdir=\"$(get_pkgconf_variable \"schemasdir\" \"gio-2.0\" \"/usr/share/glib-2.0/schemas\")\"\ncopy_tree \"$glib_schemasdir\" \"$APPDIR/\"\nglib-compile-schemas \"$APPDIR/$glib_schemasdir\"\ncat >> \"$HOOKFILE\" <<EOF\nexport GSETTINGS_SCHEMA_DIR=\"\\$APPDIR/$glib_schemasdir\"\nEOF\n\ncase \"$DEPLOY_GTK_VERSION\" in\n    2)\n        # https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/pull/20#issuecomment-826354261\n        echo \"WARNING: Gtk+2 applications are not fully supported by this plugin\"\n        ;;\n    3)\n        echo \"Installing GTK 3.0 modules\"\n        gtk3_exec_prefix=\"$(get_pkgconf_variable \"exec_prefix\" \"gtk+-3.0\")\"\n        gtk3_libdir=\"$(get_pkgconf_variable \"libdir\" \"gtk+-3.0\")/gtk-3.0\"\n        #gtk3_path=\"$gtk3_libdir/modules\" export GTK_PATH=\"\\$APPDIR/$gtk3_path\"\n        gtk3_immodulesdir=\"$gtk3_libdir/$(get_pkgconf_variable \"gtk_binary_version\" \"gtk+-3.0\")/immodules\"\n        gtk3_printbackendsdir=\"$gtk3_libdir/$(get_pkgconf_variable \"gtk_binary_version\" \"gtk+-3.0\")/printbackends\"\n        gtk3_immodules_cache_file=\"$(dirname \"$gtk3_immodulesdir\")/immodules.cache\"\n        gtk3_immodules_query=\"$(search_tool \"gtk-query-immodules-3.0\" \"libgtk-3-0\")\"\n        copy_tree \"$gtk3_libdir\" \"$APPDIR/\"\n        cat >> \"$HOOKFILE\" <<EOF\nexport GTK_EXE_PREFIX=\"\\$APPDIR/$gtk3_exec_prefix\"\nexport GTK_PATH=\"\\$APPDIR/$gtk3_libdir:/usr/lib64/gtk-3.0:/usr/lib/x86_64-linux-gnu/gtk-3.0\"\nexport GTK_IM_MODULE_FILE=\"\\$APPDIR/$gtk3_immodules_cache_file\"\n\nEOF\n        if [ -x \"$gtk3_immodules_query\" ]; then\n            echo \"Updating immodules cache in $APPDIR/$gtk3_immodules_cache_file\"\n            \"$gtk3_immodules_query\" > \"$APPDIR/$gtk3_immodules_cache_file\"\n        else\n            echo \"WARNING: gtk-query-immodules-3.0 not found\"\n        fi\n        if [ ! -f \"$APPDIR/$gtk3_immodules_cache_file\" ]; then\n            echo \"WARNING: immodules.cache file is missing\"\n        fi\n        sed -i \"s|$gtk3_libdir/3.0.0/immodules/||g\" \"$APPDIR/$gtk3_immodules_cache_file\"\n        ;;\n    4)\n        echo \"Installing GTK 4.0 modules\"\n        gtk4_exec_prefix=\"$(get_pkgconf_variable \"exec_prefix\" \"gtk4\" \"/usr\")\"\n        gtk4_libdir=\"$(get_pkgconf_variable \"libdir\" \"gtk4\")/gtk-4.0\"\n        gtk4_path=\"$gtk4_libdir/modules\"\n        copy_tree \"$gtk4_libdir\" \"$APPDIR/\"\n        cat >> \"$HOOKFILE\" <<EOF\nexport GTK_EXE_PREFIX=\"\\$APPDIR/$gtk4_exec_prefix\"\nexport GTK_PATH=\"\\$APPDIR/$gtk4_path\"\nEOF\n        ;;\n    *)\n        echo \"$0: '$DEPLOY_GTK_VERSION' is not a valid GTK major version.\"\n        echo \"Please set DEPLOY_GTK_VERSION to {2, 3, 4}.\"\n        exit 1\nesac\n\necho \"Installing GDK PixBufs\"\ngdk_libdir=\"$(get_pkgconf_variable \"libdir\" \"gdk-pixbuf-2.0\")\"\ngdk_pixbuf_binarydir=\"$(get_pkgconf_variable \"gdk_pixbuf_binarydir\" \"gdk-pixbuf-2.0\")\"\ngdk_pixbuf_cache_file=\"$(get_pkgconf_variable \"gdk_pixbuf_cache_file\" \"gdk-pixbuf-2.0\")\"\ngdk_pixbuf_moduledir=\"$(get_pkgconf_variable \"gdk_pixbuf_moduledir\" \"gdk-pixbuf-2.0\")\"\n# Note: gdk_pixbuf_query_loaders variable is not defined on some systems\ngdk_pixbuf_query=\"$(search_tool \"gdk-pixbuf-query-loaders\" \"gdk-pixbuf-2.0\")\"\ncopy_tree \"$gdk_pixbuf_binarydir\" \"$APPDIR/\"\ncat >> \"$HOOKFILE\" <<EOF\nexport GDK_PIXBUF_MODULE_FILE=\"\\$APPDIR/$gdk_pixbuf_cache_file\"\nEOF\nif [ -x \"$gdk_pixbuf_query\" ]; then\n    echo \"Updating pixbuf cache in $APPDIR/$gdk_pixbuf_cache_file\"\n    \"$gdk_pixbuf_query\" > \"$APPDIR/$gdk_pixbuf_cache_file\"\nelse\n    echo \"WARNING: gdk-pixbuf-query-loaders not found\"\nfi\nif [ ! -f \"$APPDIR/$gdk_pixbuf_cache_file\" ]; then\n    echo \"WARNING: loaders.cache file is missing\"\nfi\nsed -i \"s|$gdk_pixbuf_moduledir/||g\" \"$APPDIR/$gdk_pixbuf_cache_file\"\n\necho \"Copying more libraries\"\ngobject_libdir=\"$(get_pkgconf_variable \"libdir\" \"gobject-2.0\")\"\ngio_libdir=\"$(get_pkgconf_variable \"libdir\" \"gio-2.0\")\"\nlibrsvg_libdir=\"$(get_pkgconf_variable \"libdir\" \"librsvg-2.0\")\"\npango_libdir=\"$(get_pkgconf_variable \"libdir\" \"pango\")\"\npangocairo_libdir=\"$(get_pkgconf_variable \"libdir\" \"pangocairo\")\"\npangoft2_libdir=\"$(get_pkgconf_variable \"libdir\" \"pangoft2\")\"\nFIND_ARRAY=(\n    \"$gdk_libdir\"     \"libgdk_pixbuf-*.so*\"\n    \"$gobject_libdir\" \"libgobject-*.so*\"\n    \"$gio_libdir\"     \"libgio-*.so*\"\n    \"$librsvg_libdir\" \"librsvg-*.so*\"\n    \"$pango_libdir\"      \"libpango-*.so*\"\n    \"$pangocairo_libdir\" \"libpangocairo-*.so*\"\n    \"$pangoft2_libdir\"   \"libpangoft2-*.so*\"\n)\nLIBRARIES=()\nfor (( i=0; i<${#FIND_ARRAY[@]}; i+=2 )); do\n    directory=${FIND_ARRAY[i]}\n    library=${FIND_ARRAY[i+1]}\n    while IFS= read -r -d '' file; do\n        LIBRARIES+=( \"--library=$file\" )\n    done < <(find \"$directory\" \\( -type l -o -type f \\) -name \"$library\" -print0)\ndone\n\nenv LINUXDEPLOY_PLUGIN_MODE=1 \"$LINUXDEPLOY\" --appdir=\"$APPDIR\" \"${LIBRARIES[@]}\"\n\n# Create symbolic links as a workaround\n# Details: https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/issues/24#issuecomment-1030026529\necho \"Manually setting rpath for GTK modules\"\nPATCH_ARRAY=(\n    \"$gtk3_immodulesdir\"\n    \"$gtk3_printbackendsdir\"\n    \"$gdk_pixbuf_moduledir\"\n)\nfor directory in \"${PATCH_ARRAY[@]}\"; do\n    while IFS= read -r -d '' file; do\n        ln $verbose -s \"${file/\\/usr\\/lib\\//}\" \"$APPDIR/usr/lib\"\n    done < <(find \"$directory\" -name '*.so' -print0)\ndone\n\n# set write permission on lib64 again to make it deletable.\nchmod +w \"$APPDIR\"/usr/lib64 || true\n\n# We have to copy the files first to not get permission errors when we assign gio_extras_dir\nfind /usr/lib* -name libgiognutls.so -exec mkdir -p \"$APPDIR\"/\"$(dirname '{}')\" \\; -exec cp --parents '{}' \"$APPDIR/\" \\; || true\n# related files that we seemingly don't need:\n# libgiolibproxy.so - libgiognomeproxy.so - glib-pacrunner\n\ngio_extras_dir=$(find \"$APPDIR\"/usr/lib* -name libgiognutls.so -exec dirname '{}' \\; 2>/dev/null)\ncat >> \"$HOOKFILE\" <<EOF\nexport GIO_EXTRA_MODULES=\"\\$APPDIR/${gio_extras_dir#\"$APPDIR\"/}\"\nEOF\n\n#binary patch absolute paths in libwebkit files\nfind \"$APPDIR\"/usr/lib* -name 'libwebkit*' -exec sed -i -e \"s|/usr|././|g\" '{}' \\;\n\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/linux/appimage/linuxdeploy.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{super::debian, write_and_make_executable};\nuse crate::{\n  bundle::settings::Arch,\n  error::{Context, ErrorExt},\n  utils::{fs_utils, http_utils::download, CommandExt},\n  Settings,\n};\nuse std::{\n  fs,\n  path::{Path, PathBuf},\n  process::Command,\n};\n\n/// Bundles the project.\n/// Returns a vector of PathBuf that shows where the AppImage was created.\npub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {\n  // generate the deb binary name\n  let appimage_arch: &str = match settings.binary_arch() {\n    Arch::X86_64 => \"amd64\",\n    Arch::X86 => \"i386\",\n    Arch::AArch64 => \"aarch64\",\n    Arch::Armhf => \"armhf\",\n    target => {\n      return Err(crate::Error::ArchError(format!(\n        \"Unsupported architecture: {target:?}\"\n      )));\n    }\n  };\n\n  let tools_arch = if settings.binary_arch() == Arch::Armhf {\n    \"armhf\"\n  } else {\n    settings.target().split('-').next().unwrap()\n  };\n\n  let output_path = settings.project_out_directory().join(\"bundle/appimage\");\n  if output_path.exists() {\n    fs::remove_dir_all(&output_path)?;\n  }\n\n  let tools_path = settings\n    .local_tools_directory()\n    .map(|d| d.join(\".tauri\"))\n    .unwrap_or_else(|| {\n      dirs::cache_dir().map_or_else(|| output_path.to_path_buf(), |p| p.join(\"tauri\"))\n    });\n\n  fs::create_dir_all(&tools_path)?;\n\n  let linuxdeploy_path = prepare_tools(\n    &tools_path,\n    tools_arch,\n    settings.log_level() != log::Level::Error,\n  )?;\n\n  let package_dir = settings.project_out_directory().join(\"bundle/appimage_deb\");\n\n  let main_binary = settings.main_binary()?;\n  let product_name = settings.product_name();\n\n  let mut settings = settings.clone();\n  if main_binary.name().contains(' ') {\n    let main_binary_path = settings.binary_path(main_binary);\n    let project_out_directory = settings.project_out_directory();\n\n    let main_binary_name_kebab = heck::AsKebabCase(main_binary.name()).to_string();\n    let new_path = project_out_directory.join(&main_binary_name_kebab);\n    fs::copy(main_binary_path, new_path)?;\n\n    let main_binary = settings.main_binary_mut()?;\n    main_binary.set_name(main_binary_name_kebab);\n  }\n\n  // generate deb_folder structure\n  let (data_dir, icons) = debian::generate_data(&settings, &package_dir)\n    .with_context(|| \"Failed to build data folders and files\")?;\n  fs_utils::copy_custom_files(&settings.appimage().files, &data_dir)\n    .with_context(|| \"Failed to copy custom files\")?;\n\n  fs::create_dir_all(&output_path)?;\n  let app_dir_path = output_path.join(format!(\"{}.AppDir\", settings.product_name()));\n  let appimage_filename = format!(\n    \"{}_{}_{}.AppImage\",\n    settings.product_name(),\n    settings.version_string(),\n    appimage_arch\n  );\n  let appimage_path = output_path.join(&appimage_filename);\n  fs_utils::create_dir(&app_dir_path, true)?;\n\n  fs::create_dir_all(&tools_path)?;\n  let larger_icon = icons\n    .iter()\n    .filter(|i| i.width == i.height)\n    .max_by_key(|i| i.width)\n    .expect(\"couldn't find a square icon to use as AppImage icon\");\n  let larger_icon_path = larger_icon\n    .path\n    .strip_prefix(package_dir.join(\"data\"))\n    .unwrap()\n    .to_string_lossy()\n    .to_string();\n\n  log::info!(action = \"Bundling\"; \"{} ({})\", appimage_filename, appimage_path.display());\n\n  let app_dir_usr = app_dir_path.join(\"usr/\");\n  let app_dir_usr_bin = app_dir_usr.join(\"bin/\");\n  let app_dir_usr_lib = app_dir_usr.join(\"lib/\");\n\n  fs_utils::copy_dir(&data_dir.join(\"usr/\"), &app_dir_usr)?;\n\n  // Using create_dir_all for a single dir so we don't get errors if the path already exists\n  fs::create_dir_all(&app_dir_usr_bin)?;\n  fs::create_dir_all(app_dir_usr_lib)?;\n\n  // Copy bins and libs that linuxdeploy doesn't know about\n\n  // we also check if the user may have provided their own copy already\n  // xdg-open will be handled by the `files` config instead\n  if settings.deep_link_protocols().is_some() && !app_dir_usr_bin.join(\"xdg-open\").exists() {\n    fs::copy(\"/usr/bin/xdg-mime\", app_dir_usr_bin.join(\"xdg-mime\"))\n      .fs_context(\"xdg-mime binary not found\", \"/usr/bin/xdg-mime\".to_string())?;\n  }\n\n  // we also check if the user may have provided their own copy already\n  if settings.appimage().bundle_xdg_open && !app_dir_usr_bin.join(\"xdg-open\").exists() {\n    fs::copy(\"/usr/bin/xdg-open\", app_dir_usr_bin.join(\"xdg-open\"))\n      .fs_context(\"xdg-open binary not found\", \"/usr/bin/xdg-open\".to_string())?;\n  }\n\n  let search_dirs = [\n    match settings.binary_arch() {\n      Arch::X86_64 => \"/usr/lib/x86_64-linux-gnu/\",\n      Arch::X86 => \"/usr/lib/i386-linux-gnu/\",\n      Arch::AArch64 => \"/usr/lib/aarch64-linux-gnu/\",\n      Arch::Armhf => \"/usr/lib/arm-linux-gnueabihf/\",\n      _ => unreachable!(),\n    },\n    \"/usr/lib64\",\n    \"/usr/lib\",\n    \"/usr/libexec\",\n  ];\n\n  for file in [\n    \"WebKitNetworkProcess\",\n    \"WebKitWebProcess\",\n    \"injected-bundle/libwebkit2gtkinjectedbundle.so\",\n  ] {\n    for source in search_dirs.map(PathBuf::from) {\n      // TODO: Check if it's the same dir name on all systems\n      let source = source.join(\"webkit2gtk-4.1\").join(file);\n      if source.exists() {\n        fs_utils::copy_file(\n          &source,\n          &app_dir_path.join(source.strip_prefix(\"/\").unwrap()),\n        )?;\n      }\n    }\n  }\n\n  fs::copy(\n    tools_path.join(format!(\"AppRun-{tools_arch}\")),\n    app_dir_path.join(\"AppRun\"),\n  )?;\n  fs::copy(\n    app_dir_path.join(larger_icon_path),\n    app_dir_path.join(format!(\"{product_name}.png\")),\n  )?;\n  std::os::unix::fs::symlink(\n    app_dir_path.join(format!(\"{product_name}.png\")),\n    app_dir_path.join(\".DirIcon\"),\n  )?;\n  std::os::unix::fs::symlink(\n    app_dir_path.join(format!(\"usr/share/applications/{product_name}.desktop\")),\n    app_dir_path.join(format!(\"{product_name}.desktop\")),\n  )?;\n\n  let log_level = match settings.log_level() {\n    log::Level::Error => \"3\",\n    log::Level::Warn => \"2\",\n    log::Level::Info => \"1\",\n    _ => \"0\",\n  };\n\n  let mut cmd = Command::new(linuxdeploy_path);\n  cmd.env(\"OUTPUT\", &appimage_path);\n  cmd.env(\"ARCH\", tools_arch);\n  // Looks like the cli arg isn't enough for the updated AppImage output-plugin.\n  cmd.env(\"APPIMAGE_EXTRACT_AND_RUN\", \"1\");\n  cmd.args([\n    \"--appimage-extract-and-run\",\n    \"--verbosity\",\n    log_level,\n    \"--appdir\",\n    &app_dir_path.display().to_string(),\n    \"--plugin\",\n    \"gtk\",\n  ]);\n  if settings.appimage().bundle_media_framework {\n    cmd.args([\"--plugin\", \"gstreamer\"]);\n  }\n  cmd.args([\"--output\", \"appimage\"]);\n\n  // Linuxdeploy logs everything into stderr so we have to ignore the output ourselves here\n  if settings.log_level() == log::Level::Error {\n    log::debug!(action = \"Running\"; \"Command `linuxdeploy {}`\", cmd.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!(\"{acc} {arg}\")));\n    if !cmd.output()?.status.success() {\n      return Err(crate::Error::GenericError(\n        \"failed to run linuxdeploy\".to_string(),\n      ));\n    }\n  } else {\n    cmd.output_ok()?;\n  }\n\n  fs::remove_dir_all(&package_dir)?;\n  Ok(vec![appimage_path])\n}\n\n// returns the linuxdeploy path to keep linuxdeploy_arch contained\nfn prepare_tools(tools_path: &Path, arch: &str, verbose: bool) -> crate::Result<PathBuf> {\n  let apprun = tools_path.join(format!(\"AppRun-{arch}\"));\n  if !apprun.exists() {\n    let data = download(&format!(\n      \"https://github.com/tauri-apps/binary-releases/releases/download/apprun-old/AppRun-{arch}\"\n    ))?;\n    write_and_make_executable(&apprun, data)?;\n  }\n\n  let linuxdeploy_arch = if arch == \"i686\" { \"i386\" } else { arch };\n  let linuxdeploy = tools_path.join(format!(\"linuxdeploy-{linuxdeploy_arch}.AppImage\"));\n  if !linuxdeploy.exists() {\n    let data = download(&format!(\"https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy/linuxdeploy-{linuxdeploy_arch}.AppImage\"))?;\n    write_and_make_executable(&linuxdeploy, data)?;\n  }\n\n  let gtk = tools_path.join(\"linuxdeploy-plugin-gtk.sh\");\n  if !gtk.exists() {\n    let data = download(\"https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh\")?;\n    write_and_make_executable(&gtk, data)?;\n  }\n\n  let gstreamer = tools_path.join(\"linuxdeploy-plugin-gstreamer.sh\");\n  if !gstreamer.exists() {\n    let data = download(\"https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gstreamer/master/linuxdeploy-plugin-gstreamer.sh\")?;\n    write_and_make_executable(&gstreamer, data)?;\n  }\n\n  let appimage = tools_path.join(\"linuxdeploy-plugin-appimage.AppImage\");\n  if !appimage.exists() {\n    // This is optional, linuxdeploy will fall back to its built-in version if the download failed.\n    let data = download(&format!(\"https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-{arch}.AppImage\"));\n    match data {\n      Ok(data) => write_and_make_executable(&appimage, data)?,\n      Err(err) => {\n        log::error!(\"Download of AppImage plugin failed. Using older built-in version instead.\");\n        if verbose {\n          log::debug!(\"{err:?}\");\n        }\n      }\n    }\n  }\n\n  // This should prevent linuxdeploy to be detected by appimage integration tools\n  let _ = Command::new(\"dd\")\n    .args([\n      \"if=/dev/zero\",\n      \"bs=1\",\n      \"count=3\",\n      \"seek=8\",\n      \"conv=notrunc\",\n      &format!(\"of={}\", linuxdeploy.display()),\n    ])\n    .output();\n\n  Ok(linuxdeploy)\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/linux/appimage/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  fs,\n  path::{Path, PathBuf},\n};\n\nuse crate::Settings;\n\nmod linuxdeploy;\n\npub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {\n  linuxdeploy::bundle_project(settings)\n}\n\nfn write_and_make_executable(path: &Path, data: Vec<u8>) -> std::io::Result<()> {\n  use std::os::unix::fs::PermissionsExt;\n\n  fs::write(path, data)?;\n  fs::set_permissions(path, fs::Permissions::from_mode(0o770))?;\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/linux/debian.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// The structure of a Debian package looks something like this:\n//\n// foobar_1.2.3_i386.deb   # Actually an ar archive\n//     debian-binary           # Specifies deb format version (2.0 in our case)\n//     control.tar.gz          # Contains files controlling the installation:\n//         control                  # Basic package metadata\n//         md5sums                  # Checksums for files in data.tar.gz below\n//         postinst                 # Post-installation script (optional)\n//         prerm                    # Pre-uninstallation script (optional)\n//     data.tar.gz             # Contains files to be installed:\n//         usr/bin/foobar                            # Binary executable file\n//         usr/share/applications/foobar.desktop     # Desktop file (for apps)\n//         usr/share/icons/hicolor/...               # Icon files (for apps)\n//         usr/lib/foobar/...                        # Other resource files\n//\n// For cargo-bundle, we put bundle resource files under /usr/lib/package_name/,\n// and then generate the desktop file and control file from the bundle\n// metadata, as well as generating the md5sums file.  Currently we do not\n// generate postinst or prerm files.\n\nuse super::freedesktop;\nuse crate::{\n  bundle::settings::Arch,\n  error::{Context, ErrorExt},\n  utils::fs_utils,\n  Settings,\n};\nuse flate2::{write::GzEncoder, Compression};\nuse tar::HeaderMode;\nuse walkdir::WalkDir;\n\nuse std::{\n  fs::{self, File, OpenOptions},\n  io::{self, Write},\n  os::unix::fs::{MetadataExt, OpenOptionsExt},\n  path::{Path, PathBuf},\n};\n\n/// Bundles the project.\n/// Returns a vector of PathBuf that shows where the DEB was created.\npub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {\n  let arch = match settings.binary_arch() {\n    Arch::X86_64 => \"amd64\",\n    Arch::X86 => \"i386\",\n    Arch::AArch64 => \"arm64\",\n    Arch::Armhf => \"armhf\",\n    Arch::Armel => \"armel\",\n    Arch::Riscv64 => \"riscv64\",\n    target => {\n      return Err(crate::Error::ArchError(format!(\n        \"Unsupported architecture: {target:?}\"\n      )));\n    }\n  };\n  let package_base_name = format!(\n    \"{}_{}_{}\",\n    settings.product_name(),\n    settings.version_string(),\n    arch\n  );\n  let package_name = format!(\"{package_base_name}.deb\");\n\n  let base_dir = settings.project_out_directory().join(\"bundle/deb\");\n  let package_dir = base_dir.join(&package_base_name);\n  if package_dir.exists() {\n    fs::remove_dir_all(&package_dir).fs_context(\n      \"Failed to Remove old package directory\",\n      package_dir.clone(),\n    )?;\n  }\n  let package_path = base_dir.join(&package_name);\n\n  log::info!(action = \"Bundling\"; \"{} ({})\", package_name, package_path.display());\n\n  let (data_dir, _) =\n    generate_data(settings, &package_dir).context(\"Failed to build data folders and files\")?;\n  fs_utils::copy_custom_files(&settings.deb().files, &data_dir)\n    .context(\"Failed to copy custom files\")?;\n\n  // Generate control files.\n  let control_dir = package_dir.join(\"control\");\n  generate_control_file(settings, arch, &control_dir, &data_dir)\n    .context(\"Failed to create control file\")?;\n  generate_scripts(settings, &control_dir).context(\"Failed to create control scripts\")?;\n  generate_md5sums(&control_dir, &data_dir).context(\"Failed to create md5sums file\")?;\n\n  // Generate `debian-binary` file; see\n  // http://www.tldp.org/HOWTO/Debian-Binary-Package-Building-HOWTO/x60.html#AEN66\n  let debian_binary_path = package_dir.join(\"debian-binary\");\n  create_file_with_data(&debian_binary_path, \"2.0\\n\")\n    .context(\"Failed to create debian-binary file\")?;\n\n  // Apply tar/gzip/ar to create the final package file.\n  let control_tar_gz_path =\n    tar_and_gzip_dir(control_dir).with_context(|| \"Failed to tar/gzip control directory\")?;\n  let data_tar_gz_path =\n    tar_and_gzip_dir(data_dir).with_context(|| \"Failed to tar/gzip data directory\")?;\n  create_archive(\n    vec![debian_binary_path, control_tar_gz_path, data_tar_gz_path],\n    &package_path,\n  )\n  .with_context(|| \"Failed to create package archive\")?;\n  Ok(vec![package_path])\n}\n\n/// Generate the debian data folders and files.\npub fn generate_data(\n  settings: &Settings,\n  package_dir: &Path,\n) -> crate::Result<(PathBuf, Vec<freedesktop::Icon>)> {\n  // Generate data files.\n  let data_dir = package_dir.join(\"data\");\n  let bin_dir = data_dir.join(\"usr/bin\");\n\n  for bin in settings.binaries() {\n    let bin_path = settings.binary_path(bin);\n    let trgt = bin_dir.join(bin.name());\n    fs_utils::copy_file(&bin_path, &trgt)\n      .with_context(|| format!(\"Failed to copy binary from {bin_path:?} to {trgt:?}\"))?;\n  }\n\n  copy_resource_files(settings, &data_dir).with_context(|| \"Failed to copy resource files\")?;\n\n  settings\n    .copy_binaries(&bin_dir)\n    .with_context(|| \"Failed to copy external binaries\")?;\n\n  let icons = freedesktop::copy_icon_files(settings, &data_dir)\n    .with_context(|| \"Failed to create icon files\")?;\n  freedesktop::generate_desktop_file(settings, &settings.deb().desktop_template, &data_dir)\n    .with_context(|| \"Failed to create desktop file\")?;\n  generate_changelog_file(settings, &data_dir)\n    .with_context(|| \"Failed to create changelog.gz file\")?;\n\n  Ok((data_dir, icons))\n}\n\n/// Generate the Changelog file by compressing, to be stored at /usr/share/doc/package-name/changelog.gz. See\n/// <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>\nfn generate_changelog_file(settings: &Settings, data_dir: &Path) -> crate::Result<()> {\n  if let Some(changelog_src_path) = &settings.deb().changelog {\n    let mut src_file = File::open(changelog_src_path)?;\n    let product_name = settings.product_name();\n    let dest_path = data_dir.join(format!(\"usr/share/doc/{product_name}/changelog.gz\"));\n\n    let changelog_file = fs_utils::create_file(&dest_path)?;\n    let mut gzip_encoder = GzEncoder::new(changelog_file, Compression::new(9));\n    io::copy(&mut src_file, &mut gzip_encoder)?;\n\n    let mut changelog_file = gzip_encoder.finish()?;\n    changelog_file.flush()?;\n  }\n  Ok(())\n}\n\n/// Generates the debian control file and stores it under the `control_dir`.\nfn generate_control_file(\n  settings: &Settings,\n  arch: &str,\n  control_dir: &Path,\n  data_dir: &Path,\n) -> crate::Result<()> {\n  // For more information about the format of this file, see\n  // https://www.debian.org/doc/debian-policy/ch-controlfields.html\n  let dest_path = control_dir.join(\"control\");\n  let mut file = fs_utils::create_file(&dest_path)?;\n  let package = heck::AsKebabCase(settings.product_name());\n  writeln!(file, \"Package: {package}\")?;\n  writeln!(file, \"Version: {}\", settings.version_string())?;\n  writeln!(file, \"Architecture: {arch}\")?;\n  // Installed-Size must be divided by 1024, see https://www.debian.org/doc/debian-policy/ch-controlfields.html#installed-size\n  writeln!(file, \"Installed-Size: {}\", total_dir_size(data_dir)? / 1024)?;\n  let authors = settings\n    .authors_comma_separated()\n    .or_else(|| settings.publisher().map(ToString::to_string))\n    .unwrap_or_else(|| {\n      settings\n        .bundle_identifier()\n        .split('.')\n        .nth(1)\n        .unwrap_or(settings.bundle_identifier())\n        .to_string()\n    });\n\n  writeln!(file, \"Maintainer: {authors}\")?;\n  if let Some(section) = &settings.deb().section {\n    writeln!(file, \"Section: {section}\")?;\n  }\n  if let Some(priority) = &settings.deb().priority {\n    writeln!(file, \"Priority: {priority}\")?;\n  } else {\n    writeln!(file, \"Priority: optional\")?;\n  }\n\n  if let Some(homepage) = settings.homepage_url() {\n    writeln!(file, \"Homepage: {homepage}\")?;\n  }\n\n  let dependencies = settings.deb().depends.as_ref().cloned().unwrap_or_default();\n  if !dependencies.is_empty() {\n    writeln!(file, \"Depends: {}\", dependencies.join(\", \"))?;\n  }\n  let dependencies = settings\n    .deb()\n    .recommends\n    .as_ref()\n    .cloned()\n    .unwrap_or_default();\n  if !dependencies.is_empty() {\n    writeln!(file, \"Recommends: {}\", dependencies.join(\", \"))?;\n  }\n  let provides = settings\n    .deb()\n    .provides\n    .as_ref()\n    .cloned()\n    .unwrap_or_default();\n  if !provides.is_empty() {\n    writeln!(file, \"Provides: {}\", provides.join(\", \"))?;\n  }\n  let conflicts = settings\n    .deb()\n    .conflicts\n    .as_ref()\n    .cloned()\n    .unwrap_or_default();\n  if !conflicts.is_empty() {\n    writeln!(file, \"Conflicts: {}\", conflicts.join(\", \"))?;\n  }\n  let replaces = settings\n    .deb()\n    .replaces\n    .as_ref()\n    .cloned()\n    .unwrap_or_default();\n  if !replaces.is_empty() {\n    writeln!(file, \"Replaces: {}\", replaces.join(\", \"))?;\n  }\n  let mut short_description = settings.short_description().trim();\n  if short_description.is_empty() {\n    short_description = \"(none)\";\n  }\n  let mut long_description = settings.long_description().unwrap_or(\"\").trim();\n  if long_description.is_empty() {\n    long_description = \"(none)\";\n  }\n  writeln!(file, \"Description: {short_description}\")?;\n  for line in long_description.lines() {\n    let line = line.trim();\n    if line.is_empty() {\n      writeln!(file, \" .\")?;\n    } else {\n      writeln!(file, \" {line}\")?;\n    }\n  }\n  file.flush()?;\n  Ok(())\n}\n\nfn generate_scripts(settings: &Settings, control_dir: &Path) -> crate::Result<()> {\n  if let Some(script_path) = &settings.deb().pre_install_script {\n    let dest_path = control_dir.join(\"preinst\");\n    create_script_file_from_path(script_path, &dest_path)?\n  }\n\n  if let Some(script_path) = &settings.deb().post_install_script {\n    let dest_path = control_dir.join(\"postinst\");\n    create_script_file_from_path(script_path, &dest_path)?\n  }\n\n  if let Some(script_path) = &settings.deb().pre_remove_script {\n    let dest_path = control_dir.join(\"prerm\");\n    create_script_file_from_path(script_path, &dest_path)?\n  }\n\n  if let Some(script_path) = &settings.deb().post_remove_script {\n    let dest_path = control_dir.join(\"postrm\");\n    create_script_file_from_path(script_path, &dest_path)?\n  }\n  Ok(())\n}\n\nfn create_script_file_from_path(from: &PathBuf, to: &PathBuf) -> crate::Result<()> {\n  let mut from = File::open(from)?;\n  let mut file = OpenOptions::new()\n    .create(true)\n    .truncate(true)\n    .write(true)\n    .mode(0o755)\n    .open(to)?;\n  std::io::copy(&mut from, &mut file)?;\n  Ok(())\n}\n\n/// Create an `md5sums` file in the `control_dir` containing the MD5 checksums\n/// for each file within the `data_dir`.\nfn generate_md5sums(control_dir: &Path, data_dir: &Path) -> crate::Result<()> {\n  let md5sums_path = control_dir.join(\"md5sums\");\n  let mut md5sums_file = fs_utils::create_file(&md5sums_path)?;\n  for entry in WalkDir::new(data_dir) {\n    let entry = entry?;\n    let path = entry.path();\n    if path.is_dir() {\n      continue;\n    }\n    let mut file = File::open(path)?;\n    let mut hash = md5::Context::new();\n    io::copy(&mut file, &mut hash)?;\n    for byte in hash.finalize().iter() {\n      write!(md5sums_file, \"{byte:02x}\")?;\n    }\n    let rel_path = path.strip_prefix(data_dir)?;\n    let path_str = rel_path.to_str().ok_or_else(|| {\n      let msg = format!(\"Non-UTF-8 path: {rel_path:?}\");\n      io::Error::new(io::ErrorKind::InvalidData, msg)\n    })?;\n    writeln!(md5sums_file, \"  {path_str}\")?;\n  }\n  Ok(())\n}\n\n/// Copy the bundle's resource files into an appropriate directory under the\n/// `data_dir`.\nfn copy_resource_files(settings: &Settings, data_dir: &Path) -> crate::Result<()> {\n  let resource_dir = data_dir.join(\"usr/lib\").join(settings.product_name());\n  settings.copy_resources(&resource_dir)\n}\n\n/// Create an empty file at the given path, creating any parent directories as\n/// needed, then write `data` into the file.\nfn create_file_with_data<P: AsRef<Path>>(path: P, data: &str) -> crate::Result<()> {\n  let mut file = fs_utils::create_file(path.as_ref())?;\n  file.write_all(data.as_bytes())?;\n  file.flush()?;\n  Ok(())\n}\n\n/// Computes the total size, in bytes, of the given directory and all of its\n/// contents.\nfn total_dir_size(dir: &Path) -> crate::Result<u64> {\n  let mut total: u64 = 0;\n  for entry in WalkDir::new(dir) {\n    total += entry?.metadata()?.len();\n  }\n  Ok(total)\n}\n\n/// Writes a tar file to the given writer containing the given directory.\nfn create_tar_from_dir<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> crate::Result<W> {\n  let src_dir = src_dir.as_ref();\n  let mut tar_builder = tar::Builder::new(dest_file);\n  for entry in WalkDir::new(src_dir) {\n    let entry = entry?;\n    let src_path = entry.path();\n    if src_path == src_dir {\n      continue;\n    }\n    let dest_path = src_path.strip_prefix(src_dir)?;\n    let stat = fs::metadata(src_path)?;\n    let mut header = tar::Header::new_gnu();\n    header.set_metadata_in_mode(&stat, HeaderMode::Deterministic);\n    header.set_mtime(stat.mtime() as u64);\n\n    if entry.file_type().is_dir() {\n      tar_builder.append_data(&mut header, dest_path, &mut io::empty())?;\n    } else {\n      let mut src_file = fs::File::open(src_path)?;\n      tar_builder.append_data(&mut header, dest_path, &mut src_file)?;\n    }\n  }\n  let dest_file = tar_builder.into_inner()?;\n  Ok(dest_file)\n}\n\n/// Creates a `.tar.gz` file from the given directory (placing the new file\n/// within the given directory's parent directory), then deletes the original\n/// directory and returns the path to the new file.\nfn tar_and_gzip_dir<P: AsRef<Path>>(src_dir: P) -> crate::Result<PathBuf> {\n  let src_dir = src_dir.as_ref();\n  let dest_path = src_dir.with_extension(\"tar.gz\");\n  let dest_file = fs_utils::create_file(&dest_path)?;\n  let gzip_encoder = GzEncoder::new(dest_file, Compression::default());\n  let gzip_encoder = create_tar_from_dir(src_dir, gzip_encoder)?;\n  let mut dest_file = gzip_encoder.finish()?;\n  dest_file.flush()?;\n  Ok(dest_path)\n}\n\n/// Creates an `ar` archive from the given source files and writes it to the\n/// given destination path.\nfn create_archive(srcs: Vec<PathBuf>, dest: &Path) -> crate::Result<()> {\n  let mut builder = ar::Builder::new(fs_utils::create_file(dest)?);\n  for path in &srcs {\n    builder.append_path(path)?;\n  }\n  builder.into_inner()?.flush()?;\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/linux/freedesktop/main.desktop",
    "content": "[Desktop Entry]\nCategories={{categories}}\n{{#if comment}}\nComment={{comment}}\n{{/if}}\nExec={{exec}}\nStartupWMClass={{exec}}\nIcon={{icon}}\nName={{name}}\nTerminal=false\nType=Application\n{{#if mime_type}}\nMimeType={{mime_type}}\n{{/if}}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/linux/freedesktop/mod.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! This module provides utilities helping the packaging of desktop\n//! applications for Linux:\n//!\n//! - Generation of [desktop entries] (`.desktop` files)\n//! - Copy of icons in the [icons file hierarchy]\n//!\n//! The specifications are developed and hosted at [freedesktop.org].\n//!\n//! [freedesktop.org]: https://www.freedesktop.org\n//! [desktop entries]: https://www.freedesktop.org/wiki/Specifications/desktop-entry-spec/\n//! [icons file hierarchy]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#icon_lookup\n\nuse std::collections::BTreeMap;\nuse std::ffi::OsStr;\nuse std::fs::{read_to_string, File};\nuse std::io::BufReader;\nuse std::path::{Path, PathBuf};\n\nuse handlebars::Handlebars;\nuse image::{self, codecs::png::PngDecoder, ImageDecoder};\nuse serde::Serialize;\n\nuse crate::{\n  error::Context,\n  utils::{self, fs_utils},\n  Settings,\n};\n\n#[derive(PartialEq, Eq, PartialOrd, Ord)]\npub struct Icon {\n  pub width: u32,\n  pub height: u32,\n  pub is_high_density: bool,\n  pub path: PathBuf,\n}\n\n/// Generate the icon files, and returns a map where keys are the icons and\n/// values are their current (source) path.\npub fn list_icon_files(\n  settings: &Settings,\n  data_dir: &Path,\n) -> crate::Result<BTreeMap<Icon, PathBuf>> {\n  let base_dir = data_dir.join(\"usr/share/icons/hicolor\");\n  let main_binary_name = settings.main_binary_name()?;\n  let get_dest_path = |width: u32, height: u32, is_high_density: bool| {\n    base_dir.join(format!(\n      \"{}x{}{}/apps/{}.png\",\n      width,\n      height,\n      if is_high_density { \"@2\" } else { \"\" },\n      main_binary_name\n    ))\n  };\n  let mut icons = BTreeMap::new();\n  for icon_path in settings.icon_files() {\n    let icon_path = icon_path?;\n    if icon_path.extension() != Some(OsStr::new(\"png\")) {\n      continue;\n    }\n    // Put file in scope so that it's closed when copying it\n    let icon = {\n      let decoder = PngDecoder::new(BufReader::new(File::open(&icon_path)?))?;\n      let width = decoder.dimensions().0;\n      let height = decoder.dimensions().1;\n      let is_high_density = utils::is_retina(&icon_path);\n      let dest_path = get_dest_path(width, height, is_high_density);\n      Icon {\n        width,\n        height,\n        is_high_density,\n        path: dest_path,\n      }\n    };\n    icons.entry(icon).or_insert(icon_path);\n  }\n\n  Ok(icons)\n}\n\n/// Generate the icon files and store them under the `data_dir`.\npub fn copy_icon_files(settings: &Settings, data_dir: &Path) -> crate::Result<Vec<Icon>> {\n  let icons = list_icon_files(settings, data_dir)?;\n  for (icon, src) in &icons {\n    fs_utils::copy_file(src, &icon.path)?;\n  }\n\n  Ok(icons.into_keys().collect())\n}\n\n/// Generate the application desktop file and store it under the `data_dir`.\n/// Returns the path of the resulting file (source path) and the destination\n/// path in the package.\npub fn generate_desktop_file(\n  settings: &Settings,\n  custom_template_path: &Option<PathBuf>,\n  data_dir: &Path,\n) -> crate::Result<(PathBuf, PathBuf)> {\n  let bin_name = settings.main_binary_name()?;\n\n  let product_name = settings.product_name();\n  let desktop_file_name = format!(\"{product_name}.desktop\");\n  let path = PathBuf::from(\"usr/share/applications\").join(desktop_file_name);\n  let dest_path = PathBuf::from(\"/\").join(&path);\n  let file_path = data_dir.join(&path);\n  let file = &mut fs_utils::create_file(&file_path)?;\n\n  let mut handlebars = Handlebars::new();\n  handlebars.register_escape_fn(handlebars::no_escape);\n  if let Some(template) = custom_template_path {\n    handlebars\n      .register_template_string(\"main.desktop\", read_to_string(template)?)\n      .map_err(Into::into)\n      .context(\"Failed to setup custom handlebar template\")?;\n  } else {\n    handlebars\n      .register_template_string(\"main.desktop\", include_str!(\"./main.desktop\"))\n      .map_err(Into::into)\n      .context(\"Failed to setup default handlebar template\")?;\n  }\n\n  #[derive(Serialize)]\n  struct DesktopTemplateParams<'a> {\n    categories: &'a str,\n    comment: Option<&'a str>,\n    exec: &'a str,\n    icon: &'a str,\n    name: &'a str,\n    mime_type: Option<String>,\n    long_description: String,\n  }\n\n  let mut mime_type: Vec<String> = Vec::new();\n\n  if let Some(associations) = settings.file_associations() {\n    mime_type.extend(\n      associations\n        .iter()\n        .filter_map(|association| association.mime_type.clone()),\n    );\n  }\n\n  if let Some(protocols) = settings.deep_link_protocols() {\n    mime_type.extend(\n      protocols\n        .iter()\n        .flat_map(|protocol| &protocol.schemes)\n        .map(|s| format!(\"x-scheme-handler/{s}\")),\n    );\n  }\n\n  let mime_type = (!mime_type.is_empty()).then_some(mime_type.join(\";\"));\n\n  let bin_name_exec = if bin_name.contains(' ') {\n    format!(\"\\\"{bin_name}\\\"\")\n  } else {\n    bin_name.to_string()\n  };\n\n  handlebars.render_to_write(\n    \"main.desktop\",\n    &DesktopTemplateParams {\n      categories: settings\n        .app_category()\n        .map(|app_category| app_category.freedesktop_categories())\n        .unwrap_or(\"\"),\n      comment: if !settings.short_description().is_empty() {\n        Some(settings.short_description())\n      } else {\n        None\n      },\n      exec: &bin_name_exec,\n      icon: bin_name,\n      name: settings.product_name(),\n      mime_type,\n      long_description: settings.long_description().unwrap_or_default().to_string(),\n    },\n    file,\n  )?;\n\n  Ok((file_path, dest_path))\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/linux/mod.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npub mod appimage;\npub mod debian;\npub mod freedesktop;\npub mod rpm;\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/linux/rpm.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{bundle::settings::Arch, error::ErrorExt, Settings};\n\nuse rpm::{self, signature::pgp, Dependency, FileMode, FileOptions};\nuse std::{\n  env,\n  fs::{self, File},\n  path::{Path, PathBuf},\n};\nuse tauri_utils::config::RpmCompression;\n\nuse super::freedesktop;\n\n/// Bundles the project.\n/// Returns a vector of PathBuf that shows where the RPM was created.\npub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {\n  let product_name = settings.product_name();\n  let version = settings.version_string();\n  let release = match settings.rpm().release.as_str() {\n    \"\" => \"1\", // Considered the default. If left empty, you get file with \"-.\".\n    v => v,\n  };\n  let epoch = settings.rpm().epoch;\n  let arch = match settings.binary_arch() {\n    Arch::X86_64 => \"x86_64\",\n    Arch::X86 => \"i386\",\n    Arch::AArch64 => \"aarch64\",\n    Arch::Armhf => \"armhfp\",\n    Arch::Armel => \"armel\",\n    Arch::Riscv64 => \"riscv64\",\n    target => {\n      return Err(crate::Error::ArchError(format!(\n        \"Unsupported architecture: {target:?}\"\n      )));\n    }\n  };\n\n  let summary = settings.short_description().trim();\n\n  let package_base_name = format!(\"{product_name}-{version}-{release}.{arch}\");\n  let package_name = format!(\"{package_base_name}.rpm\");\n\n  let base_dir = settings.project_out_directory().join(\"bundle/rpm\");\n  let package_dir = base_dir.join(&package_base_name);\n  if package_dir.exists() {\n    fs::remove_dir_all(&package_dir).fs_context(\n      \"Failed to remove old package directory\",\n      package_dir.clone(),\n    )?;\n  }\n  fs::create_dir_all(&package_dir)\n    .fs_context(\"Failed to create package directory\", package_dir.clone())?;\n  let package_path = base_dir.join(&package_name);\n\n  log::info!(action = \"Bundling\"; \"{} ({})\", package_name, package_path.display());\n\n  let license = settings.license().unwrap_or_default();\n  let name = heck::AsKebabCase(settings.product_name()).to_string();\n\n  let compression = settings\n    .rpm()\n    .compression\n    .map(|c| match c {\n      RpmCompression::Gzip { level } => rpm::CompressionWithLevel::Gzip(level),\n      RpmCompression::Zstd { level } => rpm::CompressionWithLevel::Zstd(level),\n      RpmCompression::Xz { level } => rpm::CompressionWithLevel::Xz(level),\n      RpmCompression::Bzip2 { level } => rpm::CompressionWithLevel::Bzip2(level),\n      _ => rpm::CompressionWithLevel::None,\n    })\n    // This matches .deb compression. On a 240MB source binary the bundle will be 100KB larger than rpm's default while reducing build times by ~25%.\n    // TODO: Default to Zstd in v3 to match rpm-rs new default in 0.16\n    .unwrap_or(rpm::CompressionWithLevel::Gzip(6));\n\n  let mut builder = rpm::PackageBuilder::new(&name, version, &license, arch, summary)\n    .epoch(epoch)\n    .release(release)\n    .compression(compression);\n\n  if let Some(description) = settings.long_description() {\n    builder = builder.description(description);\n  }\n\n  if let Some(homepage) = settings.homepage_url() {\n    builder = builder.url(homepage);\n  }\n\n  // Add requirements\n  for dep in settings.rpm().depends.as_ref().cloned().unwrap_or_default() {\n    builder = builder.requires(Dependency::any(dep));\n  }\n\n  // Add provides\n  for dep in settings\n    .rpm()\n    .provides\n    .as_ref()\n    .cloned()\n    .unwrap_or_default()\n  {\n    builder = builder.provides(Dependency::any(dep));\n  }\n\n  // Add recommends\n  for dep in settings\n    .rpm()\n    .recommends\n    .as_ref()\n    .cloned()\n    .unwrap_or_default()\n  {\n    builder = builder.recommends(Dependency::any(dep));\n  }\n\n  // Add conflicts\n  for dep in settings\n    .rpm()\n    .conflicts\n    .as_ref()\n    .cloned()\n    .unwrap_or_default()\n  {\n    builder = builder.conflicts(Dependency::any(dep));\n  }\n\n  // Add obsoletes\n  for dep in settings\n    .rpm()\n    .obsoletes\n    .as_ref()\n    .cloned()\n    .unwrap_or_default()\n  {\n    builder = builder.obsoletes(Dependency::any(dep));\n  }\n\n  // Add binaries\n  for bin in settings.binaries() {\n    let src = settings.binary_path(bin);\n    let dest = Path::new(\"/usr/bin\").join(bin.name());\n    builder = builder.with_file(src, FileOptions::new(dest.to_string_lossy()))?;\n  }\n\n  // Add external binaries\n  for src in settings.external_binaries() {\n    let src = src?;\n    let dest = Path::new(\"/usr/bin\").join(\n      src\n        .file_name()\n        .expect(\"failed to extract external binary filename\")\n        .to_string_lossy()\n        .replace(&format!(\"-{}\", settings.target()), \"\"),\n    );\n    builder = builder.with_file(&src, FileOptions::new(dest.to_string_lossy()))?;\n  }\n\n  // Add scripts\n  if let Some(script_path) = &settings.rpm().pre_install_script {\n    let script = fs::read_to_string(script_path)?;\n    builder = builder.pre_install_script(script);\n  }\n\n  if let Some(script_path) = &settings.rpm().post_install_script {\n    let script = fs::read_to_string(script_path)?;\n    builder = builder.post_install_script(script);\n  }\n\n  if let Some(script_path) = &settings.rpm().pre_remove_script {\n    let script = fs::read_to_string(script_path)?;\n    builder = builder.pre_uninstall_script(script);\n  }\n\n  if let Some(script_path) = &settings.rpm().post_remove_script {\n    let script = fs::read_to_string(script_path)?;\n    builder = builder.post_uninstall_script(script);\n  }\n\n  // Add resources\n  if settings.resource_files().count() > 0 {\n    let resource_dir = Path::new(\"/usr/lib\").join(settings.product_name());\n    // Create an empty file, needed to add a directory to the RPM package\n    // (cf https://github.com/rpm-rs/rpm/issues/177)\n    let empty_file_path = &package_dir.join(\"empty\");\n    File::create(empty_file_path)?;\n    // Then add the resource directory `/usr/lib/<product_name>` to the package.\n    builder = builder.with_file(\n      empty_file_path,\n      FileOptions::new(resource_dir.to_string_lossy()).mode(FileMode::Dir { permissions: 0o755 }),\n    )?;\n    // Then add the resources files in that directory\n    for resource in settings.resource_files().iter() {\n      let resource = resource?;\n      let dest = resource_dir.join(resource.target());\n      builder = builder.with_file(resource.path(), FileOptions::new(dest.to_string_lossy()))?;\n    }\n  }\n\n  // Add Desktop entry file\n  let (desktop_src_path, desktop_dest_path) =\n    freedesktop::generate_desktop_file(settings, &settings.rpm().desktop_template, &package_dir)?;\n  builder = builder.with_file(\n    desktop_src_path,\n    FileOptions::new(desktop_dest_path.to_string_lossy()),\n  )?;\n\n  // Add icons\n  for (icon, src) in &freedesktop::list_icon_files(settings, &PathBuf::from(\"/\"))? {\n    builder = builder.with_file(src, FileOptions::new(icon.path.to_string_lossy()))?;\n  }\n\n  // Add custom files\n  for (rpm_path, src_path) in settings.rpm().files.iter() {\n    if src_path.is_file() {\n      builder = builder.with_file(src_path, FileOptions::new(rpm_path.to_string_lossy()))?;\n    } else {\n      for entry in walkdir::WalkDir::new(src_path) {\n        let entry_path = entry?.into_path();\n        if entry_path.is_file() {\n          let dest_path = rpm_path.join(entry_path.strip_prefix(src_path).unwrap());\n          builder =\n            builder.with_file(&entry_path, FileOptions::new(dest_path.to_string_lossy()))?;\n        }\n      }\n    }\n  }\n\n  let pkg = if let Ok(raw_secret_key) = env::var(\"TAURI_SIGNING_RPM_KEY\") {\n    let mut signer = pgp::Signer::load_from_asc(&raw_secret_key)?;\n    if let Ok(passphrase) = env::var(\"TAURI_SIGNING_RPM_KEY_PASSPHRASE\") {\n      signer = signer.with_key_passphrase(passphrase);\n    }\n    builder.build_and_sign(signer)?\n  } else {\n    builder.build()?\n  };\n\n  let mut f = fs::File::create(&package_path)?;\n  pkg.write(&mut f)?;\n  Ok(vec![package_path])\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/macos/app.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// A macOS application bundle package is laid out like:\n//\n// foobar.app    # Actually a directory\n//     Contents      # A further subdirectory\n//         Info.plist     # An xml file containing the app's metadata\n//         MacOS          # A directory to hold executable binary files\n//             foobar          # The main binary executable of the app\n//             foobar_helper   # A helper application, possibly providing a CLI\n//         Resources      # Data files such as images, sounds, translations and nib files\n//             en.lproj        # Folder containing english translation strings/data\n//         Frameworks     # A directory containing private frameworks (shared libraries)\n//         ...            # Any other optional files the developer wants to place here\n//\n// See https://developer.apple.com/go/?id=bundle-structure for a full\n// explanation.\n//\n// Currently, cargo-bundle does not support Frameworks, nor does it support placing arbitrary\n// files into the `Contents` directory of the bundle.\n\nuse super::{\n  icon::create_icns_file,\n  sign::{notarize, notarize_auth, notarize_without_stapling, sign, SignTarget},\n};\nuse crate::{\n  bundle::settings::PlistKind,\n  error::{Context, ErrorExt, NotarizeAuthError},\n  utils::{fs_utils, CommandExt},\n  Error::GenericError,\n  Settings,\n};\n\nuse std::{\n  ffi::OsStr,\n  fs,\n  path::{Path, PathBuf},\n  process::Command,\n};\n\nconst NESTED_CODE_FOLDER: [&str; 6] = [\n  \"MacOS\",\n  \"Frameworks\",\n  \"Plugins\",\n  \"Helpers\",\n  \"XPCServices\",\n  \"Libraries\",\n];\n\n/// Bundles the project.\n/// Returns a vector of PathBuf that shows where the .app was created.\npub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {\n  // we should use the bundle name (App name) as a MacOS standard.\n  // version or platform shouldn't be included in the App name.\n  let app_product_name = format!(\"{}.app\", settings.product_name());\n\n  let app_bundle_path = settings\n    .project_out_directory()\n    .join(\"bundle/macos\")\n    .join(&app_product_name);\n\n  log::info!(action = \"Bundling\"; \"{} ({})\", app_product_name, app_bundle_path.display());\n\n  if app_bundle_path.exists() {\n    fs::remove_dir_all(&app_bundle_path)\n      .fs_context(\"failed to remove old app bundle\", &app_bundle_path)?;\n  }\n  let bundle_directory = app_bundle_path.join(\"Contents\");\n  fs::create_dir_all(&bundle_directory)\n    .fs_context(\"failed to create bundle directory\", &bundle_directory)?;\n\n  let resources_dir = bundle_directory.join(\"Resources\");\n  let bin_dir = bundle_directory.join(\"MacOS\");\n  let mut sign_paths = Vec::new();\n\n  let bundle_icon_file: Option<PathBuf> =\n    { create_icns_file(&resources_dir, settings).with_context(|| \"Failed to create app icon\")? };\n\n  create_info_plist(&bundle_directory, bundle_icon_file, settings)\n    .with_context(|| \"Failed to create Info.plist\")?;\n\n  let framework_paths = copy_frameworks_to_bundle(&bundle_directory, settings)\n    .with_context(|| \"Failed to bundle frameworks\")?;\n  sign_paths.extend(framework_paths);\n\n  settings.copy_resources(&resources_dir)?;\n\n  let bin_paths = settings\n    .copy_binaries(&bin_dir)\n    .with_context(|| \"Failed to copy external binaries\")?;\n  sign_paths.extend(bin_paths.into_iter().map(|path| SignTarget {\n    path,\n    is_an_executable: true,\n  }));\n\n  let bin_paths = copy_binaries_to_bundle(&bundle_directory, settings)?;\n  sign_paths.extend(bin_paths.into_iter().map(|path| SignTarget {\n    path,\n    is_an_executable: true,\n  }));\n\n  copy_custom_files_to_bundle(&bundle_directory, settings)?;\n\n  if settings.no_sign() {\n    log::warn!(\"Skipping signing due to --no-sign flag.\",);\n  } else if let Some(keychain) =\n    super::sign::keychain(settings.macos().signing_identity.as_deref())?\n  {\n    // Sign frameworks and sidecar binaries first, per apple, signing must be done inside out\n    // https://developer.apple.com/forums/thread/701514\n    sign_paths.push(SignTarget {\n      path: app_bundle_path.clone(),\n      is_an_executable: true,\n    });\n\n    // Remove extra attributes, which could cause codesign to fail\n    // https://developer.apple.com/library/archive/qa/qa1940/_index.html\n    remove_extra_attr(&app_bundle_path)?;\n\n    // sign application\n    sign(&keychain, sign_paths, settings)?;\n\n    // notarization is required for distribution\n    match notarize_auth() {\n      Ok(auth) => {\n        if settings.macos().skip_stapling {\n          notarize_without_stapling(&keychain, app_bundle_path.clone(), &auth)?;\n        } else {\n          notarize(&keychain, app_bundle_path.clone(), &auth)?;\n        }\n      }\n      Err(e) => {\n        if matches!(e, NotarizeAuthError::MissingTeamId) {\n          return Err(e.into());\n        } else {\n          log::warn!(\"skipping app notarization, {e}\");\n        }\n      }\n    }\n  }\n\n  Ok(vec![app_bundle_path])\n}\n\nfn remove_extra_attr(app_bundle_path: &Path) -> crate::Result<()> {\n  Command::new(\"xattr\")\n    .arg(\"-crs\")\n    .arg(app_bundle_path)\n    .output_ok()\n    .context(\"failed to remove extra attributes from app bundle\")?;\n  Ok(())\n}\n\n// Copies the app's binaries to the bundle.\nfn copy_binaries_to_bundle(\n  bundle_directory: &Path,\n  settings: &Settings,\n) -> crate::Result<Vec<PathBuf>> {\n  let mut paths = Vec::new();\n  let dest_dir = bundle_directory.join(\"MacOS\");\n  for bin in settings.binaries() {\n    let bin_path = settings.binary_path(bin);\n    let dest_path = dest_dir.join(bin.name());\n    fs_utils::copy_file(&bin_path, &dest_path)\n      .with_context(|| format!(\"Failed to copy binary from {bin_path:?}\"))?;\n    paths.push(dest_path);\n  }\n  Ok(paths)\n}\n\n/// Copies user-defined files to the app under Contents.\nfn copy_custom_files_to_bundle(bundle_directory: &Path, settings: &Settings) -> crate::Result<()> {\n  for (contents_path, path) in settings.macos().files.iter() {\n    if !path.try_exists()? {\n      return Err(GenericError(format!(\n        \"Failed to copy {path:?} to {contents_path:?}. {path:?} does not exist.\"\n      )));\n    }\n\n    let contents_path = if contents_path.is_absolute() {\n      contents_path.strip_prefix(\"/\").unwrap()\n    } else {\n      contents_path\n    };\n    if path.is_file() {\n      fs_utils::copy_file(path, &bundle_directory.join(contents_path))\n        .with_context(|| format!(\"Failed to copy file {path:?} to {contents_path:?}\"))?;\n    } else if path.is_dir() {\n      fs_utils::copy_dir(path, &bundle_directory.join(contents_path))\n        .with_context(|| format!(\"Failed to copy directory {path:?} to {contents_path:?}\"))?;\n    } else {\n      return Err(GenericError(format!(\n        \"{path:?} is not a file or directory.\"\n      )));\n    }\n  }\n  Ok(())\n}\n\n// Creates the Info.plist file.\nfn create_info_plist(\n  bundle_dir: &Path,\n  bundle_icon_file: Option<PathBuf>,\n  settings: &Settings,\n) -> crate::Result<()> {\n  let mut plist = plist::Dictionary::new();\n  plist.insert(\"CFBundleDevelopmentRegion\".into(), \"English\".into());\n  plist.insert(\"CFBundleDisplayName\".into(), settings.product_name().into());\n  plist.insert(\n    \"CFBundleExecutable\".into(),\n    settings.main_binary_name()?.into(),\n  );\n  if let Some(path) = bundle_icon_file {\n    plist.insert(\n      \"CFBundleIconFile\".into(),\n      path\n        .file_name()\n        .expect(\"No file name\")\n        .to_string_lossy()\n        .into_owned()\n        .into(),\n    );\n  }\n  plist.insert(\n    \"CFBundleIdentifier\".into(),\n    settings.bundle_identifier().into(),\n  );\n  plist.insert(\"CFBundleInfoDictionaryVersion\".into(), \"6.0\".into());\n  if let Some(bundle_name) = settings\n    .macos()\n    .bundle_name\n    .as_deref()\n    .unwrap_or_else(|| settings.product_name())\n    .into()\n  {\n    plist.insert(\"CFBundleName\".into(), bundle_name.into());\n  }\n  plist.insert(\"CFBundlePackageType\".into(), \"APPL\".into());\n  plist.insert(\n    \"CFBundleShortVersionString\".into(),\n    settings.version_string().into(),\n  );\n  plist.insert(\n    \"CFBundleVersion\".into(),\n    settings\n      .macos()\n      .bundle_version\n      .as_deref()\n      .unwrap_or_else(|| settings.version_string())\n      .into(),\n  );\n  plist.insert(\"CSResourcesFileMapped\".into(), true.into());\n  if let Some(category) = settings.app_category() {\n    plist.insert(\n      \"LSApplicationCategoryType\".into(),\n      category.macos_application_category_type().into(),\n    );\n  }\n  if let Some(version) = settings.macos().minimum_system_version.clone() {\n    plist.insert(\"LSMinimumSystemVersion\".into(), version.into());\n  }\n\n  if let Some(associations) = settings.file_associations() {\n    let exported_associations = associations\n      .iter()\n      .filter_map(|association| {\n        association.exported_type.as_ref().map(|exported_type| {\n          let mut dict = plist::Dictionary::new();\n\n          dict.insert(\n            \"UTTypeIdentifier\".into(),\n            exported_type.identifier.clone().into(),\n          );\n          if let Some(description) = &association.description {\n            dict.insert(\"UTTypeDescription\".into(), description.clone().into());\n          }\n          if let Some(conforms_to) = &exported_type.conforms_to {\n            dict.insert(\n              \"UTTypeConformsTo\".into(),\n              plist::Value::Array(conforms_to.iter().map(|s| s.clone().into()).collect()),\n            );\n          }\n\n          let mut specification = plist::Dictionary::new();\n          specification.insert(\n            \"public.filename-extension\".into(),\n            plist::Value::Array(\n              association\n                .ext\n                .iter()\n                .map(|s| s.to_string().into())\n                .collect(),\n            ),\n          );\n          if let Some(mime_type) = &association.mime_type {\n            specification.insert(\"public.mime-type\".into(), mime_type.clone().into());\n          }\n\n          dict.insert(\"UTTypeTagSpecification\".into(), specification.into());\n\n          plist::Value::Dictionary(dict)\n        })\n      })\n      .collect::<Vec<_>>();\n\n    if !exported_associations.is_empty() {\n      plist.insert(\n        \"UTExportedTypeDeclarations\".into(),\n        plist::Value::Array(exported_associations),\n      );\n    }\n\n    plist.insert(\n      \"CFBundleDocumentTypes\".into(),\n      plist::Value::Array(\n        associations\n          .iter()\n          .map(|association| {\n            let mut dict = plist::Dictionary::new();\n\n            if !association.ext.is_empty() {\n              dict.insert(\n                \"CFBundleTypeExtensions\".into(),\n                plist::Value::Array(\n                  association\n                    .ext\n                    .iter()\n                    .map(|ext| ext.to_string().into())\n                    .collect(),\n                ),\n              );\n            }\n\n            if let Some(content_types) = &association.content_types {\n              dict.insert(\n                \"LSItemContentTypes\".into(),\n                plist::Value::Array(content_types.iter().map(|s| s.to_string().into()).collect()),\n              );\n            }\n\n            dict.insert(\n              \"CFBundleTypeName\".into(),\n              association\n                .name\n                .as_ref()\n                .unwrap_or(&association.ext[0].0)\n                .to_string()\n                .into(),\n            );\n            dict.insert(\n              \"CFBundleTypeRole\".into(),\n              association.role.to_string().into(),\n            );\n            dict.insert(\"LSHandlerRank\".into(), association.rank.to_string().into());\n            plist::Value::Dictionary(dict)\n          })\n          .collect(),\n      ),\n    );\n  }\n\n  if let Some(protocols) = settings.deep_link_protocols() {\n    plist.insert(\n      \"CFBundleURLTypes\".into(),\n      plist::Value::Array(\n        protocols\n          .iter()\n          .filter(|p| !p.schemes.is_empty())\n          .map(|protocol| {\n            let mut dict = plist::Dictionary::new();\n            dict.insert(\n              \"CFBundleURLSchemes\".into(),\n              plist::Value::Array(\n                protocol\n                  .schemes\n                  .iter()\n                  .map(|s| s.to_string().into())\n                  .collect(),\n              ),\n            );\n            dict.insert(\n              \"CFBundleURLName\".into(),\n              protocol\n                .name\n                .clone()\n                .unwrap_or(format!(\n                  \"{} {}\",\n                  settings.bundle_identifier(),\n                  protocol.schemes[0]\n                ))\n                .into(),\n            );\n            dict.insert(\"CFBundleTypeRole\".into(), protocol.role.to_string().into());\n            plist::Value::Dictionary(dict)\n          })\n          .collect(),\n      ),\n    );\n  }\n\n  plist.insert(\"LSRequiresCarbon\".into(), true.into());\n  plist.insert(\"NSHighResolutionCapable\".into(), true.into());\n  if let Some(copyright) = settings.copyright_string() {\n    plist.insert(\"NSHumanReadableCopyright\".into(), copyright.into());\n  }\n\n  if let Some(exception_domain) = settings.macos().exception_domain.clone() {\n    let mut security = plist::Dictionary::new();\n    let mut domain = plist::Dictionary::new();\n    domain.insert(\"NSExceptionAllowsInsecureHTTPLoads\".into(), true.into());\n    domain.insert(\"NSIncludesSubdomains\".into(), true.into());\n\n    let mut exception_domains = plist::Dictionary::new();\n    exception_domains.insert(exception_domain, domain.into());\n    security.insert(\"NSExceptionDomains\".into(), exception_domains.into());\n    plist.insert(\"NSAppTransportSecurity\".into(), security.into());\n  }\n\n  if let Some(user_plist) = &settings.macos().info_plist {\n    let user_plist = match user_plist {\n      PlistKind::Path(path) => plist::Value::from_file(path)?,\n      PlistKind::Plist(value) => value.clone(),\n    };\n    if let Some(dict) = user_plist.into_dictionary() {\n      for (key, value) in dict {\n        plist.insert(key, value);\n      }\n    }\n  }\n\n  plist::Value::Dictionary(plist).to_file_xml(bundle_dir.join(\"Info.plist\"))?;\n\n  Ok(())\n}\n\n// Copies the framework under `{src_dir}/{framework}.framework` to `{dest_dir}/{framework}.framework`.\nfn copy_framework_from(dest_dir: &Path, framework: &str, src_dir: &Path) -> crate::Result<bool> {\n  let src_name = format!(\"{framework}.framework\");\n  let src_path = src_dir.join(&src_name);\n  if src_path.exists() {\n    fs_utils::copy_dir(&src_path, &dest_dir.join(&src_name))?;\n    Ok(true)\n  } else {\n    Ok(false)\n  }\n}\n\n// Copies the macOS application bundle frameworks to the .app\nfn copy_frameworks_to_bundle(\n  bundle_directory: &Path,\n  settings: &Settings,\n) -> crate::Result<Vec<SignTarget>> {\n  let mut paths = Vec::new();\n\n  let frameworks = settings.macos().frameworks.clone().unwrap_or_default();\n  if frameworks.is_empty() {\n    return Ok(paths);\n  }\n  let dest_dir = bundle_directory.join(\"Frameworks\");\n  fs::create_dir_all(&dest_dir).fs_context(\"failed to create Frameworks directory\", &dest_dir)?;\n  for framework in frameworks.iter() {\n    if framework.ends_with(\".framework\") {\n      let src_path = PathBuf::from(framework);\n      let src_name = src_path\n        .file_name()\n        .expect(\"Couldn't get framework filename\");\n      let dest_path = dest_dir.join(src_name);\n      fs_utils::copy_dir(&src_path, &dest_path)?;\n      add_framework_sign_path(&src_path, &dest_path, &mut paths);\n      continue;\n    } else if framework.ends_with(\".dylib\") {\n      let src_path = PathBuf::from(framework);\n      if !src_path.exists() {\n        return Err(GenericError(format!(\"Library not found: {framework}\")));\n      }\n      let src_name = src_path.file_name().expect(\"Couldn't get library filename\");\n      let dest_path = dest_dir.join(src_name);\n      fs_utils::copy_file(&src_path, &dest_path)?;\n      paths.push(SignTarget {\n        path: dest_path,\n        is_an_executable: false,\n      });\n      continue;\n    } else if framework.contains('/') {\n      return Err(GenericError(format!(\n        \"Framework path should have .framework extension: {framework}\"\n      )));\n    }\n    if let Some(home_dir) = dirs::home_dir() {\n      if copy_framework_from(&dest_dir, framework, &home_dir.join(\"Library/Frameworks/\"))? {\n        continue;\n      }\n    }\n    if copy_framework_from(&dest_dir, framework, &PathBuf::from(\"/Library/Frameworks/\"))?\n      || copy_framework_from(\n        &dest_dir,\n        framework,\n        &PathBuf::from(\"/Network/Library/Frameworks/\"),\n      )?\n    {\n      continue;\n    }\n    return Err(GenericError(format!(\n      \"Could not locate framework: {framework}\"\n    )));\n  }\n  Ok(paths)\n}\n\n/// Recursively add framework's sign paths.\n/// If the framework has multiple versions, it will sign \"Current\" version by default.\nfn add_framework_sign_path(\n  framework_root: &Path,\n  dest_path: &Path,\n  sign_paths: &mut Vec<SignTarget>,\n) {\n  if framework_root.join(\"Versions/Current\").exists() {\n    add_nested_code_sign_path(\n      &framework_root.join(\"Versions/Current\"),\n      &dest_path.join(\"Versions/Current\"),\n      sign_paths,\n    );\n  } else {\n    add_nested_code_sign_path(framework_root, dest_path, sign_paths);\n  }\n  sign_paths.push(SignTarget {\n    path: dest_path.into(),\n    is_an_executable: false,\n  });\n}\n\n/// Recursively add executable bundle's sign path (.xpc, .app).\nfn add_executable_bundle_sign_path(\n  bundle_root: &Path,\n  dest_path: &Path,\n  sign_paths: &mut Vec<SignTarget>,\n) {\n  if bundle_root.join(\"Contents\").exists() {\n    add_nested_code_sign_path(\n      &bundle_root.join(\"Contents\"),\n      &dest_path.join(\"Contents\"),\n      sign_paths,\n    );\n  } else {\n    add_nested_code_sign_path(bundle_root, dest_path, sign_paths);\n  }\n  sign_paths.push(SignTarget {\n    path: dest_path.into(),\n    is_an_executable: true,\n  });\n}\n\nfn add_nested_code_sign_path(src_path: &Path, dest_path: &Path, sign_paths: &mut Vec<SignTarget>) {\n  for folder_name in NESTED_CODE_FOLDER.iter() {\n    let src_folder_path = src_path.join(folder_name);\n    let dest_folder_path = dest_path.join(folder_name);\n\n    if src_folder_path.exists() {\n      for entry in walkdir::WalkDir::new(src_folder_path)\n        .min_depth(1)\n        .max_depth(1)\n        .into_iter()\n        .filter_map(|e| e.ok())\n      {\n        if entry.path_is_symlink() || entry.file_name().to_string_lossy().starts_with('.') {\n          continue;\n        }\n\n        let dest_path = dest_folder_path.join(entry.file_name());\n        let ext = entry.path().extension();\n        if entry.path().is_dir() {\n          // Bundles, like .app, .framework, .xpc\n          if ext == Some(OsStr::new(\"framework\")) {\n            add_framework_sign_path(&entry.clone().into_path(), &dest_path, sign_paths);\n          } else if ext == Some(OsStr::new(\"xpc\")) || ext == Some(OsStr::new(\"app\")) {\n            add_executable_bundle_sign_path(&entry.clone().into_path(), &dest_path, sign_paths);\n          }\n        } else if entry.path().is_file() {\n          // Binaries, like .dylib, Mach-O executables\n          if ext == Some(OsStr::new(\"dylib\")) {\n            sign_paths.push(SignTarget {\n              path: dest_path,\n              is_an_executable: false,\n            });\n          } else if ext.is_none() {\n            sign_paths.push(SignTarget {\n              path: dest_path,\n              is_an_executable: true,\n            });\n          }\n        }\n      }\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use super::*;\n  use crate::bundle::{BundleSettings, MacOsSettings, PackageSettings, SettingsBuilder};\n  use std::{\n    collections::HashMap,\n    fs,\n    path::{Path, PathBuf},\n  };\n\n  /// Helper that builds a `Settings` instance and bundle directory for tests.\n  /// It receives a mapping of bundle-relative paths to source paths and\n  /// returns the generated bundle directory and settings.\n  fn create_test_bundle(\n    project_dir: &Path,\n    files: HashMap<PathBuf, PathBuf>,\n  ) -> (PathBuf, crate::bundle::Settings) {\n    let macos_settings = MacOsSettings {\n      files,\n      ..Default::default()\n    };\n\n    let settings = SettingsBuilder::new()\n      .project_out_directory(project_dir)\n      .package_settings(PackageSettings {\n        product_name: \"TestApp\".into(),\n        version: \"0.1.0\".into(),\n        description: \"test\".into(),\n        homepage: None,\n        authors: None,\n        default_run: None,\n      })\n      .bundle_settings(BundleSettings {\n        macos: macos_settings,\n        ..Default::default()\n      })\n      .target(\"x86_64-apple-darwin\".into())\n      .build()\n      .expect(\"failed to build settings\");\n\n    let bundle_dir = project_dir.join(\"TestApp.app/Contents\");\n    fs::create_dir_all(&bundle_dir).expect(\"failed to create bundle dir\");\n\n    (bundle_dir, settings)\n  }\n\n  #[test]\n  fn test_copy_custom_file_to_bundle_file() {\n    let tmp_dir = tempfile::tempdir().expect(\"failed to create temp dir\");\n\n    // Prepare a single file to copy.\n    let src_file = tmp_dir.path().join(\"sample.txt\");\n    fs::write(&src_file, b\"hello tauri\").expect(\"failed to write sample file\");\n\n    let files_map = HashMap::from([(PathBuf::from(\"Resources/sample.txt\"), src_file.clone())]);\n\n    let (bundle_dir, settings) = create_test_bundle(tmp_dir.path(), files_map);\n\n    copy_custom_files_to_bundle(&bundle_dir, &settings)\n      .expect(\"copy_custom_files_to_bundle failed\");\n\n    let dest_file = bundle_dir.join(\"Resources/sample.txt\");\n    assert!(dest_file.exists() && dest_file.is_file());\n    assert_eq!(fs::read_to_string(dest_file).unwrap(), \"hello tauri\");\n  }\n\n  #[test]\n  fn test_copy_custom_file_to_bundle_dir() {\n    let tmp_dir = tempfile::tempdir().expect(\"failed to create temp dir\");\n\n    // Create a source directory with a nested file.\n    let src_dir = tmp_dir.path().join(\"assets\");\n    fs::create_dir_all(&src_dir).expect(\"failed to create assets directory\");\n    let nested_file = src_dir.join(\"nested.txt\");\n    fs::write(&nested_file, b\"nested\").expect(\"failed to write nested file\");\n\n    let files_map = HashMap::from([(PathBuf::from(\"MyAssets\"), src_dir.clone())]);\n\n    let (bundle_dir, settings) = create_test_bundle(tmp_dir.path(), files_map);\n\n    copy_custom_files_to_bundle(&bundle_dir, &settings)\n      .expect(\"copy_custom_files_to_bundle failed\");\n\n    let dest_nested_file = bundle_dir.join(\"MyAssets/nested.txt\");\n    assert!(\n      dest_nested_file.exists(),\n      \"{dest_nested_file:?} does not exist\"\n    );\n    assert!(\n      dest_nested_file.is_file(),\n      \"{dest_nested_file:?} is not a file\"\n    );\n    assert_eq!(\n      fs::read_to_string(dest_nested_file).unwrap().trim(),\n      \"nested\"\n    );\n  }\n\n  #[test]\n  fn test_copy_custom_files_to_bundle_missing_source() {\n    let tmp_dir = tempfile::tempdir().expect(\"failed to create temp dir\");\n\n    // Intentionally reference a non-existent path.\n    let missing_path = tmp_dir.path().join(\"does_not_exist.txt\");\n\n    let files_map = HashMap::from([(PathBuf::from(\"Missing.txt\"), missing_path)]);\n\n    let (bundle_dir, settings) = create_test_bundle(tmp_dir.path(), files_map);\n\n    let result = copy_custom_files_to_bundle(&bundle_dir, &settings);\n\n    assert!(result.is_err());\n    assert!(result.err().unwrap().to_string().contains(\"does not exist\"));\n  }\n\n  #[test]\n  fn test_copy_custom_files_to_bundle_invalid_source() {\n    let tmp_dir = tempfile::tempdir().expect(\"failed to create temp dir\");\n\n    let files_map = HashMap::from([(PathBuf::from(\"Invalid.txt\"), PathBuf::from(\"///\"))]);\n\n    let (bundle_dir, settings) = create_test_bundle(tmp_dir.path(), files_map);\n\n    let result = copy_custom_files_to_bundle(&bundle_dir, &settings);\n    assert!(result.is_err());\n    assert!(result\n      .err()\n      .unwrap()\n      .to_string()\n      .contains(\"Failed to copy directory\"));\n  }\n\n  #[test]\n  fn test_copy_custom_files_to_bundle_dev_null() {\n    let tmp_dir = tempfile::tempdir().expect(\"failed to create temp dir\");\n\n    let files_map = HashMap::from([(PathBuf::from(\"Invalid.txt\"), PathBuf::from(\"/dev/null\"))]);\n\n    let (bundle_dir, settings) = create_test_bundle(tmp_dir.path(), files_map);\n\n    let result = copy_custom_files_to_bundle(&bundle_dir, &settings);\n    assert!(result.is_err());\n    assert!(result\n      .err()\n      .unwrap()\n      .to_string()\n      .contains(\"is not a file or directory.\"));\n  }\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/macos/dmg/bundle_dmg",
    "content": "#!/usr/bin/env bash\n# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\n# Create a read-only disk image of the contents of a folder\n# forked from https://github.com/create-dmg/create-dmg\n\n# Bail out on any unhandled errors\nset -e;\n# Any command that exits with non-zero code will cause the pipeline to fail\nset -o pipefail;\n\nCDMG_VERSION='1.2.1'\n\n# The full path to the \"support/\" directory this script is using\n# (This will be set up by code later in the script.)\nCDMG_SUPPORT_DIR=\"\"\n\nOS_FULL_VERSION=\"$(sw_vers | sed -n 2p | cut -d : -f 2 | tr -d '[:space:]' | cut -c1-)\"\nOS_MAJOR_VERSION=\"$(echo $OS_FULL_VERSION | cut -d . -f 1)\"\nOS_MINOR_VERSION=\"$(echo $OS_FULL_VERSION | cut -d . -f 2)\"\nWINX=10\nWINY=60\nWINW=500\nWINH=350\nICON_SIZE=128\nTEXT_SIZE=16\nFORMAT=\"UDZO\"\nFILESYSTEM=\"HFS+\"\nADD_FILE_SOURCES=()\nADD_FILE_TARGETS=()\nIMAGEKEY=\"\"\nHDIUTIL_VERBOSITY=\"\"\nSANDBOX_SAFE=0\nBLESS=0\nSKIP_JENKINS=0\nMAXIMUM_UNMOUNTING_ATTEMPTS=3\nSIGNATURE=\"\"\nNOTARIZE=\"\"\n\nfunction pure_version() {\n\techo \"$CDMG_VERSION\"\n}\n\nfunction hdiutil_detach_retry() {\n\t# Unmount\n\tunmounting_attempts=0\n\tuntil\n\t\techo \"Unmounting disk image...\"\n\t\t(( unmounting_attempts++ ))\n\t\thdiutil detach \"$1\"\n\t\texit_code=$?\n\t\t(( exit_code ==  0 )) && break            # nothing goes wrong\n\t\t(( exit_code != 16 )) && exit $exit_code  # exit with the original exit code\n\t\t# The above statement returns 1 if test failed (exit_code == 16).\n\t\t#   It can make the code in the {do... done} block to be executed\n\tdo\n\t\t(( unmounting_attempts == MAXIMUM_UNMOUNTING_ATTEMPTS )) && exit 16  # patience exhausted, exit with code EBUSY\n\t\techo \"Wait a moment...\"\n\t\tsleep $(( 1 * (2 ** unmounting_attempts) ))\n\tdone\n\tunset unmounting_attempts\n}\n\nfunction version() {\n\techo \"create-dmg $(pure_version)\"\n}\n\nfunction usage() {\n\tversion\n\tcat <<EOHELP\n\nCreates a fancy DMG file.\n\nUsage:  $(basename $0) [options] <output_name.dmg> <source_folder>\n\nAll contents of <source_folder> will be copied into the disk image.\n\nOptions:\n  --volname <name>\n      set volume name (displayed in the Finder sidebar and window title)\n  --volicon <icon.icns>\n      set volume icon\n  --background <pic.png>\n      set folder background image (provide png, gif, or jpg)\n  --window-pos <x> <y>\n      set position the folder window\n  --window-size <width> <height>\n      set size of the folder window\n  --text-size <text_size>\n      set window text size (10-16)\n  --icon-size <icon_size>\n      set window icons size (up to 128)\n  --icon file_name <x> <y>\n      set position of the file's icon\n  --hide-extension <file_name>\n      hide the extension of file\n  --app-drop-link <x> <y>\n      make a drop link to Applications, at location x,y\n  --ql-drop-link <x> <y>\n      make a drop link to user QuickLook install dir, at location x,y\n  --eula <eula_file>\n      attach a license file to the dmg (plain text or RTF)\n  --no-internet-enable\n      disable automatic mount & copy\n  --format <format>\n      specify the final disk image format (UDZO|UDBZ|ULFO|ULMO) (default is UDZO)\n  --filesystem <filesystem>\n      specify the disk image filesystem (HFS+|APFS) (default is HFS+, APFS supports macOS 10.13 or newer)\n  --encrypt\n      enable encryption for the resulting disk image (AES-256 - you will be prompted for password)\n  --encrypt-aes128\n      enable encryption for the resulting disk image (AES-128 - you will be prompted for password)\n  --add-file <target_name> <file>|<folder> <x> <y>\n      add additional file or folder (can be used multiple times)\n  --disk-image-size <x>\n      set the disk image size manually to x MB\n  --hdiutil-verbose\n      execute hdiutil in verbose mode\n  --hdiutil-quiet\n      execute hdiutil in quiet mode\n  --bless\n      bless the mount folder (deprecated, needs macOS 12.2.1 or older)\n  --codesign <signature>\n      codesign the disk image with the specified signature\n  --notarize <credentials>\n      notarize the disk image (waits and staples) with the keychain stored credentials\n  --sandbox-safe\n      execute hdiutil with sandbox compatibility and do not bless (not supported for APFS disk images)\n  --skip-jenkins\n      skip Finder-prettifying AppleScript, useful in Sandbox and non-GUI environments\n  --version\n\t    show create-dmg version number\n  -h, --help\n\t    display this help screen\n\nEOHELP\n\texit 0\n}\n\n# factors can cause interstitial disk images to contain more than a single\n# partition - expand the hunt for the temporary disk image by checking for\n# the path of the volume, versus assuming its the first result (as in pr/152).\nfunction find_mount_dir() {\n\tlocal dev_name=\"${1}\"\n\n\t>&2 echo \"Searching for mounted interstitial disk image using ${dev_name}... \"\n\t# enumerate up to 9 partitions\n\tfor i in {1..9}; do\n\t\t# attempt to find the partition\n\t\tlocal found_dir\n\t\tfound_dir=$(hdiutil info | grep -E --color=never \"${dev_name}\" | head -${i} | awk '{print $3}' | xargs)\n\t\tif [[ -n \"${found_dir}\" ]]; then\n\t\t\t\techo \"${found_dir}\"\n\t\t\t\treturn 0\n\t\tfi\n\tdone\n}\n\n# Argument parsing\n\nwhile [[ \"${1:0:1}\" = \"-\" ]]; do\n\tcase $1 in\n\t\t--volname)\n\t\t\tVOLUME_NAME=\"$2\"\n\t\t\tshift; shift;;\n\t\t--volicon)\n\t\t\tVOLUME_ICON_FILE=\"$2\"\n\t\t\tshift; shift;;\n\t\t--background)\n\t\t\tBACKGROUND_FILE=\"$2\"\n\t\t\tBACKGROUND_FILE_NAME=\"$(basename \"$BACKGROUND_FILE\")\"\n\t\t\tBACKGROUND_CLAUSE=\"set background picture of opts to file \\\".background:$BACKGROUND_FILE_NAME\\\"\"\n\t\t\tREPOSITION_HIDDEN_FILES_CLAUSE=\"set position of every item to {theBottomRightX + 100, 100}\"\n\t\t\tshift; shift;;\n\t\t--icon-size)\n\t\t\tICON_SIZE=\"$2\"\n\t\t\tshift; shift;;\n\t\t--text-size)\n\t\t\tTEXT_SIZE=\"$2\"\n\t\t\tshift; shift;;\n\t\t--window-pos)\n\t\t\tWINX=$2; WINY=$3\n\t\t\tshift; shift; shift;;\n\t\t--window-size)\n\t\t\tWINW=$2; WINH=$3\n\t\t\tshift; shift; shift;;\n\t\t--icon)\n\t\t\tPOSITION_CLAUSE=\"${POSITION_CLAUSE}set position of item \\\"$2\\\" to {$3, $4}\n\t\t\t\"\n\t\t\tshift; shift; shift; shift;;\n\t\t--hide-extension)\n\t\t\tHIDING_CLAUSE=\"${HIDING_CLAUSE}set the extension hidden of item \\\"$2\\\" to true\n\t\t\t\"\n\t\t\tshift; shift;;\n\t\t-h | --help)\n\t\t\tusage;;\n\t\t--version)\n\t\t\tversion; exit 0;;\n\t\t--pure-version)\n\t\t\tpure_version; exit 0;;\n\t\t--ql-drop-link)\n\t\t\tQL_LINK=$2\n\t\t\tQL_CLAUSE=\"set position of item \\\"QuickLook\\\" to {$2, $3}\n\t\t\t\"\n\t\t\tshift; shift; shift;;\n\t\t--app-drop-link)\n\t\t\tAPPLICATION_LINK=$2\n\t\t\tAPPLICATION_CLAUSE=\"set position of item \\\"Applications\\\" to {$2, $3}\n\t\t\t\"\n\t\t\tshift; shift; shift;;\n\t\t--eula)\n\t\t\tEULA_RSRC=$2\n\t\t\tshift; shift;;\n\t\t--no-internet-enable)\n\t\t\tNOINTERNET=1\n\t\t\tshift;;\n\t\t--format)\n\t\t\tFORMAT=\"$2\"\n\t\t\tshift; shift;;\n\t\t--filesystem)\n\t\t\tFILESYSTEM=\"$2\"\n\t\t\tshift; shift;;\n\t\t--encrypt)\n\t\t\tENABLE_ENCRYPTION=1\n\t\t\tAESBITS=256\n\t\t\tshift;;\n\t\t--encrypt-aes128)\n\t\t\tENABLE_ENCRYPTION=1\n\t\t\tAESBITS=128\n\t\t\tshift;;\n\t\t--add-file | --add-folder)\n\t\t\tADD_FILE_TARGETS+=(\"$2\")\n\t\t\tADD_FILE_SOURCES+=(\"$3\")\n\t\t\tPOSITION_CLAUSE=\"${POSITION_CLAUSE}\n\t\t\tset position of item \\\"$2\\\" to {$4, $5}\n\t\t\t\"\n\t\t\tshift; shift; shift; shift; shift;;\n\t\t--disk-image-size)\n\t\t\tDISK_IMAGE_SIZE=\"$2\"\n\t\t\tshift; shift;;\n\t\t--hdiutil-verbose)\n\t\t\tHDIUTIL_VERBOSITY='-verbose'\n\t\t\tshift;;\n\t\t--hdiutil-quiet)\n\t\t\tHDIUTIL_VERBOSITY='-quiet'\n\t\t\tshift;;\n\t\t--codesign)\n\t\t\tSIGNATURE=\"$2\"\n\t\t\tshift; shift;;\n\t\t--notarize)\n\t\t\tNOTARIZE=\"$2\"\n\t\t\tshift; shift;;\n\t\t--sandbox-safe)\n\t\t\tSANDBOX_SAFE=1\n\t\t\tshift;;\n\t\t--bless)\n\t\t\tBLESS=1\n\t\t\tshift;;\n\t\t--rez)\n\t\t\techo \"REZ is no more directly used. You can remove the --rez argument.\"\n\t\t\tshift; shift;;\n\t\t--skip-jenkins)\n\t\t\tSKIP_JENKINS=1\n\t\t\tshift;;\n\t\t-*)\n\t\t\techo \"Unknown option: $1. Run 'create-dmg --help' for help.\"\n\t\t\texit 1;;\n\tesac\n\tcase $FORMAT in\n\t\tUDZO)\n\t\t\tIMAGEKEY=\"-imagekey zlib-level=9\";;\n\t\tUDBZ)\n\t\t\tIMAGEKEY=\"-imagekey bzip2-level=9\";;\n\t\tULFO)\n\t\t\t;;\n\t\tULMO)\n\t\t\t;;\n\t\t*)\n\t\t\techo >&2 \"Unknown disk image format: $FORMAT\"\n\t\t\texit 1;;\n\tesac\ndone\n\nif [[ -z \"$2\" ]]; then\n\techo \"Not enough arguments. Run 'create-dmg --help' for help.\"\n\texit 1\nfi\n\nDMG_PATH=\"$1\"\nSRC_FOLDER=\"$(cd \"$2\" > /dev/null; pwd)\"\n\n# Argument validation checks\n\nif [[ \"${DMG_PATH: -4}\" != \".dmg\" ]]; then\n\techo \"Output file name must end with a .dmg extension. Run 'create-dmg --help' for help.\"\n\texit 1\nfi\n\nif [[ \"${FILESYSTEM}\" != \"HFS+\" ]] && [[ \"${FILESYSTEM}\" != \"APFS\" ]]; then\n\techo \"Unknown disk image filesystem: ${FILESYSTEM}. Run 'create-dmg --help' for help.\"\n\texit 1\nfi\n\nif [[ \"${FILESYSTEM}\" == \"APFS\" ]] && [[ ${SANDBOX_SAFE} -eq 1 ]]; then\n\techo \"Creating an APFS disk image that is sandbox safe is not supported.\"\n\texit 1\nfi\n\n# Main script logic\n\nSCRIPT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nDMG_DIRNAME=\"$(dirname \"$DMG_PATH\")\"\nDMG_DIR=\"$(cd \"$DMG_DIRNAME\" > /dev/null; pwd)\"\nDMG_NAME=\"$(basename \"$DMG_PATH\")\"\nDMG_TEMP_NAME=\"$DMG_DIR/rw.$$.${DMG_NAME}\"\n\n# Detect where we're running from\n\nsentinel_file=\"$SCRIPT_DIR/.this-is-the-create-dmg-repo\"\nif [[ -f \"$sentinel_file\" ]]; then\n\t# We're running from inside a repo\n\tCDMG_SUPPORT_DIR=\"$SCRIPT_DIR/support\"\nelse\n\t# We're running inside an installed location\n\tbin_dir=\"$SCRIPT_DIR\"\n\tprefix_dir=$(dirname \"$bin_dir\")\n\tCDMG_SUPPORT_DIR=\"$prefix_dir/share/create-dmg/support\"\nfi\n\nif [[ -z \"$VOLUME_NAME\" ]]; then\n\tVOLUME_NAME=\"$(basename \"$DMG_PATH\" .dmg)\"\nfi\n\nif [[ ! -d \"$CDMG_SUPPORT_DIR\" ]]; then\n\techo >&2 \"Cannot find support/ directory: expected at: $CDMG_SUPPORT_DIR\"\n\texit 1\nfi\n\nif [[ -f \"$SRC_FOLDER/.DS_Store\" ]]; then\n\techo \"Deleting .DS_Store found in source folder\"\n\trm \"$SRC_FOLDER/.DS_Store\"\nfi\n\n# Create the image\necho \"Creating disk image...\"\nif [[ -f \"${DMG_TEMP_NAME}\" ]]; then\n\trm -f \"${DMG_TEMP_NAME}\"\nfi\n\n# Use Megabytes since hdiutil fails with very large byte numbers\nfunction blocks_to_megabytes() {\n\t# Add 1 extra MB, since there's no decimal retention here\n\tMB_SIZE=$((($1 * 512 / 1000 / 1000) + 1))\n\techo $MB_SIZE\n}\n\nfunction get_size() {\n\t# Get block size in disk\n\tif [[ $OS_MAJOR_VERSION -ge 12 ]]; then\n\t\tbytes_size=$(du -B 512 -s \"$1\")\n\telse\n\t\tbytes_size=$(du -s \"$1\")\n\tfi\n\tbytes_size=$(echo $bytes_size | sed -e 's/\t.*//g')\n\techo $(blocks_to_megabytes $bytes_size)\n}\n\n# Create the DMG with the specified size or the hdiutil estimation\nCUSTOM_SIZE=''\nif [[ -n \"$DISK_IMAGE_SIZE\" ]]; then\n\tCUSTOM_SIZE=\"-size ${DISK_IMAGE_SIZE}m\"\nfi\n\nif [[ $SANDBOX_SAFE -eq 0 ]]; then\n\tif [[ \"$FILESYSTEM\" == \"APFS\" ]]; then\n\t\tFILESYSTEM_ARGUMENTS=\"\"\n\telse\n\t\tFILESYSTEM_ARGUMENTS=\"-c c=64,a=16,e=16\"\n\tfi\n\thdiutil create ${HDIUTIL_VERBOSITY} -srcfolder \"$SRC_FOLDER\" -volname \"${VOLUME_NAME}\" \\\n\t\t-fs \"${FILESYSTEM}\" -fsargs \"${FILESYSTEM_ARGUMENTS}\" -format UDRW ${CUSTOM_SIZE} \"${DMG_TEMP_NAME}\"\nelse\n\thdiutil makehybrid ${HDIUTIL_VERBOSITY} -default-volume-name \"${VOLUME_NAME}\" -hfs -o \"${DMG_TEMP_NAME}\" \"$SRC_FOLDER\"\n\thdiutil convert -format UDRW -ov -o \"${DMG_TEMP_NAME}\" \"${DMG_TEMP_NAME}\"\n\tDISK_IMAGE_SIZE_CUSTOM=$DISK_IMAGE_SIZE\nfi\n\n# Get the created DMG actual size\nDISK_IMAGE_SIZE=$(get_size \"${DMG_TEMP_NAME}\")\n\n# Use the custom size if bigger\nif [[ $SANDBOX_SAFE -eq 1 ]] && [[ ! -z \"$DISK_IMAGE_SIZE_CUSTOM\" ]] && [[ $DISK_IMAGE_SIZE_CUSTOM -gt $DISK_IMAGE_SIZE ]]; then\n\tDISK_IMAGE_SIZE=$DISK_IMAGE_SIZE_CUSTOM\nfi\n\n# Estimate the additional sources size\nif [[ -n \"$ADD_FILE_SOURCES\" ]]; then\n\tfor i in \"${!ADD_FILE_SOURCES[@]}\"; do\n\t\tSOURCE_SIZE=$(get_size \"${ADD_FILE_SOURCES[$i]}\")\n\t\tDISK_IMAGE_SIZE=$(expr $DISK_IMAGE_SIZE + $SOURCE_SIZE)\n\tdone\nfi\n\n# Add extra space for additional resources\nDISK_IMAGE_SIZE=$(expr $DISK_IMAGE_SIZE + 20)\n\n# Make sure target image size is within limits\nMIN_DISK_IMAGE_SIZE=$(hdiutil resize -limits \"${DMG_TEMP_NAME}\" | awk 'NR=1{print int($1/2048+1)}')\nif [ $MIN_DISK_IMAGE_SIZE -gt $DISK_IMAGE_SIZE ]; then\n       DISK_IMAGE_SIZE=$MIN_DISK_IMAGE_SIZE\nfi\n\n# Resize the image for the extra stuff\nhdiutil resize ${HDIUTIL_VERBOSITY} -size ${DISK_IMAGE_SIZE}m \"${DMG_TEMP_NAME}\"\n\n# Mount the new DMG\n\necho \"Mounting disk image...\"\n\nMOUNT_RANDOM_PATH=\"/Volumes\"\nif [[ $SANDBOX_SAFE -eq 1 ]]; then\n\tMOUNT_RANDOM_PATH=\"/tmp\"\nfi\nif [[ \"$FILESYSTEM\" == \"APFS\" ]]; then\n  HDIUTIL_FILTER=\"tail -n 1\"\nelse\n  HDIUTIL_FILTER=\"sed 1q\"\nfi\nDEV_NAME=$(hdiutil attach -mountrandom ${MOUNT_RANDOM_PATH} -readwrite -noverify -noautoopen -nobrowse \"${DMG_TEMP_NAME}\" | grep -E --color=never '^/dev/' | ${HDIUTIL_FILTER} | awk '{print $1}')\necho \"Device name:     $DEV_NAME\"\nif [[ \"$FILESYSTEM\" == \"APFS\" ]]; then\n  MOUNT_DIR=$(find_mount_dir \"${DEV_NAME}\")\nelse\n\tMOUNT_DIR=$(find_mount_dir \"${DEV_NAME}s\")\nfi\nif [[ -z \"${MOUNT_DIR}\" ]]; then\n  >&2 echo \"ERROR: unable to proceed with final disk image creation because the interstitial disk image was not found.\"\n  >&2 echo \"The interstitial disk image will likely be mounted and will need to be cleaned up manually.\"\n  exit 1\nfi\n\necho \"Mount dir:       $MOUNT_DIR\"\n\nif [[ -n \"$BACKGROUND_FILE\" ]]; then\n\techo \"Copying background file '$BACKGROUND_FILE'...\"\n\t[[ -d \"$MOUNT_DIR/.background\" ]] || mkdir \"$MOUNT_DIR/.background\"\n\tcp \"$BACKGROUND_FILE\" \"$MOUNT_DIR/.background/$BACKGROUND_FILE_NAME\"\nfi\n\nif [[ -n \"$APPLICATION_LINK\" ]]; then\n\techo \"Making link to Applications dir...\"\n\techo $MOUNT_DIR\n\tln -s /Applications \"$MOUNT_DIR/Applications\"\nfi\n\nif [[ -n \"$QL_LINK\" ]]; then\n\techo \"Making link to QuickLook install dir...\"\n\techo $MOUNT_DIR\n\tln -s \"/Library/QuickLook\" \"$MOUNT_DIR/QuickLook\"\nfi\n\nif [[ -n \"$VOLUME_ICON_FILE\" ]]; then\n\techo \"Copying volume icon file '$VOLUME_ICON_FILE'...\"\n\tcp \"$VOLUME_ICON_FILE\" \"$MOUNT_DIR/.VolumeIcon.icns\"\n\tSetFile -c icnC \"$MOUNT_DIR/.VolumeIcon.icns\"\nfi\n\nif [[ -n \"$ADD_FILE_SOURCES\" ]]; then\n\techo \"Copying custom files...\"\n\tfor i in \"${!ADD_FILE_SOURCES[@]}\"; do\n\t\techo \"${ADD_FILE_SOURCES[$i]}\"\n\t\tcp -a \"${ADD_FILE_SOURCES[$i]}\" \"$MOUNT_DIR/${ADD_FILE_TARGETS[$i]}\"\n\tdone\nfi\n\nVOLUME_NAME=$(basename $MOUNT_DIR)\n\n# Run AppleScript to do all the Finder cosmetic stuff\nAPPLESCRIPT_FILE=$(mktemp -t createdmg.tmp.XXXXXXXXXX)\nif [[ $SANDBOX_SAFE -eq 1 ]]; then\n\techo \"Skipping Finder-prettifying AppleScript because we are in Sandbox...\"\nelse\n\tif [[ $SKIP_JENKINS -eq 0 ]]; then\n\t\tcat \"$CDMG_SUPPORT_DIR/template.applescript\" \\\n\t\t\t| sed -e \"s/WINX/$WINX/g\" -e \"s/WINY/$WINY/g\" -e \"s/WINW/$WINW/g\" \\\n\t\t\t\t\t-e \"s/WINH/$WINH/g\" -e \"s/BACKGROUND_CLAUSE/$BACKGROUND_CLAUSE/g\" \\\n\t\t\t\t\t-e \"s/REPOSITION_HIDDEN_FILES_CLAUSE/$REPOSITION_HIDDEN_FILES_CLAUSE/g\" \\\n\t\t\t\t\t-e \"s/ICON_SIZE/$ICON_SIZE/g\" -e \"s/TEXT_SIZE/$TEXT_SIZE/g\" \\\n\t\t\t| perl -pe \"s/POSITION_CLAUSE/$POSITION_CLAUSE/g\" \\\n\t\t\t| perl -pe \"s/QL_CLAUSE/$QL_CLAUSE/g\" \\\n\t\t\t| perl -pe \"s/APPLICATION_CLAUSE/$APPLICATION_CLAUSE/g\" \\\n\t\t\t| perl -pe \"s/HIDING_CLAUSE/$HIDING_CLAUSE/\" \\\n\t\t\t> \"$APPLESCRIPT_FILE\"\n\n\t\t# pause to workaround occasional \"Can’t get disk\" (-1728) issues\n\t\tERROR_1728_WORKAROUND_SLEEP_INTERVAL=2\n\t\techo \"Will sleep for $ERROR_1728_WORKAROUND_SLEEP_INTERVAL seconds to workaround occasions \\\"Can't get disk (-1728)\\\" issues...\"\n\t\tsleep $ERROR_1728_WORKAROUND_SLEEP_INTERVAL\n\n\t\techo \"Running AppleScript to make Finder stuff pretty: /usr/bin/osascript \\\"${APPLESCRIPT_FILE}\\\" \\\"${VOLUME_NAME}\\\"\"\n\t\tif /usr/bin/osascript \"${APPLESCRIPT_FILE}\" \"${VOLUME_NAME}\"; then\n\t\t\t# Okay, we're cool\n\t\t\ttrue\n\t\telse\n\t\t\techo >&2 \"Failed running AppleScript\"\n\t\t\thdiutil_detach_retry \"${DEV_NAME}\"\n\t\t\texit 64\n\t\tfi\n\t\techo \"Done running the AppleScript...\"\n\t\tsleep 4\n\t\trm \"$APPLESCRIPT_FILE\"\n\telse\n\t\techo ''\n\t\techo \"Will skip running AppleScript to configure DMG aesthetics because of --skip-jenkins option.\"\n\t\techo \"This will result in a DMG without any custom background or icons positioning.\"\n\t\techo \"More info at https://github.com/create-dmg/create-dmg/issues/72\"\n\t\techo ''\n\tfi\nfi\n\n# Make sure it's not world writeable\necho \"Fixing permissions...\"\nchmod -Rf go-w \"${MOUNT_DIR}\" &> /dev/null || true\necho \"Done fixing permissions\"\n\n# Make the top window open itself on mount:\nif [[ $BLESS -eq 1 && $SANDBOX_SAFE -eq 0 ]]; then\n\techo \"Blessing started\"\n\tif [ $(uname -m) == \"arm64\" ]; then\n\t\tbless --folder \"${MOUNT_DIR}\"\n\telse\n\t\tbless --folder \"${MOUNT_DIR}\" --openfolder \"${MOUNT_DIR}\"\n\tfi\n\techo \"Blessing finished\"\nelse\n\techo \"Skipping blessing on sandbox\"\nfi\n\nif [[ -n \"$VOLUME_ICON_FILE\" ]]; then\n\t# Tell the volume that it has a special file attribute\n\tSetFile -a C \"$MOUNT_DIR\"\nfi\n\n# Delete unnecessary file system events log if possible\necho \"Deleting .fseventsd\"\nrm -rf \"${MOUNT_DIR}/.fseventsd\" || true\n\nhdiutil_detach_retry \"${DEV_NAME}\"\n\n# Compress image and optionally encrypt\nif [[ $ENABLE_ENCRYPTION -eq 0 ]]; then\n\techo \"Compressing disk image...\"\n\thdiutil convert ${HDIUTIL_VERBOSITY} \"${DMG_TEMP_NAME}\" -format ${FORMAT} ${IMAGEKEY} -o \"${DMG_DIR}/${DMG_NAME}\"\nelse\n\techo \"Compressing and encrypting disk image...\"\n\techo \"NOTE: hdiutil will only prompt a single time for a password - ensure entry is correct.\"\n\thdiutil convert ${HDIUTIL_VERBOSITY} \"${DMG_TEMP_NAME}\" -format ${FORMAT} ${IMAGEKEY} -encryption AES-${AESBITS} -stdinpass -o \"${DMG_DIR}/${DMG_NAME}\"\nfi\nrm -f \"${DMG_TEMP_NAME}\"\n\n# Adding EULA resources\nif [[ -n \"${EULA_RSRC}\" && \"${EULA_RSRC}\" != \"-null-\" ]]; then\n\techo \"Adding EULA resources...\"\n\t#\n\t# Use udifrez instead flatten/rez/unflatten\n\t# https://github.com/create-dmg/create-dmg/issues/109\n\t#\n\t# Based on a thread from dawn2dusk & peterguy\n\t# https://developer.apple.com/forums/thread/668084\n\t#\n\tEULA_RESOURCES_FILE=$(mktemp -t createdmg.tmp.XXXXXXXXXX)\n\tEULA_FORMAT=$(file -b ${EULA_RSRC})\n\tif [[ ${EULA_FORMAT} == 'Rich Text Format data'* ]] ; then\n\t\tEULA_FORMAT='RTF '\n\telse\n\t\tEULA_FORMAT='TEXT'\n\tfi\n\t# Encode the EULA to base64\n\t# Replace 'openssl base64' with 'base64' if Mac OS X 10.6 support is no more needed\n\t# EULA_DATA=\"$(base64 -b 52 \"${EULA_RSRC}\" | sed s$'/^\\(.*\\)$/\\t\\t\\t\\\\1/')\"\n\tEULA_DATA=\"$(openssl base64 -in \"${EULA_RSRC}\" | tr -d '\\n' | awk '{gsub(/.{52}/,\"&\\n\")}1' | sed s$'/^\\(.*\\)$/\\t\\t\\t\\\\1/')\"\n\t# Fill the template with the custom EULA contents\n\teval \"cat > \\\"${EULA_RESOURCES_FILE}\\\" <<EOF\n\t$(<${CDMG_SUPPORT_DIR}/eula-resources-template.xml)\n\tEOF\n\t\"\n\t# Apply the resources\n\thdiutil udifrez -xml \"${EULA_RESOURCES_FILE}\" '' -quiet \"${DMG_DIR}/${DMG_NAME}\" || {\n\t\techo \"Failed to add the EULA license\"\n\t\texit 1\n\t}\n\techo \"Successfully added the EULA license\"\nfi\n\n# Enable \"internet\", whatever that is\nif [[ ! -z \"${NOINTERNET}\" && \"${NOINTERNET}\" == 1 ]]; then\n\techo \"Not setting 'internet-enable' on the dmg, per caller request\"\nelse\n\t# Check if hdiutil supports internet-enable\n\t# Support was removed in macOS 10.15. See https://github.com/andreyvit/create-dmg/issues/76\n\tif hdiutil internet-enable -help >/dev/null 2>/dev/null; then\n\t\thdiutil internet-enable -yes \"${DMG_DIR}/${DMG_NAME}\"\n\telse\n\t\techo \"hdiutil does not support internet-enable. Note it was removed in macOS 10.15.\"\n\tfi\nfi\n\nif [[ -n \"${SIGNATURE}\" && \"${SIGNATURE}\" != \"-null-\" ]]; then\n\techo \"Codesign started\"\n\tcodesign -s \"${SIGNATURE}\" \"${DMG_DIR}/${DMG_NAME}\"\n\tdmgsignaturecheck=\"$(codesign --verify --deep --verbose=2 --strict \"${DMG_DIR}/${DMG_NAME}\" 2>&1 >/dev/null)\"\n\tif [ $? -eq 0 ]; then\n\t\techo \"The disk image is now codesigned\"\n\telse\n\t\techo \"The signature seems invalid${NC}\"\n\t\texit 1\n\tfi\nfi\n\nif [[ -n \"${NOTARIZE}\" && \"${NOTARIZE}\" != \"-null-\" ]]; then\n\techo \"Notarization started\"\n\txcrun notarytool submit \"${DMG_DIR}/${DMG_NAME}\" --keychain-profile \"${NOTARIZE}\" --wait\n\techo \"Stapling the notarization ticket\"\n\tstaple=\"$(xcrun stapler staple \"${DMG_DIR}/${DMG_NAME}\")\"\n\tif [ $? -eq 0 ]; then\n\t\techo \"The disk image is now notarized\"\n\telse\n\t\techo \"$staple\"\n\t\techo \"The notarization failed with error $?\"\n\t\texit 1\n\tfi\nfi\n\n# All done!\necho \"Disk image done\"\nexit 0\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/macos/dmg/eula-resources-template.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>LPic</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>Attributes</key>\n\t\t\t<string>0x0000</string>\n\t\t\t<key>Data</key>\n\t\t\t<data>\n\t\t\tAAAAAgAAAAAAAAAAAAQAAA==\n\t\t\t</data>\n\t\t\t<key>ID</key>\n\t\t\t<string>5000</string>\n\t\t\t<key>Name</key>\n\t\t\t<string></string>\n\t\t</dict>\n\t</array>\n\t<key>STR#</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>Attributes</key>\n\t\t\t<string>0x0000</string>\n\t\t\t<key>Data</key>\n\t\t\t<data>\n\t\t\tAAYNRW5nbGlzaCB0ZXN0MQVBZ3JlZQhEaXNhZ3JlZQVQcmludAdT\n\t\t\tYXZlLi4ueklmIHlvdSBhZ3JlZSB3aXRoIHRoZSB0ZXJtcyBvZiB0\n\t\t\taGlzIGxpY2Vuc2UsIGNsaWNrICJBZ3JlZSIgdG8gYWNjZXNzIHRo\n\t\t\tZSBzb2Z0d2FyZS4gIElmIHlvdSBkbyBub3QgYWdyZWUsIHByZXNz\n\t\t\tICJEaXNhZ3JlZS4i\n\t\t\t</data>\n\t\t\t<key>ID</key>\n\t\t\t<string>5000</string>\n\t\t\t<key>Name</key>\n\t\t\t<string>English buttons</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>Attributes</key>\n\t\t\t<string>0x0000</string>\n\t\t\t<key>Data</key>\n\t\t\t<data>\n\t\t\tAAYHRW5nbGlzaAVBZ3JlZQhEaXNhZ3JlZQVQcmludAdTYXZlLi4u\n\t\t\te0lmIHlvdSBhZ3JlZSB3aXRoIHRoZSB0ZXJtcyBvZiB0aGlzIGxp\n\t\t\tY2Vuc2UsIHByZXNzICJBZ3JlZSIgdG8gaW5zdGFsbCB0aGUgc29m\n\t\t\tdHdhcmUuICBJZiB5b3UgZG8gbm90IGFncmVlLCBwcmVzcyAiRGlz\n\t\t\tYWdyZWUiLg==\n\t\t\t</data>\n\t\t\t<key>ID</key>\n\t\t\t<string>5002</string>\n\t\t\t<key>Name</key>\n\t\t\t<string>English</string>\n\t\t</dict>\n\t</array>\n\t<key>${EULA_FORMAT}</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>Attributes</key>\n\t\t\t<string>0x0000</string>\n\t\t\t<key>Data</key>\n\t\t\t<data>\n${EULA_DATA}\n\t\t\t</data>\n\t\t\t<key>ID</key>\n\t\t\t<string>5000</string>\n\t\t\t<key>Name</key>\n\t\t\t<string>English</string>\n\t\t</dict>\n\t</array>\n\t<key>TMPL</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>Attributes</key>\n\t\t\t<string>0x0000</string>\n\t\t\t<key>Data</key>\n\t\t\t<data>\n\t\t\tE0RlZmF1bHQgTGFuZ3VhZ2UgSUREV1JEBUNvdW50T0NOVAQqKioq\n\t\t\tTFNUQwtzeXMgbGFuZyBJRERXUkQebG9jYWwgcmVzIElEIChvZmZz\n\t\t\tZXQgZnJvbSA1MDAwRFdSRBAyLWJ5dGUgbGFuZ3VhZ2U/RFdSRAQq\n\t\t\tKioqTFNURQ==\n\t\t\t</data>\n\t\t\t<key>ID</key>\n\t\t\t<string>128</string>\n\t\t\t<key>Name</key>\n\t\t\t<string>LPic</string>\n\t\t</dict>\n\t</array>\n\t<key>styl</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>Attributes</key>\n\t\t\t<string>0x0000</string>\n\t\t\t<key>Data</key>\n\t\t\t<data>\n\t\t\tAAMAAAAAAAwACQAUAAAAAAAAAAAAAAAAACcADAAJABQBAAAAAAAA\n\t\t\tAAAAAAAAKgAMAAkAFAAAAAAAAAAAAAA=\n\t\t\t</data>\n\t\t\t<key>ID</key>\n\t\t\t<string>5000</string>\n\t\t\t<key>Name</key>\n\t\t\t<string>English</string>\n\t\t</dict>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/macos/dmg/mod.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{app, icon::create_icns_file};\nuse crate::{\n  bundle::{settings::Arch, Bundle},\n  error::{Context, ErrorExt},\n  utils::CommandExt,\n  PackageType, Settings,\n};\n\nuse std::{\n  env,\n  fs::{self, write},\n  path::PathBuf,\n  process::{Command, Stdio},\n};\n\npub struct Bundled {\n  pub dmg: Vec<PathBuf>,\n  pub app: Vec<PathBuf>,\n}\n\n/// Bundles the project.\n/// Returns a vector of PathBuf that shows where the DMG was created.\npub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<Bundled> {\n  // generate the .app bundle if needed\n  let app_bundle_paths = if !bundles\n    .iter()\n    .any(|bundle| bundle.package_type == PackageType::MacOsBundle)\n  {\n    app::bundle_project(settings)?\n  } else {\n    Vec::new()\n  };\n\n  // get the target path\n  let output_path = settings.project_out_directory().join(\"bundle/dmg\");\n  let package_base_name = format!(\n    \"{}_{}_{}\",\n    settings.product_name(),\n    settings.version_string(),\n    match settings.binary_arch() {\n      Arch::X86_64 => \"x64\",\n      Arch::AArch64 => \"aarch64\",\n      Arch::Universal => \"universal\",\n      target => {\n        return Err(crate::Error::ArchError(format!(\n          \"Unsupported architecture: {target:?}\"\n        )));\n      }\n    }\n  );\n  let dmg_name = format!(\"{}.dmg\", &package_base_name);\n  let dmg_path = output_path.join(&dmg_name);\n\n  let product_name = settings.product_name();\n  let bundle_file_name = format!(\"{product_name}.app\");\n  let bundle_dir = settings.project_out_directory().join(\"bundle/macos\");\n\n  let support_directory_path = output_path\n    .parent()\n    .unwrap()\n    .join(\"share/create-dmg/support\");\n\n  for path in &[&support_directory_path, &output_path] {\n    if path.exists() {\n      fs::remove_dir_all(path).fs_context(\"failed to remove old dmg\", path.to_path_buf())?;\n    }\n    fs::create_dir_all(path).fs_context(\"failed to create output directory\", path.to_path_buf())?;\n  }\n\n  // create paths for script\n  let bundle_script_path = output_path.join(\"bundle_dmg.sh\");\n\n  log::info!(action = \"Bundling\"; \"{} ({})\", dmg_name, dmg_path.display());\n\n  // write the scripts\n  write(&bundle_script_path, include_str!(\"./bundle_dmg\"))?;\n  write(\n    support_directory_path.join(\"template.applescript\"),\n    include_str!(\"./template.applescript\"),\n  )?;\n  write(\n    support_directory_path.join(\"eula-resources-template.xml\"),\n    include_str!(\"./eula-resources-template.xml\"),\n  )?;\n\n  // chmod script for execution\n  Command::new(\"chmod\")\n    .arg(\"777\")\n    .arg(&bundle_script_path)\n    .current_dir(&output_path)\n    .stdout(Stdio::piped())\n    .stderr(Stdio::piped())\n    .output()\n    .expect(\"Failed to chmod script\");\n\n  let dmg_settings = settings.dmg();\n\n  let app_position = &dmg_settings.app_position;\n  let application_folder_position = &dmg_settings.application_folder_position;\n  let window_size = &dmg_settings.window_size;\n\n  let app_position_x = app_position.x.to_string();\n  let app_position_y = app_position.y.to_string();\n  let application_folder_position_x = application_folder_position.x.to_string();\n  let application_folder_position_y = application_folder_position.y.to_string();\n  let window_size_width = window_size.width.to_string();\n  let window_size_height = window_size.height.to_string();\n\n  let mut bundle_dmg_cmd = Command::new(&bundle_script_path);\n\n  bundle_dmg_cmd.args([\n    \"--volname\",\n    product_name,\n    \"--icon\",\n    &bundle_file_name,\n    &app_position_x,\n    &app_position_y,\n    \"--app-drop-link\",\n    &application_folder_position_x,\n    &application_folder_position_y,\n    \"--window-size\",\n    &window_size_width,\n    &window_size_height,\n    \"--hide-extension\",\n    &bundle_file_name,\n  ]);\n\n  let window_position = dmg_settings\n    .window_position\n    .as_ref()\n    .map(|position| (position.x.to_string(), position.y.to_string()));\n\n  if let Some(window_position) = &window_position {\n    bundle_dmg_cmd.arg(\"--window-pos\");\n    bundle_dmg_cmd.arg(&window_position.0);\n    bundle_dmg_cmd.arg(&window_position.1);\n  }\n\n  let background_path = if let Some(background_path) = &dmg_settings.background {\n    Some(env::current_dir()?.join(background_path))\n  } else {\n    None\n  };\n\n  if let Some(background_path) = &background_path {\n    bundle_dmg_cmd.arg(\"--background\");\n    bundle_dmg_cmd.arg(background_path);\n  }\n\n  let icns_icon_path = create_icns_file(&output_path, settings)?;\n  if let Some(icon) = &icns_icon_path {\n    bundle_dmg_cmd.arg(\"--volicon\");\n    bundle_dmg_cmd.arg(icon);\n  }\n\n  let license_path = if let Some(license_path) = settings.license_file() {\n    Some(env::current_dir()?.join(license_path))\n  } else {\n    None\n  };\n\n  if let Some(license_path) = &license_path {\n    bundle_dmg_cmd.arg(\"--eula\");\n    bundle_dmg_cmd.arg(license_path);\n  }\n\n  // Issue #592 - Building MacOS dmg files on CI\n  // https://github.com/tauri-apps/tauri/issues/592\n  if env::var_os(\"TAURI_BUNDLER_DMG_IGNORE_CI\").unwrap_or_default() != \"true\" {\n    if let Some(value) = env::var_os(\"CI\") {\n      if value == \"true\" {\n        bundle_dmg_cmd.arg(\"--skip-jenkins\");\n      }\n    }\n  }\n\n  log::info!(action = \"Running\"; \"bundle_dmg.sh\");\n\n  // execute the bundle script\n  bundle_dmg_cmd\n    .current_dir(bundle_dir.clone())\n    .args(vec![dmg_name.as_str(), bundle_file_name.as_str()])\n    .output_ok()\n    .context(\"error running bundle_dmg.sh\")?;\n\n  fs::rename(bundle_dir.join(dmg_name), dmg_path.clone())?;\n\n  // Sign DMG if needed\n  // skipping self-signing DMGs https://github.com/tauri-apps/tauri/issues/12288\n  let identity = settings.macos().signing_identity.as_deref();\n  if !settings.no_sign() && identity != Some(\"-\") {\n    if let Some(keychain) = super::sign::keychain(identity)? {\n      super::sign::sign(\n        &keychain,\n        vec![super::sign::SignTarget {\n          path: dmg_path.clone(),\n          is_an_executable: false,\n        }],\n        settings,\n      )?;\n    }\n  }\n\n  Ok(Bundled {\n    dmg: vec![dmg_path],\n    app: app_bundle_paths,\n  })\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/macos/dmg/template.applescript",
    "content": "on run (volumeName)\n\ttell application \"Finder\"\n\t\ttell disk (volumeName as string)\n\t\t\topen\n\n\t\t\tset theXOrigin to WINX\n\t\t\tset theYOrigin to WINY\n\t\t\tset theWidth to WINW\n\t\t\tset theHeight to WINH\n\n\t\t\tset theBottomRightX to (theXOrigin + theWidth)\n\t\t\tset theBottomRightY to (theYOrigin + theHeight)\n\t\t\tset dsStore to \"\\\"\" & \"/Volumes/\" & volumeName & \"/\" & \".DS_STORE\\\"\"\n\n\t\t\ttell container window\n\t\t\t\tset current view to icon view\n\t\t\t\tset toolbar visible to false\n\t\t\t\tset statusbar visible to false\n\t\t\t\tset the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY}\n\t\t\t\tset statusbar visible to false\n\t\t\t\tREPOSITION_HIDDEN_FILES_CLAUSE\n\t\t\tend tell\n\n\t\t\tset opts to the icon view options of container window\n\t\t\ttell opts\n\t\t\t\tset icon size to ICON_SIZE\n\t\t\t\tset text size to TEXT_SIZE\n\t\t\t\tset arrangement to not arranged\n\t\t\tend tell\n\t\t\tBACKGROUND_CLAUSE\n\n\t\t\t-- Positioning\n\t\t\tPOSITION_CLAUSE\n\n\t\t\t-- Hiding\n\t\t\tHIDING_CLAUSE\n\n\t\t\t-- Application and QL Link Clauses\n\t\t\tAPPLICATION_CLAUSE\n\t\t\tQL_CLAUSE\n\t\t\tclose\n\t\t\topen\n\t\t\t-- Force saving of the size\n\t\t\tdelay 1\n\n\t\t\ttell container window\n\t\t\t\tset statusbar visible to false\n\t\t\t\tset the bounds to {theXOrigin, theYOrigin, theBottomRightX - 10, theBottomRightY - 10}\n\t\t\tend tell\n\t\tend tell\n\n\t\tdelay 1\n\n\t\ttell disk (volumeName as string)\n\t\t\ttell container window\n\t\t\t\tset statusbar visible to false\n\t\t\t\tset the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY}\n\t\t\tend tell\n\t\tend tell\n\n\t\t--give the finder some time to write the .DS_Store file\n\t\tdelay 3\n\n\t\tset waitTime to 0\n\t\tset ejectMe to false\n\t\trepeat while ejectMe is false\n\t\t\tdelay 1\n\t\t\tset waitTime to waitTime + 1\n\t\t\t\n\t\t\tif (do shell script \"[ -f \" & dsStore & \" ]; echo $?\") = \"0\" then set ejectMe to true\n\t\tend repeat\n\t\tlog \"waited \" & waitTime & \" seconds for .DS_STORE to be created.\"\n\tend tell\nend run\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/macos/icon.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::bundle::Settings;\nuse crate::utils::{self, fs_utils};\nuse std::{\n  cmp::min,\n  ffi::OsStr,\n  fs::{self, File},\n  io::{self, BufWriter},\n  path::{Path, PathBuf},\n};\n\nuse image::GenericImageView;\n\n// Given a list of icon files, try to produce an ICNS file in the out_dir\n// and return the path to it.  Returns `Ok(None)` if no usable icons\n// were provided.\npub fn create_icns_file(out_dir: &Path, settings: &Settings) -> crate::Result<Option<PathBuf>> {\n  if settings.icon_files().count() == 0 {\n    return Ok(None);\n  }\n\n  // If one of the icon files is already an ICNS file, just use that.\n  for icon_path in settings.icon_files() {\n    let icon_path = icon_path?;\n    if icon_path.extension() == Some(OsStr::new(\"icns\")) {\n      let mut dest_path = out_dir.to_path_buf();\n      dest_path.push(icon_path.file_name().expect(\"Could not get icon filename\"));\n      fs_utils::copy_file(&icon_path, &dest_path)?;\n      return Ok(Some(dest_path));\n    }\n  }\n\n  // Otherwise, read available images and pack them into a new ICNS file.\n  let mut family = icns::IconFamily::new();\n\n  fn add_icon_to_family(\n    icon: image::DynamicImage,\n    density: u32,\n    family: &mut icns::IconFamily,\n  ) -> io::Result<()> {\n    // Try to add this image to the icon family.  Ignore images whose sizes\n    // don't map to any ICNS icon type; print warnings and skip images that\n    // fail to encode.\n    match icns::IconType::from_pixel_size_and_density(icon.width(), icon.height(), density) {\n      Some(icon_type) => {\n        if !family.has_icon_with_type(icon_type) {\n          let icon = make_icns_image(icon)?;\n          family.add_icon_with_type(&icon, icon_type)?;\n        }\n        Ok(())\n      }\n      None => Err(io::Error::new(\n        io::ErrorKind::InvalidData,\n        \"No matching IconType\",\n      )),\n    }\n  }\n\n  let mut images_to_resize: Vec<(image::DynamicImage, u32, u32)> = vec![];\n  for icon_path in settings.icon_files() {\n    let icon_path = icon_path?;\n    let icon = image::open(&icon_path)?;\n    let density = if utils::is_retina(&icon_path) { 2 } else { 1 };\n    let (w, h) = icon.dimensions();\n    let orig_size = min(w, h);\n    let next_size_down = 2f32.powf((orig_size as f32).log2().floor()) as u32;\n    if orig_size > next_size_down {\n      images_to_resize.push((icon, next_size_down, density));\n    } else {\n      add_icon_to_family(icon, density, &mut family)?;\n    }\n  }\n\n  for (icon, next_size_down, density) in images_to_resize {\n    let icon = icon.resize_exact(\n      next_size_down,\n      next_size_down,\n      image::imageops::FilterType::Lanczos3,\n    );\n    add_icon_to_family(icon, density, &mut family)?;\n  }\n\n  if !family.is_empty() {\n    fs::create_dir_all(out_dir)?;\n    let mut dest_path = out_dir.to_path_buf();\n    dest_path.push(settings.product_name());\n    dest_path.set_extension(\"icns\");\n    let icns_file = BufWriter::new(File::create(&dest_path)?);\n    family.write(icns_file)?;\n    Ok(Some(dest_path))\n  } else {\n    Err(crate::Error::GenericError(\n      \"No usable Icon files found\".to_owned(),\n    ))\n  }\n}\n\n// Converts an image::DynamicImage into an icns::Image.\nfn make_icns_image(img: image::DynamicImage) -> io::Result<icns::Image> {\n  let pixel_format = match img.color() {\n    image::ColorType::Rgba8 => icns::PixelFormat::RGBA,\n    image::ColorType::Rgb8 => icns::PixelFormat::RGB,\n    image::ColorType::La8 => icns::PixelFormat::GrayAlpha,\n    image::ColorType::L8 => icns::PixelFormat::Gray,\n    _ => {\n      let msg = format!(\"unsupported ColorType: {:?}\", img.color());\n      return Err(io::Error::new(io::ErrorKind::InvalidData, msg));\n    }\n  };\n  icns::Image::from_data(pixel_format, img.width(), img.height(), img.into_bytes())\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/macos/ios.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// An iOS package is laid out like:\n//\n// Foobar.app         # Actually a directory\n//     Foobar             # The main binary executable of the app\n//     Info.plist         # An XML file containing the app's metadata\n//     ...                # Icons and other resource files\n//\n// See https://developer.apple.com/go/?id=bundle-structure for a full\n// explanation.\n\nuse crate::{\n  error::{Context, ErrorExt},\n  utils::{self, fs_utils},\n  Settings,\n};\n\nuse image::{codecs::png::PngDecoder, GenericImageView, ImageDecoder};\n\nuse std::{\n  collections::BTreeSet,\n  ffi::OsStr,\n  fs::{self, File},\n  io::{BufReader, Write},\n  path::{Path, PathBuf},\n};\n\n/// Bundles the project.\n/// Returns a vector of PathBuf that shows where the .app was created.\npub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {\n  log::warn!(\"iOS bundle support is still experimental.\");\n\n  let app_product_name = format!(\"{}.app\", settings.product_name());\n\n  let app_bundle_path = settings\n    .project_out_directory()\n    .join(\"bundle/ios\")\n    .join(&app_product_name);\n\n  log::info!(action = \"Bundling\"; \"{} ({})\", app_product_name, app_bundle_path.display());\n\n  if app_bundle_path.exists() {\n    fs::remove_dir_all(&app_bundle_path)\n      .fs_context(\"failed to remove old app bundle\", &app_bundle_path)?;\n  }\n  fs::create_dir_all(&app_bundle_path)\n    .fs_context(\"failed to create bundle directory\", &app_bundle_path)?;\n\n  for src in settings.resource_files() {\n    let src = src?;\n    let dest = app_bundle_path.join(tauri_utils::resources::resource_relpath(&src));\n    fs_utils::copy_file(&src, &dest)\n      .with_context(|| format!(\"Failed to copy resource file {src:?}\"))?;\n  }\n\n  let icon_filenames = generate_icon_files(&app_bundle_path, settings)\n    .with_context(|| \"Failed to create app icons\")?;\n  generate_info_plist(&app_bundle_path, settings, &icon_filenames)\n    .with_context(|| \"Failed to create Info.plist\")?;\n\n  for bin in settings.binaries() {\n    let bin_path = settings.binary_path(bin);\n    fs_utils::copy_file(&bin_path, &app_bundle_path.join(bin.name()))\n      .with_context(|| format!(\"Failed to copy binary from {bin_path:?}\"))?;\n  }\n\n  Ok(vec![app_bundle_path])\n}\n\n/// Generate the icon files and store them under the `bundle_dir`.\nfn generate_icon_files(bundle_dir: &Path, settings: &Settings) -> crate::Result<Vec<String>> {\n  let mut filenames = Vec::new();\n  {\n    let mut get_dest_path = |width: u32, height: u32, is_retina: bool| {\n      let filename = format!(\n        \"icon_{}x{}{}.png\",\n        width,\n        height,\n        if is_retina { \"@2x\" } else { \"\" }\n      );\n      let path = bundle_dir.join(&filename);\n      filenames.push(filename);\n      path\n    };\n    let mut sizes = BTreeSet::new();\n    // Prefer PNG files.\n    for icon_path in settings.icon_files() {\n      let icon_path = icon_path?;\n      if icon_path.extension() != Some(OsStr::new(\"png\")) {\n        continue;\n      }\n      let decoder = PngDecoder::new(BufReader::new(File::open(&icon_path)?))?;\n      let width = decoder.dimensions().0;\n      let height = decoder.dimensions().1;\n      let is_retina = utils::is_retina(&icon_path);\n      if !sizes.contains(&(width, height, is_retina)) {\n        sizes.insert((width, height, is_retina));\n        let dest_path = get_dest_path(width, height, is_retina);\n        fs_utils::copy_file(&icon_path, &dest_path)?;\n      }\n    }\n    // Fall back to non-PNG files for any missing sizes.\n    for icon_path in settings.icon_files() {\n      let icon_path = icon_path?;\n      if icon_path.extension() == Some(OsStr::new(\"png\")) {\n        continue;\n      } else if icon_path.extension() == Some(OsStr::new(\"icns\")) {\n        let icon_family = icns::IconFamily::read(File::open(&icon_path)?)?;\n        for icon_type in icon_family.available_icons() {\n          let width = icon_type.screen_width();\n          let height = icon_type.screen_height();\n          let is_retina = icon_type.pixel_density() > 1;\n          if !sizes.contains(&(width, height, is_retina)) {\n            sizes.insert((width, height, is_retina));\n            let dest_path = get_dest_path(width, height, is_retina);\n            let icon = icon_family.get_icon_with_type(icon_type)?;\n            icon.write_png(File::create(dest_path)?)?;\n          }\n        }\n      } else {\n        let icon = image::open(&icon_path)?;\n        let (width, height) = icon.dimensions();\n        let is_retina = utils::is_retina(&icon_path);\n        if !sizes.contains(&(width, height, is_retina)) {\n          sizes.insert((width, height, is_retina));\n          let dest_path = get_dest_path(width, height, is_retina);\n          icon.write_to(\n            &mut fs_utils::create_file(&dest_path)?,\n            image::ImageFormat::Png,\n          )?;\n        }\n      }\n    }\n  }\n  Ok(filenames)\n}\n\n/// Generates the Info.plist file\nfn generate_info_plist(\n  bundle_dir: &Path,\n  settings: &Settings,\n  icon_filenames: &[String],\n) -> crate::Result<()> {\n  let file = &mut fs_utils::create_file(&bundle_dir.join(\"Info.plist\"))?;\n  writeln!(\n    file,\n    \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n\\\n          <!DOCTYPE plist PUBLIC \\\"-//Apple Computer//DTD PLIST 1.0//EN\\\" \\\n          \\\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\\\">\\n\\\n          <plist version=\\\"1.0\\\">\\n\\\n          <dict>\"\n  )?;\n\n  writeln!(\n    file,\n    \"  <key>CFBundleIdentifier</key>\\n  <string>{}</string>\",\n    settings.bundle_identifier()\n  )?;\n  writeln!(\n    file,\n    \"  <key>CFBundleDisplayName</key>\\n  <string>{}</string>\",\n    settings.product_name()\n  )?;\n  writeln!(\n    file,\n    \"  <key>CFBundleName</key>\\n  <string>{}</string>\",\n    settings.product_name()\n  )?;\n  writeln!(\n    file,\n    \"  <key>CFBundleExecutable</key>\\n  <string>{}</string>\",\n    settings.main_binary_name()?\n  )?;\n  writeln!(\n    file,\n    \"  <key>CFBundleVersion</key>\\n  <string>{}</string>\",\n    settings\n      .ios()\n      .bundle_version\n      .as_deref()\n      .unwrap_or_else(|| settings.version_string())\n  )?;\n  writeln!(\n    file,\n    \"  <key>CFBundleShortVersionString</key>\\n  <string>{}</string>\",\n    settings.version_string()\n  )?;\n  writeln!(\n    file,\n    \"  <key>CFBundleDevelopmentRegion</key>\\n  <string>en_US</string>\"\n  )?;\n\n  if !icon_filenames.is_empty() {\n    writeln!(file, \"  <key>CFBundleIconFiles</key>\\n  <array>\")?;\n    for filename in icon_filenames {\n      writeln!(file, \"    <string>{filename}</string>\")?;\n    }\n    writeln!(file, \"  </array>\")?;\n  }\n  writeln!(file, \"  <key>LSRequiresIPhoneOS</key>\\n  <true/>\")?;\n  writeln!(file, \"</dict>\\n</plist>\")?;\n  file.flush()?;\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/macos/mod.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npub mod app;\npub mod dmg;\npub mod icon;\npub mod ios;\npub mod sign;\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/macos/sign.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  env::{var, var_os},\n  ffi::OsString,\n  path::PathBuf,\n};\n\nuse crate::{error::NotarizeAuthError, Entitlements, Settings};\n\npub struct SignTarget {\n  pub path: PathBuf,\n  pub is_an_executable: bool,\n}\n\npub fn keychain(identity: Option<&str>) -> crate::Result<Option<tauri_macos_sign::Keychain>> {\n  if let (Some(certificate_encoded), Some(certificate_password)) = (\n    var_os(\"APPLE_CERTIFICATE\"),\n    var_os(\"APPLE_CERTIFICATE_PASSWORD\"),\n  ) {\n    // import user certificate - useful for CI build\n    let keychain =\n      tauri_macos_sign::Keychain::with_certificate(&certificate_encoded, &certificate_password)\n        .map_err(Box::new)?;\n    if let Some(identity) = identity {\n      let certificate_identity = keychain.signing_identity();\n      if !certificate_identity.contains(identity) {\n        return Err(crate::Error::GenericError(format!(\n          \"certificate from APPLE_CERTIFICATE \\\"{certificate_identity}\\\" environment variable does not match provided identity \\\"{identity}\\\"\"\n        )));\n      }\n    }\n    Ok(Some(keychain))\n  } else if let Some(identity) = identity {\n    Ok(Some(tauri_macos_sign::Keychain::with_signing_identity(\n      identity,\n    )))\n  } else {\n    Ok(None)\n  }\n}\n\npub fn sign(\n  keychain: &tauri_macos_sign::Keychain,\n  targets: Vec<SignTarget>,\n  settings: &Settings,\n) -> crate::Result<()> {\n  log::info!(action = \"Signing\"; \"with identity \\\"{}\\\"\", keychain.signing_identity());\n\n  for target in targets {\n    let (entitlements_path, _temp_file) = match settings.macos().entitlements.as_ref() {\n      Some(Entitlements::Path(path)) => (Some(path.to_owned()), None),\n      Some(Entitlements::Plist(plist)) => {\n        let mut temp_file = tempfile::NamedTempFile::new()?;\n        plist::to_writer_xml(temp_file.as_file_mut(), &plist)?;\n        (Some(temp_file.path().to_path_buf()), Some(temp_file))\n      }\n      None => (None, None),\n    };\n\n    keychain\n      .sign(\n        &target.path,\n        entitlements_path.as_deref(),\n        target.is_an_executable && settings.macos().hardened_runtime,\n      )\n      .map_err(Box::new)?;\n  }\n\n  Ok(())\n}\n\npub fn notarize(\n  keychain: &tauri_macos_sign::Keychain,\n  app_bundle_path: PathBuf,\n  credentials: &tauri_macos_sign::AppleNotarizationCredentials,\n) -> crate::Result<()> {\n  tauri_macos_sign::notarize(keychain, &app_bundle_path, credentials)\n    .map_err(Box::new)\n    .map_err(Into::into)\n}\n\npub fn notarize_without_stapling(\n  keychain: &tauri_macos_sign::Keychain,\n  app_bundle_path: PathBuf,\n  credentials: &tauri_macos_sign::AppleNotarizationCredentials,\n) -> crate::Result<()> {\n  tauri_macos_sign::notarize_without_stapling(keychain, &app_bundle_path, credentials)\n    .map_err(Box::new)\n    .map_err(Into::into)\n}\n\npub fn notarize_auth() -> Result<tauri_macos_sign::AppleNotarizationCredentials, NotarizeAuthError>\n{\n  match (\n    var_os(\"APPLE_ID\"),\n    var_os(\"APPLE_PASSWORD\"),\n    var_os(\"APPLE_TEAM_ID\"),\n  ) {\n    (Some(apple_id), Some(password), Some(team_id)) => {\n      Ok(tauri_macos_sign::AppleNotarizationCredentials::AppleId {\n        apple_id,\n        password,\n        team_id,\n      })\n    }\n    (Some(_apple_id), Some(_password), None) => Err(NotarizeAuthError::MissingTeamId),\n    _ => {\n      match (\n        var_os(\"APPLE_API_KEY\"),\n        var_os(\"APPLE_API_ISSUER\"),\n        var(\"APPLE_API_KEY_PATH\"),\n      ) {\n        (Some(key_id), Some(issuer), Ok(key_path)) => {\n          Ok(tauri_macos_sign::AppleNotarizationCredentials::ApiKey {\n            key_id,\n            key: tauri_macos_sign::ApiKey::Path(key_path.into()),\n            issuer,\n          })\n        }\n        (Some(key_id), Some(issuer), Err(_)) => {\n          let mut api_key_file_name = OsString::from(\"AuthKey_\");\n          api_key_file_name.push(&key_id);\n          api_key_file_name.push(\".p8\");\n          let mut key_path = None;\n\n          let mut search_paths = vec![\"./private_keys\".into()];\n          if let Some(home_dir) = dirs::home_dir() {\n            search_paths.push(home_dir.join(\"private_keys\"));\n            search_paths.push(home_dir.join(\".private_keys\"));\n            search_paths.push(home_dir.join(\".appstoreconnect\").join(\"private_keys\"));\n          }\n\n          for folder in search_paths {\n            if let Some(path) = find_api_key(folder, &api_key_file_name) {\n              key_path = Some(path);\n              break;\n            }\n          }\n\n          if let Some(key_path) = key_path {\n            Ok(tauri_macos_sign::AppleNotarizationCredentials::ApiKey {\n              key_id,\n              key: tauri_macos_sign::ApiKey::Path(key_path),\n              issuer,\n            })\n          } else {\n            Err(NotarizeAuthError::MissingApiKey {\n              file_name: api_key_file_name.to_string_lossy().into_owned(),\n            })\n          }\n        }\n        _ => Err(NotarizeAuthError::MissingCredentials),\n      }\n    }\n  }\n}\n\nfn find_api_key(folder: PathBuf, file_name: &OsString) -> Option<PathBuf> {\n  let path = folder.join(file_name);\n  if path.exists() {\n    Some(path)\n  } else {\n    None\n  }\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/platform.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::utils::CommandExt;\nuse std::process::Command;\n\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#[derive(Debug, PartialEq, Eq)]\nstruct RustCfg {\n  target_arch: Option<String>,\n}\n\nfn parse_rust_cfg(cfg: String) -> RustCfg {\n  let target_line = \"target_arch=\\\"\";\n  let mut target_arch = None;\n  for line in cfg.split('\\n') {\n    if line.starts_with(target_line) {\n      let len = target_line.len();\n      let arch = line.chars().skip(len).take(line.len() - len - 1).collect();\n      target_arch.replace(arch);\n    }\n  }\n  RustCfg { target_arch }\n}\n\n/// Try to determine the current target triple.\n///\n/// Returns a target triple (e.g. `x86_64-unknown-linux-gnu` or `i686-pc-windows-msvc`) or an\n/// `Error::Config` if the current config cannot be determined or is not some combination of the\n/// following values:\n/// `linux, mac, windows` -- `i686, x86, armv7` -- `gnu, musl, msvc`\n///\n/// * Errors:\n///     * Unexpected system config\npub fn target_triple() -> Result<String, crate::Error> {\n  let arch_res = Command::new(\"rustc\").args([\"--print\", \"cfg\"]).output_ok();\n\n  let arch = match arch_res {\n    Ok(output) => parse_rust_cfg(String::from_utf8_lossy(&output.stdout).into())\n      .target_arch\n      .expect(\"could not find `target_arch` when running `rustc --print cfg`.\"),\n    Err(err) => {\n      log::warn!(\n      \"failed to determine target arch using rustc, error: `{}`. The fallback is the architecture of the machine that compiled this crate.\",\n      err,\n    );\n      if cfg!(target_arch = \"x86\") {\n        \"i686\".into()\n      } else if cfg!(target_arch = \"x86_64\") {\n        \"x86_64\".into()\n      } else if cfg!(target_arch = \"arm\") {\n        \"armv7\".into()\n      } else if cfg!(target_arch = \"aarch64\") {\n        \"aarch64\".into()\n      } else if cfg!(target_arch = \"riscv64\") {\n        \"riscv64\".into()\n      } else {\n        return Err(crate::Error::ArchError(String::from(\n          \"Unable to determine target-architecture\",\n        )));\n      }\n    }\n  };\n\n  let os = if cfg!(target_os = \"linux\") {\n    \"unknown-linux\"\n  } else if cfg!(target_os = \"macos\") {\n    \"apple-darwin\"\n  } else if cfg!(target_os = \"windows\") {\n    \"pc-windows\"\n  } else if cfg!(target_os = \"freebsd\") {\n    \"unknown-freebsd\"\n  } else {\n    return Err(crate::Error::ArchError(String::from(\n      \"Unable to determine target-os\",\n    )));\n  };\n\n  let os = if cfg!(target_os = \"macos\") || cfg!(target_os = \"freebsd\") {\n    String::from(os)\n  } else {\n    let env = if cfg!(target_env = \"gnu\") {\n      \"gnu\"\n    } else if cfg!(target_env = \"musl\") {\n      \"musl\"\n    } else if cfg!(target_env = \"msvc\") {\n      \"msvc\"\n    } else {\n      return Err(crate::Error::ArchError(String::from(\n        \"Unable to determine target-environment\",\n      )));\n    };\n\n    format!(\"{os}-{env}\")\n  };\n\n  Ok(format!(\"{arch}-{os}\"))\n}\n\n#[cfg(test)]\nmod tests {\n  use super::RustCfg;\n\n  #[test]\n  fn parse_rust_cfg() {\n    assert_eq!(\n      super::parse_rust_cfg(\"target_arch\".into()),\n      RustCfg { target_arch: None }\n    );\n\n    assert_eq!(\n      super::parse_rust_cfg(\n        r#\"debug_assertions\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\"#\n          .into()\n      ),\n      RustCfg {\n        target_arch: Some(\"aarch64\".into())\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/settings.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::category::AppCategory;\nuse crate::{bundle::platform::target_triple, error::Context, utils::fs_utils};\npub use tauri_utils::config::WebviewInstallMode;\nuse tauri_utils::{\n  config::{\n    BundleType, DeepLinkProtocol, FileAssociation, NSISInstallerMode, NsisCompression,\n    RpmCompression,\n  },\n  platform::Target as TargetPlatform,\n  resources::{external_binaries, ResourcePaths},\n};\n\nuse std::{\n  collections::HashMap,\n  path::{Path, PathBuf},\n};\n\n/// The type of the package we're bundling.\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n#[non_exhaustive]\npub enum PackageType {\n  /// The macOS application bundle (.app).\n  MacOsBundle,\n  /// The iOS app bundle.\n  IosBundle,\n  /// The Windows bundle (.msi).\n  WindowsMsi,\n  /// The NSIS bundle (.exe).\n  Nsis,\n  /// The Linux Debian package bundle (.deb).\n  Deb,\n  /// The Linux RPM bundle (.rpm).\n  Rpm,\n  /// The Linux AppImage bundle (.AppImage).\n  AppImage,\n  /// The macOS DMG bundle (.dmg).\n  Dmg,\n  /// The Updater bundle.\n  Updater,\n}\n\nimpl From<BundleType> for PackageType {\n  fn from(bundle: BundleType) -> Self {\n    match bundle {\n      BundleType::Deb => Self::Deb,\n      BundleType::Rpm => Self::Rpm,\n      BundleType::AppImage => Self::AppImage,\n      BundleType::Msi => Self::WindowsMsi,\n      BundleType::Nsis => Self::Nsis,\n      BundleType::App => Self::MacOsBundle,\n      BundleType::Dmg => Self::Dmg,\n    }\n  }\n}\n\nimpl PackageType {\n  /// Maps a short name to a PackageType.\n  /// Possible values are \"deb\", \"ios\", \"msi\", \"app\", \"rpm\", \"appimage\", \"dmg\", \"updater\".\n  pub fn from_short_name(name: &str) -> Option<PackageType> {\n    // Other types we may eventually want to support: apk.\n    match name {\n      \"deb\" => Some(PackageType::Deb),\n      \"ios\" => Some(PackageType::IosBundle),\n      \"msi\" => Some(PackageType::WindowsMsi),\n      \"nsis\" => Some(PackageType::Nsis),\n      \"app\" => Some(PackageType::MacOsBundle),\n      \"rpm\" => Some(PackageType::Rpm),\n      \"appimage\" => Some(PackageType::AppImage),\n      \"dmg\" => Some(PackageType::Dmg),\n      \"updater\" => Some(PackageType::Updater),\n      _ => None,\n    }\n  }\n\n  /// Gets the short name of this PackageType.\n  #[allow(clippy::trivially_copy_pass_by_ref)]\n  pub fn short_name(&self) -> &'static str {\n    match *self {\n      PackageType::Deb => \"deb\",\n      PackageType::IosBundle => \"ios\",\n      PackageType::WindowsMsi => \"msi\",\n      PackageType::Nsis => \"nsis\",\n      PackageType::MacOsBundle => \"app\",\n      PackageType::Rpm => \"rpm\",\n      PackageType::AppImage => \"appimage\",\n      PackageType::Dmg => \"dmg\",\n      PackageType::Updater => \"updater\",\n    }\n  }\n\n  /// Gets the list of the possible package types.\n  pub fn all() -> &'static [PackageType] {\n    ALL_PACKAGE_TYPES\n  }\n\n  /// Gets a number representing priority which used to sort package types\n  /// in an order that guarantees that if a certain package type\n  /// depends on another (like Dmg depending on MacOsBundle), the dependency\n  /// will be built first\n  ///\n  /// The lower the number, the higher the priority\n  pub fn priority(&self) -> u32 {\n    match self {\n      PackageType::MacOsBundle => 0,\n      PackageType::IosBundle => 0,\n      PackageType::WindowsMsi => 0,\n      PackageType::Nsis => 0,\n      PackageType::Deb => 0,\n      PackageType::Rpm => 0,\n      PackageType::AppImage => 0,\n      PackageType::Dmg => 1,\n      PackageType::Updater => 2,\n    }\n  }\n}\n\nconst ALL_PACKAGE_TYPES: &[PackageType] = &[\n  #[cfg(target_os = \"linux\")]\n  PackageType::Deb,\n  #[cfg(target_os = \"macos\")]\n  PackageType::IosBundle,\n  #[cfg(target_os = \"windows\")]\n  PackageType::WindowsMsi,\n  // NSIS installers can be built on all platforms but it's hidden in the --help output on macOS/Linux.\n  PackageType::Nsis,\n  #[cfg(target_os = \"macos\")]\n  PackageType::MacOsBundle,\n  #[cfg(target_os = \"linux\")]\n  PackageType::Rpm,\n  #[cfg(target_os = \"macos\")]\n  PackageType::Dmg,\n  #[cfg(target_os = \"linux\")]\n  PackageType::AppImage,\n  PackageType::Updater,\n];\n\n/// The package settings.\n#[derive(Debug, Clone)]\npub struct PackageSettings {\n  /// the package's product name.\n  pub product_name: String,\n  /// the package's version.\n  pub version: String,\n  /// the package's description.\n  pub description: String,\n  /// the package's homepage.\n  pub homepage: Option<String>,\n  /// the package's authors.\n  pub authors: Option<Vec<String>>,\n  /// the default binary to run.\n  pub default_run: Option<String>,\n}\n\n/// The updater settings.\n#[derive(Debug, Default, Clone)]\npub struct UpdaterSettings {\n  /// Should generate v1 compatible zipped updater\n  pub v1_compatible: bool,\n  /// Signature public key.\n  pub pubkey: String,\n  /// Args to pass to `msiexec.exe` to run the updater on Windows.\n  pub msiexec_args: &'static [&'static str],\n}\n\n/// The Linux debian bundle settings.\n#[derive(Clone, Debug, Default)]\npub struct DebianSettings {\n  // OS-specific settings:\n  /// the list of debian dependencies.\n  pub depends: Option<Vec<String>>,\n  /// the list of debian dependencies recommendations.\n  pub recommends: Option<Vec<String>>,\n  /// the list of dependencies the package provides.\n  pub provides: Option<Vec<String>>,\n  /// the list of package conflicts.\n  pub conflicts: Option<Vec<String>>,\n  /// the list of package replaces.\n  pub replaces: Option<Vec<String>>,\n  /// List of custom files to add to the deb package.\n  /// Maps the path on the debian package to the path of the file to include (relative to the current working directory).\n  pub files: HashMap<PathBuf, PathBuf>,\n  /// Path to a custom desktop file Handlebars template.\n  ///\n  /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n  ///\n  /// Default file contents:\n  /// ```text\n  #[doc = include_str!(\"./linux/freedesktop/main.desktop\")]\n  /// ```\n  pub desktop_template: Option<PathBuf>,\n  /// Define the section in Debian Control file. See : <https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections>\n  pub section: Option<String>,\n  /// Change the priority of the Debian Package. By default, it is set to `optional`.\n  /// Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\n  pub priority: Option<String>,\n  /// Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\n  /// <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>\n  pub changelog: Option<PathBuf>,\n  /// Path to script that will be executed before the package is unpacked. See\n  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\n  pub pre_install_script: Option<PathBuf>,\n  /// Path to script that will be executed after the package is unpacked. See\n  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\n  pub post_install_script: Option<PathBuf>,\n  /// Path to script that will be executed before the package is removed. See\n  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\n  pub pre_remove_script: Option<PathBuf>,\n  /// Path to script that will be executed after the package is removed. See\n  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\n  pub post_remove_script: Option<PathBuf>,\n}\n\n/// The Linux AppImage bundle settings.\n#[derive(Clone, Debug, Default)]\npub struct AppImageSettings {\n  /// The files to include in the Appimage Binary.\n  pub files: HashMap<PathBuf, PathBuf>,\n  /// Whether to include gstreamer plugins for audio/media support.\n  pub bundle_media_framework: bool,\n  /// Whether to include the `xdg-open` binary.\n  pub bundle_xdg_open: bool,\n}\n\n/// The RPM bundle settings.\n#[derive(Clone, Debug, Default)]\npub struct RpmSettings {\n  /// The list of RPM dependencies your application relies on.\n  pub depends: Option<Vec<String>>,\n  /// the list of RPM dependencies your application recommends.\n  pub recommends: Option<Vec<String>>,\n  /// The list of RPM dependencies your application provides.\n  pub provides: Option<Vec<String>>,\n  /// The list of RPM dependencies your application conflicts with. They must not be present\n  /// in order for the package to be installed.\n  pub conflicts: Option<Vec<String>>,\n  /// The list of RPM dependencies your application supersedes - if this package is installed,\n  /// packages listed as \"obsoletes\" will be automatically removed (if they are present).\n  pub obsoletes: Option<Vec<String>>,\n  /// The RPM release tag.\n  pub release: String,\n  /// The RPM epoch.\n  pub epoch: u32,\n  /// List of custom files to add to the RPM package.\n  /// Maps the path on the RPM package to the path of the file to include (relative to the current working directory).\n  pub files: HashMap<PathBuf, PathBuf>,\n  /// Path to a custom desktop file Handlebars template.\n  ///\n  /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n  ///\n  /// Default file contents:\n  /// ```text\n  #[doc = include_str!(\"./linux/freedesktop/main.desktop\")]\n  /// ```\n  pub desktop_template: Option<PathBuf>,\n  /// Path to script that will be executed before the package is unpacked. See\n  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\n  pub pre_install_script: Option<PathBuf>,\n  /// Path to script that will be executed after the package is unpacked. See\n  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\n  pub post_install_script: Option<PathBuf>,\n  /// Path to script that will be executed before the package is removed. See\n  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\n  pub pre_remove_script: Option<PathBuf>,\n  /// Path to script that will be executed after the package is removed. See\n  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\n  pub post_remove_script: Option<PathBuf>,\n  /// Compression algorithm and level. Defaults to `Gzip` with level 6.\n  pub compression: Option<RpmCompression>,\n}\n\n/// Position coordinates struct.\n#[derive(Clone, Debug, Default)]\npub struct Position {\n  /// X coordinate.\n  pub x: u32,\n  /// Y coordinate.\n  pub y: u32,\n}\n\n/// Size of the window.\n#[derive(Clone, Debug, Default)]\npub struct Size {\n  /// Width of the window.\n  pub width: u32,\n  /// Height of the window.\n  pub height: u32,\n}\n\n/// The DMG bundle settings.\n#[derive(Clone, Debug, Default)]\npub struct DmgSettings {\n  /// Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.\n  pub background: Option<PathBuf>,\n  /// Position of volume window on screen.\n  pub window_position: Option<Position>,\n  /// Size of volume window.\n  pub window_size: Size,\n  /// Position of app file on window.\n  pub app_position: Position,\n  /// Position of application folder on window.\n  pub application_folder_position: Position,\n}\n\n/// The iOS bundle settings.\n#[derive(Clone, Debug, Default)]\npub struct IosSettings {\n  /// The version of the build that identifies an iteration of the bundle.\n  pub bundle_version: Option<String>,\n}\n\n/// The macOS bundle settings.\n#[derive(Clone, Debug, Default)]\npub struct MacOsSettings {\n  /// MacOS frameworks that need to be bundled with the app.\n  ///\n  /// Each string can either be the name of a framework (without the `.framework` extension, e.g. `\"SDL2\"`),\n  /// in which case we will search for that framework in the standard install locations (`~/Library/Frameworks/`, `/Library/Frameworks/`, and `/Network/Library/Frameworks/`),\n  /// or a path to a specific framework bundle (e.g. `./data/frameworks/SDL2.framework`).  Note that this setting just makes tauri-bundler copy the specified frameworks into the OS X app bundle\n  /// (under `Foobar.app/Contents/Frameworks/`); you are still responsible for:\n  ///\n  /// - arranging for the compiled binary to link against those frameworks (e.g. by emitting lines like `cargo:rustc-link-lib=framework=SDL2` from your `build.rs` script)\n  ///\n  /// - embedding the correct rpath in your binary (e.g. by running `install_name_tool -add_rpath \"@executable_path/../Frameworks\" path/to/binary` after compiling)\n  pub frameworks: Option<Vec<String>>,\n  /// List of custom files to add to the application bundle.\n  /// Maps the path in the Contents directory in the app to the path of the file to include (relative to the current working directory).\n  pub files: HashMap<PathBuf, PathBuf>,\n  /// The version of the build that identifies an iteration of the bundle.\n  pub bundle_version: Option<String>,\n  /// The name of the build that identifies a string of the bundle.\n  ///\n  /// If not set, defaults to the package's product name.\n  pub bundle_name: Option<String>,\n  /// A version string indicating the minimum MacOS version that the bundled app supports (e.g. `\"10.11\"`).\n  /// If you are using this config field, you may also want have your `build.rs` script emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.11`.\n  pub minimum_system_version: Option<String>,\n  /// The exception domain to use on the macOS .app bundle.\n  ///\n  /// This allows communication to the outside world e.g. a web server you're shipping.\n  pub exception_domain: Option<String>,\n  /// Code signing identity.\n  pub signing_identity: Option<String>,\n  /// Whether to wait for notarization to finish and `staple` the ticket onto the app.\n  ///\n  /// Gatekeeper will look for stapled tickets to tell whether your app was notarized without\n  /// reaching out to Apple's servers which is helpful in offline environments.\n  ///\n  /// Enabling this option will also result in `tauri build` not waiting for notarization to finish\n  /// which is helpful for the very first time your app is notarized as this can take multiple hours.\n  /// On subsequent runs, it's recommended to disable this setting again.\n  pub skip_stapling: bool,\n  /// Preserve the hardened runtime version flag, see <https://developer.apple.com/documentation/security/hardened_runtime>\n  ///\n  /// Settings this to `false` is useful when using an ad-hoc signature, making it less strict.\n  pub hardened_runtime: bool,\n  /// Provider short name for notarization.\n  pub provider_short_name: Option<String>,\n  /// Path or contents of the entitlements.plist file.\n  pub entitlements: Option<Entitlements>,\n  /// Path to the Info.plist file or raw plist value to merge with the bundle Info.plist.\n  pub info_plist: Option<PlistKind>,\n}\n\n/// Entitlements for macOS code signing.\n#[derive(Debug, Clone)]\npub enum Entitlements {\n  /// Path to the entitlements.plist file.\n  Path(PathBuf),\n  /// Raw plist::Value.\n  Plist(plist::Value),\n}\n\n/// Plist format.\n#[derive(Debug, Clone)]\npub enum PlistKind {\n  /// Path to a .plist file.\n  Path(PathBuf),\n  /// Raw plist value.\n  Plist(plist::Value),\n}\n\n/// Configuration for a target language for the WiX build.\n#[derive(Debug, Clone, Default)]\npub struct WixLanguageConfig {\n  /// The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.\n  pub locale_path: Option<PathBuf>,\n}\n\n/// The languages to build using WiX.\n#[derive(Debug, Clone)]\npub struct WixLanguage(pub Vec<(String, WixLanguageConfig)>);\n\nimpl Default for WixLanguage {\n  fn default() -> Self {\n    Self(vec![(\"en-US\".into(), Default::default())])\n  }\n}\n\n/// Settings specific to the WiX implementation.\n#[derive(Clone, Debug, Default)]\npub struct WixSettings {\n  /// MSI installer version in the format `major.minor.patch.build` (build is optional).\n  ///\n  /// Because a valid version is required for MSI installer, it will be derived from [`PackageSettings::version`] if this field is not set.\n  ///\n  /// The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\n  /// The third and fourth fields have a maximum value of 65,535.\n  ///\n  /// See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.\n  pub version: Option<String>,\n  /// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,\n  /// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\n  ///\n  /// By default, tauri generates this code by generating a Uuid v5 using the string `<productName>.exe.app.x64` in the DNS namespace.\n  /// You can use Tauri's CLI to generate and print this code for you by running `tauri inspect wix-upgrade-code`.\n  ///\n  /// It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code\n  /// whenever you want to change your product name.\n  pub upgrade_code: Option<uuid::Uuid>,\n  /// The app languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.\n  pub language: WixLanguage,\n  /// By default, the bundler uses an internal template.\n  /// This option allows you to define your own wix file.\n  pub template: Option<PathBuf>,\n  /// A list of paths to .wxs files with WiX fragments to use.\n  pub fragment_paths: Vec<PathBuf>,\n  /// The ComponentGroup element ids you want to reference from the fragments.\n  pub component_group_refs: Vec<String>,\n  /// The Component element ids you want to reference from the fragments.\n  pub component_refs: Vec<String>,\n  /// The FeatureGroup element ids you want to reference from the fragments.\n  pub feature_group_refs: Vec<String>,\n  /// The Feature element ids you want to reference from the fragments.\n  pub feature_refs: Vec<String>,\n  /// The Merge element ids you want to reference from the fragments.\n  pub merge_refs: Vec<String>,\n  /// Create an elevated update task within Windows Task Scheduler.\n  pub enable_elevated_update_task: bool,\n  /// Path to a bitmap file to use as the installation user interface banner.\n  /// This bitmap will appear at the top of all but the first page of the installer.\n  ///\n  /// The required dimensions are 493px × 58px.\n  pub banner_path: Option<PathBuf>,\n  /// Path to a bitmap file to use on the installation user interface dialogs.\n  /// It is used on the welcome and completion dialogs.\n  ///\n  /// The required dimensions are 493px × 312px.\n  pub dialog_image_path: Option<PathBuf>,\n  /// Enables FIPS compliant algorithms.\n  pub fips_compliant: bool,\n}\n\n/// Settings specific to the NSIS implementation.\n#[derive(Clone, Debug, Default)]\npub struct NsisSettings {\n  /// A custom .nsi template to use.\n  pub template: Option<PathBuf>,\n  /// The path to a bitmap file to display on the header of installers pages.\n  ///\n  /// The recommended dimensions are 150px x 57px.\n  pub header_image: Option<PathBuf>,\n  /// The path to a bitmap file for the Welcome page and the Finish page.\n  ///\n  /// The recommended dimensions are 164px x 314px.\n  pub sidebar_image: Option<PathBuf>,\n  /// The path to an icon file used as the installer icon.\n  pub installer_icon: Option<PathBuf>,\n  /// Whether the installation will be for all users or just the current user.\n  pub install_mode: NSISInstallerMode,\n  /// A list of installer languages.\n  /// By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.\n  /// To allow the user to select the language, set `display_language_selector` to `true`.\n  ///\n  /// See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.\n  pub languages: Option<Vec<String>>,\n  /// An key-value pair where the key is the language and the\n  /// value is the path to a custom `.nsi` file that holds the translated text for tauri's custom messages.\n  ///\n  /// See <https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-bundler/src/bundle/windows/nsis/languages/English.nsh> for an example `.nsi` file.\n  ///\n  /// **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`]languages array,\n  pub custom_language_files: Option<HashMap<String, PathBuf>>,\n  /// Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.\n  /// By default the OS language is selected, with a fallback to the first language in the `languages` array.\n  pub display_language_selector: bool,\n  /// Set compression algorithm used to compress files in the installer.\n  pub compression: NsisCompression,\n  /// Set the folder name for the start menu shortcut.\n  ///\n  /// Use this option if you have multiple apps and wish to group their shortcuts under one folder\n  /// or if you generally prefer to set your shortcut inside a folder.\n  ///\n  /// Examples:\n  /// - `AwesomePublisher`, shortcut will be placed in `%AppData%\\Microsoft\\Windows\\Start Menu\\Programs\\AwesomePublisher\\<your-app>.lnk`\n  /// - If unset, shortcut will be placed in `%AppData%\\Microsoft\\Windows\\Start Menu\\Programs\\<your-app>.lnk`\n  pub start_menu_folder: Option<String>,\n  /// A path to a `.nsh` file that contains special NSIS macros to be hooked into the\n  /// main installer.nsi script.\n  ///\n  /// Supported hooks are:\n  /// - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\n  /// - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\n  /// - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\n  /// - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\n  ///\n  ///\n  /// ### Example\n  ///\n  /// ```nsh\n  /// !macro NSIS_HOOK_PREINSTALL\n  ///   MessageBox MB_OK \"PreInstall\"\n  /// !macroend\n  ///\n  /// !macro NSIS_HOOK_POSTINSTALL\n  ///   MessageBox MB_OK \"PostInstall\"\n  /// !macroend\n  ///\n  /// !macro NSIS_HOOK_PREUNINSTALL\n  ///   MessageBox MB_OK \"PreUnInstall\"\n  /// !macroend\n  ///\n  /// !macro NSIS_HOOK_POSTUNINSTALL\n  ///   MessageBox MB_OK \"PostUninstall\"\n  /// !macroend\n  /// ```\n  pub installer_hooks: Option<PathBuf>,\n  /// Try to ensure that the WebView2 version is equal to or newer than this version,\n  /// if the user's WebView2 is older than this version,\n  /// the installer will try to trigger a WebView2 update.\n  pub minimum_webview2_version: Option<String>,\n}\n\n/// The Custom Signing Command Settings for Windows exe\n#[derive(Clone, Debug)]\npub struct CustomSignCommandSettings {\n  /// The command to run to sign the binary.\n  pub cmd: String,\n  /// The arguments to pass to the command.\n  ///\n  /// \"%1\" will be replaced with the path to the binary to be signed.\n  pub args: Vec<String>,\n}\n\n/// The Windows bundle settings.\n#[derive(Clone, Debug)]\npub struct WindowsSettings {\n  /// The file digest algorithm to use for creating file signatures. Required for code signing. SHA-256 is recommended.\n  pub digest_algorithm: Option<String>,\n  /// The SHA1 hash of the signing certificate.\n  pub certificate_thumbprint: Option<String>,\n  /// Server to use during timestamping.\n  pub timestamp_url: Option<String>,\n  /// Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may\n  /// use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.\n  pub tsp: bool,\n  /// WiX configuration.\n  pub wix: Option<WixSettings>,\n  /// Nsis configuration.\n  pub nsis: Option<NsisSettings>,\n  /// The path to the application icon. Defaults to `./icons/icon.ico`.\n  #[deprecated = \"This is used for the MSI installer and will be removed in 3.0.0, use `BundleSettings::icon` field and make sure a `.ico` icon exists instead.\"]\n  pub icon_path: PathBuf,\n  /// The installation mode for the Webview2 runtime.\n  pub webview_install_mode: WebviewInstallMode,\n  /// Validates a second app installation, blocking the user from installing an older version if set to `false`.\n  ///\n  /// For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\n  ///\n  /// /// The default value of this flag is `true`.\n  pub allow_downgrades: bool,\n\n  /// Specify a custom command to sign the binaries.\n  /// This command needs to have a `%1` in it which is just a placeholder for the binary path,\n  /// which we will detect and replace before calling the command.\n  ///\n  /// Example:\n  /// ```text\n  /// sign-cli --arg1 --arg2 %1\n  /// ```\n  ///\n  /// By Default we use `signtool.exe` which can be found only on Windows so\n  /// if you are on another platform and want to cross-compile and sign you will\n  /// need to use another tool like `osslsigncode`.\n  pub sign_command: Option<CustomSignCommandSettings>,\n}\n\nimpl WindowsSettings {\n  pub(crate) fn can_sign(&self) -> bool {\n    self.sign_command.is_some() || self.certificate_thumbprint.is_some()\n  }\n}\n\n#[allow(deprecated)]\nmod _default {\n  use super::*;\n\n  impl Default for WindowsSettings {\n    fn default() -> Self {\n      Self {\n        digest_algorithm: None,\n        certificate_thumbprint: None,\n        timestamp_url: None,\n        tsp: false,\n        wix: None,\n        nsis: None,\n        icon_path: PathBuf::from(\"icons/icon.ico\"),\n        webview_install_mode: Default::default(),\n        allow_downgrades: true,\n        sign_command: None,\n      }\n    }\n  }\n}\n\n/// The bundle settings of the BuildArtifact we're bundling.\n#[derive(Clone, Debug, Default)]\npub struct BundleSettings {\n  /// the app's identifier.\n  pub identifier: Option<String>,\n  /// The app's publisher. Defaults to the second element in the identifier string.\n  ///\n  /// Currently maps to the Manufacturer property of the Windows Installer\n  /// and the Maintainer field of debian packages if the Cargo.toml does not have the authors field.\n  pub publisher: Option<String>,\n  /// A url to the home page of your application. If None, will\n  /// fallback to [PackageSettings::homepage].\n  ///\n  /// Supported bundle targets: `deb`, `rpm`, `nsis` and `msi`\n  pub homepage: Option<String>,\n  /// the app's icon list.\n  pub icon: Option<Vec<String>>,\n  /// the app's resources to bundle.\n  ///\n  /// each item can be a path to a file or a path to a folder.\n  ///\n  /// supports glob patterns.\n  pub resources: Option<Vec<String>>,\n  /// The app's resources to bundle. Takes precedence over `Self::resources` when specified.\n  ///\n  /// Maps each resource path to its target directory in the bundle resources directory.\n  ///\n  /// Supports glob patterns.\n  pub resources_map: Option<HashMap<String, String>>,\n  /// the app's copyright.\n  pub copyright: Option<String>,\n  /// The package's license identifier to be included in the appropriate bundles.\n  /// If not set, defaults to the license from the Cargo.toml file.\n  pub license: Option<String>,\n  /// The path to the license file to be included in the appropriate bundles.\n  pub license_file: Option<PathBuf>,\n  /// the app's category.\n  pub category: Option<AppCategory>,\n  /// the file associations\n  pub file_associations: Option<Vec<FileAssociation>>,\n  /// the app's short description.\n  pub short_description: Option<String>,\n  /// the app's long description.\n  pub long_description: Option<String>,\n  // Bundles for other binaries:\n  /// Configuration map for the apps to bundle.\n  pub bin: Option<HashMap<String, BundleSettings>>,\n  /// External binaries to add to the bundle.\n  ///\n  /// Note that each binary name should have the target platform's target triple appended,\n  /// as well as `.exe` for Windows.\n  /// For example, if you're bundling a sidecar called `sqlite3`, the bundler expects\n  /// a binary named `sqlite3-x86_64-unknown-linux-gnu` on linux,\n  /// and `sqlite3-x86_64-pc-windows-gnu.exe` on windows.\n  ///\n  /// Run `tauri build --help` for more info on targets.\n  ///\n  /// If you are building a universal binary for MacOS, the bundler expects\n  /// your external binary to also be universal, and named after the target triple,\n  /// e.g. `sqlite3-universal-apple-darwin`. See\n  /// <https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary>\n  pub external_bin: Option<Vec<String>>,\n  /// Deep-link protocols.\n  pub deep_link_protocols: Option<Vec<DeepLinkProtocol>>,\n  /// Debian-specific settings.\n  pub deb: DebianSettings,\n  /// AppImage-specific settings.\n  pub appimage: AppImageSettings,\n  /// Rpm-specific settings.\n  pub rpm: RpmSettings,\n  /// DMG-specific settings.\n  pub dmg: DmgSettings,\n  /// iOS-specific settings.\n  pub ios: IosSettings,\n  /// MacOS-specific settings.\n  pub macos: MacOsSettings,\n  /// Updater configuration.\n  pub updater: Option<UpdaterSettings>,\n  /// Windows-specific settings.\n  pub windows: WindowsSettings,\n}\n\n/// A binary to bundle.\n#[derive(Clone, Debug)]\npub struct BundleBinary {\n  name: String,\n  main: bool,\n  src_path: Option<String>,\n}\n\nimpl BundleBinary {\n  /// Creates a new bundle binary.\n  pub fn new(name: String, main: bool) -> Self {\n    Self {\n      name,\n      main,\n      src_path: None,\n    }\n  }\n\n  /// Creates a new bundle binary with path.\n  pub fn with_path(name: String, main: bool, src_path: Option<String>) -> Self {\n    Self {\n      name,\n      src_path,\n      main,\n    }\n  }\n\n  /// Mark the binary as the main executable.\n  pub fn set_main(&mut self, main: bool) {\n    self.main = main;\n  }\n\n  /// Sets the binary name.\n  pub fn set_name(&mut self, name: String) {\n    self.name = name;\n  }\n\n  /// Sets the src path of the binary.\n  #[must_use]\n  pub fn set_src_path(mut self, src_path: Option<String>) -> Self {\n    self.src_path = src_path;\n    self\n  }\n\n  /// Returns the binary `main` flag.\n  pub fn main(&self) -> bool {\n    self.main\n  }\n\n  /// Returns the binary name.\n  pub fn name(&self) -> &str {\n    &self.name\n  }\n\n  /// Returns the binary source path.\n  pub fn src_path(&self) -> Option<&String> {\n    self.src_path.as_ref()\n  }\n}\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\npub enum Arch {\n  /// For the x86_64 / x64 / AMD64 instruction sets (64 bits).\n  X86_64,\n  /// For the x86 / i686 / i686 / 8086 instruction sets (32 bits).\n  X86,\n  /// For the AArch64 / ARM64 instruction sets (64 bits).\n  AArch64,\n  /// For the AArch32 / ARM32 instruction sets with hard-float (32 bits).\n  Armhf,\n  /// For the AArch32 / ARM32 instruction sets with soft-float (32 bits).\n  Armel,\n  /// For the RISC-V instruction sets (64 bits).\n  Riscv64,\n  /// For universal macOS applications.\n  Universal,\n}\n\n/// The Settings exposed by the module.\n#[derive(Clone, Debug)]\npub struct Settings {\n  /// The log level.\n  log_level: log::Level,\n  /// the package settings.\n  package: PackageSettings,\n  /// the package types we're bundling.\n  ///\n  /// if not present, we'll use the PackageType list for the target OS.\n  package_types: Option<Vec<PackageType>>,\n  /// the directory where the bundles will be placed.\n  project_out_directory: PathBuf,\n  /// the directory to place tools used by the bundler,\n  /// if `None`, tools are placed in the current user's platform-specific cache directory.\n  local_tools_directory: Option<PathBuf>,\n  /// the bundle settings.\n  bundle_settings: BundleSettings,\n  /// the binaries to bundle.\n  binaries: Vec<BundleBinary>,\n  /// The target platform.\n  target_platform: TargetPlatform,\n  /// The target triple.\n  target: String,\n  /// Whether to disable code signing during the bundling process.\n  no_sign: bool,\n}\n\n/// A builder for [`Settings`].\n#[derive(Default)]\npub struct SettingsBuilder {\n  log_level: Option<log::Level>,\n  project_out_directory: Option<PathBuf>,\n  package_types: Option<Vec<PackageType>>,\n  package_settings: Option<PackageSettings>,\n  bundle_settings: BundleSettings,\n  binaries: Vec<BundleBinary>,\n  target: Option<String>,\n  local_tools_directory: Option<PathBuf>,\n  no_sign: bool,\n}\n\nimpl SettingsBuilder {\n  /// Creates the default settings builder.\n  pub fn new() -> Self {\n    Default::default()\n  }\n\n  /// Sets the project output directory. It's used as current working directory.\n  #[must_use]\n  pub fn project_out_directory<P: AsRef<Path>>(mut self, path: P) -> Self {\n    self\n      .project_out_directory\n      .replace(path.as_ref().to_path_buf());\n    self\n  }\n\n  /// Sets the directory to place tools used by the bundler\n  /// when [`BundleSettings::use_local_tools_dir`] is true.\n  #[must_use]\n  pub fn local_tools_directory<P: AsRef<Path>>(mut self, path: P) -> Self {\n    self\n      .local_tools_directory\n      .replace(path.as_ref().to_path_buf());\n    self\n  }\n\n  /// Sets the package types to create.\n  #[must_use]\n  pub fn package_types(mut self, package_types: Vec<PackageType>) -> Self {\n    self.package_types = Some(package_types);\n    self\n  }\n\n  /// Sets the package settings.\n  #[must_use]\n  pub fn package_settings(mut self, settings: PackageSettings) -> Self {\n    self.package_settings.replace(settings);\n    self\n  }\n\n  /// Sets the bundle settings.\n  #[must_use]\n  pub fn bundle_settings(mut self, settings: BundleSettings) -> Self {\n    self.bundle_settings = settings;\n    self\n  }\n\n  /// Sets the binaries to bundle.\n  #[must_use]\n  pub fn binaries(mut self, binaries: Vec<BundleBinary>) -> Self {\n    self.binaries = binaries;\n    self\n  }\n\n  /// Sets the target triple.\n  #[must_use]\n  pub fn target(mut self, target: String) -> Self {\n    self.target.replace(target);\n    self\n  }\n\n  /// Sets the log level for spawned commands. Defaults to [`log::Level::Error`].\n  #[must_use]\n  pub fn log_level(mut self, level: log::Level) -> Self {\n    self.log_level.replace(level);\n    self\n  }\n\n  /// Sets whether to skip code signing.\n  #[must_use]\n  pub fn no_sign(mut self, no_sign: bool) -> Self {\n    self.no_sign = no_sign;\n    self\n  }\n\n  /// Builds a Settings from the CLI args.\n  ///\n  /// Package settings will be read from Cargo.toml.\n  ///\n  /// Bundle settings will be read from $TAURI_DIR/tauri.conf.json if it exists and fallback to Cargo.toml's [package.metadata.bundle].\n  pub fn build(self) -> crate::Result<Settings> {\n    let target = if let Some(t) = self.target {\n      t\n    } else {\n      target_triple()?\n    };\n    let target_platform = TargetPlatform::from_triple(&target);\n\n    Ok(Settings {\n      log_level: self.log_level.unwrap_or(log::Level::Error),\n      package: self\n        .package_settings\n        .ok_or_else(|| crate::Error::GenericError(\"package settings is required\".into()))?,\n      package_types: self.package_types,\n      project_out_directory: self\n        .project_out_directory\n        .ok_or_else(|| crate::Error::GenericError(\"out directory is required\".into()))?,\n      local_tools_directory: self.local_tools_directory,\n      binaries: self.binaries,\n      bundle_settings: BundleSettings {\n        external_bin: self\n          .bundle_settings\n          .external_bin\n          .as_ref()\n          .map(|bins| external_binaries(bins, &target, &target_platform)),\n        ..self.bundle_settings\n      },\n      target_platform,\n      target,\n      no_sign: self.no_sign,\n    })\n  }\n}\n\nimpl Settings {\n  /// Sets the log level for spawned commands.\n  pub fn set_log_level(&mut self, level: log::Level) {\n    self.log_level = level;\n  }\n\n  /// Returns the log level for spawned commands.\n  pub fn log_level(&self) -> log::Level {\n    self.log_level\n  }\n\n  /// Returns the directory where the bundle should be placed.\n  pub fn project_out_directory(&self) -> &Path {\n    &self.project_out_directory\n  }\n\n  /// Returns the target triple.\n  pub fn target(&self) -> &str {\n    &self.target\n  }\n\n  /// Returns the [`TargetPlatform`].\n  pub fn target_platform(&self) -> &TargetPlatform {\n    &self.target_platform\n  }\n\n  /// Returns the architecture for the binary being bundled (e.g. \"arm\", \"x86\" or \"x86_64\").\n  pub fn binary_arch(&self) -> Arch {\n    if self.target.starts_with(\"x86_64\") {\n      Arch::X86_64\n    } else if self.target.starts_with('i') {\n      Arch::X86\n    } else if self.target.starts_with(\"arm\") && self.target.ends_with(\"hf\") {\n      Arch::Armhf\n    } else if self.target.starts_with(\"arm\") {\n      Arch::Armel\n    } else if self.target.starts_with(\"aarch64\") {\n      Arch::AArch64\n    } else if self.target.starts_with(\"riscv64\") {\n      Arch::Riscv64\n    } else if self.target.starts_with(\"universal\") {\n      Arch::Universal\n    } else {\n      panic!(\"Unexpected target triple {}\", self.target)\n    }\n  }\n\n  /// Returns the file name of the binary being bundled.\n  pub fn main_binary(&self) -> crate::Result<&BundleBinary> {\n    self\n      .binaries\n      .iter()\n      .find(|bin| bin.main)\n      .context(\"failed to find main binary, make sure you have a `package > default-run` in the Cargo.toml file\")\n  }\n\n  /// Returns the file name of the binary being bundled.\n  pub fn main_binary_mut(&mut self) -> crate::Result<&mut BundleBinary> {\n    self\n      .binaries\n      .iter_mut()\n      .find(|bin| bin.main)\n      .context(\"failed to find main binary, make sure you have a `package > default-run` in the Cargo.toml file\")\n  }\n\n  /// Returns the file name of the binary being bundled.\n  pub fn main_binary_name(&self) -> crate::Result<&str> {\n    self\n      .binaries\n      .iter()\n      .find(|bin| bin.main)\n      .context(\"failed to find main binary, make sure you have a `package > default-run` in the Cargo.toml file\")\n      .map(|b| b.name())\n  }\n\n  /// Returns the path to the specified binary.\n  pub fn binary_path(&self, binary: &BundleBinary) -> PathBuf {\n    let target_os = self.target_platform();\n\n    let mut path = self.project_out_directory.join(binary.name());\n\n    if matches!(target_os, TargetPlatform::Windows) {\n      // Append the `.exe` extension without overriding the existing extensions\n      let extension = if let Some(extension) = path.extension() {\n        let mut extension = extension.to_os_string();\n        extension.push(\".exe\");\n        extension\n      } else {\n        \"exe\".into()\n      };\n      path.set_extension(extension);\n    };\n\n    path\n  }\n\n  /// Returns the list of binaries to bundle.\n  pub fn binaries(&self) -> &Vec<BundleBinary> {\n    &self.binaries\n  }\n\n  /// If a list of package types was specified by the command-line, returns\n  /// that list filtered by the current target OS available targets.\n  ///\n  /// If a target triple was specified by the\n  /// command-line, returns the native package type(s) for that target.\n  ///\n  /// Otherwise returns the native package type(s) for the host platform.\n  ///\n  /// Fails if the host/target's native package type is not supported.\n  pub fn package_types(&self) -> crate::Result<Vec<PackageType>> {\n    let target_os = self.target_platform();\n\n    let platform_types = match target_os {\n      TargetPlatform::MacOS => vec![PackageType::MacOsBundle, PackageType::Dmg],\n      TargetPlatform::Ios => vec![PackageType::IosBundle],\n      TargetPlatform::Linux => vec![PackageType::Deb, PackageType::Rpm, PackageType::AppImage],\n      TargetPlatform::Windows => vec![PackageType::WindowsMsi, PackageType::Nsis],\n      os => {\n        return Err(crate::Error::GenericError(format!(\n          \"Native {os} bundles not yet supported.\"\n        )))\n      }\n    };\n\n    if let Some(package_types) = &self.package_types {\n      let mut types = vec![];\n      for package_type in package_types {\n        let package_type = *package_type;\n        if platform_types\n          .clone()\n          .into_iter()\n          .any(|t| t == package_type)\n        {\n          types.push(package_type);\n        }\n      }\n      Ok(types)\n    } else {\n      Ok(platform_types)\n    }\n  }\n\n  /// Returns the product name.\n  pub fn product_name(&self) -> &str {\n    &self.package.product_name\n  }\n\n  /// Returns the bundle's identifier\n  pub fn bundle_identifier(&self) -> &str {\n    self.bundle_settings.identifier.as_deref().unwrap_or(\"\")\n  }\n\n  /// Returns the bundle's publisher\n  pub fn publisher(&self) -> Option<&str> {\n    self.bundle_settings.publisher.as_deref()\n  }\n\n  /// Returns an iterator over the icon files to be used for this bundle.\n  pub fn icon_files(&self) -> ResourcePaths<'_> {\n    match self.bundle_settings.icon {\n      Some(ref paths) => ResourcePaths::new(paths.as_slice(), false),\n      None => ResourcePaths::new(&[], false),\n    }\n  }\n\n  /// Returns an iterator over the resource files to be included in this\n  /// bundle.\n  pub fn resource_files(&self) -> ResourcePaths<'_> {\n    match (\n      &self.bundle_settings.resources,\n      &self.bundle_settings.resources_map,\n    ) {\n      (Some(paths), None) => ResourcePaths::new(paths.as_slice(), true),\n      (None, Some(map)) => ResourcePaths::from_map(map, true),\n      (Some(_), Some(_)) => panic!(\"cannot use both `resources` and `resources_map`\"),\n      (None, None) => ResourcePaths::new(&[], true),\n    }\n  }\n\n  /// Returns an iterator over the external binaries to be included in this\n  /// bundle.\n  pub fn external_binaries(&self) -> ResourcePaths<'_> {\n    match self.bundle_settings.external_bin {\n      Some(ref paths) => ResourcePaths::new(paths.as_slice(), true),\n      None => ResourcePaths::new(&[], true),\n    }\n  }\n\n  /// Copies external binaries to a path.\n  ///\n  /// Returns the list of destination paths.\n  pub fn copy_binaries(&self, path: &Path) -> crate::Result<Vec<PathBuf>> {\n    let mut paths = Vec::new();\n\n    for src in self.external_binaries() {\n      let src = src?;\n      let dest = path.join(\n        src\n          .file_name()\n          .expect(\"failed to extract external binary filename\")\n          .to_string_lossy()\n          .replace(&format!(\"-{}\", self.target), \"\"),\n      );\n      fs_utils::copy_file(&src, &dest)?;\n      paths.push(dest);\n    }\n    Ok(paths)\n  }\n\n  /// Copies resources to a path.\n  pub fn copy_resources(&self, path: &Path) -> crate::Result<()> {\n    for resource in self.resource_files().iter() {\n      let resource = resource?;\n      let dest = path.join(resource.target());\n      fs_utils::copy_file(resource.path(), &dest)?;\n    }\n    Ok(())\n  }\n\n  /// Returns the version string of the bundle.\n  pub fn version_string(&self) -> &str {\n    &self.package.version\n  }\n\n  /// Returns the copyright text.\n  pub fn copyright_string(&self) -> Option<&str> {\n    self.bundle_settings.copyright.as_deref()\n  }\n\n  /// Returns the list of authors name.\n  pub fn author_names(&self) -> &[String] {\n    match self.package.authors {\n      Some(ref names) => names.as_slice(),\n      None => &[],\n    }\n  }\n\n  /// Returns the authors as a comma-separated string.\n  pub fn authors_comma_separated(&self) -> Option<String> {\n    let names = self.author_names();\n    if names.is_empty() {\n      None\n    } else {\n      Some(names.join(\", \"))\n    }\n  }\n\n  /// Returns the bundle license.\n  pub fn license(&self) -> Option<String> {\n    self.bundle_settings.license.clone()\n  }\n\n  /// Returns the bundle license file.\n  pub fn license_file(&self) -> Option<PathBuf> {\n    self.bundle_settings.license_file.clone()\n  }\n\n  /// Returns the package's homepage URL, defaulting to \"\" if not defined.\n  pub fn homepage_url(&self) -> Option<&str> {\n    self\n      .bundle_settings\n      .homepage\n      .as_deref()\n      .or(self.package.homepage.as_deref())\n  }\n\n  /// Returns the app's category.\n  pub fn app_category(&self) -> Option<AppCategory> {\n    self.bundle_settings.category\n  }\n\n  /// Return file associations.\n  pub fn file_associations(&self) -> Option<&Vec<FileAssociation>> {\n    self.bundle_settings.file_associations.as_ref()\n  }\n\n  /// Return the list of deep link protocols to be registered for\n  /// this bundle.\n  pub fn deep_link_protocols(&self) -> Option<&Vec<DeepLinkProtocol>> {\n    self.bundle_settings.deep_link_protocols.as_ref()\n  }\n\n  /// Returns the app's short description.\n  pub fn short_description(&self) -> &str {\n    self\n      .bundle_settings\n      .short_description\n      .as_ref()\n      .unwrap_or(&self.package.description)\n  }\n\n  /// Returns the app's long description.\n  pub fn long_description(&self) -> Option<&str> {\n    self.bundle_settings.long_description.as_deref()\n  }\n\n  /// Returns the directory for local tools path.\n  pub fn local_tools_directory(&self) -> Option<&Path> {\n    self.local_tools_directory.as_deref()\n  }\n\n  /// Returns the debian settings.\n  pub fn deb(&self) -> &DebianSettings {\n    &self.bundle_settings.deb\n  }\n\n  /// Returns the appimage settings.\n  pub fn appimage(&self) -> &AppImageSettings {\n    &self.bundle_settings.appimage\n  }\n\n  /// Returns the RPM settings.\n  pub fn rpm(&self) -> &RpmSettings {\n    &self.bundle_settings.rpm\n  }\n\n  /// Returns the DMG settings.\n  pub fn dmg(&self) -> &DmgSettings {\n    &self.bundle_settings.dmg\n  }\n\n  /// Returns the iOS settings.\n  pub fn ios(&self) -> &IosSettings {\n    &self.bundle_settings.ios\n  }\n\n  /// Returns the MacOS settings.\n  pub fn macos(&self) -> &MacOsSettings {\n    &self.bundle_settings.macos\n  }\n\n  /// Returns the Windows settings.\n  pub fn windows(&self) -> &WindowsSettings {\n    &self.bundle_settings.windows\n  }\n\n  /// Returns the Updater settings.\n  pub fn updater(&self) -> Option<&UpdaterSettings> {\n    self.bundle_settings.updater.as_ref()\n  }\n\n  /// Whether to skip signing.\n  pub fn no_sign(&self) -> bool {\n    self.no_sign\n  }\n\n  /// Set whether to skip signing.\n  pub fn set_no_sign(&mut self, no_sign: bool) {\n    self.no_sign = no_sign;\n  }\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/updater_bundle.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  bundle::{\n    windows::{\n      NSIS_OUTPUT_FOLDER_NAME, NSIS_UPDATER_OUTPUT_FOLDER_NAME, WIX_OUTPUT_FOLDER_NAME,\n      WIX_UPDATER_OUTPUT_FOLDER_NAME,\n    },\n    Bundle,\n  },\n  error::{Context, ErrorExt},\n  utils::fs_utils,\n  Settings,\n};\nuse tauri_utils::{display_path, platform::Target as TargetPlatform};\n\nuse std::{\n  fs::{self, File},\n  io::prelude::*,\n  path::{Path, PathBuf},\n};\n\nuse zip::write::SimpleFileOptions;\n\n// Build update\npub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<Vec<PathBuf>> {\n  let target_os = settings.target_platform();\n\n  if matches!(target_os, TargetPlatform::Windows) {\n    return bundle_update_windows(settings, bundles);\n  }\n\n  #[cfg(target_os = \"macos\")]\n  return bundle_update_macos(bundles);\n  #[cfg(target_os = \"linux\")]\n  return bundle_update_linux(bundles);\n\n  #[cfg(not(any(target_os = \"macos\", target_os = \"linux\")))]\n  {\n    log::error!(\"Current platform does not support updates\");\n    Ok(vec![])\n  }\n}\n\n// Create simple update-macos.tar.gz\n// This is the Mac OS App packaged\n#[cfg(target_os = \"macos\")]\nfn bundle_update_macos(bundles: &[Bundle]) -> crate::Result<Vec<PathBuf>> {\n  use std::ffi::OsStr;\n\n  // find our .app or rebuild our bundle\n  if let Some(source_path) = bundles\n    .iter()\n    .filter(|bundle| bundle.package_type == crate::PackageType::MacOsBundle)\n    .find_map(|bundle| {\n      bundle\n        .bundle_paths\n        .iter()\n        .find(|path| path.extension() == Some(OsStr::new(\"app\")))\n    })\n  {\n    // add .tar.gz to our path\n    let osx_archived = format!(\"{}.tar.gz\", source_path.display());\n    let osx_archived_path = PathBuf::from(&osx_archived);\n\n    // Create our gzip file (need to send parent)\n    // as we walk the source directory (source isnt added)\n    create_tar(source_path, &osx_archived_path)\n      .with_context(|| \"Failed to tar.gz update directory\")?;\n\n    log::info!(action = \"Bundling\"; \"{} ({})\", osx_archived, display_path(&osx_archived_path));\n\n    Ok(vec![osx_archived_path])\n  } else {\n    Err(crate::Error::UnableToFindProject)\n  }\n}\n\n// Create simple update-linux_<arch>.tar.gz\n// Including the AppImage\n// Right now in linux we hot replace the bin and request a restart\n// No assets are replaced\n#[cfg(target_os = \"linux\")]\nfn bundle_update_linux(bundles: &[Bundle]) -> crate::Result<Vec<PathBuf>> {\n  use std::ffi::OsStr;\n\n  // build our app actually we support only appimage on linux\n  if let Some(source_path) = bundles\n    .iter()\n    .filter(|bundle| bundle.package_type == crate::PackageType::AppImage)\n    .find_map(|bundle| {\n      bundle\n        .bundle_paths\n        .iter()\n        .find(|path| path.extension() == Some(OsStr::new(\"AppImage\")))\n    })\n  {\n    // add .tar.gz to our path\n    let appimage_archived = format!(\"{}.tar.gz\", source_path.display());\n    let appimage_archived_path = PathBuf::from(&appimage_archived);\n\n    // Create our gzip file\n    create_tar(source_path, &appimage_archived_path)\n      .with_context(|| \"Failed to tar.gz update directory\")?;\n\n    log::info!(action = \"Bundling\"; \"{} ({})\", appimage_archived, display_path(&appimage_archived_path));\n\n    Ok(vec![appimage_archived_path])\n  } else {\n    Err(crate::Error::UnableToFindProject)\n  }\n}\n\n// Create simple update-win_<arch>.zip\n// Including the binary as root\n// Right now in windows we hot replace the bin and request a restart\n// No assets are replaced\nfn bundle_update_windows(settings: &Settings, bundles: &[Bundle]) -> crate::Result<Vec<PathBuf>> {\n  use crate::bundle::settings::WebviewInstallMode;\n  #[cfg(target_os = \"windows\")]\n  use crate::bundle::windows::msi;\n  use crate::bundle::windows::nsis;\n  use crate::PackageType;\n\n  // find our installers or rebuild\n  let mut bundle_paths = Vec::new();\n  let mut rebuild_installers = || -> crate::Result<()> {\n    for bundle in bundles {\n      match bundle.package_type {\n        #[cfg(target_os = \"windows\")]\n        PackageType::WindowsMsi => bundle_paths.extend(msi::bundle_project(settings, true)?),\n        PackageType::Nsis => bundle_paths.extend(nsis::bundle_project(settings, true)?),\n        _ => {}\n      };\n    }\n    Ok(())\n  };\n\n  if matches!(\n    settings.windows().webview_install_mode,\n    WebviewInstallMode::OfflineInstaller { .. } | WebviewInstallMode::EmbedBootstrapper { .. }\n  ) {\n    rebuild_installers()?;\n  } else {\n    let paths = bundles\n      .iter()\n      .filter(|bundle| {\n        matches!(\n          bundle.package_type,\n          PackageType::WindowsMsi | PackageType::Nsis\n        )\n      })\n      .flat_map(|bundle| bundle.bundle_paths.clone())\n      .collect::<Vec<_>>();\n\n    // we expect our installer files to be on `bundle_paths`\n    if paths.is_empty() {\n      rebuild_installers()?;\n    } else {\n      bundle_paths.extend(paths);\n    }\n  };\n\n  let mut installers_archived_paths = Vec::new();\n  for source_path in bundle_paths {\n    // add .zip to our path\n    let (archived_path, bundle_name) =\n      source_path\n        .components()\n        .fold((PathBuf::new(), String::new()), |(mut p, mut b), c| {\n          if let std::path::Component::Normal(name) = c {\n            if let Some(name) = name.to_str() {\n              // installers bundled for updater should be put in a directory named `${bundle_name}-updater`\n              if name == WIX_UPDATER_OUTPUT_FOLDER_NAME || name == NSIS_UPDATER_OUTPUT_FOLDER_NAME {\n                b = name.strip_suffix(\"-updater\").unwrap().to_string();\n                p.push(&b);\n                return (p, b);\n              }\n\n              if name == WIX_OUTPUT_FOLDER_NAME || name == NSIS_OUTPUT_FOLDER_NAME {\n                b = name.to_string();\n              }\n            }\n          }\n          p.push(c);\n          (p, b)\n        });\n    let archived_path = archived_path.with_extension(format!(\"{bundle_name}.zip\"));\n\n    log::info!(action = \"Bundling\"; \"{}\", display_path(&archived_path));\n\n    // Create our gzip file\n    create_zip(&source_path, &archived_path).with_context(|| \"Failed to zip update bundle\")?;\n\n    installers_archived_paths.push(archived_path);\n  }\n\n  Ok(installers_archived_paths)\n}\n\npub fn create_zip(src_file: &Path, dst_file: &Path) -> crate::Result<PathBuf> {\n  let parent_dir = dst_file.parent().expect(\"No data in parent\");\n  fs::create_dir_all(parent_dir)?;\n  let writer = fs_utils::create_file(dst_file)?;\n\n  let file_name = src_file\n    .file_name()\n    .expect(\"Can't extract file name from path\");\n\n  let mut zip = zip::ZipWriter::new(writer);\n  let options = SimpleFileOptions::default()\n    .compression_method(zip::CompressionMethod::Stored)\n    .unix_permissions(0o755);\n\n  zip.start_file(file_name.to_string_lossy(), options)?;\n  let mut f =\n    File::open(src_file).fs_context(\"failed to open updater ZIP file\", src_file.to_path_buf())?;\n\n  let mut buffer = Vec::new();\n  f.read_to_end(&mut buffer)?;\n  zip.write_all(&buffer)?;\n  buffer.clear();\n\n  Ok(dst_file.to_owned())\n}\n\n#[cfg(any(target_os = \"linux\", target_os = \"macos\"))]\nfn create_tar(src_dir: &Path, dest_path: &Path) -> crate::Result<PathBuf> {\n  use flate2::{write::GzEncoder, Compression};\n\n  let dest_file = fs_utils::create_file(dest_path)?;\n  let gzip_encoder = GzEncoder::new(dest_file, Compression::default());\n\n  let gzip_encoder = create_tar_from_src(src_dir, gzip_encoder)?;\n\n  let mut dest_file = gzip_encoder.finish()?;\n  dest_file.flush()?;\n  Ok(dest_path.to_owned())\n}\n\n#[cfg(target_os = \"macos\")]\nfn create_tar_from_src<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> crate::Result<W> {\n  let src_dir = src_dir.as_ref();\n  let mut builder = tar::Builder::new(dest_file);\n  builder.follow_symlinks(false);\n  builder.append_dir_all(src_dir.file_name().expect(\"Path has no file_name\"), src_dir)?;\n  builder.into_inner().map_err(Into::into)\n}\n\n#[cfg(target_os = \"linux\")]\nfn create_tar_from_src<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> crate::Result<W> {\n  let src_dir = src_dir.as_ref();\n  let mut tar_builder = tar::Builder::new(dest_file);\n\n  // validate source type\n  let file_type = fs::metadata(src_dir).expect(\"Can't read source directory\");\n  // if it's a file don't need to walkdir\n  if file_type.is_file() {\n    let mut src_file = fs::File::open(src_dir)?;\n    let file_name = src_dir\n      .file_name()\n      .expect(\"Can't extract file name from path\");\n\n    tar_builder.append_file(file_name, &mut src_file)?;\n  } else {\n    for entry in walkdir::WalkDir::new(src_dir) {\n      let entry = entry?;\n      let src_path = entry.path();\n      if src_path == src_dir {\n        continue;\n      }\n\n      // We add the .parent() because example if we send a path\n      // /dev/src-tauri/target/debug/bundle/osx/app.app\n      // We need a tar with app.app/<...> (source root folder should be included)\n      // safe to unwrap: the path has a parent\n      let dest_path = src_path.strip_prefix(src_dir.parent().unwrap())?;\n      if entry.file_type().is_dir() {\n        tar_builder.append_dir(dest_path, src_path)?;\n      } else {\n        let mut src_file = fs::File::open(src_path)?;\n        tar_builder.append_file(dest_path, &mut src_file)?;\n      }\n    }\n  }\n  let dest_file = tar_builder.into_inner()?;\n  Ok(dest_file)\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/mod.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#[cfg(target_os = \"windows\")]\npub mod msi;\n\npub mod nsis;\npub mod sign;\n\nmod util;\npub use util::{\n  NSIS_OUTPUT_FOLDER_NAME, NSIS_UPDATER_OUTPUT_FOLDER_NAME, WIX_OUTPUT_FOLDER_NAME,\n  WIX_UPDATER_OUTPUT_FOLDER_NAME,\n};\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/msi/default-locale-strings.xml",
    "content": "<String Id=\"TauriLanguage\">__language__</String>\n<String Id=\"TauriCodepage\">__codepage__</String>\n<String Id=\"LaunchApp\">Launch __productName__</String>\n<String Id=\"DowngradeErrorMessage\">A newer version of __productName__ is already installed.</String>\n<String Id=\"PathEnvVarFeature\">Add the install location of the __productName__ executable to the PATH system environment variable. This allows the __productName__ executable to be called from any location.</String>\n<String Id=\"InstallAppFeature\">Installs __productName__.</String>"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/msi/install-task.ps1",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n# Adapted from https://superuser.com/a/532109\nparam([string]$ChangeDir, [switch]$Elevated)\n\nfunction Test-Admin {\n    $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())\n    $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)\n}\n\nif ((Test-Admin) -eq $false) {\n    if ($elevated) {\n        # tried to elevate, did not work, aborting\n    }\n    else {\n        $InstallDirectory = Get-Location\n        $ArgList = ('-File \"{0}\" -ChangeDir \"{1}\" -Elevated' -f ($myinvocation.MyCommand.Definition, $InstallDirectory))\n        Start-Process \"$env:SYSTEMROOT\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -WindowStyle hidden -Verb RunAs -ArgumentList $ArgList\n    }\n    exit\n}\n\nif ($ChangeDir -ne \"\") {\n    # Change directories to the install path\n    Set-Location -Path $ChangeDir\n}\nSCHTASKS.EXE /CREATE /XML update.xml /TN \"Update {{product_name}} - Skip UAC\" /F\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/msi/languages.json",
    "content": "{\n  \"ar-SA\": {\n    \"langId\": 1025,\n    \"asciiCode\": 1256\n  },\n  \"ca-ES\": {\n    \"langId\": 1027,\n    \"asciiCode\": 1252\n  },\n  \"zh-TW\": {\n    \"langId\": 1028,\n    \"asciiCode\": 950\n  },\n  \"zh-CN\": {\n    \"langId\": 2052,\n    \"asciiCode\": 936\n  },\n  \"cs-CZ\": {\n    \"langId\": 1029,\n    \"asciiCode\": 1250\n  },\n  \"da-DK\": {\n    \"langId\": 1030,\n    \"asciiCode\": 1252\n  },\n  \"de-DE\": {\n    \"langId\": 1031,\n    \"asciiCode\": 1252\n  },\n  \"el-GR\": {\n    \"langId\": 1032,\n    \"asciiCode\": 1253\n  },\n  \"en-US\": {\n    \"langId\": 1033,\n    \"asciiCode\": 1252\n  },\n  \"es-ES\": {\n    \"langId\": 3082,\n    \"asciiCode\": 1252\n  },\n  \"et-EE\": {\n    \"langId\": 1061,\n    \"asciiCode\": 1257\n  },\n  \"fi-FI\": {\n    \"langId\": 1035,\n    \"asciiCode\": 1252\n  },\n  \"fr-FR\": {\n    \"langId\": 1036,\n    \"asciiCode\": 1252\n  },\n  \"he-IL\": {\n    \"langId\": 1037,\n    \"asciiCode\": 1255\n  },\n  \"hu-HU\": {\n    \"langId\": 1038,\n    \"asciiCode\": 1250\n  },\n  \"it-IT\": {\n    \"langId\": 1040,\n    \"asciiCode\": 1252\n  },\n  \"ja-JP\": {\n    \"langId\": 1041,\n    \"asciiCode\": 932\n  },\n  \"ko-KR\": {\n    \"langId\": 1042,\n    \"asciiCode\": 949\n  },\n  \"lt-LT\": {\n    \"langId\": 1063,\n    \"asciiCode\": 1257\n  },\n  \"lv-LV\": {\n    \"langId\": 1062,\n    \"asciiCode\": 1257\n  },\n  \"nl-NL\": {\n    \"langId\": 1043,\n    \"asciiCode\": 1252\n  },\n  \"nb-NO\": {\n    \"langId\": 1044,\n    \"asciiCode\": 1252\n  },\n  \"pl-PL\": {\n    \"langId\": 1045,\n    \"asciiCode\": 1250\n  },\n  \"pt-BR\": {\n    \"langId\": 1046,\n    \"asciiCode\": 1252\n  },\n  \"pt-PT\": {\n    \"langId\": 2070,\n    \"asciiCode\": 1252\n  },\n  \"ro-RO\": {\n    \"langId\": 1048,\n    \"asciiCode\": 1250\n  },\n  \"ru-RU\": {\n    \"langId\": 1049,\n    \"asciiCode\": 1251\n  },\n  \"hr-HR\": {\n    \"langId\": 1050,\n    \"asciiCode\": 1250\n  },\n  \"sk-SK\": {\n    \"langId\": 1051,\n    \"asciiCode\": 1250\n  },\n  \"sv-SE\": {\n    \"langId\": 1053,\n    \"asciiCode\": 1252\n  },\n  \"th-TH\": {\n    \"langId\": 1054,\n    \"asciiCode\": 874\n  },\n  \"tr-TR\": {\n    \"langId\": 1055,\n    \"asciiCode\": 1254\n  },\n  \"sl-SI\": {\n    \"langId\": 1060,\n    \"asciiCode\": 1250\n  },\n  \"vi-VN\": {\n    \"langId\": 1066,\n    \"asciiCode\": 1258\n  },\n  \"eu-ES\": {\n    \"langId\": 1069,\n    \"asciiCode\": 1252\n  },\n  \"bg-BG\": {\n    \"langId\": 1026,\n    \"asciiCode\": 1251\n  },\n  \"uk-UA\": {\n    \"langId\": 1058,\n    \"asciiCode\": 1251\n  },\n  \"sr-Latn-CS\": {\n    \"langId\": 2074,\n    \"asciiCode\": 1250\n  }\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/msi/main.wxs",
    "content": "<?if $(sys.BUILDARCH)=\"x86\"?>\n    <?define Win64 = \"no\" ?>\n    <?define PlatformProgramFilesFolder = \"ProgramFilesFolder\" ?>\n<?elseif $(sys.BUILDARCH)=\"x64\"?>\n    <?define Win64 = \"yes\" ?>\n    <?define PlatformProgramFilesFolder = \"ProgramFiles64Folder\" ?>\n<?elseif $(sys.BUILDARCH)=\"arm64\"?>\n    <?define Win64 = \"yes\" ?>\n    <?define PlatformProgramFilesFolder = \"ProgramFiles64Folder\" ?>\n<?else?>\n    <?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>\n<?endif?>\n\n<Wix xmlns=\"http://schemas.microsoft.com/wix/2006/wi\">\n    <Product\n            Id=\"*\"\n            Name=\"{{product_name}}\"\n            UpgradeCode=\"{{upgrade_code}}\"\n            Language=\"!(loc.TauriLanguage)\"\n            Manufacturer=\"{{manufacturer}}\"\n            Version=\"{{version}}\">\n\n        <Package Id=\"*\"\n                 Keywords=\"Installer\"\n                 InstallerVersion=\"450\"\n                 Languages=\"0\"\n                 Compressed=\"yes\"\n                 InstallScope=\"perMachine\"\n                 SummaryCodepage=\"!(loc.TauriCodepage)\"/>\n\n        <!-- https://docs.microsoft.com/en-us/windows/win32/msi/reinstallmode -->\n        <!-- reinstall all files; rewrite all registry entries; reinstall all shortcuts -->\n        <Property Id=\"REINSTALLMODE\" Value=\"amus\" />\n\n        <!-- Auto launch app after installation, useful for passive mode which usually used in updates -->\n        <Property Id=\"AUTOLAUNCHAPP\" Secure=\"yes\" />\n        <!-- Property to forward cli args to the launched app to not lose those of the pre-update instance -->\n        <Property Id=\"LAUNCHAPPARGS\" Secure=\"yes\" />\n\n        {{#if allow_downgrades}}\n            <MajorUpgrade Schedule=\"afterInstallInitialize\" AllowDowngrades=\"yes\" />\n        {{else}}\n            <MajorUpgrade Schedule=\"afterInstallInitialize\" DowngradeErrorMessage=\"!(loc.DowngradeErrorMessage)\" AllowSameVersionUpgrades=\"yes\" />\n        {{/if}}\n\n        <InstallExecuteSequence>\n            <RemoveShortcuts>Installed AND NOT UPGRADINGPRODUCTCODE</RemoveShortcuts>\n        </InstallExecuteSequence>\n\n        <Media Id=\"1\" Cabinet=\"app.cab\" EmbedCab=\"yes\" />\n\n        {{#if banner_path}}\n        <WixVariable Id=\"WixUIBannerBmp\" Value=\"{{banner_path}}\" />\n        {{/if}}\n        {{#if dialog_image_path}}\n        <WixVariable Id=\"WixUIDialogBmp\" Value=\"{{dialog_image_path}}\" />\n        {{/if}}\n        {{#if license}}\n        <WixVariable Id=\"WixUILicenseRtf\" Value=\"{{license}}\" />\n        {{/if}}\n\n        <Icon Id=\"ProductIcon\" SourceFile=\"{{icon_path}}\"/>\n        <Property Id=\"ARPPRODUCTICON\" Value=\"ProductIcon\" />\n        <Property Id=\"ARPNOREPAIR\" Value=\"yes\" Secure=\"yes\" />      <!-- Remove repair -->\n        <SetProperty Id=\"ARPNOMODIFY\" Value=\"1\" After=\"InstallValidate\" Sequence=\"execute\"/>\n\n        {{#if homepage}}\n        <Property Id=\"ARPURLINFOABOUT\" Value=\"{{homepage}}\"/>\n        <Property Id=\"ARPHELPLINK\" Value=\"{{homepage}}\"/>\n        <Property Id=\"ARPURLUPDATEINFO\" Value=\"{{homepage}}\"/>\n        {{/if}}\n\n        <!-- NOTE: The order of RegistrySearch elements below matters. In WIX, when multiple\n             RegistrySearch elements are listed under a single Property, the LAST successful\n             match wins. We list the NSIS default-key search first and the MSI InstallDir\n             search second so that the MSI-specific path takes priority when both keys exist. -->\n        <Property Id=\"INSTALLDIR\">\n          <!-- First attempt: Search for the default key value (this is how the nsis installer stores the path) -->\n          <RegistrySearch Id=\"PrevInstallDirNoName\" Root=\"HKCU\" Key=\"Software\\\\{{manufacturer}}\\\\{{product_name}}\" Type=\"raw\" />\n\n          <!-- Second attempt: Search for \"InstallDir\" which takes priority if found (this is how the msi installer stores the path) -->\n          <RegistrySearch Id=\"PrevInstallDirWithName\" Root=\"HKCU\" Key=\"Software\\\\{{manufacturer}}\\\\{{product_name}}\" Name=\"InstallDir\" Type=\"raw\" />\n        </Property>\n\n        <!-- launch app checkbox -->\n        <Property Id=\"WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT\" Value=\"!(loc.LaunchApp)\" />\n        <Property Id=\"WIXUI_EXITDIALOGOPTIONALCHECKBOX\" Value=\"1\"/>\n        <CustomAction Id=\"LaunchApplication\" Impersonate=\"yes\" FileKey=\"Path\" ExeCommand=\"[LAUNCHAPPARGS]\" Return=\"asyncNoWait\" />\n\n        <UI>\n            <!-- launch app checkbox -->\n            <Publish Dialog=\"ExitDialog\" Control=\"Finish\" Event=\"DoAction\" Value=\"LaunchApplication\">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>\n\n            <Property Id=\"WIXUI_INSTALLDIR\" Value=\"INSTALLDIR\" />\n\n            {{#unless license}}\n            <!-- Skip license dialog -->\n            <Publish Dialog=\"WelcomeDlg\"\n                     Control=\"Next\"\n                     Event=\"NewDialog\"\n                     Value=\"InstallDirDlg\"\n                     Order=\"2\">1</Publish>\n            <Publish Dialog=\"InstallDirDlg\"\n                     Control=\"Back\"\n                     Event=\"NewDialog\"\n                     Value=\"WelcomeDlg\"\n                     Order=\"2\">1</Publish>\n            {{/unless}}\n        </UI>\n\n        <UIRef Id=\"WixUI_InstallDir\" />\n\n        <Directory Id=\"TARGETDIR\" Name=\"SourceDir\">\n            <Directory Id=\"DesktopFolder\" Name=\"Desktop\">\n                <Component Id=\"ApplicationShortcutDesktop\" Guid=\"*\">\n                    <Shortcut Id=\"ApplicationDesktopShortcut\" Name=\"{{product_name}}\" Description=\"Runs {{product_name}}\" Target=\"[!Path]\" WorkingDirectory=\"INSTALLDIR\" />\n                    <RemoveFolder Id=\"DesktopFolder\" On=\"uninstall\" />\n                    <RegistryValue Root=\"HKCU\" Key=\"Software\\\\{{manufacturer}}\\\\{{product_name}}\" Name=\"Desktop Shortcut\" Type=\"integer\" Value=\"1\" KeyPath=\"yes\" />\n                </Component>\n            </Directory>\n            <Directory Id=\"$(var.PlatformProgramFilesFolder)\" Name=\"PFiles\">\n                <Directory Id=\"INSTALLDIR\" Name=\"{{product_name}}\"/>\n            </Directory>\n            <Directory Id=\"ProgramMenuFolder\">\n                <Directory Id=\"ApplicationProgramsFolder\" Name=\"{{product_name}}\"/>\n            </Directory>\n        </Directory>\n\n        <DirectoryRef Id=\"INSTALLDIR\">\n            <Component Id=\"RegistryEntries\" Guid=\"*\">\n                <RegistryKey Root=\"HKCU\" Key=\"Software\\\\{{manufacturer}}\\\\{{product_name}}\">\n                    <RegistryValue Name=\"InstallDir\" Type=\"string\" Value=\"[INSTALLDIR]\" KeyPath=\"yes\" />\n                </RegistryKey>\n                <!-- Change the Root to HKCU for perUser installations -->\n                {{#each deep_link_protocols as |protocol| ~}}\n                <RegistryKey Root=\"HKLM\" Key=\"Software\\Classes\\\\{{protocol}}\">\n                    <RegistryValue Type=\"string\" Name=\"URL Protocol\" Value=\"\"/>\n                    <RegistryValue Type=\"string\" Value=\"URL:{{bundle_id}} protocol\"/>\n                    <RegistryKey Key=\"DefaultIcon\">\n                        <RegistryValue Type=\"string\" Value=\"&quot;[!Path]&quot;,0\" />\n                    </RegistryKey>\n                    <RegistryKey Key=\"shell\\open\\command\">\n                        <RegistryValue Type=\"string\" Value=\"&quot;[!Path]&quot; &quot;%1&quot;\" />\n                    </RegistryKey>\n                </RegistryKey>\n                {{/each~}}\n            </Component>\n            <Component Id=\"Path\" Guid=\"{{path_component_guid}}\" Win64=\"$(var.Win64)\">\n                <File Id=\"Path\" Source=\"{{main_binary_path}}\" KeyPath=\"yes\" Checksum=\"yes\"/>\n                {{#each file_associations as |association| ~}}\n                {{#each association.ext as |ext| ~}}\n                <ProgId Id=\"{{../../product_name}}.{{ext}}\" Advertise=\"yes\" Description=\"{{association.description}}\">\n                    <Extension Id=\"{{ext}}\" Advertise=\"yes\">\n                        <Verb Id=\"open\" Command=\"Open with {{../../product_name}}\" Argument=\"&quot;%1&quot;\" />\n                    </Extension>\n                </ProgId>\n                {{/each~}}\n                {{/each~}}\n            </Component>\n            {{#each binaries as |bin| ~}}\n            <Component Id=\"{{ bin.id }}\" Guid=\"{{bin.guid}}\" Win64=\"$(var.Win64)\">\n                <File Id=\"Bin_{{ bin.id }}\" Source=\"{{bin.path}}\" KeyPath=\"yes\"/>\n            </Component>\n            {{/each~}}\n            {{#if enable_elevated_update_task}}\n            <Component Id=\"UpdateTask\" Guid=\"C492327D-9720-4CD5-8DB8-F09082AF44BE\" Win64=\"$(var.Win64)\">\n                <File Id=\"UpdateTask\" Source=\"update.xml\" KeyPath=\"yes\" Checksum=\"yes\"/>\n            </Component>\n            <Component Id=\"UpdateTaskInstaller\" Guid=\"011F25ED-9BE3-50A7-9E9B-3519ED2B9932\" Win64=\"$(var.Win64)\">\n                <File Id=\"UpdateTaskInstaller\" Source=\"install-task.ps1\" KeyPath=\"yes\" Checksum=\"yes\"/>\n            </Component>\n            <Component Id=\"UpdateTaskUninstaller\" Guid=\"D4F6CC3F-32DC-5FD0-95E8-782FFD7BBCE1\" Win64=\"$(var.Win64)\">\n                <File Id=\"UpdateTaskUninstaller\" Source=\"uninstall-task.ps1\" KeyPath=\"yes\" Checksum=\"yes\"/>\n            </Component>\n            {{/if}}\n            {{resources}}\n            <Component Id=\"CMP_UninstallShortcut\" Guid=\"*\">\n\n                <Shortcut Id=\"UninstallShortcut\"\n\t\t\t\t\t\t  Name=\"Uninstall {{product_name}}\"\n\t\t\t\t\t\t  Description=\"Uninstalls {{product_name}}\"\n\t\t\t\t\t\t  Target=\"[System64Folder]msiexec.exe\"\n\t\t\t\t\t\t  Arguments=\"/x [ProductCode]\" />\n\n\t\t\t\t<RemoveFolder Id=\"INSTALLDIR\"\n\t\t\t\t\t\t\t  On=\"uninstall\" />\n\n\t\t\t\t<RegistryValue Root=\"HKCU\"\n\t\t\t\t\t\t\t   Key=\"Software\\\\{{manufacturer}}\\\\{{product_name}}\"\n\t\t\t\t\t\t\t   Name=\"Uninstaller Shortcut\"\n\t\t\t\t\t\t\t   Type=\"integer\"\n\t\t\t\t\t\t\t   Value=\"1\"\n\t\t\t\t\t\t\t   KeyPath=\"yes\" />\n            </Component>\n        </DirectoryRef>\n\n        <DirectoryRef Id=\"ApplicationProgramsFolder\">\n            <Component Id=\"ApplicationShortcut\" Guid=\"*\">\n                <Shortcut Id=\"ApplicationStartMenuShortcut\"\n                    Name=\"{{product_name}}\"\n                    Description=\"Runs {{product_name}}\"\n                    Target=\"[!Path]\"\n                    Icon=\"ProductIcon\"\n                    WorkingDirectory=\"INSTALLDIR\">\n                    <ShortcutProperty Key=\"System.AppUserModel.ID\" Value=\"{{bundle_id}}\"/>\n                </Shortcut>\n                <RemoveFolder Id=\"ApplicationProgramsFolder\" On=\"uninstall\"/>\n                <RegistryValue Root=\"HKCU\" Key=\"Software\\\\{{manufacturer}}\\\\{{product_name}}\" Name=\"Start Menu Shortcut\" Type=\"integer\" Value=\"1\" KeyPath=\"yes\"/>\n           </Component>\n        </DirectoryRef>\n\n        {{#each merge_modules as |msm| ~}}\n        <DirectoryRef Id=\"TARGETDIR\">\n            <Merge Id=\"{{ msm.name }}\" SourceFile=\"{{ msm.path }}\" DiskId=\"1\" Language=\"!(loc.TauriLanguage)\" />\n        </DirectoryRef>\n\n        <Feature Id=\"{{ msm.name }}\" Title=\"{{ msm.name }}\" AllowAdvertise=\"no\" Display=\"hidden\" Level=\"1\">\n            <MergeRef Id=\"{{ msm.name }}\"/>\n        </Feature>\n        {{/each~}}\n\n        <Feature\n                Id=\"MainProgram\"\n                Title=\"Application\"\n                Description=\"!(loc.InstallAppFeature)\"\n                Level=\"1\"\n                ConfigurableDirectory=\"INSTALLDIR\"\n                AllowAdvertise=\"no\"\n                Display=\"expand\"\n                Absent=\"disallow\">\n\n            <ComponentRef Id=\"RegistryEntries\"/>\n\n            {{#each resource_file_ids as |resource_file_id| ~}}\n                <ComponentRef Id=\"{{ resource_file_id }}\"/>\n            {{/each~}}\n\n            {{#if enable_elevated_update_task}}\n                <ComponentRef Id=\"UpdateTask\" />\n                <ComponentRef Id=\"UpdateTaskInstaller\" />\n                <ComponentRef Id=\"UpdateTaskUninstaller\" />\n            {{/if}}\n\n            <Feature Id=\"ShortcutsFeature\"\n                Title=\"Shortcuts\"\n                Level=\"1\">\n                <ComponentRef Id=\"Path\"/>\n                <ComponentRef Id=\"CMP_UninstallShortcut\" />\n                <ComponentRef Id=\"ApplicationShortcut\" />\n                <ComponentRef Id=\"ApplicationShortcutDesktop\" />\n            </Feature>\n\n            <Feature\n                Id=\"Environment\"\n                Title=\"PATH Environment Variable\"\n                Description=\"!(loc.PathEnvVarFeature)\"\n                Level=\"1\"\n                Absent=\"allow\">\n            <ComponentRef Id=\"Path\"/>\n            {{#each binaries as |bin| ~}}\n            <ComponentRef Id=\"{{ bin.id }}\"/>\n            {{/each~}}\n            </Feature>\n        </Feature>\n\n        <Feature Id=\"External\" AllowAdvertise=\"no\" Absent=\"disallow\">\n            {{#each component_group_refs as |id| ~}}\n            <ComponentGroupRef Id=\"{{ id }}\"/>\n            {{/each~}}\n            {{#each component_refs as |id| ~}}\n            <ComponentRef Id=\"{{ id }}\"/>\n            {{/each~}}\n            {{#each feature_group_refs as |id| ~}}\n            <FeatureGroupRef Id=\"{{ id }}\"/>\n            {{/each~}}\n            {{#each feature_refs as |id| ~}}\n            <FeatureRef Id=\"{{ id }}\"/>\n            {{/each~}}\n            {{#each merge_refs as |id| ~}}\n            <MergeRef Id=\"{{ id }}\"/>\n            {{/each~}}\n        </Feature>\n\n        {{#if install_webview}}\n        <!-- WebView2 -->\n        <Property Id=\"WVRTINSTALLED\">\n            <RegistrySearch Id=\"WVRTInstalledSystem\" Root=\"HKLM\" Key=\"SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" Name=\"pv\" Type=\"raw\" Win64=\"no\" />\n            <RegistrySearch Id=\"WVRTInstalledUser\" Root=\"HKCU\" Key=\"SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" Name=\"pv\" Type=\"raw\"/>\n        </Property>\n\n        {{#if download_bootstrapper}}\n        <CustomAction Id='DownloadAndInvokeBootstrapper' Directory=\"INSTALLDIR\" Execute=\"deferred\" ExeCommand='powershell.exe -NoProfile -windowstyle hidden try [\\{] [\\[]Net.ServicePointManager[\\]]::SecurityProtocol = [\\[]Net.SecurityProtocolType[\\]]::Tls12 [\\}] catch [\\{][\\}]; Invoke-WebRequest -Uri \"https://go.microsoft.com/fwlink/p/?LinkId=2124703\" -OutFile \"$env:TEMP\\MicrosoftEdgeWebview2Setup.exe\" ; Start-Process -FilePath \"$env:TEMP\\MicrosoftEdgeWebview2Setup.exe\" -ArgumentList ({{webview_installer_args}} &apos;/install&apos;) -Wait' Return='check'/>\n        <InstallExecuteSequence>\n            <Custom Action='DownloadAndInvokeBootstrapper' Before='InstallFinalize'>\n                <![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>\n            </Custom>\n        </InstallExecuteSequence>\n        {{/if}}\n\n        <!-- Embedded webview bootstrapper mode -->\n        {{#if webview2_bootstrapper_path}}\n        <Binary Id=\"MicrosoftEdgeWebview2Setup.exe\" SourceFile=\"{{webview2_bootstrapper_path}}\"/>\n        <CustomAction Id='InvokeBootstrapper' BinaryKey='MicrosoftEdgeWebview2Setup.exe' Execute=\"deferred\" ExeCommand='{{webview_installer_args}} /install' Return='check' />\n        <InstallExecuteSequence>\n            <Custom Action='InvokeBootstrapper' Before='InstallFinalize'>\n                <![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>\n            </Custom>\n        </InstallExecuteSequence>\n        {{/if}}\n\n        <!-- Embedded offline installer -->\n        {{#if webview2_installer_path}}\n        <Binary Id=\"MicrosoftEdgeWebView2RuntimeInstaller.exe\" SourceFile=\"{{webview2_installer_path}}\"/>\n        <CustomAction Id='InvokeStandalone' BinaryKey='MicrosoftEdgeWebView2RuntimeInstaller.exe' Execute=\"deferred\" ExeCommand='{{webview_installer_args}} /install' Return='check' />\n        <InstallExecuteSequence>\n            <Custom Action='InvokeStandalone' Before='InstallFinalize'>\n                <![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>\n            </Custom>\n        </InstallExecuteSequence>\n        {{/if}}\n\n        {{/if}}\n\n        {{#if enable_elevated_update_task}}\n        <!-- Install an elevated update task within Windows Task Scheduler -->\n        <CustomAction\n            Id=\"CreateUpdateTask\"\n            Return=\"check\"\n            Directory=\"INSTALLDIR\"\n            Execute=\"commit\"\n            Impersonate=\"yes\"\n            ExeCommand=\"powershell.exe -WindowStyle hidden .\\install-task.ps1\" />\n        <InstallExecuteSequence>\n            <Custom Action='CreateUpdateTask' Before='InstallFinalize'>\n                NOT(REMOVE)\n            </Custom>\n        </InstallExecuteSequence>\n        <!-- Remove elevated update task during uninstall -->\n        <CustomAction\n            Id=\"DeleteUpdateTask\"\n            Return=\"check\"\n            Directory=\"INSTALLDIR\"\n            ExeCommand=\"powershell.exe -WindowStyle hidden .\\uninstall-task.ps1\" />\n        <InstallExecuteSequence>\n            <Custom Action=\"DeleteUpdateTask\" Before='InstallFinalize'>\n                (REMOVE = \"ALL\") AND NOT UPGRADINGPRODUCTCODE\n            </Custom>\n        </InstallExecuteSequence>\n        {{/if}}\n\n        <InstallExecuteSequence>\n          <Custom Action=\"LaunchApplication\" After=\"InstallFinalize\">AUTOLAUNCHAPP AND NOT Installed</Custom>\n        </InstallExecuteSequence>\n\n        <SetProperty Id=\"ARPINSTALLLOCATION\" Value=\"[INSTALLDIR]\" After=\"CostFinalize\"/>\n    </Product>\n</Wix>\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/msi/mod.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  bundle::{\n    settings::{Arch, Settings},\n    windows::{\n      sign::{should_sign, try_sign},\n      util::{\n        download_webview2_bootstrapper, download_webview2_offline_installer,\n        WIX_OUTPUT_FOLDER_NAME, WIX_UPDATER_OUTPUT_FOLDER_NAME,\n      },\n    },\n  },\n  error::Context,\n  utils::{\n    fs_utils::copy_file,\n    http_utils::{download_and_verify, extract_zip, HashAlgorithm},\n    CommandExt,\n  },\n};\nuse handlebars::{html_escape, to_json, Handlebars};\nuse regex::Regex;\nuse serde::{Deserialize, Serialize};\nuse std::{\n  collections::{BTreeMap, HashMap, HashSet},\n  ffi::OsStr,\n  fs::{self, File},\n  io::Write,\n  path::{Path, PathBuf},\n  process::Command,\n};\nuse tauri_utils::{config::WebviewInstallMode, display_path};\nuse uuid::Uuid;\n\n// URLS for the WIX toolchain.  Can be used for cross-platform compilation.\npub const WIX_URL: &str =\n  \"https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314-binaries.zip\";\npub const WIX_SHA256: &str = \"6ac824e1642d6f7277d0ed7ea09411a508f6116ba6fae0aa5f2c7daa2ff43d31\";\n\nconst WIX_REQUIRED_FILES: &[&str] = &[\n  \"candle.exe\",\n  \"candle.exe.config\",\n  \"darice.cub\",\n  \"light.exe\",\n  \"light.exe.config\",\n  \"wconsole.dll\",\n  \"winterop.dll\",\n  \"wix.dll\",\n  \"WixUIExtension.dll\",\n  \"WixUtilExtension.dll\",\n];\n\n/// Runs all of the commands to build the MSI installer.\n/// Returns a vector of PathBuf that shows where the MSI was created.\npub fn bundle_project(settings: &Settings, updater: bool) -> crate::Result<Vec<PathBuf>> {\n  let tauri_tools_path = settings\n    .local_tools_directory()\n    .map(|d| d.join(\".tauri\"))\n    .unwrap_or_else(|| dirs::cache_dir().unwrap().join(\"tauri\"));\n\n  let wix_path = tauri_tools_path.join(\"WixTools314\");\n\n  if !wix_path.exists() {\n    get_and_extract_wix(&wix_path)?;\n  } else if WIX_REQUIRED_FILES\n    .iter()\n    .any(|p| !wix_path.join(p).exists())\n  {\n    log::warn!(\"WixTools directory is missing some files. Recreating it.\");\n    std::fs::remove_dir_all(&wix_path)?;\n    get_and_extract_wix(&wix_path)?;\n  }\n\n  build_wix_app_installer(settings, &wix_path, updater)\n}\n\n// For Cross Platform Compilation.\n\n// const VC_REDIST_X86_URL: &str =\n//     \"https://download.visualstudio.microsoft.com/download/pr/c8edbb87-c7ec-4500-a461-71e8912d25e9/99ba493d660597490cbb8b3211d2cae4/vc_redist.x86.exe\";\n\n// const VC_REDIST_X86_SHA256: &str =\n//   \"3a43e8a55a3f3e4b73d01872c16d47a19dd825756784f4580187309e7d1fcb74\";\n\n// const VC_REDIST_X64_URL: &str =\n//     \"https://download.visualstudio.microsoft.com/download/pr/9e04d214-5a9d-4515-9960-3d71398d98c3/1e1e62ab57bbb4bf5199e8ce88f040be/vc_redist.x64.exe\";\n\n// const VC_REDIST_X64_SHA256: &str =\n//   \"d6cd2445f68815fe02489fafe0127819e44851e26dfbe702612bc0d223cbbc2b\";\n\n// A v4 UUID that was generated specifically for tauri-bundler, to be used as a\n// namespace for generating v5 UUIDs from bundle identifier strings.\nconst UUID_NAMESPACE: [u8; 16] = [\n  0xfd, 0x85, 0x95, 0xa8, 0x17, 0xa3, 0x47, 0x4e, 0xa6, 0x16, 0x76, 0x14, 0x8d, 0xfa, 0x0c, 0x7b,\n];\n\n/// Mapper between a resource directory name and its ResourceDirectory descriptor.\ntype ResourceMap = BTreeMap<String, ResourceDirectory>;\n\n#[derive(Debug, Deserialize)]\nstruct LanguageMetadata {\n  #[serde(rename = \"asciiCode\")]\n  ascii_code: usize,\n  #[serde(rename = \"langId\")]\n  lang_id: usize,\n}\n\n/// A binary to bundle with WIX.\n/// External binaries or additional project binaries are represented with this data structure.\n/// This data structure is needed because WIX requires each path to have its own `id` and `guid`.\n#[derive(Serialize)]\nstruct Binary {\n  /// the GUID to use on the WIX XML.\n  guid: String,\n  /// the id to use on the WIX XML.\n  id: String,\n  /// the binary path.\n  path: String,\n}\n\n/// A Resource file to bundle with WIX.\n/// This data structure is needed because WIX requires each path to have its own `id` and `guid`.\n#[derive(Serialize, Clone)]\nstruct ResourceFile {\n  /// the GUID to use on the WIX XML.\n  guid: String,\n  /// the id to use on the WIX XML.\n  id: String,\n  /// the file path.\n  path: PathBuf,\n}\n\n/// A resource directory to bundle with WIX.\n/// This data structure is needed because WIX requires each path to have its own `id` and `guid`.\n#[derive(Serialize)]\nstruct ResourceDirectory {\n  /// the directory path.\n  path: String,\n  /// the directory name of the described resource.\n  name: String,\n  /// the files of the described resource directory.\n  files: Vec<ResourceFile>,\n  /// the directories that are children of the described resource directory.\n  directories: Vec<ResourceDirectory>,\n}\n\nimpl ResourceDirectory {\n  /// Adds a file to this directory descriptor.\n  fn add_file(&mut self, file: ResourceFile) {\n    self.files.push(file);\n  }\n\n  /// Generates the wix XML string to bundle this directory resources recursively\n  fn get_wix_data(self) -> crate::Result<(String, Vec<String>)> {\n    let mut files = String::from(\"\");\n    let mut file_ids = Vec::new();\n    for file in self.files {\n      file_ids.push(file.id.clone());\n      files.push_str(\n        format!(\n          r#\"<Component Id=\"{id}\" Guid=\"{guid}\" Win64=\"$(var.Win64)\" KeyPath=\"yes\"><File Id=\"PathFile_{id}\" Source=\"{path}\" /></Component>\"#,\n          id = file.id,\n          guid = file.guid,\n          path = html_escape(&file.path.display().to_string())\n        ).as_str()\n      );\n    }\n    let mut directories = String::from(\"\");\n    for directory in self.directories {\n      let (wix_string, ids) = directory.get_wix_data()?;\n      for id in ids {\n        file_ids.push(id)\n      }\n      directories.push_str(wix_string.as_str());\n    }\n    let wix_string = if self.name.is_empty() {\n      format!(\"{files}{directories}\")\n    } else {\n      format!(\n        r#\"<Directory Id=\"I{id}\" Name=\"{name}\">{files}{directories}</Directory>\"#,\n        id = Uuid::new_v4().as_simple(),\n        name = html_escape(&self.name),\n        files = files,\n        directories = directories,\n      )\n    };\n\n    Ok((wix_string, file_ids))\n  }\n}\n\n/// Copies the icon to the binary path, under the `resources` folder,\n/// and returns the path to the file.\nfn copy_icon(settings: &Settings, filename: &str, path: &Path) -> crate::Result<PathBuf> {\n  let base_dir = settings.project_out_directory();\n\n  let resource_dir = base_dir.join(\"resources\");\n  fs::create_dir_all(&resource_dir)?;\n  let icon_target_path = resource_dir.join(filename);\n\n  let icon_path = std::env::current_dir()?.join(path);\n\n  copy_file(&icon_path, &icon_target_path)?;\n\n  Ok(icon_target_path)\n}\n\n/// The app installer output path.\nfn app_installer_output_path(\n  settings: &Settings,\n  language: &str,\n  version: &str,\n  updater: bool,\n) -> crate::Result<PathBuf> {\n  let arch = match settings.binary_arch() {\n    Arch::X86_64 => \"x64\",\n    Arch::X86 => \"x86\",\n    Arch::AArch64 => \"arm64\",\n    target => {\n      return Err(crate::Error::ArchError(format!(\n        \"Unsupported architecture: {target:?}\"\n      )))\n    }\n  };\n\n  let package_base_name = format!(\n    \"{}_{}_{}_{}\",\n    settings.product_name(),\n    version,\n    arch,\n    language,\n  );\n\n  Ok(settings.project_out_directory().to_path_buf().join(format!(\n    \"bundle/{}/{}.msi\",\n    if updater {\n      WIX_UPDATER_OUTPUT_FOLDER_NAME\n    } else {\n      WIX_OUTPUT_FOLDER_NAME\n    },\n    package_base_name\n  )))\n}\n\n/// Generates the UUID for the Wix template.\nfn generate_package_guid(settings: &Settings) -> Uuid {\n  generate_guid(settings.bundle_identifier().as_bytes())\n}\n\n/// Generates a GUID.\nfn generate_guid(key: &[u8]) -> Uuid {\n  let namespace = Uuid::from_bytes(UUID_NAMESPACE);\n  Uuid::new_v5(&namespace, key)\n}\n\n// Specifically goes and gets Wix and verifies the download via Sha256\npub fn get_and_extract_wix(path: &Path) -> crate::Result<()> {\n  log::info!(\"Verifying wix package\");\n\n  let data = download_and_verify(WIX_URL, WIX_SHA256, HashAlgorithm::Sha256)?;\n\n  log::info!(\"extracting WIX\");\n\n  extract_zip(&data, path)\n}\n\nfn clear_env_for_wix(cmd: &mut Command) {\n  cmd.env_clear();\n  let required_vars: Vec<std::ffi::OsString> =\n    vec![\"SYSTEMROOT\".into(), \"TMP\".into(), \"TEMP\".into()];\n  for (k, v) in std::env::vars_os() {\n    let k = k.to_ascii_uppercase();\n    if required_vars.contains(&k) || k.to_string_lossy().starts_with(\"TAURI\") {\n      cmd.env(k, v);\n    }\n  }\n}\n\nfn validate_wix_version(version_str: &str) -> crate::Result<()> {\n  let components = version_str\n    .split('.')\n    .flat_map(|c| c.parse::<u64>().ok())\n    .collect::<Vec<_>>();\n\n  if components.len() < 3 {\n    crate::error::bail!(\n      \"app wix version should be in the format major.minor.patch.build (build is optional)\"\n    );\n  }\n\n  if components[0] > 255 {\n    crate::error::bail!(\"app version major number cannot be greater than 255\");\n  }\n  if components[1] > 255 {\n    crate::error::bail!(\"app version minor number cannot be greater than 255\");\n  }\n  if components[2] > 65535 {\n    crate::error::bail!(\"app version patch number cannot be greater than 65535\");\n  }\n\n  if components.len() == 4 && components[3] > 65535 {\n    crate::error::bail!(\"app version build number cannot be greater than 65535\");\n  }\n\n  Ok(())\n}\n\n// WiX requires versions to be numeric only in a `major.minor.patch.build` format\nfn convert_version(version_str: &str) -> crate::Result<String> {\n  let version = semver::Version::parse(version_str)\n    .map_err(Into::into)\n    .context(\"invalid app version\")?;\n  if !version.build.is_empty() {\n    let build = version.build.parse::<u64>();\n    if build.map(|b| b <= 65535).unwrap_or_default() {\n      return Ok(format!(\n        \"{}.{}.{}.{}\",\n        version.major, version.minor, version.patch, version.build\n      ));\n    } else {\n      crate::error::bail!(\"optional build metadata in app version must be numeric-only and cannot be greater than 65535 for msi target\");\n    }\n  }\n\n  if !version.pre.is_empty() {\n    let pre = version.pre.parse::<u64>();\n    if pre.is_ok() && pre.unwrap() <= 65535 {\n      return Ok(format!(\n        \"{}.{}.{}.{}\",\n        version.major, version.minor, version.patch, version.pre\n      ));\n    } else {\n      crate::error::bail!(\"optional pre-release identifier in app version must be numeric-only and cannot be greater than 65535 for msi target\");\n    }\n  }\n\n  Ok(version_str.to_string())\n}\n\n/// Runs the Candle.exe executable for Wix. Candle parses the wxs file and generates the code for building the installer.\nfn run_candle(\n  settings: &Settings,\n  wix_toolset_path: &Path,\n  cwd: &Path,\n  wxs_file_path: PathBuf,\n  extensions: Vec<PathBuf>,\n) -> crate::Result<()> {\n  let arch = match settings.binary_arch() {\n    Arch::X86_64 => \"x64\",\n    Arch::X86 => \"x86\",\n    Arch::AArch64 => \"arm64\",\n    target => {\n      return Err(crate::Error::ArchError(format!(\n        \"unsupported architecture: {target:?}\"\n      )))\n    }\n  };\n\n  let main_binary = settings.main_binary()?;\n\n  let mut args = vec![\n    \"-arch\".to_string(),\n    arch.to_string(),\n    wxs_file_path.to_string_lossy().to_string(),\n    format!(\n      \"-dSourceDir={}\",\n      display_path(settings.binary_path(main_binary))\n    ),\n  ];\n\n  if settings\n    .windows()\n    .wix\n    .as_ref()\n    .map(|w| w.fips_compliant)\n    .unwrap_or_default()\n  {\n    args.push(\"-fips\".into());\n  }\n\n  let candle_exe = wix_toolset_path.join(\"candle.exe\");\n\n  log::info!(action = \"Running\"; \"candle for {:?}\", wxs_file_path);\n  let mut cmd = Command::new(candle_exe);\n  for ext in extensions {\n    cmd.arg(\"-ext\");\n    cmd.arg(ext);\n  }\n  clear_env_for_wix(&mut cmd);\n  cmd.args(&args).current_dir(cwd).output_ok()?;\n\n  Ok(())\n}\n\n/// Runs the Light.exe file. Light takes the generated code from Candle and produces an MSI Installer.\nfn run_light(\n  wix_toolset_path: &Path,\n  build_path: &Path,\n  arguments: Vec<String>,\n  extensions: &Vec<PathBuf>,\n  output_path: &Path,\n) -> crate::Result<()> {\n  let light_exe = wix_toolset_path.join(\"light.exe\");\n\n  let mut args: Vec<String> = vec![\"-o\".to_string(), display_path(output_path)];\n\n  args.extend(arguments);\n\n  let mut cmd = Command::new(light_exe);\n  for ext in extensions {\n    cmd.arg(\"-ext\");\n    cmd.arg(ext);\n  }\n  clear_env_for_wix(&mut cmd);\n  cmd.args(&args).current_dir(build_path).output_ok()?;\n\n  Ok(())\n}\n\n// fn get_icon_data() -> crate::Result<()> {\n//   Ok(())\n// }\n\n// Entry point for bundling and creating the MSI installer. For now the only supported platform is Windows x64.\npub fn build_wix_app_installer(\n  settings: &Settings,\n  wix_toolset_path: &Path,\n  updater: bool,\n) -> crate::Result<Vec<PathBuf>> {\n  let arch = match settings.binary_arch() {\n    Arch::X86_64 => \"x64\",\n    Arch::X86 => \"x86\",\n    Arch::AArch64 => \"arm64\",\n    target => {\n      return Err(crate::Error::ArchError(format!(\n        \"unsupported architecture: {target:?}\"\n      )))\n    }\n  };\n\n  let app_version = if let Some(version) = settings\n    .windows()\n    .wix\n    .as_ref()\n    .and_then(|wix| wix.version.clone())\n  {\n    version\n  } else {\n    convert_version(settings.version_string())?\n  };\n\n  validate_wix_version(&app_version)?;\n\n  // target only supports x64.\n  log::info!(\"Target: {}\", arch);\n\n  let output_path = settings.project_out_directory().join(\"wix\").join(arch);\n\n  if output_path.exists() {\n    fs::remove_dir_all(&output_path)?;\n  }\n  fs::create_dir_all(&output_path)?;\n\n  // when we're performing code signing, we'll sign some WiX DLLs, so we make a local copy\n  let wix_toolset_path = if settings.windows().can_sign() {\n    let wix_path = output_path.join(\"wix\");\n    crate::utils::fs_utils::copy_dir(wix_toolset_path, &wix_path)?;\n    wix_path\n  } else {\n    wix_toolset_path.to_path_buf()\n  };\n\n  let mut data = BTreeMap::new();\n\n  let silent_webview_install = if let WebviewInstallMode::DownloadBootstrapper { silent }\n  | WebviewInstallMode::EmbedBootstrapper { silent }\n  | WebviewInstallMode::OfflineInstaller { silent } =\n    settings.windows().webview_install_mode\n  {\n    silent\n  } else {\n    true\n  };\n\n  let webview_install_mode = if updater {\n    WebviewInstallMode::DownloadBootstrapper {\n      silent: silent_webview_install,\n    }\n  } else {\n    settings.windows().webview_install_mode.clone()\n  };\n\n  data.insert(\"install_webview\", to_json(true));\n  data.insert(\n    \"webview_installer_args\",\n    to_json(if silent_webview_install {\n      \"/silent\"\n    } else {\n      \"\"\n    }),\n  );\n\n  match webview_install_mode {\n    WebviewInstallMode::Skip | WebviewInstallMode::FixedRuntime { .. } => {\n      data.insert(\"install_webview\", to_json(false));\n    }\n    WebviewInstallMode::DownloadBootstrapper { silent: _ } => {\n      data.insert(\"download_bootstrapper\", to_json(true));\n      data.insert(\n        \"webview_installer_args\",\n        to_json(if silent_webview_install {\n          \"&apos;/silent&apos;,\"\n        } else {\n          \"\"\n        }),\n      );\n    }\n    WebviewInstallMode::EmbedBootstrapper { silent: _ } => {\n      let webview2_bootstrapper_path = download_webview2_bootstrapper(&output_path)?;\n      data.insert(\n        \"webview2_bootstrapper_path\",\n        to_json(webview2_bootstrapper_path),\n      );\n    }\n    WebviewInstallMode::OfflineInstaller { silent: _ } => {\n      let webview2_installer_path =\n        download_webview2_offline_installer(&output_path.join(arch), arch)?;\n      data.insert(\"webview2_installer_path\", to_json(webview2_installer_path));\n    }\n  }\n\n  if let Some(license) = settings.license_file() {\n    if license.ends_with(\".rtf\") {\n      data.insert(\"license\", to_json(license));\n    } else {\n      let license_contents = fs::read_to_string(license)?;\n      let license_rtf = format!(\n        r#\"{{\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat\\deflang1033{{\\fonttbl{{\\f0\\fnil\\fcharset0 Calibri;}}}}\n{{\\*\\generator Riched20 10.0.18362}}\\viewkind4\\uc1\n\\pard\\sa200\\sl276\\slmult1\\f0\\fs22\\lang9 {}\\par\n}}\n\"#,\n        license_contents.replace('\\n', \"\\\\par \")\n      );\n      let rtf_output_path = settings\n        .project_out_directory()\n        .join(\"wix\")\n        .join(\"LICENSE.rtf\");\n      std::fs::write(&rtf_output_path, license_rtf)?;\n      data.insert(\"license\", to_json(rtf_output_path));\n    }\n  }\n\n  let language_map: HashMap<String, LanguageMetadata> =\n    serde_json::from_str(include_str!(\"./languages.json\")).unwrap();\n\n  let configured_languages = settings\n    .windows()\n    .wix\n    .as_ref()\n    .map(|w| w.language.clone())\n    .unwrap_or_default();\n\n  data.insert(\"product_name\", to_json(settings.product_name()));\n  data.insert(\"version\", to_json(app_version));\n  data.insert(\n    \"long_description\",\n    to_json(settings.long_description().unwrap_or_default()),\n  );\n  data.insert(\"homepage\", to_json(settings.homepage_url()));\n  let bundle_id = settings.bundle_identifier();\n  let manufacturer = settings\n    .publisher()\n    .unwrap_or_else(|| bundle_id.split('.').nth(1).unwrap_or(bundle_id));\n  data.insert(\"bundle_id\", to_json(bundle_id));\n  data.insert(\"manufacturer\", to_json(manufacturer));\n\n  // NOTE: if this is ever changed, make sure to also update `tauri inspect wix-upgrade-code` subcommand\n  let upgrade_code = settings\n    .windows()\n    .wix\n    .as_ref()\n    .and_then(|w| w.upgrade_code)\n    .unwrap_or_else(|| {\n      Uuid::new_v5(\n        &Uuid::NAMESPACE_DNS,\n        format!(\"{}.exe.app.x64\", &settings.product_name()).as_bytes(),\n      )\n    });\n  data.insert(\"upgrade_code\", to_json(upgrade_code.to_string()));\n  data.insert(\n    \"allow_downgrades\",\n    to_json(settings.windows().allow_downgrades),\n  );\n\n  let path_guid = generate_package_guid(settings).to_string();\n  data.insert(\"path_component_guid\", to_json(path_guid.as_str()));\n\n  let shortcut_guid = generate_package_guid(settings).to_string();\n  data.insert(\"shortcut_guid\", to_json(shortcut_guid.as_str()));\n\n  let binaries = generate_binaries_data(settings)?;\n\n  let binaries_json = to_json(binaries);\n  data.insert(\"binaries\", binaries_json);\n\n  let resources = generate_resource_data(settings)?;\n  let mut resources_wix_string = String::from(\"\");\n  let mut files_ids = Vec::new();\n  for (_, dir) in resources {\n    let (wix_string, ids) = dir.get_wix_data()?;\n    resources_wix_string.push_str(wix_string.as_str());\n    for id in ids {\n      files_ids.push(id);\n    }\n  }\n\n  data.insert(\"resources\", to_json(resources_wix_string));\n  data.insert(\"resource_file_ids\", to_json(files_ids));\n\n  let merge_modules = get_merge_modules(settings)?;\n  data.insert(\"merge_modules\", to_json(merge_modules));\n\n  // Note: `main_binary_name` is not used in our template but we keep it as it is potentially useful for custom temples\n  let main_binary_name = settings.main_binary_name()?;\n  data.insert(\"main_binary_name\", to_json(main_binary_name));\n\n  let main_binary = settings.main_binary()?;\n  let main_binary_path = settings.binary_path(main_binary);\n  data.insert(\"main_binary_path\", to_json(main_binary_path));\n\n  // copy icon from `settings.windows().icon_path` folder to resource folder near msi\n  #[allow(deprecated)]\n  let icon_path = if !settings.windows().icon_path.as_os_str().is_empty() {\n    settings.windows().icon_path.clone()\n  } else {\n    settings\n      .icon_files()\n      .flatten()\n      .find(|i| i.extension() == Some(OsStr::new(\"ico\")))\n      .context(\"Couldn't find a .ico icon\")?\n  };\n  let icon_path = copy_icon(settings, \"icon.ico\", &icon_path)?;\n\n  data.insert(\"icon_path\", to_json(icon_path));\n\n  let mut fragment_paths = Vec::new();\n  let mut handlebars = Handlebars::new();\n  handlebars.register_escape_fn(handlebars::no_escape);\n  let mut custom_template_path = None;\n  let mut enable_elevated_update_task = false;\n\n  if let Some(wix) = &settings.windows().wix {\n    data.insert(\"component_group_refs\", to_json(&wix.component_group_refs));\n    data.insert(\"component_refs\", to_json(&wix.component_refs));\n    data.insert(\"feature_group_refs\", to_json(&wix.feature_group_refs));\n    data.insert(\"feature_refs\", to_json(&wix.feature_refs));\n    data.insert(\"merge_refs\", to_json(&wix.merge_refs));\n    fragment_paths.clone_from(&wix.fragment_paths);\n    enable_elevated_update_task = wix.enable_elevated_update_task;\n    custom_template_path.clone_from(&wix.template);\n\n    if let Some(banner_path) = &wix.banner_path {\n      let filename = banner_path\n        .file_name()\n        .unwrap()\n        .to_string_lossy()\n        .into_owned();\n      data.insert(\n        \"banner_path\",\n        to_json(copy_icon(settings, &filename, banner_path)?),\n      );\n    }\n\n    if let Some(dialog_image_path) = &wix.dialog_image_path {\n      let filename = dialog_image_path\n        .file_name()\n        .unwrap()\n        .to_string_lossy()\n        .into_owned();\n      data.insert(\n        \"dialog_image_path\",\n        to_json(copy_icon(settings, &filename, dialog_image_path)?),\n      );\n    }\n  }\n\n  if let Some(file_associations) = settings.file_associations() {\n    data.insert(\"file_associations\", to_json(file_associations));\n  }\n\n  if let Some(protocols) = settings.deep_link_protocols() {\n    let schemes = protocols\n      .iter()\n      .flat_map(|p| &p.schemes)\n      .collect::<Vec<_>>();\n    if !schemes.is_empty() {\n      data.insert(\"deep_link_protocols\", to_json(schemes));\n    }\n  }\n\n  if let Some(path) = custom_template_path {\n    handlebars\n      .register_template_string(\"main.wxs\", fs::read_to_string(path)?)\n      .map_err(|e| e.to_string())\n      .expect(\"Failed to setup custom handlebar template\");\n  } else {\n    handlebars\n      .register_template_string(\"main.wxs\", include_str!(\"./main.wxs\"))\n      .map_err(|e| e.to_string())\n      .expect(\"Failed to setup handlebar template\");\n  }\n\n  if enable_elevated_update_task {\n    data.insert(\n      \"msiexec_args\",\n      to_json(\n        settings\n          .updater()\n          .map(|updater| updater.msiexec_args)\n          .map(|args| args.join(\" \"))\n          .unwrap_or_else(|| \"/passive\".to_string()),\n      ),\n    );\n\n    // Create the update task XML\n    let skip_uac_task = Handlebars::new();\n    let xml = include_str!(\"./update-task.xml\");\n    let update_content = skip_uac_task.render_template(xml, &data)?;\n    let temp_xml_path = output_path.join(\"update.xml\");\n    fs::write(temp_xml_path, update_content)?;\n\n    // Create the Powershell script to install the task\n    let mut skip_uac_task_installer = Handlebars::new();\n    skip_uac_task_installer.register_escape_fn(handlebars::no_escape);\n    let xml = include_str!(\"./install-task.ps1\");\n    let install_script_content = skip_uac_task_installer.render_template(xml, &data)?;\n    let temp_ps1_path = output_path.join(\"install-task.ps1\");\n    fs::write(temp_ps1_path, install_script_content)?;\n\n    // Create the Powershell script to uninstall the task\n    let mut skip_uac_task_uninstaller = Handlebars::new();\n    skip_uac_task_uninstaller.register_escape_fn(handlebars::no_escape);\n    let xml = include_str!(\"./uninstall-task.ps1\");\n    let install_script_content = skip_uac_task_uninstaller.render_template(xml, &data)?;\n    let temp_ps1_path = output_path.join(\"uninstall-task.ps1\");\n    fs::write(temp_ps1_path, install_script_content)?;\n\n    data.insert(\"enable_elevated_update_task\", to_json(true));\n  }\n\n  let main_wxs_path = output_path.join(\"main.wxs\");\n  fs::write(&main_wxs_path, handlebars.render(\"main.wxs\", &data)?)?;\n\n  let mut candle_inputs = vec![];\n\n  let current_dir = std::env::current_dir()?;\n  let extension_regex = Regex::new(\"\\\"http://schemas.microsoft.com/wix/(\\\\w+)\\\"\")?;\n  let input_paths =\n    std::iter::once(main_wxs_path).chain(fragment_paths.iter().map(|p| current_dir.join(p)));\n\n  for input_path in input_paths {\n    let input_content = fs::read_to_string(&input_path)?;\n    let input_handlebars = Handlebars::new();\n    let input = input_handlebars.render_template(&input_content, &data)?;\n    let mut extensions = Vec::new();\n    for cap in extension_regex.captures_iter(&input) {\n      let path = wix_toolset_path.join(format!(\"Wix{}.dll\", &cap[1]));\n      if settings.windows().can_sign() {\n        try_sign(&path, settings)?;\n      }\n      extensions.push(path);\n    }\n    candle_inputs.push((input_path, extensions));\n  }\n\n  let mut fragment_extensions = HashSet::new();\n  //Default extensions\n  fragment_extensions.insert(wix_toolset_path.join(\"WixUIExtension.dll\"));\n  fragment_extensions.insert(wix_toolset_path.join(\"WixUtilExtension.dll\"));\n\n  // sign default extensions\n  if settings.windows().can_sign() {\n    for path in &fragment_extensions {\n      try_sign(path, settings)?;\n    }\n  }\n\n  for (path, extensions) in candle_inputs {\n    for ext in &extensions {\n      fragment_extensions.insert(ext.clone());\n    }\n    run_candle(settings, &wix_toolset_path, &output_path, path, extensions)?;\n  }\n\n  let mut output_paths = Vec::new();\n\n  for (language, language_config) in configured_languages.0 {\n    let language_metadata = language_map.get(&language).unwrap_or_else(|| {\n      panic!(\n        \"Language {} not found. It must be one of {}\",\n        language,\n        language_map\n          .keys()\n          .cloned()\n          .collect::<Vec<String>>()\n          .join(\", \")\n      )\n    });\n\n    let locale_contents = match language_config.locale_path {\n      Some(p) => fs::read_to_string(p)?,\n      None => format!(\n        r#\"<WixLocalization Culture=\"{}\" xmlns=\"http://schemas.microsoft.com/wix/2006/localization\"></WixLocalization>\"#,\n        language.to_lowercase(),\n      ),\n    };\n\n    let locale_strings = include_str!(\"./default-locale-strings.xml\")\n      .replace(\"__language__\", &language_metadata.lang_id.to_string())\n      .replace(\"__codepage__\", &language_metadata.ascii_code.to_string())\n      .replace(\"__productName__\", settings.product_name());\n\n    let mut unset_locale_strings = String::new();\n    let prefix_len = \"<String \".len();\n    for locale_string in locale_strings.split('\\n').filter(|s| !s.is_empty()) {\n      // strip `<String ` prefix and `>{value}</String` suffix.\n      let id = locale_string\n        .chars()\n        .skip(prefix_len)\n        .take(locale_string.find('>').unwrap() - prefix_len)\n        .collect::<String>();\n      if !locale_contents.contains(&id) {\n        unset_locale_strings.push_str(locale_string);\n      }\n    }\n\n    let locale_contents = locale_contents.replace(\n      \"</WixLocalization>\",\n      &format!(\"{unset_locale_strings}</WixLocalization>\"),\n    );\n    let locale_path = output_path.join(\"locale.wxl\");\n    {\n      let mut fileout = File::create(&locale_path).expect(\"Failed to create locale file\");\n      fileout.write_all(locale_contents.as_bytes())?;\n    }\n\n    let arguments = vec![\n      format!(\n        \"-cultures:{}\",\n        if language == \"en-US\" {\n          language.to_lowercase()\n        } else {\n          format!(\"{};en-US\", language.to_lowercase())\n        }\n      ),\n      \"-loc\".into(),\n      display_path(&locale_path),\n      \"*.wixobj\".into(),\n    ];\n    let msi_output_path = output_path.join(\"output.msi\");\n    let msi_path =\n      app_installer_output_path(settings, &language, settings.version_string(), updater)?;\n    fs::create_dir_all(msi_path.parent().unwrap())?;\n\n    log::info!(action = \"Running\"; \"light to produce {}\", display_path(&msi_path));\n\n    run_light(\n      &wix_toolset_path,\n      &output_path,\n      arguments,\n      &(fragment_extensions.clone().into_iter().collect()),\n      &msi_output_path,\n    )?;\n    fs::rename(&msi_output_path, &msi_path)?;\n\n    if settings.windows().can_sign() {\n      try_sign(&msi_path, settings)?;\n    }\n\n    output_paths.push(msi_path);\n  }\n\n  Ok(output_paths)\n}\n\n/// Generates the data required for the external binaries and extra binaries bundling.\nfn generate_binaries_data(settings: &Settings) -> crate::Result<Vec<Binary>> {\n  let mut binaries = Vec::new();\n  let cwd = std::env::current_dir()?;\n  let tmp_dir = std::env::temp_dir();\n  let regex = Regex::new(r\"[^\\w\\d\\.]\")?;\n  for src in settings.external_binaries() {\n    let src = src?;\n    let binary_path = cwd.join(&src);\n    let dest_filename = src\n      .file_name()\n      .expect(\"failed to extract external binary filename\")\n      .to_string_lossy()\n      .replace(&format!(\"-{}\", settings.target()), \"\");\n    let dest = tmp_dir.join(&dest_filename);\n    std::fs::copy(binary_path, &dest)?;\n\n    binaries.push(Binary {\n      guid: Uuid::new_v4().to_string(),\n      path: dest\n        .into_os_string()\n        .into_string()\n        .expect(\"failed to read external binary path\"),\n      id: regex\n        .replace_all(&dest_filename.replace('-', \"_\"), \"\")\n        .to_string(),\n    });\n  }\n\n  for bin in settings.binaries() {\n    if !bin.main() {\n      binaries.push(Binary {\n        guid: Uuid::new_v4().to_string(),\n        path: settings\n          .binary_path(bin)\n          .into_os_string()\n          .into_string()\n          .expect(\"failed to read binary path\"),\n        id: regex\n          .replace_all(&bin.name().replace('-', \"_\"), \"\")\n          .to_string(),\n      })\n    }\n  }\n\n  Ok(binaries)\n}\n\n#[derive(Serialize)]\nstruct MergeModule {\n  name: String,\n  path: String,\n}\n\nfn get_merge_modules(settings: &Settings) -> crate::Result<Vec<MergeModule>> {\n  let mut merge_modules = Vec::new();\n  let regex = Regex::new(r\"[^\\w\\d\\.]\")?;\n  for msm in glob::glob(\n    &PathBuf::from(glob::Pattern::escape(\n      &settings.project_out_directory().to_string_lossy(),\n    ))\n    .join(\"*.msm\")\n    .to_string_lossy(),\n  )? {\n    let path = msm?;\n    let filename = path\n      .file_name()\n      .expect(\"failed to extract merge module filename\")\n      .to_os_string()\n      .into_string()\n      .expect(\"failed to convert merge module filename to string\");\n    merge_modules.push(MergeModule {\n      name: regex.replace_all(&filename, \"\").to_string(),\n      path: path.to_string_lossy().to_string(),\n    });\n  }\n  Ok(merge_modules)\n}\n\n/// Generates the data required for the resource bundling on wix\nfn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {\n  let mut resources = ResourceMap::new();\n  let cwd = std::env::current_dir()?;\n\n  let mut added_resources = Vec::new();\n\n  for resource in settings.resource_files().iter() {\n    let resource = resource?;\n\n    let src = cwd.join(resource.path());\n    let resource_path = dunce::simplified(&src).to_path_buf();\n    // In some glob resource paths like `assets/**/*` a file might appear twice\n    // because the `tauri_utils::resources::ResourcePaths` iterator also reads a directory\n    // when it finds one. So we must check it before processing the file.\n    if added_resources.contains(&resource_path) {\n      continue;\n    }\n    added_resources.push(resource_path.clone());\n\n    if settings.windows().can_sign() && should_sign(&resource_path)? {\n      try_sign(&resource_path, settings)?;\n    }\n\n    let resource_entry = ResourceFile {\n      id: format!(\"I{}\", Uuid::new_v4().as_simple()),\n      guid: Uuid::new_v4().to_string(),\n      path: resource_path.clone(),\n    };\n\n    // split the resource path directories\n    let target_path = resource.target();\n    let components_count = target_path.components().count();\n    let directories = target_path\n      .components()\n      .take(components_count - 1) // the last component is the file\n      .collect::<Vec<_>>();\n\n    // transform the directory structure to a chained vec structure\n    let first_directory = directories\n      .first()\n      .map(|d| d.as_os_str().to_string_lossy().into_owned())\n      .unwrap_or_else(String::new);\n\n    if !resources.contains_key(&first_directory) {\n      resources.insert(\n        first_directory.clone(),\n        ResourceDirectory {\n          path: first_directory.clone(),\n          name: first_directory.clone(),\n          directories: vec![],\n          files: vec![],\n        },\n      );\n    }\n\n    let mut directory_entry = resources\n      .get_mut(&first_directory)\n      .expect(\"Unable to handle resources\");\n\n    let mut path = String::new();\n    // the first component is already parsed on `first_directory` so we skip(1)\n    for directory in directories.into_iter().skip(1) {\n      let directory_name = directory\n        .as_os_str()\n        .to_os_string()\n        .into_string()\n        .expect(\"failed to read resource folder name\");\n      path.push_str(directory_name.as_str());\n      path.push(std::path::MAIN_SEPARATOR);\n\n      let index = directory_entry\n        .directories\n        .iter()\n        .position(|f| f.path == path);\n      match index {\n        Some(i) => directory_entry = directory_entry.directories.get_mut(i).unwrap(),\n        None => {\n          directory_entry.directories.push(ResourceDirectory {\n            path: path.clone(),\n            name: directory_name,\n            directories: vec![],\n            files: vec![],\n          });\n          directory_entry = directory_entry.directories.iter_mut().last().unwrap();\n        }\n      }\n    }\n    directory_entry.add_file(resource_entry);\n  }\n\n  let mut dlls = Vec::new();\n\n  // TODO: The bundler should not include all DLLs it finds. Instead it should only include WebView2Loader.dll if present and leave the rest to the resources config.\n  let out_dir = settings.project_out_directory();\n  for dll in glob::glob(\n    &PathBuf::from(glob::Pattern::escape(&out_dir.to_string_lossy()))\n      .join(\"*.dll\")\n      .to_string_lossy(),\n  )? {\n    let path = dll?;\n    let resource_path = dunce::simplified(&path);\n    let relative_path = path\n      .strip_prefix(out_dir)\n      .unwrap()\n      .to_string_lossy()\n      .into_owned();\n    if !added_resources.iter().any(|r| r.ends_with(&relative_path)) {\n      if settings.windows().can_sign() {\n        try_sign(resource_path, settings)?;\n      }\n\n      dlls.push(ResourceFile {\n        id: format!(\"I{}\", Uuid::new_v4().as_simple()),\n        guid: Uuid::new_v4().to_string(),\n        path: resource_path.to_path_buf(),\n      });\n    }\n  }\n\n  if !dlls.is_empty() {\n    resources\n      .entry(\"\".to_string())\n      .and_modify(|r| r.files.append(&mut dlls))\n      .or_insert(ResourceDirectory {\n        path: \"\".to_string(),\n        name: \"\".to_string(),\n        directories: vec![],\n        files: dlls,\n      });\n  }\n\n  Ok(resources)\n}\n\n#[cfg(test)]\nmod tests {\n  use super::*;\n\n  #[test]\n  fn validates_wix_version() {\n    assert!(validate_wix_version(\"1.1.1\").is_ok());\n    assert!(validate_wix_version(\"1.1.1.1\").is_ok());\n    assert!(validate_wix_version(\"255.1.1.1\").is_ok());\n    assert!(validate_wix_version(\"1.255.1.1\").is_ok());\n    assert!(validate_wix_version(\"1.1.65535.1\").is_ok());\n    assert!(validate_wix_version(\"1.1.1.65535\").is_ok());\n\n    assert!(validate_wix_version(\"256.1.1.1\").is_err());\n    assert!(validate_wix_version(\"1.256.1.1\").is_err());\n    assert!(validate_wix_version(\"1.1.65536.1\").is_err());\n    assert!(validate_wix_version(\"1.1.1.65536\").is_err());\n  }\n\n  #[test]\n  fn converts_version_to_wix() {\n    assert_eq!(convert_version(\"1.1.2\").unwrap(), \"1.1.2\");\n    assert_eq!(convert_version(\"1.1.2-4\").unwrap(), \"1.1.2.4\");\n    assert_eq!(convert_version(\"1.1.2-65535\").unwrap(), \"1.1.2.65535\");\n    assert_eq!(convert_version(\"1.1.2+2\").unwrap(), \"1.1.2.2\");\n\n    assert!(convert_version(\"1.1.2-alpha\").is_err());\n    assert!(convert_version(\"1.1.2-alpha.4\").is_err());\n    assert!(convert_version(\"1.1.2+asd.3\").is_err());\n  }\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/msi/uninstall-task.ps1",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n# Adapted from https://superuser.com/a/532109\nparam([switch]$Elevated)\n\nfunction Test-Admin {\n    $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())\n    $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)\n}\n\nif ((Test-Admin) -eq $false) {\n    if ($elevated) {\n        # tried to elevate, did not work, aborting\n    }\n    else {\n        $ArgList = ('-File \"{0}\" -Elevated' -f $myinvocation.MyCommand.Definition)\n        Start-Process \"$env:SYSTEMROOT\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -WindowStyle hidden -Verb RunAs -ArgumentList $ArgList\n    }\n    exit\n}\n\nSCHTASKS.EXE /DELETE /TN 'Update {{product_name}} - Skip UAC' /F\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/msi/update-task.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-16\"?>\n<!--\n  Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n  SPDX-License-Identifier: Apache-2.0\n  SPDX-License-Identifier: MIT\n-->\n<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n  <RegistrationInfo>\n    <URI>\\Update {{product_name}} - Skip UAC</URI>\n  </RegistrationInfo>\n  <Triggers />\n  <Principals>\n    <Principal id=\"Author\">\n      <LogonType>InteractiveToken</LogonType>\n      <RunLevel>HighestAvailable</RunLevel>\n    </Principal>\n  </Principals>\n  <Settings>\n    <MultipleInstancesPolicy>Parallel</MultipleInstancesPolicy>\n    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>\n    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>\n    <AllowHardTerminate>false</AllowHardTerminate>\n    <StartWhenAvailable>false</StartWhenAvailable>\n    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>\n    <IdleSettings>\n      <StopOnIdleEnd>true</StopOnIdleEnd>\n      <RestartOnIdle>false</RestartOnIdle>\n    </IdleSettings>\n    <AllowStartOnDemand>true</AllowStartOnDemand>\n    <Enabled>true</Enabled>\n    <Hidden>false</Hidden>\n    <RunOnlyIfIdle>false</RunOnlyIfIdle>\n    <WakeToRun>false</WakeToRun>\n    <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>\n    <Priority>7</Priority>\n  </Settings>\n  <Actions Context=\"Author\">\n    <Exec>\n      <Command>cmd.exe</Command>\n      <Arguments>/c ^\"%SYSTEMROOT%\\System32\\msiexec.exe /i \"%TEMP%\\\\{{product_name}}.msi\" {{msiexec_args}} /promptrestart^\"</Arguments>\n    </Exec>\n  </Actions>\n</Task>\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/FileAssociation.nsh",
    "content": "; from https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b\n; fileassoc.nsh\n; File association helper macros\n; Written by Saivert\n;\n; Improved by Nikku<https://github.com/nikku>.\n;\n; Features automatic backup system and UPDATEFILEASSOC macro for\n; shell change notification.\n;\n; |> How to use <|\n; To associate a file with an application so you can double-click it in explorer, use\n; the APP_ASSOCIATE macro like this:\n;\n;   Example:\n;   !insertmacro APP_ASSOCIATE \"txt\" \"myapp.textfile\" \"Description of txt files\" \\\n;     \"$INSTDIR\\myapp.exe,0\" \"Open with myapp\" \"$INSTDIR\\myapp.exe $\\\"%1$\\\"\"\n;\n; Never insert the APP_ASSOCIATE macro multiple times, it is only meant\n; to associate an application with a single file and using the\n; the \"open\" verb as default. To add more verbs (actions) to a file\n; use the APP_ASSOCIATE_ADDVERB macro.\n;\n;   Example:\n;   !insertmacro APP_ASSOCIATE_ADDVERB \"myapp.textfile\" \"edit\" \"Edit with myapp\" \\\n;     \"$INSTDIR\\myapp.exe /edit $\\\"%1$\\\"\"\n;\n; To have access to more options when registering the file association use the\n; APP_ASSOCIATE_EX macro. Here you can specify the verb and what verb is to be the\n; standard action (default verb).\n;\n; Note, that this script takes into account user versus global installs.\n; To properly work you must initialize the SHELL_CONTEXT variable via SetShellVarContext.\n;\n; And finally: To remove the association from the registry use the APP_UNASSOCIATE\n; macro. Here is another example just to wrap it up:\n;   !insertmacro APP_UNASSOCIATE \"txt\" \"myapp.textfile\"\n;\n; |> Note <|\n; When defining your file class string always use the short form of your application title\n; then a period (dot) and the type of file. This keeps the file class sort of unique.\n;   Examples:\n;   Winamp.Playlist\n;   NSIS.Script\n;   Photoshop.JPEGFile\n;\n; |> Tech info <|\n; The registry key layout for a global file association is:\n;\n; HKEY_LOCAL_MACHINE\\Software\\Classes\n;     <\".ext\"> = <applicationID>\n;     <applicationID> = <\"description\">\n;         shell\n;             <verb> = <\"menu-item text\">\n;                 command = <\"command string\">\n;\n;\n; The registry key layout for a per-user file association is:\n;\n; HKEY_CURRENT_USER\\Software\\Classes\n;     <\".ext\"> = <applicationID>\n;     <applicationID> = <\"description\">\n;         shell\n;             <verb> = <\"menu-item text\">\n;                 command = <\"command string\">\n;\n\n!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND\n  ; Backup the previously associated file class\n  ReadRegStr $R0 SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"\"\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"${FILECLASS}_backup\" \"$R0\"\n\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"\" \"${FILECLASS}\"\n\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\" \"\" `${DESCRIPTION}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\DefaultIcon\" \"\" `${ICON}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\" \"\" \"open\"\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\\open\" \"\" `${COMMANDTEXT}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\\open\\command\" \"\" `${COMMAND}`\n!macroend\n\n!macro APP_ASSOCIATE_EX EXT FILECLASS DESCRIPTION ICON VERB DEFAULTVERB SHELLNEW COMMANDTEXT COMMAND\n  ; Backup the previously associated file class\n  ReadRegStr $R0 SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"\"\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"${FILECLASS}_backup\" \"$R0\"\n\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"\" \"${FILECLASS}\"\n  StrCmp \"${SHELLNEW}\" \"0\" +2\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\.${EXT}\\ShellNew\" \"NullFile\" \"\"\n\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\" \"\" `${DESCRIPTION}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\DefaultIcon\" \"\" `${ICON}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\" \"\" `${DEFAULTVERB}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\\${VERB}\" \"\" `${COMMANDTEXT}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\\${VERB}\\command\" \"\" `${COMMAND}`\n!macroend\n\n!macro APP_ASSOCIATE_ADDVERB FILECLASS VERB COMMANDTEXT COMMAND\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\\${VERB}\" \"\" `${COMMANDTEXT}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\\${VERB}\\command\" \"\" `${COMMAND}`\n!macroend\n\n!macro APP_ASSOCIATE_REMOVEVERB FILECLASS VERB\n  DeleteRegKey SHELL_CONTEXT `Software\\Classes\\${FILECLASS}\\shell\\${VERB}`\n!macroend\n\n\n!macro APP_UNASSOCIATE EXT FILECLASS\n  ; Backup the previously associated file class\n  ReadRegStr $R0 SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" `${FILECLASS}_backup`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"\" \"$R0\"\n\n  DeleteRegKey SHELL_CONTEXT `Software\\Classes\\${FILECLASS}`\n!macroend\n\n!macro APP_ASSOCIATE_GETFILECLASS OUTPUT EXT\n  ReadRegStr ${OUTPUT} SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"\"\n!macroend\n\n\n; !defines for use with SHChangeNotify\n!ifdef SHCNE_ASSOCCHANGED\n!undef SHCNE_ASSOCCHANGED\n!endif\n!define SHCNE_ASSOCCHANGED 0x08000000\n!ifdef SHCNF_FLUSH\n!undef SHCNF_FLUSH\n!endif\n!define SHCNF_FLUSH        0x1000\n\n!macro UPDATEFILEASSOC\n; Using the system.dll plugin to call the SHChangeNotify Win32 API function so we\n; can update the shell.\n  System::Call \"shell32::SHChangeNotify(i,i,i,i) (${SHCNE_ASSOCCHANGED}, ${SHCNF_FLUSH}, 0, 0)\"\n!macroend\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/installer.nsi",
    "content": "Unicode true\nManifestDPIAware true\n; Add in `dpiAwareness` `PerMonitorV2` to manifest for Windows 10 1607+ (note this should not affect lower versions since they should be able to ignore this and pick up `dpiAware` `true` set by `ManifestDPIAware true`)\n; Currently undocumented on NSIS's website but is in the Docs folder of source tree, see\n; https://github.com/kichik/nsis/blob/5fc0b87b819a9eec006df4967d08e522ddd651c9/Docs/src/attributes.but#L286-L300\n; https://github.com/tauri-apps/tauri/pull/10106\nManifestDPIAwareness PerMonitorV2\n\n!if \"{{compression}}\" == \"none\"\n  SetCompress off\n!else\n  ; Set the compression algorithm. We default to LZMA.\n  SetCompressor /SOLID \"{{compression}}\"\n!endif\n\n!include MUI2.nsh\n!include FileFunc.nsh\n!include x64.nsh\n!include WordFunc.nsh\n!include \"utils.nsh\"\n!include \"FileAssociation.nsh\"\n!include \"Win\\COM.nsh\"\n!include \"Win\\Propkey.nsh\"\n!include \"StrFunc.nsh\"\n${StrCase}\n${StrLoc}\n\n{{#if installer_hooks}}\n!include \"{{installer_hooks}}\"\n{{/if}}\n\n!define WEBVIEW2APPGUID \"{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\"\n\n!define MANUFACTURER \"{{manufacturer}}\"\n!define PRODUCTNAME \"{{product_name}}\"\n!define VERSION \"{{version}}\"\n!define VERSIONWITHBUILD \"{{version_with_build}}\"\n!define HOMEPAGE \"{{homepage}}\"\n!define INSTALLMODE \"{{install_mode}}\"\n!define LICENSE \"{{license}}\"\n!define INSTALLERICON \"{{installer_icon}}\"\n!define SIDEBARIMAGE \"{{sidebar_image}}\"\n!define HEADERIMAGE \"{{header_image}}\"\n!define MAINBINARYNAME \"{{main_binary_name}}\"\n!define MAINBINARYSRCPATH \"{{main_binary_path}}\"\n!define BUNDLEID \"{{bundle_id}}\"\n!define COPYRIGHT \"{{copyright}}\"\n!define OUTFILE \"{{out_file}}\"\n!define ARCH \"{{arch}}\"\n!define ADDITIONALPLUGINSPATH \"{{additional_plugins_path}}\"\n!define ALLOWDOWNGRADES \"{{allow_downgrades}}\"\n!define DISPLAYLANGUAGESELECTOR \"{{display_language_selector}}\"\n!define INSTALLWEBVIEW2MODE \"{{install_webview2_mode}}\"\n!define WEBVIEW2INSTALLERARGS \"{{webview2_installer_args}}\"\n!define WEBVIEW2BOOTSTRAPPERPATH \"{{webview2_bootstrapper_path}}\"\n!define WEBVIEW2INSTALLERPATH \"{{webview2_installer_path}}\"\n!define MINIMUMWEBVIEW2VERSION \"{{minimum_webview2_version}}\"\n!define UNINSTKEY \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${PRODUCTNAME}\"\n!define MANUKEY \"Software\\${MANUFACTURER}\"\n!define MANUPRODUCTKEY \"${MANUKEY}\\${PRODUCTNAME}\"\n!define UNINSTALLERSIGNCOMMAND \"{{uninstaller_sign_cmd}}\"\n!define ESTIMATEDSIZE \"{{estimated_size}}\"\n!define STARTMENUFOLDER \"{{start_menu_folder}}\"\n\nVar PassiveMode\nVar UpdateMode\nVar NoShortcutMode\nVar WixMode\nVar OldMainBinaryName\n\nName \"${PRODUCTNAME}\"\nBrandingText \"${COPYRIGHT}\"\nOutFile \"${OUTFILE}\"\n\n; We don't actually use this value as default install path,\n; it's just for nsis to append the product name folder in the directory selector\n; https://nsis.sourceforge.io/Reference/InstallDir\n!define PLACEHOLDER_INSTALL_DIR \"placeholder\\${PRODUCTNAME}\"\nInstallDir \"${PLACEHOLDER_INSTALL_DIR}\"\n\nVIProductVersion \"${VERSIONWITHBUILD}\"\nVIAddVersionKey \"ProductName\" \"${PRODUCTNAME}\"\nVIAddVersionKey \"FileDescription\" \"${PRODUCTNAME}\"\nVIAddVersionKey \"LegalCopyright\" \"${COPYRIGHT}\"\nVIAddVersionKey \"FileVersion\" \"${VERSION}\"\nVIAddVersionKey \"ProductVersion\" \"${VERSION}\"\n\n# additional plugins\n!addplugindir \"${ADDITIONALPLUGINSPATH}\"\n\n; Uninstaller signing command\n!if \"${UNINSTALLERSIGNCOMMAND}\" != \"\"\n  !uninstfinalize '${UNINSTALLERSIGNCOMMAND}'\n!endif\n\n; Handle install mode, `perUser`, `perMachine` or `both`\n!if \"${INSTALLMODE}\" == \"perMachine\"\n  RequestExecutionLevel admin\n!endif\n\n!if \"${INSTALLMODE}\" == \"currentUser\"\n  RequestExecutionLevel user\n!endif\n\n!if \"${INSTALLMODE}\" == \"both\"\n  !define MULTIUSER_MUI\n  !define MULTIUSER_INSTALLMODE_INSTDIR \"${PRODUCTNAME}\"\n  !define MULTIUSER_INSTALLMODE_COMMANDLINE\n  !if \"${ARCH}\" == \"x64\"\n    !define MULTIUSER_USE_PROGRAMFILES64\n  !else if \"${ARCH}\" == \"arm64\"\n    !define MULTIUSER_USE_PROGRAMFILES64\n  !endif\n  !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY \"${UNINSTKEY}\"\n  !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME \"CurrentUser\"\n  !define MULTIUSER_INSTALLMODEPAGE_SHOWUSERNAME\n  !define MULTIUSER_INSTALLMODE_FUNCTION RestorePreviousInstallLocation\n  !define MULTIUSER_EXECUTIONLEVEL Highest\n  !include MultiUser.nsh\n!endif\n\n; Installer icon\n!if \"${INSTALLERICON}\" != \"\"\n  !define MUI_ICON \"${INSTALLERICON}\"\n!endif\n\n; Installer sidebar image\n!if \"${SIDEBARIMAGE}\" != \"\"\n  !define MUI_WELCOMEFINISHPAGE_BITMAP \"${SIDEBARIMAGE}\"\n!endif\n\n; Installer header image\n!if \"${HEADERIMAGE}\" != \"\"\n  !define MUI_HEADERIMAGE\n  !define MUI_HEADERIMAGE_BITMAP  \"${HEADERIMAGE}\"\n!endif\n\n; Define registry key to store installer language\n!define MUI_LANGDLL_REGISTRY_ROOT \"HKCU\"\n!define MUI_LANGDLL_REGISTRY_KEY \"${MANUPRODUCTKEY}\"\n!define MUI_LANGDLL_REGISTRY_VALUENAME \"Installer Language\"\n\n; Installer pages, must be ordered as they appear\n; 1. Welcome Page\n!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n!insertmacro MUI_PAGE_WELCOME\n\n; 2. License Page (if defined)\n!if \"${LICENSE}\" != \"\"\n  !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n  !insertmacro MUI_PAGE_LICENSE \"${LICENSE}\"\n!endif\n\n; 3. Install mode (if it is set to `both`)\n!if \"${INSTALLMODE}\" == \"both\"\n  !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n  !insertmacro MULTIUSER_PAGE_INSTALLMODE\n!endif\n\n; 4. Custom page to ask user if he wants to reinstall/uninstall\n;    only if a previous installation was detected\nVar ReinstallPageCheck\nPage custom PageReinstall PageLeaveReinstall\nFunction PageReinstall\n  ; Uninstall previous WiX installation if exists.\n  ;\n  ; A WiX installer stores the installation info in registry\n  ; using a UUID and so we have to loop through all keys under\n  ; `HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall`\n  ; and check if `DisplayName` and `Publisher` keys match ${PRODUCTNAME} and ${MANUFACTURER}\n  ;\n  ; This has a potential issue that there maybe another installation that matches\n  ; our ${PRODUCTNAME} and ${MANUFACTURER} but wasn't installed by our WiX installer,\n  ; however, this should be fine since the user will have to confirm the uninstallation\n  ; and they can chose to abort it if doesn't make sense.\n  StrCpy $0 0\n  wix_loop:\n    EnumRegKey $1 HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\" $0\n    StrCmp $1 \"\" wix_loop_done ; Exit loop if there is no more keys to loop on\n    IntOp $0 $0 + 1\n    ReadRegStr $R0 HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$1\" \"DisplayName\"\n    ReadRegStr $R1 HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$1\" \"Publisher\"\n    StrCmp \"$R0$R1\" \"${PRODUCTNAME}${MANUFACTURER}\" 0 wix_loop\n    ReadRegStr $R0 HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$1\" \"UninstallString\"\n    ${StrCase} $R1 $R0 \"L\"\n    ${StrLoc} $R0 $R1 \"msiexec\" \">\"\n    StrCmp $R0 0 0 wix_loop_done\n    StrCpy $WixMode 1\n    StrCpy $R6 \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$1\"\n    Goto compare_version\n  wix_loop_done:\n\n  ; Check if there is an existing installation, if not, abort the reinstall page\n  ReadRegStr $R0 SHCTX \"${UNINSTKEY}\" \"\"\n  ReadRegStr $R1 SHCTX \"${UNINSTKEY}\" \"UninstallString\"\n  ${IfThen} \"$R0$R1\" == \"\" ${|} Abort ${|}\n\n  ; Compare this installar version with the existing installation\n  ; and modify the messages presented to the user accordingly\n  compare_version:\n  StrCpy $R4 \"$(older)\"\n  ${If} $WixMode = 1\n    ReadRegStr $R0 HKLM \"$R6\" \"DisplayVersion\"\n  ${Else}\n    ReadRegStr $R0 SHCTX \"${UNINSTKEY}\" \"DisplayVersion\"\n  ${EndIf}\n  ${IfThen} $R0 == \"\" ${|} StrCpy $R4 \"$(unknown)\" ${|}\n\n  nsis_tauri_utils::SemverCompare \"${VERSION}\" $R0\n  Pop $R0\n  ; Reinstalling the same version\n  ${If} $R0 = 0\n    StrCpy $R1 \"$(alreadyInstalledLong)\"\n    StrCpy $R2 \"$(addOrReinstall)\"\n    StrCpy $R3 \"$(uninstallApp)\"\n    !insertmacro MUI_HEADER_TEXT \"$(alreadyInstalled)\" \"$(chooseMaintenanceOption)\"\n  ; Upgrading\n  ${ElseIf} $R0 = 1\n    StrCpy $R1 \"$(olderOrUnknownVersionInstalled)\"\n    StrCpy $R2 \"$(uninstallBeforeInstalling)\"\n    StrCpy $R3 \"$(dontUninstall)\"\n    !insertmacro MUI_HEADER_TEXT \"$(alreadyInstalled)\" \"$(choowHowToInstall)\"\n  ; Downgrading\n  ${ElseIf} $R0 = -1\n    StrCpy $R1 \"$(newerVersionInstalled)\"\n    StrCpy $R2 \"$(uninstallBeforeInstalling)\"\n    !if \"${ALLOWDOWNGRADES}\" == \"true\"\n      StrCpy $R3 \"$(dontUninstall)\"\n    !else\n      StrCpy $R3 \"$(dontUninstallDowngrade)\"\n    !endif\n    !insertmacro MUI_HEADER_TEXT \"$(alreadyInstalled)\" \"$(choowHowToInstall)\"\n  ${Else}\n    Abort\n  ${EndIf}\n\n  ; Skip showing the page if passive\n  ;\n  ; Note that we don't call this earlier at the begining\n  ; of this function because we need to populate some variables\n  ; related to current installed version if detected and whether\n  ; we are downgrading or not.\n  ${If} $PassiveMode = 1\n    Call PageLeaveReinstall\n  ${Else}\n    nsDialogs::Create 1018\n    Pop $R4\n    ${IfThen} $(^RTL) = 1 ${|} nsDialogs::SetRTL $(^RTL) ${|}\n\n    ${NSD_CreateLabel} 0 0 100% 24u $R1\n    Pop $R1\n\n    ${NSD_CreateRadioButton} 30u 50u -30u 8u $R2\n    Pop $R2\n    ${NSD_OnClick} $R2 PageReinstallUpdateSelection\n\n    ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3\n    Pop $R3\n    ; Disable this radio button if downgrading and downgrades are disabled\n    !if \"${ALLOWDOWNGRADES}\" == \"false\"\n      ${IfThen} $R0 = -1 ${|} EnableWindow $R3 0 ${|}\n    !endif\n    ${NSD_OnClick} $R3 PageReinstallUpdateSelection\n\n    ; Check the first radio button if this the first time\n    ; we enter this page or if the second button wasn't\n    ; selected the last time we were on this page\n    ${If} $ReinstallPageCheck <> 2\n      SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0\n    ${Else}\n      SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0\n    ${EndIf}\n\n    ${NSD_SetFocus} $R2\n    nsDialogs::Show\n  ${EndIf}\nFunctionEnd\nFunction PageReinstallUpdateSelection\n  ${NSD_GetState} $R2 $R1\n  ${If} $R1 == ${BST_CHECKED}\n    StrCpy $ReinstallPageCheck 1\n  ${Else}\n    StrCpy $ReinstallPageCheck 2\n  ${EndIf}\nFunctionEnd\nFunction PageLeaveReinstall\n  ${NSD_GetState} $R2 $R1\n\n  ; If migrating from Wix, always uninstall\n  ${If} $WixMode = 1\n    Goto reinst_uninstall\n  ${EndIf}\n\n  ; In update mode, always proceeds without uninstalling\n  ${If} $UpdateMode = 1\n    Goto reinst_done\n  ${EndIf}\n\n  ; $R0 holds whether same(0)/upgrading(1)/downgrading(-1) version\n  ; $R1 holds the radio buttons state:\n  ;   1 => first choice was selected\n  ;   0 => second choice was selected\n  ${If} $R0 = 0 ; Same version, proceed\n    ${If} $R1 = 1              ; User chose to add/reinstall\n      Goto reinst_done\n    ${Else}                    ; User chose to uninstall\n      Goto reinst_uninstall\n    ${EndIf}\n  ${ElseIf} $R0 = 1 ; Upgrading\n    ${If} $R1 = 1              ; User chose to uninstall\n      Goto reinst_uninstall\n    ${Else}\n      Goto reinst_done         ; User chose NOT to uninstall\n    ${EndIf}\n  ${ElseIf} $R0 = -1 ; Downgrading\n    ${If} $R1 = 1              ; User chose to uninstall\n      Goto reinst_uninstall\n    ${Else}\n      Goto reinst_done         ; User chose NOT to uninstall\n    ${EndIf}\n  ${EndIf}\n\n  reinst_uninstall:\n    HideWindow\n    ClearErrors\n\n    ${If} $WixMode = 1\n      ReadRegStr $R1 HKLM \"$R6\" \"UninstallString\"\n      ExecWait '$R1' $0\n    ${Else}\n      ReadRegStr $4 SHCTX \"${MANUPRODUCTKEY}\" \"\"\n      ReadRegStr $R1 SHCTX \"${UNINSTKEY}\" \"UninstallString\"\n      ${IfThen} $UpdateMode = 1 ${|} StrCpy $R1 \"$R1 /UPDATE\" ${|} ; append /UPDATE\n      ${IfThen} $PassiveMode = 1 ${|} StrCpy $R1 \"$R1 /P\" ${|} ; append /P\n      StrCpy $R1 \"$R1 _?=$4\" ; append uninstall directory\n      ExecWait '$R1' $0\n    ${EndIf}\n\n    BringToFront\n\n    ${IfThen} ${Errors} ${|} StrCpy $0 2 ${|} ; ExecWait failed, set fake exit code\n\n    ${If} $0 <> 0\n    ${OrIf} ${FileExists} \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n      ; User cancelled wix uninstaller? return to select un/reinstall page\n      ${If} $WixMode = 1\n      ${AndIf} $0 = 1602\n        Abort\n      ${EndIf}\n\n      ; User cancelled NSIS uninstaller? return to select un/reinstall page\n      ${If} $0 = 1\n        Abort\n      ${EndIf}\n\n      ; Other erros? show generic error message and return to select un/reinstall page\n      MessageBox MB_ICONEXCLAMATION \"$(unableToUninstall)\"\n      Abort\n    ${EndIf}\n  reinst_done:\nFunctionEnd\n\n; 5. Choose install directory page\n!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n!insertmacro MUI_PAGE_DIRECTORY\n\n; 6. Start menu shortcut page\nVar AppStartMenuFolder\n!if \"${STARTMENUFOLDER}\" != \"\"\n  !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n  !define MUI_STARTMENUPAGE_DEFAULTFOLDER \"${STARTMENUFOLDER}\"\n!else\n  !define MUI_PAGE_CUSTOMFUNCTION_PRE Skip\n!endif\n!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder\n\n; 7. Installation page\n!insertmacro MUI_PAGE_INSTFILES\n\n; 8. Finish page\n;\n; Don't auto jump to finish page after installation page,\n; because the installation page has useful info that can be used debug any issues with the installer.\n!define MUI_FINISHPAGE_NOAUTOCLOSE\n; Use show readme button in the finish page as a button create a desktop shortcut\n!define MUI_FINISHPAGE_SHOWREADME\n!define MUI_FINISHPAGE_SHOWREADME_TEXT \"$(createDesktop)\"\n!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateOrUpdateDesktopShortcut\n; Show run app after installation.\n!define MUI_FINISHPAGE_RUN\n!define MUI_FINISHPAGE_RUN_FUNCTION RunMainBinary\n!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n!insertmacro MUI_PAGE_FINISH\n\nFunction RunMainBinary\n  nsis_tauri_utils::RunAsUser \"$INSTDIR\\${MAINBINARYNAME}.exe\" \"\"\nFunctionEnd\n\n; Uninstaller Pages\n; 1. Confirm uninstall page\nVar DeleteAppDataCheckbox\nVar DeleteAppDataCheckboxState\n!define /ifndef WS_EX_LAYOUTRTL         0x00400000\n!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.ConfirmShow\nFunction un.ConfirmShow ; Add add a `Delete app data` check box\n  ; $1 inner dialog HWND\n  ; $2 window DPI\n  ; $3 style\n  ; $4 x\n  ; $5 y\n  ; $6 width\n  ; $7 height\n  FindWindow $1 \"#32770\" \"\" $HWNDPARENT ; Find inner dialog\n  System::Call \"user32::GetDpiForWindow(p r1) i .r2\"\n  ${If} $(^RTL) = 1\n    StrCpy $3 \"${__NSD_CheckBox_EXSTYLE} | ${WS_EX_LAYOUTRTL}\"\n    IntOp $4 50 * $2\n  ${Else}\n    StrCpy $3 \"${__NSD_CheckBox_EXSTYLE}\"\n    IntOp $4 0 * $2\n  ${EndIf}\n  IntOp $5 100 * $2\n  IntOp $6 400 * $2\n  IntOp $7 25 * $2\n  IntOp $4 $4 / 96\n  IntOp $5 $5 / 96\n  IntOp $6 $6 / 96\n  IntOp $7 $7 / 96\n  System::Call 'user32::CreateWindowEx(i r3, w \"${__NSD_CheckBox_CLASS}\", w \"$(deleteAppData)\", i ${__NSD_CheckBox_STYLE}, i r4, i r5, i r6, i r7, p r1, i0, i0, i0) i .s'\n  Pop $DeleteAppDataCheckbox\n  SendMessage $HWNDPARENT ${WM_GETFONT} 0 0 $1\n  SendMessage $DeleteAppDataCheckbox ${WM_SETFONT} $1 1\nFunctionEnd\n!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.ConfirmLeave\nFunction un.ConfirmLeave\n  SendMessage $DeleteAppDataCheckbox ${BM_GETCHECK} 0 0 $DeleteAppDataCheckboxState\nFunctionEnd\n!define MUI_PAGE_CUSTOMFUNCTION_PRE un.SkipIfPassive\n!insertmacro MUI_UNPAGE_CONFIRM\n\n; 2. Uninstalling Page\n!insertmacro MUI_UNPAGE_INSTFILES\n\n;Languages\n{{#each languages}}\n!insertmacro MUI_LANGUAGE \"{{this}}\"\n{{/each}}\n!insertmacro MUI_RESERVEFILE_LANGDLL\n{{#each language_files}}\n  !include \"{{this}}\"\n{{/each}}\n\nFunction .onInit\n  ${GetOptions} $CMDLINE \"/P\" $PassiveMode\n  ${IfNot} ${Errors}\n    StrCpy $PassiveMode 1\n  ${EndIf}\n\n  ${GetOptions} $CMDLINE \"/NS\" $NoShortcutMode\n  ${IfNot} ${Errors}\n    StrCpy $NoShortcutMode 1\n  ${EndIf}\n\n  ${GetOptions} $CMDLINE \"/UPDATE\" $UpdateMode\n  ${IfNot} ${Errors}\n    StrCpy $UpdateMode 1\n  ${EndIf}\n\n  !if \"${DISPLAYLANGUAGESELECTOR}\" == \"true\"\n    !insertmacro MUI_LANGDLL_DISPLAY\n  !endif\n\n  !insertmacro SetContext\n\n  ${If} $INSTDIR == \"${PLACEHOLDER_INSTALL_DIR}\"\n    ; Set default install location\n    !if \"${INSTALLMODE}\" == \"perMachine\"\n      ${If} ${RunningX64}\n        !if \"${ARCH}\" == \"x64\"\n          StrCpy $INSTDIR \"$PROGRAMFILES64\\${PRODUCTNAME}\"\n        !else if \"${ARCH}\" == \"arm64\"\n          StrCpy $INSTDIR \"$PROGRAMFILES64\\${PRODUCTNAME}\"\n        !else\n          StrCpy $INSTDIR \"$PROGRAMFILES\\${PRODUCTNAME}\"\n        !endif\n      ${Else}\n        StrCpy $INSTDIR \"$PROGRAMFILES\\${PRODUCTNAME}\"\n      ${EndIf}\n    !else if \"${INSTALLMODE}\" == \"currentUser\"\n      StrCpy $INSTDIR \"$LOCALAPPDATA\\${PRODUCTNAME}\"\n    !endif\n\n    Call RestorePreviousInstallLocation\n  ${EndIf}\n\n\n  !if \"${INSTALLMODE}\" == \"both\"\n    !insertmacro MULTIUSER_INIT\n  !endif\nFunctionEnd\n\n\nSection EarlyChecks\n  ; Abort silent installer if downgrades is disabled\n  !if \"${ALLOWDOWNGRADES}\" == \"false\"\n  ${If} ${Silent}\n    ; If downgrading\n    ${If} $R0 = -1\n      System::Call 'kernel32::AttachConsole(i -1)i.r0'\n      ${If} $0 <> 0\n        System::Call 'kernel32::GetStdHandle(i -11)i.r0'\n        System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color\n        FileWrite $0 \"$(silentDowngrades)\"\n      ${EndIf}\n      Abort\n    ${EndIf}\n  ${EndIf}\n  !endif\n\nSectionEnd\n\nSection WebView2\n  ; Check if Webview2 is already installed and skip this section\n  ${If} ${RunningX64}\n    ReadRegStr $4 HKLM \"SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\${WEBVIEW2APPGUID}\" \"pv\"\n  ${Else}\n    ReadRegStr $4 HKLM \"SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\${WEBVIEW2APPGUID}\" \"pv\"\n  ${EndIf}\n  ${If} $4 == \"\"\n    ReadRegStr $4 HKCU \"SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\${WEBVIEW2APPGUID}\" \"pv\"\n  ${EndIf}\n\n  ${If} $4 == \"\"\n    ; Webview2 installation\n    ;\n    ; Skip if updating\n    ${If} $UpdateMode <> 1\n      !if \"${INSTALLWEBVIEW2MODE}\" == \"downloadBootstrapper\"\n        Delete \"$TEMP\\MicrosoftEdgeWebview2Setup.exe\"\n        DetailPrint \"$(webview2Downloading)\"\n        NSISdl::download \"https://go.microsoft.com/fwlink/p/?LinkId=2124703\" \"$TEMP\\MicrosoftEdgeWebview2Setup.exe\"\n        Pop $0\n        ${If} $0 == \"success\"\n          DetailPrint \"$(webview2DownloadSuccess)\"\n        ${Else}\n          DetailPrint \"$(webview2DownloadError)\"\n          Abort \"$(webview2AbortError)\"\n        ${EndIf}\n        StrCpy $6 \"$TEMP\\MicrosoftEdgeWebview2Setup.exe\"\n        Goto install_webview2\n      !endif\n\n      !if \"${INSTALLWEBVIEW2MODE}\" == \"embedBootstrapper\"\n        Delete \"$TEMP\\MicrosoftEdgeWebview2Setup.exe\"\n        File \"/oname=$TEMP\\MicrosoftEdgeWebview2Setup.exe\" \"${WEBVIEW2BOOTSTRAPPERPATH}\"\n        DetailPrint \"$(installingWebview2)\"\n        StrCpy $6 \"$TEMP\\MicrosoftEdgeWebview2Setup.exe\"\n        Goto install_webview2\n      !endif\n\n      !if \"${INSTALLWEBVIEW2MODE}\" == \"offlineInstaller\"\n        Delete \"$TEMP\\MicrosoftEdgeWebView2RuntimeInstaller.exe\"\n        File \"/oname=$TEMP\\MicrosoftEdgeWebView2RuntimeInstaller.exe\" \"${WEBVIEW2INSTALLERPATH}\"\n        DetailPrint \"$(installingWebview2)\"\n        StrCpy $6 \"$TEMP\\MicrosoftEdgeWebView2RuntimeInstaller.exe\"\n        Goto install_webview2\n      !endif\n\n      Goto webview2_done\n\n      install_webview2:\n        DetailPrint \"$(installingWebview2)\"\n        ; $6 holds the path to the webview2 installer\n        ExecWait \"$6 ${WEBVIEW2INSTALLERARGS} /install\" $1\n        ${If} $1 = 0\n          DetailPrint \"$(webview2InstallSuccess)\"\n        ${Else}\n          DetailPrint \"$(webview2InstallError)\"\n          Abort \"$(webview2AbortError)\"\n        ${EndIf}\n      webview2_done:\n    ${EndIf}\n  ${Else}\n    !if \"${MINIMUMWEBVIEW2VERSION}\" != \"\"\n      ${VersionCompare} \"${MINIMUMWEBVIEW2VERSION}\" \"$4\" $R0\n      ${If} $R0 = 1\n        update_webview:\n          DetailPrint \"$(installingWebview2)\"\n          ${If} ${RunningX64}\n            ReadRegStr $R1 HKLM \"SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\" \"path\"\n          ${Else}\n            ReadRegStr $R1 HKLM \"SOFTWARE\\Microsoft\\EdgeUpdate\" \"path\"\n          ${EndIf}\n          ${If} $R1 == \"\"\n            ReadRegStr $R1 HKCU \"SOFTWARE\\Microsoft\\EdgeUpdate\" \"path\"\n          ${EndIf}\n          ${If} $R1 != \"\"\n            ; Chromium updater docs: https://source.chromium.org/chromium/chromium/src/+/main:docs/updater/user_manual.md\n            ; Modified from \"HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Microsoft EdgeWebView\\ModifyPath\"\n            ExecWait `\"$R1\" /install appguid=${WEBVIEW2APPGUID}&needsadmin=true` $1\n            ${If} $1 = 0\n              DetailPrint \"$(webview2InstallSuccess)\"\n            ${Else}\n              MessageBox MB_ICONEXCLAMATION|MB_ABORTRETRYIGNORE \"$(webview2InstallError)\" IDIGNORE ignore IDRETRY update_webview\n              Quit\n              ignore:\n            ${EndIf}\n          ${EndIf}\n      ${EndIf}\n    !endif\n  ${EndIf}\nSectionEnd\n\nSection Install\n  SetOutPath $INSTDIR\n\n  !ifmacrodef NSIS_HOOK_PREINSTALL\n    !insertmacro NSIS_HOOK_PREINSTALL\n  !endif\n\n  !insertmacro CheckIfAppIsRunning \"${MAINBINARYNAME}.exe\" \"${PRODUCTNAME}\"\n\n  ; Copy main executable\n  File \"${MAINBINARYSRCPATH}\"\n\n  ; Copy resources\n  {{#each resources_dirs}}\n    CreateDirectory \"$INSTDIR\\\\{{this}}\"\n  {{/each}}\n  {{#each resources}}\n    File /a \"/oname={{this.[1]}}\" \"{{no-escape @key}}\"\n  {{/each}}\n\n  ; Copy external binaries\n  {{#each binaries}}\n    File /a \"/oname={{this}}\" \"{{no-escape @key}}\"\n  {{/each}}\n\n  ; Create file associations\n  {{#each file_associations as |association| ~}}\n    {{#each association.ext as |ext| ~}}\n       !insertmacro APP_ASSOCIATE \"{{ext}}\" \"{{or association.name ext}}\" \"{{association-description association.description ext}}\" \"$INSTDIR\\${MAINBINARYNAME}.exe,0\" \"Open with ${PRODUCTNAME}\" \"$INSTDIR\\${MAINBINARYNAME}.exe $\\\"%1$\\\"\"\n    {{/each}}\n  {{/each}}\n\n  ; Register deep links\n  {{#each deep_link_protocols as |protocol| ~}}\n    WriteRegStr SHCTX \"Software\\Classes\\\\{{protocol}}\" \"URL Protocol\" \"\"\n    WriteRegStr SHCTX \"Software\\Classes\\\\{{protocol}}\" \"\" \"URL:${BUNDLEID} protocol\"\n    WriteRegStr SHCTX \"Software\\Classes\\\\{{protocol}}\\DefaultIcon\" \"\" \"$\\\"$INSTDIR\\${MAINBINARYNAME}.exe$\\\",0\"\n    WriteRegStr SHCTX \"Software\\Classes\\\\{{protocol}}\\shell\\open\\command\" \"\" \"$\\\"$INSTDIR\\${MAINBINARYNAME}.exe$\\\" $\\\"%1$\\\"\"\n  {{/each}}\n\n  ; Create uninstaller\n  WriteUninstaller \"$INSTDIR\\uninstall.exe\"\n\n  ; Save $INSTDIR in registry for future installations\n  WriteRegStr SHCTX \"${MANUPRODUCTKEY}\" \"\" $INSTDIR\n\n  !if \"${INSTALLMODE}\" == \"both\"\n    ; Save install mode to be selected by default for the next installation such as updating\n    ; or when uninstalling\n    WriteRegStr SHCTX \"${UNINSTKEY}\" $MultiUser.InstallMode 1\n  !endif\n\n  ; Remove old main binary if it doesn't match new main binary name\n  ReadRegStr $OldMainBinaryName SHCTX \"${UNINSTKEY}\" \"MainBinaryName\"\n  ${If} $OldMainBinaryName != \"\"\n  ${AndIf} $OldMainBinaryName != \"${MAINBINARYNAME}.exe\"\n    Delete \"$INSTDIR\\$OldMainBinaryName\"\n  ${EndIf}\n\n  ; Save current MAINBINARYNAME for future updates\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"MainBinaryName\" \"${MAINBINARYNAME}.exe\"\n\n  ; Registry information for add/remove programs\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"DisplayName\" \"${PRODUCTNAME}\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"DisplayIcon\" \"$\\\"$INSTDIR\\${MAINBINARYNAME}.exe$\\\"\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"DisplayVersion\" \"${VERSION}\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"Publisher\" \"${MANUFACTURER}\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"InstallLocation\" \"$\\\"$INSTDIR$\\\"\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"UninstallString\" \"$\\\"$INSTDIR\\uninstall.exe$\\\"\"\n  WriteRegDWORD SHCTX \"${UNINSTKEY}\" \"NoModify\" \"1\"\n  WriteRegDWORD SHCTX \"${UNINSTKEY}\" \"NoRepair\" \"1\"\n\n  ${GetSize} \"$INSTDIR\" \"/M=uninstall.exe /S=0K /G=0\" $0 $1 $2\n  IntOp $0 $0 + ${ESTIMATEDSIZE}\n  IntFmt $0 \"0x%08X\" $0\n  WriteRegDWORD SHCTX \"${UNINSTKEY}\" \"EstimatedSize\" \"$0\"\n\n  !if \"${HOMEPAGE}\" != \"\"\n    WriteRegStr SHCTX \"${UNINSTKEY}\" \"URLInfoAbout\" \"${HOMEPAGE}\"\n    WriteRegStr SHCTX \"${UNINSTKEY}\" \"URLUpdateInfo\" \"${HOMEPAGE}\"\n    WriteRegStr SHCTX \"${UNINSTKEY}\" \"HelpLink\" \"${HOMEPAGE}\"\n  !endif\n\n  ; Create start menu shortcut\n  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n    Call CreateOrUpdateStartMenuShortcut\n  !insertmacro MUI_STARTMENU_WRITE_END\n\n  ; Create desktop shortcut for silent and passive installers\n  ; because finish page will be skipped\n  ${If} $PassiveMode = 1\n  ${OrIf} ${Silent}\n    Call CreateOrUpdateDesktopShortcut\n  ${EndIf}\n\n  !ifmacrodef NSIS_HOOK_POSTINSTALL\n    !insertmacro NSIS_HOOK_POSTINSTALL\n  !endif\n\n  ; Auto close this page for passive mode\n  ${If} $PassiveMode = 1\n    SetAutoClose true\n  ${EndIf}\nSectionEnd\n\nFunction .onInstSuccess\n  ; Check for `/R` flag only in silent and passive installers because\n  ; GUI installer has a toggle for the user to (re)start the app\n  ${If} $PassiveMode = 1\n  ${OrIf} ${Silent}\n    ${GetOptions} $CMDLINE \"/R\" $R0\n    ${IfNot} ${Errors}\n      ${GetOptions} $CMDLINE \"/ARGS\" $R0\n      nsis_tauri_utils::RunAsUser \"$INSTDIR\\${MAINBINARYNAME}.exe\" \"$R0\"\n    ${EndIf}\n  ${EndIf}\nFunctionEnd\n\nFunction un.onInit\n  !insertmacro SetContext\n\n  !if \"${INSTALLMODE}\" == \"both\"\n    !insertmacro MULTIUSER_UNINIT\n  !endif\n\n  !insertmacro MUI_UNGETLANGUAGE\n\n  ${GetOptions} $CMDLINE \"/P\" $PassiveMode\n  ${IfNot} ${Errors}\n    StrCpy $PassiveMode 1\n  ${EndIf}\n\n  ${GetOptions} $CMDLINE \"/UPDATE\" $UpdateMode\n  ${IfNot} ${Errors}\n    StrCpy $UpdateMode 1\n  ${EndIf}\nFunctionEnd\n\nSection Uninstall\n\n  !ifmacrodef NSIS_HOOK_PREUNINSTALL\n    !insertmacro NSIS_HOOK_PREUNINSTALL\n  !endif\n\n  !insertmacro CheckIfAppIsRunning \"${MAINBINARYNAME}.exe\" \"${PRODUCTNAME}\"\n\n  ; Delete the app directory and its content from disk\n  ; Copy main executable\n  Delete \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n\n  ; Delete resources\n  {{#each resources}}\n    Delete \"$INSTDIR\\\\{{this.[1]}}\"\n  {{/each}}\n\n  ; Delete external binaries\n  {{#each binaries}}\n    Delete \"$INSTDIR\\\\{{this}}\"\n  {{/each}}\n\n  ; Delete app associations\n  {{#each file_associations as |association| ~}}\n    {{#each association.ext as |ext| ~}}\n      !insertmacro APP_UNASSOCIATE \"{{ext}}\" \"{{or association.name ext}}\"\n    {{/each}}\n  {{/each}}\n\n  ; Delete deep links\n  {{#each deep_link_protocols as |protocol| ~}}\n    ReadRegStr $R7 SHCTX \"Software\\Classes\\\\{{protocol}}\\shell\\open\\command\" \"\"\n    ${If} $R7 == \"$\\\"$INSTDIR\\${MAINBINARYNAME}.exe$\\\" $\\\"%1$\\\"\"\n      DeleteRegKey SHCTX \"Software\\Classes\\\\{{protocol}}\"\n    ${EndIf}\n  {{/each}}\n\n\n  ; Delete uninstaller\n  Delete \"$INSTDIR\\uninstall.exe\"\n\n  {{#each resources_ancestors}}\n  RMDir /REBOOTOK \"$INSTDIR\\\\{{this}}\"\n  {{/each}}\n  RMDir \"$INSTDIR\"\n\n  ; Remove shortcuts if not updating\n  ${If} $UpdateMode <> 1\n    !insertmacro DeleteAppUserModelId\n\n    ; Remove start menu shortcut\n    !insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder\n    !insertmacro IsShortcutTarget \"$SMPROGRAMS\\$AppStartMenuFolder\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n    Pop $0\n    ${If} $0 = 1\n      !insertmacro UnpinShortcut \"$SMPROGRAMS\\$AppStartMenuFolder\\${PRODUCTNAME}.lnk\"\n      Delete \"$SMPROGRAMS\\$AppStartMenuFolder\\${PRODUCTNAME}.lnk\"\n      RMDir \"$SMPROGRAMS\\$AppStartMenuFolder\"\n    ${EndIf}\n    !insertmacro IsShortcutTarget \"$SMPROGRAMS\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n    Pop $0\n    ${If} $0 = 1\n      !insertmacro UnpinShortcut \"$SMPROGRAMS\\${PRODUCTNAME}.lnk\"\n      Delete \"$SMPROGRAMS\\${PRODUCTNAME}.lnk\"\n    ${EndIf}\n\n    ; Remove desktop shortcuts\n    !insertmacro IsShortcutTarget \"$DESKTOP\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n    Pop $0\n    ${If} $0 = 1\n      !insertmacro UnpinShortcut \"$DESKTOP\\${PRODUCTNAME}.lnk\"\n      Delete \"$DESKTOP\\${PRODUCTNAME}.lnk\"\n    ${EndIf}\n  ${EndIf}\n\n  ; Remove registry information for add/remove programs\n  !if \"${INSTALLMODE}\" == \"both\"\n    DeleteRegKey SHCTX \"${UNINSTKEY}\"\n  !else if \"${INSTALLMODE}\" == \"perMachine\"\n    DeleteRegKey HKLM \"${UNINSTKEY}\"\n  !else\n    DeleteRegKey HKCU \"${UNINSTKEY}\"\n  !endif\n\n  ; Removes the Autostart entry for ${PRODUCTNAME} from the HKCU Run key if it exists.\n  ; This ensures the program does not launch automatically after uninstallation if it exists.\n  ; If it doesn't exist, it does nothing.\n  ; We do this when not updating (to preserve the registry value on updates)\n  ${If} $UpdateMode <> 1\n    DeleteRegValue HKCU \"Software\\Microsoft\\Windows\\CurrentVersion\\Run\" \"${PRODUCTNAME}\"\n  ${EndIf}\n\n  ; Delete app data if the checkbox is selected\n  ; and if not updating\n  ${If} $DeleteAppDataCheckboxState = 1\n  ${AndIf} $UpdateMode <> 1\n    ; Clear the install location $INSTDIR from registry\n    DeleteRegKey SHCTX \"${MANUPRODUCTKEY}\"\n    DeleteRegKey /ifempty SHCTX \"${MANUKEY}\"\n\n    ; Clear the install language from registry\n    DeleteRegValue HKCU \"${MANUPRODUCTKEY}\" \"Installer Language\"\n    DeleteRegKey /ifempty HKCU \"${MANUPRODUCTKEY}\"\n    DeleteRegKey /ifempty HKCU \"${MANUKEY}\"\n\n    SetShellVarContext current\n    RmDir /r \"$APPDATA\\${BUNDLEID}\"\n    RmDir /r \"$LOCALAPPDATA\\${BUNDLEID}\"\n  ${EndIf}\n\n  !ifmacrodef NSIS_HOOK_POSTUNINSTALL\n    !insertmacro NSIS_HOOK_POSTUNINSTALL\n  !endif\n\n  ; Auto close if passive mode or updating\n  ${If} $PassiveMode = 1\n  ${OrIf} $UpdateMode = 1\n    SetAutoClose true\n  ${EndIf}\nSectionEnd\n\nFunction RestorePreviousInstallLocation\n  ReadRegStr $4 SHCTX \"${MANUPRODUCTKEY}\" \"\"\n  StrCmp $4 \"\" +2 0\n    StrCpy $INSTDIR $4\nFunctionEnd\n\nFunction Skip\n  Abort\nFunctionEnd\n\nFunction SkipIfPassive\n  ${IfThen} $PassiveMode = 1  ${|} Abort ${|}\nFunctionEnd\nFunction un.SkipIfPassive\n  ${IfThen} $PassiveMode = 1  ${|} Abort ${|}\nFunctionEnd\n\nFunction CreateOrUpdateStartMenuShortcut\n  ; We used to use product name as MAINBINARYNAME\n  ; migrate old shortcuts to target the new MAINBINARYNAME\n  StrCpy $R0 0\n\n  !insertmacro IsShortcutTarget \"$SMPROGRAMS\\$AppStartMenuFolder\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\$OldMainBinaryName\"\n  Pop $0\n  ${If} $0 = 1\n    !insertmacro SetShortcutTarget \"$SMPROGRAMS\\$AppStartMenuFolder\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n    StrCpy $R0 1\n  ${EndIf}\n\n  !insertmacro IsShortcutTarget \"$SMPROGRAMS\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\$OldMainBinaryName\"\n  Pop $0\n  ${If} $0 = 1\n    !insertmacro SetShortcutTarget \"$SMPROGRAMS\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n    StrCpy $R0 1\n  ${EndIf}\n\n  ${If} $R0 = 1\n    Return\n  ${EndIf}\n\n  ; Skip creating shortcut if in update mode or no shortcut mode\n  ; but always create if migrating from wix\n  ${If} $WixMode = 0\n    ${If} $UpdateMode = 1\n    ${OrIf} $NoShortcutMode = 1\n      Return\n    ${EndIf}\n  ${EndIf}\n\n  !if \"${STARTMENUFOLDER}\" != \"\"\n    CreateDirectory \"$SMPROGRAMS\\$AppStartMenuFolder\"\n    CreateShortcut \"$SMPROGRAMS\\$AppStartMenuFolder\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n    !insertmacro SetLnkAppUserModelId \"$SMPROGRAMS\\$AppStartMenuFolder\\${PRODUCTNAME}.lnk\"\n  !else\n    CreateShortcut \"$SMPROGRAMS\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n    !insertmacro SetLnkAppUserModelId \"$SMPROGRAMS\\${PRODUCTNAME}.lnk\"\n  !endif\nFunctionEnd\n\nFunction CreateOrUpdateDesktopShortcut\n  ; We used to use product name as MAINBINARYNAME\n  ; migrate old shortcuts to target the new MAINBINARYNAME\n  !insertmacro IsShortcutTarget \"$DESKTOP\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\$OldMainBinaryName\"\n  Pop $0\n  ${If} $0 = 1\n    !insertmacro SetShortcutTarget \"$DESKTOP\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n    Return\n  ${EndIf}\n\n  ; Skip creating shortcut if in update mode or no shortcut mode\n  ; but always create if migrating from wix\n  ${If} $WixMode = 0\n    ${If} $UpdateMode = 1\n    ${OrIf} $NoShortcutMode = 1\n      Return\n    ${EndIf}\n  ${EndIf}\n\n  CreateShortcut \"$DESKTOP\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n  !insertmacro SetLnkAppUserModelId \"$DESKTOP\\${PRODUCTNAME}.lnk\"\nFunctionEnd\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Arabic.nsh",
    "content": "LangString addOrReinstall ${LANG_ARABIC} \"إضافة أو إزالة المكونات\"\nLangString alreadyInstalled ${LANG_ARABIC} \"التطبيق مثبت بالفعل\"\nLangString alreadyInstalledLong ${LANG_ARABIC} \"${PRODUCTNAME} ${VERSION} مثبت بالفعل. قم باختيار العملية التى تريدها ثم اضغط على التالى.\"\nLangString appRunning ${LANG_ARABIC} \"{{product_name}} مازال يعمل! من فضلك، قم بإغلاق التطبيق أولاً ثم حاول مرة أخرى.\"\nLangString appRunningOkKill ${LANG_ARABIC} \"{{product_name}} مازال يعمل!$\\nاضغط OK لإغلاقه\"\nLangString chooseMaintenanceOption ${LANG_ARABIC} \"قم باختيار نوع الصيانة التى تريدها.\"\nLangString choowHowToInstall ${LANG_ARABIC} \"قم باختيار طريقة تنصيب ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_ARABIC} \"اضف اختصار على سطح المكتب\"\nLangString dontUninstall ${LANG_ARABIC} \"عدم إزالة\"\nLangString dontUninstallDowngrade ${LANG_ARABIC} \"عدم إزالة (التخفيض بدون إزالة غير مسموح لهذا المثبت)\"\nLangString failedToKillApp ${LANG_ARABIC} \"فشل فى غلف {{product_name}}. من فضلك، قم بإغلاق التطبيق أولاً ثم حاول مرة أخرى.\"\nLangString installingWebview2 ${LANG_ARABIC} \"تنصيب WebView2...\"\nLangString newerVersionInstalled ${LANG_ARABIC} \"يوجد نسخة جديدة من ${PRODUCTNAME} مثبتة بالغعل! لا ينصح بتنصيب نسخة اقدم من النسخة الحالية. اذا مازلت ترغب فى تنصيب النسخة الأقدم، فينصح بإزالة النسخة الحالية أولاً. قم باختيار العملية التى تريدها ثم اضغط على التالى للاستمرار.\"\nLangString older ${LANG_ARABIC} \"أقدم\"\nLangString olderOrUnknownVersionInstalled ${LANG_ARABIC} \"نسخة $R4 من ${PRODUCTNAME} مثبتة بالفعل على نظامك. ينصح بإزالة النسخة الحالية قبل التنصيب. قم باختيار العملية التى تريدها ثم اضغط على التالى للاستمرار.\"\nLangString silentDowngrades ${LANG_ARABIC} \"التخفيض غير مسموح لهذا المثبت ولا يمكن الاستمرار فى الوضع الصامت. من فضلك، قم باستخدام الواجهة الرسومية.\"\nLangString unableToUninstall ${LANG_ARABIC} \"غير قادر على الإزالة!\"\nLangString uninstallApp ${LANG_ARABIC} \"إزالة ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_ARABIC} \"قم بإزالة التطبيق قبل التثبيت\"\nLangString unknown ${LANG_ARABIC} \"غير معروفة\"\nLangString webview2AbortError ${LANG_ARABIC} \"فشل فى تنصيب WebView2! لا يمكن تشغيل التطبيق من غيره. من فضلك، حاول إعادة تشغيل المثبت.\"\nLangString webview2DownloadError ${LANG_ARABIC} \"خطأ: فشل تنزيل WebView2 - $0\"\nLangString webview2DownloadSuccess ${LANG_ARABIC} \"تم تنزيل WebView2 bootstrapper بنجاح\"\nLangString webview2Downloading ${LANG_ARABIC} \"يتم تنزيل WebView2 bootstrapper...\"\nLangString webview2InstallError ${LANG_ARABIC} \"خطأ: فشل فى تنصيب WebView2 بكود $1\"\nLangString webview2InstallSuccess ${LANG_ARABIC} \"تم تنصيب WebView2 بنجاح\"\nLangString deleteAppData ${LANG_ARABIC} \"مسح بيانات التطبيق\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Bulgarian.nsh",
    "content": "LangString addOrReinstall ${LANG_BULGARIAN} \"Добавяне/Преинсталиране на компоненти\"\nLangString alreadyInstalled ${LANG_BULGARIAN} \"Вече инсталиран\"\nLangString alreadyInstalledLong ${LANG_BULGARIAN} \"${PRODUCTNAME} ${VERSION} е вече е инсталиран. Изберете операцията, която искате да извършите и натиснете Напред, за да продължите.\"\nLangString appRunning ${LANG_BULGARIAN} \"{{product_name}} е отворен! Моля, затворете го първо и опитайте отново.\"\nLangString appRunningOkKill ${LANG_BULGARIAN} \"{{product_name}} е отворен!$\\nНатиснете ОК, за да го затворите.\"\nLangString chooseMaintenanceOption ${LANG_BULGARIAN} \"Изберете опция за поддръжка.\"\nLangString choowHowToInstall ${LANG_BULGARIAN} \"Изберете как искате да инсталирате ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_BULGARIAN} \"Създайте пряк път на работния плот\"\nLangString dontUninstall ${LANG_BULGARIAN} \"Не деинсталирайте\"\nLangString dontUninstallDowngrade ${LANG_BULGARIAN} \"Не деинсталирайте (Понижаването без деинсталация е забранено за този инсталатор)\"\nLangString failedToKillApp ${LANG_BULGARIAN} \"Неуспешно прекратяване на {{product_name}}. Моля, затворете го първо и опитайте отново.\"\nLangString installingWebview2 ${LANG_BULGARIAN} \"Инсталиране на WebView2...\"\nLangString newerVersionInstalled ${LANG_BULGARIAN} \"Вече е инсталирана по-нова версия на ${PRODUCTNAME}! Не се препоръчва да инсталирате по-стара версия. Ако наистина желаете да инсталирате тази по-стара версия, по-добре е да деинсталирате текущата версия първо. Изберете операцията, която искате да извършите и натиснете Напред, за да продължите.\"\nLangString older ${LANG_BULGARIAN} \"по-стара\"\nLangString olderOrUnknownVersionInstalled ${LANG_BULGARIAN} \"На вашата система е инсталирана $R4 версия на ${PRODUCTNAME}. Препоръчително е да деинсталирате текущата версия преди да инсталирате нова. Изберете операцията, която искате да извършите и натиснете Напред, за да продължите.\"\nLangString silentDowngrades ${LANG_BULGARIAN} \"Понижаванията не са позволени за този инсталатор. Не е възможно да продължите с безшумен инсталатор. Моля, използвайте графичния интерфейсен инсталатор.\"\nLangString unableToUninstall ${LANG_BULGARIAN} \"Неуспешна деинсталация!\"\nLangString uninstallApp ${LANG_BULGARIAN} \"Деинсталиране на ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_BULGARIAN} \"Деинсталирай преди инсталиране\"\nLangString unknown ${LANG_BULGARIAN} \"неизвестно\"\nLangString webview2AbortError ${LANG_BULGARIAN} \"Неуспешно инсталиране на WebView2! Приложението не може да работи без него. Опитайте да рестартирате инсталатора.\"\nLangString webview2DownloadError ${LANG_BULGARIAN} \"Грешка: Неуспешно изтегляне на WebView2 - $0\"\nLangString webview2DownloadSuccess ${LANG_BULGARIAN} \"Стартиращият файл на WebView2 е изтеглен успешно\"\nLangString webview2Downloading ${LANG_BULGARIAN} \"Изтегляне на стартиращят файл на WebView2...\"\nLangString webview2InstallError ${LANG_BULGARIAN} \"Грешка: Инсталирането на WebView2 неуспешно с код на изход $1\"\nLangString webview2InstallSuccess ${LANG_BULGARIAN} \"WebView2 инсталиран успешно\"\nLangString deleteAppData ${LANG_BULGARIAN} \"Изтриване на данните на приложението\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Dutch.nsh",
    "content": "LangString addOrReinstall ${LANG_DUTCH} \"(Her)installeer componenten\"\nLangString alreadyInstalled ${LANG_DUTCH} \"Al geïnstalleerd\"\nLangString alreadyInstalledLong ${LANG_DUTCH} \"${PRODUCTNAME} ${VERSION} is al geïnstalleerd. Kies een van de volgende opties en klik op Volgende om door te gaan.\"\nLangString appRunning ${LANG_DUTCH} \"{{product_name}} is geopend! Sluit het programma eerst en probeer het dan opnieuw.\"\nLangString appRunningOkKill ${LANG_DUTCH} \"{{product_name}} is geopend!$\\nKlik op OK om het te stoppen.\"\nLangString chooseMaintenanceOption ${LANG_DUTCH} \"Kies de onderhoudsoptie die u wilt uitvoeren.\"\nLangString choowHowToInstall ${LANG_DUTCH} \"Kies hoe u ${PRODUCTNAME} wilt installeren.\"\nLangString createDesktop ${LANG_DUTCH} \"Maak een snelkoppeling aan op het bureaublad\"\nLangString dontUninstall ${LANG_DUTCH} \"Deïnstalleer niet\"\nLangString dontUninstallDowngrade ${LANG_DUTCH} \"Deïnstalleer niet (Downgraden zonder deïnstalleren is uitgeschakeld voor deze installer)\"\nLangString failedToKillApp ${LANG_DUTCH} \"Het is niet gelukt {{product_name}} te stoppen. Sluit het eerst zelf en probeer het dan nog een keer\"\nLangString installingWebview2 ${LANG_DUTCH} \"WebView2 wordt geïnstalleerd...\"\nLangString newerVersionInstalled ${LANG_DUTCH} \"Een nieuwere versie van ${PRODUCTNAME} is al geïnstalleerd! Het word niet aangeraden om een oudere versie te installeren. Als u echt deze oudere versie wilt installeren, kunt u beter de huidige versie eerst deïnstalleren. Kies een van de volgende opties en klik op Volgende om door te gaan.\"\nLangString older ${LANG_DUTCH} \"oudere\"\nLangString olderOrUnknownVersionInstalled ${LANG_DUTCH} \"Een $R4 versie van ${PRODUCTNAME} is al geïnstalleerd. Het word aangeraden om de huidige versie eerst te deïnstalleren. Kies een van de volgende opties en klik op Volgende om door te gaan.\"\nLangString silentDowngrades ${LANG_DUTCH} \"Downgrades zijn uitgeschakeld voor deze installer, de stille installatie kan niet worden voltooid, gebruik a.u.b. de grafische installatie methode.$\\n\"\nLangString unableToUninstall ${LANG_DUTCH} \"De installatie kan niet ongedaan worden gemaakt.\"\nLangString uninstallApp ${LANG_DUTCH} \"Deïnstalleer ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_DUTCH} \"Deïnstalleer voor installatie\"\nLangString unknown ${LANG_DUTCH} \"onbekende\"\nLangString webview2AbortError ${LANG_DUTCH} \"De installatie van WebView2 is mislukt! De software kan niet draaien zonder. Probeer de installatie opnieuw te starten.\"\nLangString webview2DownloadError ${LANG_DUTCH} \"Error: Het downloaden van WebView2 is mislukt - $0\"\nLangString webview2DownloadSuccess ${LANG_DUTCH} \"De download van WebView2 bootstrapper is gelukt\"\nLangString webview2Downloading ${LANG_DUTCH} \"WebView2 bootstrapper aan het downloaden...\"\nLangString webview2InstallError ${LANG_DUTCH} \"Error: Het installeren van WebView2 is mislukt met exit-code $1\"\nLangString webview2InstallSuccess ${LANG_DUTCH} \"De installatie van WebView2 is gelukt\"\nLangString deleteAppData ${LANG_DUTCH} \"Verwijder de data van de applicatie\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/English.nsh",
    "content": "LangString addOrReinstall ${LANG_ENGLISH} \"Add/Reinstall components\"\nLangString alreadyInstalled ${LANG_ENGLISH} \"Already Installed\"\nLangString alreadyInstalledLong ${LANG_ENGLISH} \"${PRODUCTNAME} ${VERSION} is already installed. Select the operation you want to perform and click Next to continue.\"\nLangString appRunning ${LANG_ENGLISH} \"{{product_name}} is running! Please close it first then try again.\"\nLangString appRunningOkKill ${LANG_ENGLISH} \"{{product_name}} is running!$\\nClick OK to kill it\"\nLangString chooseMaintenanceOption ${LANG_ENGLISH} \"Choose the maintenance option to perform.\"\nLangString choowHowToInstall ${LANG_ENGLISH} \"Choose how you want to install ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_ENGLISH} \"Create desktop shortcut\"\nLangString dontUninstall ${LANG_ENGLISH} \"Do not uninstall\"\nLangString dontUninstallDowngrade ${LANG_ENGLISH} \"Do not uninstall (Downgrading without uninstall is disabled for this installer)\"\nLangString failedToKillApp ${LANG_ENGLISH} \"Failed to kill {{product_name}}. Please close it first then try again\"\nLangString installingWebview2 ${LANG_ENGLISH} \"Installing WebView2...\"\nLangString newerVersionInstalled ${LANG_ENGLISH} \"A newer version of ${PRODUCTNAME} is already installed! It is not recommended that you install an older version. If you really want to install this older version, it's better to uninstall the current version first. Select the operation you want to perform and click Next to continue.\"\nLangString older ${LANG_ENGLISH} \"older\"\nLangString olderOrUnknownVersionInstalled ${LANG_ENGLISH} \"An $R4 version of ${PRODUCTNAME} is installed on your system. It's recommended that you uninstall the current version before installing. Select the operation you want to perform and click Next to continue.\"\nLangString silentDowngrades ${LANG_ENGLISH} \"Downgrades are disabled for this installer, can't proceed with the silent installer, please use the graphical interface installer instead.$\\n\"\nLangString unableToUninstall ${LANG_ENGLISH} \"Unable to uninstall!\"\nLangString uninstallApp ${LANG_ENGLISH} \"Uninstall ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_ENGLISH} \"Uninstall before installing\"\nLangString unknown ${LANG_ENGLISH} \"unknown\"\nLangString webview2AbortError ${LANG_ENGLISH} \"Failed to install WebView2! The app can't run without it. Try restarting the installer.\"\nLangString webview2DownloadError ${LANG_ENGLISH} \"Error: Downloading WebView2 Failed - $0\"\nLangString webview2DownloadSuccess ${LANG_ENGLISH} \"WebView2 bootstrapper downloaded successfully\"\nLangString webview2Downloading ${LANG_ENGLISH} \"Downloading WebView2 bootstrapper...\"\nLangString webview2InstallError ${LANG_ENGLISH} \"Error: Installing WebView2 failed with exit code $1\"\nLangString webview2InstallSuccess ${LANG_ENGLISH} \"WebView2 installed successfully\"\nLangString deleteAppData ${LANG_ENGLISH} \"Delete the application data\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/French.nsh",
    "content": "LangString addOrReinstall ${LANG_FRENCH} \"Ajouter/Réinstaller un composant.\"\nLangString alreadyInstalled ${LANG_FRENCH} \"Déja installé.\"\nLangString alreadyInstalledLong ${LANG_FRENCH} \"${PRODUCTNAME} ${VERSION} est déja installé. Sélectionnez l'opération que vous souhaitez effectuer, puis cliquez sur Suivant pour continuer.\"\nLangString appRunning ${LANG_FRENCH} \"{{product_name}} est en cours d'exécution. Veuillez fermer l'application avant de réessayer.\"\nLangString appRunningOkKill ${LANG_FRENCH} \"{{product_name}} est en cours d'exécution.$\\nCliquez sur OK pour fermer l'application.\"\nLangString chooseMaintenanceOption ${LANG_FRENCH} \"Veuillez choisir l'option de maintenance à effectuer.\"\nLangString choowHowToInstall ${LANG_FRENCH} \"Veuillez choisir l'emplacement d'installation de ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_FRENCH} \"Créer un raccourci sur le bureau.\"\nLangString dontUninstall ${LANG_FRENCH} \"Ne pas désinstaller\"\nLangString dontUninstallDowngrade ${LANG_FRENCH} \"Ne pas désinstaller (revenir à une ancienne version sans désinstallation est désactivé pour cet installateur)\"\nLangString failedToKillApp ${LANG_FRENCH} \"La fermeture de {{product_name}} a échoué. Veuillez fermer l'application et réessayer.\"\nLangString installingWebview2 ${LANG_FRENCH} \"Installation de WebView2...\"\nLangString newerVersionInstalled ${LANG_FRENCH} \"Une version plus récente de ${PRODUCTNAME} est déja installée. Il n'est pas recommandé d'installer une ancienne version. Si vous souhaitez installer cette ancienne version, il est conseillé de désinstaller la version courante en premier. Veuillez sélectionner l'opération que vous souhaitez effectuer, puis cliquez sur Suivant pour continer.\"\nLangString older ${LANG_FRENCH} \"ancien\"\nLangString olderOrUnknownVersionInstalled ${LANG_FRENCH} \"La version $R4 de ${PRODUCTNAME} est installée sur le système. Il est recommandé de désinstaller la version actuelle avant d'installer celle-ci. Sélectionnez l'opération que vous souhaitez effectuer, puis cliquez sur Suivant pour continuer.\"\nLangString silentDowngrades ${LANG_FRENCH} \"Revenir à une version antérieure est désactivé pour cet installateur. Impossible de continuer avec l'installation silencieuse, veuillez utiliser l'interface graphique à la place.$\\n\"\nLangString unableToUninstall ${LANG_FRENCH} \"Impossible de désinstaller le programme !\"\nLangString uninstallApp ${LANG_FRENCH} \"Désinstaller ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_FRENCH} \"Désinstaller avant d'installer\"\nLangString unknown ${LANG_FRENCH} \"inconnu\"\nLangString webview2AbortError ${LANG_FRENCH} \"L'installation de WebView2 a échoué ! L'application ne peut s'éxécuter sans WebView2. Veuillez essayer de redémarrer l'installation.\"\nLangString webview2DownloadError ${LANG_FRENCH} \"Erreur : le téléchargement de WebView2 a échoué - $0\"\nLangString webview2DownloadSuccess ${LANG_FRENCH} \"Le composant WebView2 a été téléchargé avec succès !\"\nLangString webview2Downloading ${LANG_FRENCH} \"Téléchargement du composant WebView2...\"\nLangString webview2InstallError ${LANG_FRENCH} \"Erreur : l'installation de WebView2 a échoué avec le code d'erreur $1\"\nLangString webview2InstallSuccess ${LANG_FRENCH} \"L'installation de WebView2 a réussi\"\nLangString deleteAppData ${LANG_FRENCH} \"Supprimer les données de l'application\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/German.nsh",
    "content": "LangString addOrReinstall ${LANG_GERMAN} \"Komponenten hinzufügen/neu installieren\"\nLangString alreadyInstalled ${LANG_GERMAN} \"Bereits installiert\"\nLangString alreadyInstalledLong ${LANG_GERMAN} \"${PRODUCTNAME} ${VERSION} ist bereits installiert. Wählen Sie den gewünschten Vorgang aus und klicken Sie auf Weiter, um fortzufahren.\"\nLangString appRunning ${LANG_GERMAN} \"{{product_name}} wird ausgeführt! Bitte schließen Sie es zuerst und versuchen Sie es dann erneut.\"\nLangString appRunningOkKill ${LANG_GERMAN} \"{{product_name}} läuft! $\\nKlicken Sie auf OK, um es zu beenden\"\nLangString chooseMaintenanceOption ${LANG_GERMAN} \"Wählen Sie die auszuführende Wartungsoption.\"\nLangString choowHowToInstall ${LANG_GERMAN} \"Wählen Sie, wie Sie ${PRODUCTNAME} installieren möchten.\"\nLangString createDesktop ${LANG_GERMAN} \"Desktop-Verknüpfung erstellen\"\nLangString dontUninstall ${LANG_GERMAN} \"Nicht deinstallieren\"\nLangString dontUninstallDowngrade ${LANG_GERMAN} \"Nicht deinstallieren (Downgrading ohne Deinstallation ist für dieses Installationsprogramm deaktiviert)\"\nLangString failedToKillApp ${LANG_GERMAN} \"Failed to kill {{product_name}}. Bitte schließen Sie es zuerst und versuchen Sie es dann erneut\"\nLangString installingWebview2 ${LANG_GERMAN} \"Installiere WebView2...\"\nLangString newerVersionInstalled ${LANG_GERMAN} \"Eine neuere Version von ${PRODUCTNAME} ist bereits installiert! Es wird nicht empfohlen, eine ältere Version zu installieren. Wenn Sie diese ältere Version wirklich installieren wollen, ist es besser, die aktuelle Version zuerst zu deinstallieren. Wählen Sie den gewünschten Vorgang aus und klicken Sie auf Weiter, um fortzufahren.\"\nLangString älter ${LANG_GERMAN} \"älter\"\nLangString olderOrUnknownVersionInstalled ${LANG_GERMAN} \"Eine $R4-Version von ${PRODUCTNAME} ist auf Ihrem System installiert. Es wird empfohlen, dass Sie die aktuelle Version vor der Installation deinstallieren. Wählen Sie den gewünschten Vorgang aus und klicken Sie auf Weiter, um fortzufahren.\"\nLangString silentDowngrades ${LANG_GERMAN} \"Downgrades sind für dieses Installationsprogramm deaktiviert, Sie können nicht mit dem Silent-Installationsprogramm fortfahren, bitte verwenden Sie stattdessen das Installationsprogramm mit grafischer Benutzeroberfläche.$\\n\"\nLangString unableToUninstall ${LANG_GERMAN} \"Unable to uninstall!\"\nLangString uninstallApp ${LANG_GERMAN} \"Deinstalliere ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_GERMAN} \"Vor der Installation deinstallieren\"\nLangString unbekannt ${LANG_GERMAN} \"unbekannt\"\nLangString webview2AbortError ${LANG_GERMAN} \"Die Installation von WebView2 ist fehlgeschlagen! Die Anwendung kann ohne es nicht laufen. Versuchen Sie, das Installationsprogramm neu zu starten.\"\nLangString webview2DownloadError ${LANG_GERMAN} \"Fehler: Herunterladen von WebView2 fehlgeschlagen - $0\"\nLangString webview2DownloadSuccess ${LANG_GERMAN} \"WebView2 Bootstrapper erfolgreich heruntergeladen\"\nLangString webview2Downloading ${LANG_GERMAN} \"Herunterladen des WebView2 Bootstrappers...\"\nLangString webview2InstallError ${LANG_GERMAN} \"Fehler: Die Installation von WebView2 ist mit Exit Code $1 fehlgeschlagen\"\nLangString webview2InstallSuccess ${LANG_GERMAN} \"WebView2 erfolgreich installiert\"\nLangString deleteAppData ${LANG_GERMAN} \"Lösche die Anwendungsdaten\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Hebrew.nsh",
    "content": "LangString addOrReinstall ${LANG_HEBREW} \"הוסף או התקן מחדש\"\nLangString alreadyInstalled ${LANG_HEBREW} \"כבר מותקן\"\nLangString alreadyInstalledLong ${LANG_HEBREW} \"${PRODUCTNAME} ${VERSION} כבר מותקן. בחר את הפעולה שברצונך לבצע ולחץ על הבא כדי להמשיך.\"\nLangString appRunning ${LANG_HEBREW} \"{{product_name}} פועל! נא לסגור אותו ולנסות שוב.\"\nLangString appRunningOkKill ${LANG_HEBREW} \"{{product_name}} פועל!$\\nלחץ אישור כדי לסגור אותו.\"\nLangString chooseMaintenanceOption ${LANG_HEBREW} \"בחר את פעולת התחזוקה לביצוע\"\nLangString choowHowToInstall ${LANG_HEBREW} \"בחר איך תרצה להתקין את ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_HEBREW} \"צור קיצור דרך בשולחן העבודה\"\nLangString dontUninstall ${LANG_HEBREW} \"אל תסיר\"\nLangString dontUninstallDowngrade ${LANG_HEBREW} \"אל תסיר (התקנת גרסה ישנה ללא הסרת הגרסה הנוכחית מושעית עבור התקנה זו)\"\nLangString failedToKillApp ${LANG_HEBREW} \"עצירת {{product_name}} נכשלה. נא לסגור את היישום ולנסות שוב.\"\nLangString installingWebview2 ${LANG_HEBREW} \"מתקין את WebView2...\"\nLangString newerVersionInstalled ${LANG_HEBREW} \"גרסה חדשה יותר של ${PRODUCTNAME} כבר מותקנת! לא מומלץ להתקין גרסה ישנה. אם בכל זאת תרצה להתקין את הגרסה הזו, מומלץ קודם להסיר את הגרסה הנוכחית. בחר את הפעולה שברצונך לבצע ולחץ הבא להמשך.\"\nLangString older ${LANG_HEBREW} \"ישנה\"\nLangString olderOrUnknownVersionInstalled ${LANG_HEBREW} \"גרסה $R4 של ${PRODUCTNAME} מותקנת במערכת שלך. מומלץ להסיר את הגרסה הנוכחית לפני ההתקנה. בחר את הפעולה שברצונך לבצע ולחץ הבא להמשך.\"\nLangString silentDowngrades ${LANG_HEBREW} \"התקנת גרסה ישנה לא נתמכת בהתקנה זו, אין אפשרות להמשיך עם ההתקנה השקטה, נא להמשיך עם ההתקנה בממשק הגרפי.$\\n\"\nLangString unableToUninstall ${LANG_HEBREW} \"לא ניתן להסיר!\"\nLangString uninstallApp ${LANG_HEBREW} \"הסר את ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_HEBREW} \"הסר את הגרסה הנוכחית לפני התקנת גרסה זו\"\nLangString unknown ${LANG_HEBREW} \"לא ידועה\"\nLangString webview2AbortError ${LANG_HEBREW} \"התקנת WebView2 נכשלה! היישום אינו יכולה לפעול בלי זה. נסה להפעיל את ההתקנה שוב.\"\nLangString webview2DownloadError ${LANG_HEBREW} \"שגיאה: הורדת WebView2 נכשלה - $0\"\nLangString webview2DownloadSuccess ${LANG_HEBREW} \"מאתחל WebView2 הורד בהצלחה\"\nLangString webview2Downloading ${LANG_HEBREW} \"מוריד את מאתחל WebView2...\"\nLangString webview2InstallError ${LANG_HEBREW} \"שגיאה: התקנת WebView2 נכשלה עם קוד שגיאה $1\"\nLangString webview2InstallSuccess ${LANG_HEBREW} \"WebView2 הותקן בהצלחה\"\nLangString deleteAppData ${LANG_HEBREW} \"מחק את נתוני היישום\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Italian.nsh",
    "content": "LangString addOrReinstall ${LANG_ITALIAN} \"Aggiungi/Reinstalla componenti\"\nLangString alreadyInstalled ${LANG_ITALIAN} \"Già installato\"\nLangString alreadyInstalledLong ${LANG_ITALIAN} \"${PRODUCTNAME} ${VERSION} è già installato. Seleziona l'operazione che vuoi eseguire e clicca Avanti per continuare.\"\nLangString appRunning ${LANG_ITALIAN} \"{{product_name}} è in esecuzione! Chiudi e poi riprova.\"\nLangString appRunningOkKill ${LANG_ITALIAN} \"{{product_name}} è in esecuzione!$\\nSeleziona OK per chiuderlo\"\nLangString chooseMaintenanceOption ${LANG_ITALIAN} \"Seleziona l'operazione di manutenzione da eseguire.\"\nLangString choowHowToInstall ${LANG_ITALIAN} \"Seleziona come vuoi installare ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_ITALIAN} \"Crea scorciatoia sul Desktop\"\nLangString dontUninstall ${LANG_ITALIAN} \"Non disinstallare\"\nLangString dontUninstallDowngrade ${LANG_ITALIAN} \"Non disinstallare (Il downgrade senza la disinstallazione è disabilitato per questo installer)\"\nLangString failedToKillApp ${LANG_ITALIAN} \"Impossibile chiudere {{product_name}}. Chiudi e poi riprova\"\nLangString installingWebview2 ${LANG_ITALIAN} \"Installando WebView2...\"\nLangString newerVersionInstalled ${LANG_ITALIAN} \"Una versione più recente di ${PRODUCTNAME} è già installata! Non è consigliato installare una versione più vecchia. Se vuoi comunque procedere, è meglio prima disinstallare la versione corrente. Seleziona l'operazione che vuoi eseguire e clicca Avanti per continuare.\"\nLangString older ${LANG_ITALIAN} \"più vecchia\"\nLangString olderOrUnknownVersionInstalled ${LANG_ITALIAN} \"Una versione $R4 di ${PRODUCTNAME} è installata nel tuo sistema. È consigliato che disinstalli la versione corrente prima di procedere all'installazione. Seleziona l'operazione che vuoi eseguire e clicca Avanti per continuare.\"\nLangString silentDowngrades ${LANG_ITALIAN} \"I downgrade sono disabilitati per questo installer, impossibile procedere con l'installer silenzioso, usa invece l'installer con interfaccia grafica.$\\n\"\nLangString unableToUninstall ${LANG_ITALIAN} \"Impossibile disinstallare!\"\nLangString uninstallApp ${LANG_ITALIAN} \"Disinstalla ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_ITALIAN} \"Disinstalla prima di installare\"\nLangString unknown ${LANG_ITALIAN} \"sconosciuta\"\nLangString webview2AbortError ${LANG_ITALIAN} \"Errore nell'installazione di WebView2! L'app non può funzionare senza. Prova a riavviare l'installer.\"\nLangString webview2DownloadError ${LANG_ITALIAN} \"Errore: Il download di WebView2 è fallito - $0\"\nLangString webview2DownloadSuccess ${LANG_ITALIAN} \"Bootstrapper WebView2 scaricato con successo\"\nLangString webview2Downloading ${LANG_ITALIAN} \"Scaricando il bootstrapper WebView2...\"\nLangString webview2InstallError ${LANG_ITALIAN} \"Errore: L'installazione di WebView2 è fallita con il codice $1\"\nLangString webview2InstallSuccess ${LANG_ITALIAN} \"WebView2 installato correttamente\"\nLangString deleteAppData ${LANG_ITALIAN} \"Cancella i dati dell'applicazione\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Japanese.nsh",
    "content": "LangString addOrReinstall ${LANG_JAPANESE} \"コンポーネントの追加・再インストール\"\nLangString alreadyInstalled ${LANG_JAPANESE} \"既にインストールされています\"\nLangString alreadyInstalledLong ${LANG_JAPANESE} \"${PRODUCTNAME} ${VERSION} は既にインストールされています。実行したい操作を選択し、「次へ」をクリックして続行します。\"\nLangString appRunning ${LANG_JAPANESE} \"{{product_name}} は動作中です。動作中のプログラムを終了し、もう一度やり直してください。\"\nLangString appRunningOkKill ${LANG_JAPANESE} \"{{product_name}} は動作中です。$\\n「OK」を押すと動作中のプログラムを終了します。\"\nLangString chooseMaintenanceOption ${LANG_JAPANESE} \"メンテナンスオプションを選択して実行します。\"\nLangString choowHowToInstall ${LANG_JAPANESE} \"${PRODUCTNAME} のインストール方法を選択してください。\"\nLangString createDesktop ${LANG_JAPANESE} \"デスクトップショートカットを作成する\"\nLangString dontUninstall ${LANG_JAPANESE} \"アンインストールしない\"\nLangString dontUninstallDowngrade ${LANG_JAPANESE} \"アンインストールしない (このインストーラーでは、アンインストールをせずにダウングレードすることはできません)\"\nLangString failedToKillApp ${LANG_JAPANESE} \"{{product_name}} の終了に失敗しました。動作中のプログラムを終了し、もう一度やり直してください。\"\nLangString installingWebview2 ${LANG_JAPANESE} \"WebView2 をインストール中です...\"\nLangString newerVersionInstalled ${LANG_JAPANESE} \"既に新しいバージョンの ${PRODUCTNAME} がインストールされています。古いバージョンをインストールすることは推奨されません。どうしてもこの旧バージョンをインストールしたい場合は、先に現行バージョンをアンインストールしておく方がよいでしょう。実行したい操作を選択し、「次へ」をクリックして続行します。\"\nLangString older ${LANG_JAPANESE} \"旧\"\nLangString olderOrUnknownVersionInstalled ${LANG_JAPANESE} \"お使いのシステムには、 ${PRODUCTNAME} のバージョン $R4 がインストールされています。インストールする前に、現在のバージョンをアンインストールすることをお勧めします。実行したい操作を選択し、「次へ」をクリックして続行します。\"\nLangString silentDowngrades ${LANG_JAPANESE} \"このインストーラーではダウングレードはできません。サイレントインストーラーを続行できないので、代わりにグラフィカルインターフェースインストーラーを使用してください。$\\n\"\nLangString unableToUninstall ${LANG_JAPANESE} \"アンインストールできません。\"\nLangString uninstallApp ${LANG_JAPANESE} \"${PRODUCTNAME} をアンインストールする\"\nLangString uninstallBeforeInstalling ${LANG_JAPANESE} \"インストールする前にアンインストールする\"\nLangString unknown ${LANG_JAPANESE} \"不明\"\nLangString webview2AbortError ${LANG_JAPANESE} \"WebView2 のインストールに失敗しました。 WebView2 がないとアプリは実行できません。インストーラーを再起動してください。\"\nLangString webview2DownloadError ${LANG_JAPANESE} \"エラー: WebView2 のダウンロードに失敗しました - $0\"\nLangString webview2DownloadSuccess ${LANG_JAPANESE} \"WebView2 ブートストラップ が正常にダウンロードされました\"\nLangString webview2Downloading ${LANG_JAPANESE} \"WebView2 ブートストラップ をダウンロード中です...\"\nLangString webview2InstallError ${LANG_JAPANESE} \"エラー: WebView2 のインストールは終了コード $1 で失敗しました。\"\nLangString webview2InstallSuccess ${LANG_JAPANESE} \"WebView2 が正常にインストールされました\"\nLangString deleteAppData ${LANG_JAPANESE} \"アプリケーションデータを削除する\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Korean.nsh",
    "content": "LangString addOrReinstall ${LANG_KOREAN} \"컴포넌트 추가 및 재설치\"\nLangString alreadyInstalled ${LANG_KOREAN} \"이미 설치되어 있습니다\"\nLangString alreadyInstalledLong ${LANG_KOREAN} \"${PRODUCTNAME} ${VERSION}이(가) 이미 설치되어 있습니다. 수행하고자 하는 작업을 선택하고 '다음'을 클릭하여 계속합니다.\"\nLangString appRunning ${LANG_KOREAN} \"{{product_name}}이(가) 실행 중입니다! 먼저 닫은 후 다시 시도하세요.\"\nLangString appRunningOkKill ${LANG_KOREAN} \"{{product_name}}이(가) 실행 중입니다!$\\n'OK'를 누르면 실행 중인 프로그램을 종료합니다.\"\nLangString chooseMaintenanceOption ${LANG_KOREAN} \"수행하려는 관리 옵션을 선택합니다.\"\nLangString choowHowToInstall ${LANG_KOREAN} \"${PRODUCTNAME}의 설치 방법을 선택하세요..\"\nLangString createDesktop ${LANG_KOREAN} \"바탕화면 바로가기 만들기\"\nLangString dontUninstall ${LANG_KOREAN} \"제거하지 않기\"\nLangString dontUninstallDowngrade ${LANG_KOREAN} \"제거하지 않기 (이 설치 프로그램에서는 제거하지 않고 다운그레이드할 수 없습니다.)\"\nLangString failedToKillApp ${LANG_KOREAN} \"{{product_name}}을(를) 종료하지 못했습니다. 먼저 닫은 후 다시 시도하세요.\"\nLangString installingWebview2 ${LANG_KOREAN} \"WebView2를 설치하는 중입니다...\"\nLangString newerVersionInstalled ${LANG_KOREAN} \"${PRODUCTNAME}의 최신 버전이 이미 설치되어 있습니다! 이전 버전을 설치하지 않는 것이 좋습니다. 이 이전 버전을 꼭 설치하려면 먼저 현재 버전을 제거하는 것이 좋습니다. 수행하려는 작업을 선택하고 '다음'을 클릭하여 계속합니다.\"\nLangString older ${LANG_KOREAN} \"구\"\nLangString olderOrUnknownVersionInstalled ${LANG_KOREAN} \"시스템에 ${PRODUCTNAME}의 $R4 버전이 설치되어 있습니다. 설치하기 전에 현재 버전을 제거하는 것이 좋습니다. 수행하려는 작업을 선택하고 다음을 클릭하여 계속합니다.\"\nLangString silentDowngrades ${LANG_KOREAN} \"이 설치 프로그램에서는 다운그레이드가 비활성화되어 자동 설치 프로그램을 진행할 수 없습니다. 대신 그래픽 인터페이스 설치 프로그램을 사용하세요.$\\n\"\nLangString unableToUninstall ${LANG_KOREAN} \"제거할 수 없습니다!\"\nLangString uninstallApp ${LANG_KOREAN} \"${PRODUCTNAME} 제거하기\"\nLangString uninstallBeforeInstalling ${LANG_KOREAN} \"설치하기 전에 제거하기\"\nLangString unknown ${LANG_KOREAN} \"알 수 없음\"\nLangString webview2AbortError ${LANG_KOREAN} \"WebView2를 설치하지 못했습니다! WebView2가 없으면 앱을 실행할 수 없습니다. 인스톨러를 다시 시작해보세요.\"\nLangString webview2DownloadError ${LANG_KOREAN} \"오류: WebView2 다운로드를 실패하였습니다. - $0\"\nLangString webview2DownloadSuccess ${LANG_KOREAN} \"WebView2 부트스트래퍼가 성공적으로 다운로드되었습니다.\"\nLangString webview2Downloading ${LANG_KOREAN} \"WebView2 부트스트래퍼 다운로드 중...\"\nLangString webview2InstallError ${LANG_KOREAN} \"오류: 종료 코드 $1로 WebView2를 설치하지 못했습니다.\"\nLangString webview2InstallSuccess ${LANG_KOREAN} \"WebView2가 성공적으로 설치되었습니다.\"\nLangString deleteAppData ${LANG_KOREAN} \"애플리케이션 데이터 삭제하기\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Norwegian.nsh",
    "content": "LangString addOrReinstall ${LANG_NORWEGIAN} \"Legg til/reinstaller komponenter\"\nLangString alreadyInstalled ${LANG_NORWEGIAN} \"Allerede installert\"\nLangString alreadyInstalledLong ${LANG_NORWEGIAN} \"${PRODUCTNAME} ${VERSION} er allerede installert. Velg operasjonen du vil utføre og klikk Neste for å fortsette.\"\nLangString appRunning ${LANG_NORWEGIAN} \"{{product_name}} kjører! Lukk den først og prøv igjen.\"\nLangString appRunningOkKill ${LANG_NORWEGIAN} \"{{product_name}} kjører!$\\nKlikk OK for å avslutte den\"\nLangString chooseMaintenanceOption ${LANG_NORWEGIAN} \"Velg vedlikeholdsoperasjonen som skal utføres.\"\nLangString choowHowToInstall ${LANG_NORWEGIAN} \"Velg hvordan du vil installere ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_NORWEGIAN} \"Opprett skrivebordssnarvei\"\nLangString dontUninstall ${LANG_NORWEGIAN} \"Ikke avinstaller\"\nLangString dontUninstallDowngrade ${LANG_NORWEGIAN} \"Ikke avinstaller (nedgradering uten avinstallasjon er deaktivert for denne installasjonen)\"\nLangString failedToKillApp ${LANG_NORWEGIAN} \"Kunne ikke avslutte {{product_name}}. Lukk den først og prøv igjen\"\nLangString installingWebview2 ${LANG_NORWEGIAN} \"Installerer WebView2...\"\nLangString newerVersionInstalled ${LANG_NORWEGIAN} \"En nyere versjon av ${PRODUCTNAME} er allerede installert! Det anbefales ikke at du installerer en eldre versjon. Hvis du virkelig vil installere denne eldre versjonen, er det bedre å avinstallere den nåværende versjonen først. Velg operasjonen du vil utføre og klikk Neste for å fortsette.\"\nLangString older ${LANG_NORWEGIAN} \"eldre\"\nLangString olderOrUnknownVersionInstalled ${LANG_NORWEGIAN} \"En $R4-versjon av ${PRODUCTNAME} er installert på systemet ditt. Det anbefales at du avinstallerer den nåværende versjonen før installasjon. Velg operasjonen du vil utføre og klikk Neste for å fortsette.\"\nLangString silentDowngrades ${LANG_NORWEGIAN} \"Nedgraderinger er deaktivert for denne installasjonen. Kan ikke fortsette med stille installasjon; bruk den grafiske installasjonen i stedet.$\\n\"\nLangString unableToUninstall ${LANG_NORWEGIAN} \"Kunne ikke avinstallere!\"\nLangString uninstallApp ${LANG_NORWEGIAN} \"Avinstaller ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_NORWEGIAN} \"Avinstaller før installasjon\"\nLangString unknown ${LANG_NORWEGIAN} \"ukjent\"\nLangString webview2AbortError ${LANG_NORWEGIAN} \"Kunne ikke installere WebView2! Appen kan ikke kjøre uten den. Prøv å starte installasjonen på nytt.\"\nLangString webview2DownloadError ${LANG_NORWEGIAN} \"Feil: Nedlasting av WebView2 mislyktes - $0\"\nLangString webview2DownloadSuccess ${LANG_NORWEGIAN} \"WebView2-bootstrapper lastet ned\"\nLangString webview2Downloading ${LANG_NORWEGIAN} \"Laster ned WebView2-bootstrapper...\"\nLangString webview2InstallError ${LANG_NORWEGIAN} \"Feil: Installering av WebView2 mislyktes med avslutningskode $1\"\nLangString webview2InstallSuccess ${LANG_NORWEGIAN} \"WebView2 ble installert\"\nLangString deleteAppData ${LANG_NORWEGIAN} \"Slett programdata\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Persian.nsh",
    "content": "LangString addOrReinstall ${LANG_PERSIAN} \"اضافه کردن/نصب مجدد کامپونتت\"\nLangString alreadyInstalled ${LANG_PERSIAN} \"قبلا نصب شده است\"\nLangString alreadyInstalledLong ${LANG_PERSIAN} \"${PRODUCTNAME} ${VERSION} قبلا نصب شده است. عملیات مدنظر را انتخاب کنید و بروی بعدی کلیک کنید.\"\nLangString appRunning ${LANG_PERSIAN} \"{{product_name}} در حال اجر می باشد ! لطفا اول الان را ببندید و دوباره تلاش کنید\"\nLangString appRunningOkKill ${LANG_PERSIAN} \"{{product_name}} در حال اجرا می باشد!$\\nبرای از بین بردن اوکی را انتخاب کنید\"\nLangString chooseMaintenanceOption ${LANG_PERSIAN} \"عملیات نگهداری مدنظر را برای اجرا انتخاب کنید\"\nLangString choowHowToInstall ${LANG_PERSIAN} \"نحوه نصب ${PRODUCTNAME} را انتخاب کنید\"\nLangString createDesktop ${LANG_PERSIAN} \"ایجاد میانبر دسکتاپ\"\nLangString dontUninstall ${LANG_PERSIAN} \"حذف نکنید\"\nLangString dontUninstallDowngrade ${LANG_PERSIAN} \"حذف نکنید (تنزل ورژن بدون حذف برای نصب کننده غیرفعال است)\"\nLangString failedToKillApp ${LANG_PERSIAN} \"{{product_name}} قابل کشته شدن نیست. اول آن را ببندید و دوباره تلاش کنید\"\nLangString installingWebview2 ${LANG_PERSIAN} \"در حال نصب WebView2 ...\"\nLangString newerVersionInstalled ${LANG_PERSIAN} \"ورژن جدید ${PRODUCTNAME} قبلا نصب شده است! نصب ورژن قدیمی تر به هیچ عنوان پیشنهاد نمی شود. اگر از این بابت اطمینان دارید , بهتر است ورژن فعلی را حذف کنید. عملیات مدنظر را انتخاب کنید و بروی بعدی کلیک کنید.\"\nLangString older ${LANG_PERSIAN} \"قدیمی تر\"\nLangString olderOrUnknownVersionInstalled ${LANG_PERSIAN} \"ورژن $R4 ${PRODUCTNAME} قبلا بروی سیستم شما نصب شده است. ر. عملیات مدنظر را انتخاب کنید و بروی بعدی کلیک کنید.\"\nLangString silentDowngrades ${LANG_PERSIAN} \"تنزل ورژن بدون حذف غیرفعال می باشد, عملیات نصب خاموش غیرقابل انجام است , از رابط گرافیکی برای نصب استفاده کنید.$\\n\"\nLangString unableToUninstall ${LANG_PERSIAN} \"قابل نصب نیست!\"\nLangString uninstallApp ${LANG_PERSIAN} \"حذف ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_PERSIAN} \"قبل از نصب , حذف کنید\"\nLangString unknown ${LANG_PERSIAN} \"ناشناس\"\nLangString webview2AbortError ${LANG_PERSIAN} \"نصب WebView2 شکست خورد! اپ بدون ان کار نمی کند. نصب کننده را دوباره نصب کنید\"\nLangString webview2DownloadError ${LANG_PERSIAN} \"ارور: دانلود WebView2 شکست خورد - $0\"\nLangString webview2DownloadSuccess ${LANG_PERSIAN} \"WebView2 بوت استرپر با موفقیت نصب شد\"\nLangString webview2Downloading ${LANG_PERSIAN} \"دانلود بوت استرپر WebView2...\"\nLangString webview2InstallError ${LANG_PERSIAN} \"ارور: نصب WebView2 با کد $1 شکست خورد\"\nLangString webview2InstallSuccess ${LANG_PERSIAN} \"WebView2 با موفقیت نصب شد\"\nLangString deleteAppData ${LANG_PERSIAN} \"حذف دیتا های اپلیکیشن\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Portuguese.nsh",
    "content": "LangString addOrReinstall ${LANG_PORTUGUESE} \"Adicionar/Reinstalar componentes\"\nLangString alreadyInstalled ${LANG_PORTUGUESE} \"Já instalado\"\nLangString alreadyInstalledLong ${LANG_PORTUGUESE} \"${PRODUCTNAME} ${VERSION} já está instalado. Selecione a operação que deseja realizar e clique em Seguinte para continuar.\"\nLangString appRunning ${LANG_PORTUGUESE} \"{{product_name}} está em execução! Por favor, feche-o primeiro e tente novamente.\"\nLangString appRunningOkKill ${LANG_PORTUGUESE} \"{{product_name}} está em execução!$\\nClique em OK para encerrá-lo.\"\nLangString chooseMaintenanceOption ${LANG_PORTUGUESE} \"Escolha a opção de manutenção a realizar.\"\nLangString choowHowToInstall ${LANG_PORTUGUESE} \"Escolha como deseja instalar o ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_PORTUGUESE} \"Criar atalho no ambiente de trabalho\"\nLangString dontUninstall ${LANG_PORTUGUESE} \"Não desinstalar\"\nLangString dontUninstallDowngrade ${LANG_PORTUGUESE} \"Não desinstalar (Instalar uma versão anterior sem desinstalar está desativado neste instalador)\"\nLangString failedToKillApp ${LANG_PORTUGUESE} \"Falha ao encerrar {{product_name}}. Por favor, feche-o primeiro e tente novamente.\"\nLangString installingWebview2 ${LANG_PORTUGUESE} \"A instalar WebView2...\"\nLangString newerVersionInstalled ${LANG_PORTUGUESE} \"Uma versão mais recente do ${PRODUCTNAME} já está instalada! Não é recomendada a instalação de uma versão mais antiga. Se realmente deseja instalar esta versão mais antiga, é melhor desinstalar a versão atual primeiro. Selecione a operação que deseja realizar e clique em Seguinte para continuar.\"\nLangString older ${LANG_PORTUGUESE} \"mais antiga\"\nLangString olderOrUnknownVersionInstalled ${LANG_PORTUGUESE} \"Uma versão $R4 do ${PRODUCTNAME} está instalada no sistema. Recomenda-se desinstalar a versão atual antes de instalar. Selecione a operação que deseja realizar e clique em Seguinte para continuar.\"\nLangString silentDowngrades ${LANG_PORTUGUESE} \"Rebaixamentos estão desativados neste instalador, não é possível prosseguir com a instalação silenciosa. Por favor, utilize o instalador com interface gráfica.$\\n\"\nLangString unableToUninstall ${LANG_PORTUGUESE} \"Não foi possível desinstalar!\"\nLangString uninstallApp ${LANG_PORTUGUESE} \"Desinstalar ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_PORTUGUESE} \"Desinstalar antes de instalar\"\nLangString unknown ${LANG_PORTUGUESE} \"desconhecida\"\nLangString webview2AbortError ${LANG_PORTUGUESE} \"Falha ao instalar o WebView2! A aplicação não pode ser executada sem ele. Tente reiniciar o instalador.\"\nLangString webview2DownloadError ${LANG_PORTUGUESE} \"Erro: Falha ao transferir o WebView2 - $0\"\nLangString webview2DownloadSuccess ${LANG_PORTUGUESE} \"Bootstrapper do WebView2 transferido com sucesso\"\nLangString webview2Downloading ${LANG_PORTUGUESE} \"A transferir o Bootstrapper do WebView2...\"\nLangString webview2InstallError ${LANG_PORTUGUESE} \"Erro: Instalação do WebView2 falhou com o código $1\"\nLangString webview2InstallSuccess ${LANG_PORTUGUESE} \"WebView2 instalado com sucesso\"\nLangString deleteAppData ${LANG_PORTUGUESE} \"Eliminar os dados da aplicação\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/PortugueseBR.nsh",
    "content": "LangString addOrReinstall ${LANG_PORTUGUESEBR} \"Adicionar/Reinstalar componentes\"\nLangString alreadyInstalled ${LANG_PORTUGUESEBR} \"Já instalado\"\nLangString alreadyInstalledLong ${LANG_PORTUGUESEBR} \"${PRODUCTNAME} ${VERSION} já está instalado. Selecione a operação que deseja realizar e clique Próximo para continuar.\"\nLangString appRunning ${LANG_PORTUGUESEBR} \"{{product_name}} está aberto! Por favor feche a janela dele e tente novamente.\"\nLangString appRunningOkKill ${LANG_PORTUGUESEBR} \"{{product_name}} está aberto!$\\nClique OK para fechar ele.\"\nLangString chooseMaintenanceOption ${LANG_PORTUGUESEBR} \"Escolha a opção de manutenção a realizar.\"\nLangString choowHowToInstall ${LANG_PORTUGUESEBR} \"Escolha como deseja instalar ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_PORTUGUESEBR} \"Criar atalho na área de trabalho\"\nLangString dontUninstall ${LANG_PORTUGUESEBR} \"Não desinstalar\"\nLangString dontUninstallDowngrade ${LANG_PORTUGUESEBR} \"Não desinstalar (Instalar versão anterior sem desinstalar está desabilitado nesse instalador)\"\nLangString failedToKillApp ${LANG_PORTUGUESEBR} \"Falha ao fechar {{product_name}}. Por favor feche a janela dele primeiro e tente novamente\"\nLangString installingWebview2 ${LANG_PORTUGUESEBR} \"Instalando WebView2...\"\nLangString newerVersionInstalled ${LANG_PORTUGUESEBR} \"Uma nova versão do ${PRODUCTNAME} já está instalado! Não é recomendado instalar uma versão anterior. Se realmente deseja instalar essa versão antiga, é recomendado desinstalar a versão atual primeirl. Selecione a operação que deseja executare clique Próximo para continuar.\"\nLangString older ${LANG_PORTUGUESEBR} \"mais antiga\"\nLangString olderOrUnknownVersionInstalled ${LANG_PORTUGUESEBR} \"Uma versão $R4 do ${PRODUCTNAME} está instalada no seu sistema. É recomendado desinstalar a versão atual antes de prosseguir com a instalação. Selecione a operação que deseja executare clique Próximo para continuar.\"\nLangString silentDowngrades ${LANG_PORTUGUESEBR} \"Instalar versão anterior está desabilitado nesse instalador, não foi possível proceder com a instalação silenciosa, por favor use a interface gráfica.$\\n\"\nLangString unableToUninstall ${LANG_PORTUGUESEBR} \"Não foi possível instalar!\"\nLangString uninstallApp ${LANG_PORTUGUESEBR} \"Desinstalar ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_PORTUGUESEBR} \"Desinstalar antes de instalar\"\nLangString unknown ${LANG_PORTUGUESEBR} \"desconhecida\"\nLangString webview2AbortError ${LANG_PORTUGUESEBR} \"Falha ao instalar WebView2! O programa não pode executar sem ele. Tente reiniciar o instalador.\"\nLangString webview2DownloadError ${LANG_PORTUGUESEBR} \"Erro: Falha ao baixar WebView2 - $0\"\nLangString webview2DownloadSuccess ${LANG_PORTUGUESEBR} \"Bootstrapper do WebView2 baixado com sucesso\"\nLangString webview2Downloading ${LANG_PORTUGUESEBR} \"Baixando o Bootstrapper do WebView2...\"\nLangString webview2InstallError ${LANG_PORTUGUESEBR} \"Erro: Instalação do Webview2 falhou com código $1\"\nLangString webview2InstallSuccess ${LANG_PORTUGUESEBR} \"WebView2 instalado com sucesso\"\nLangString deleteAppData ${LANG_PORTUGUESEBR} \"Remover dados do programa\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Russian.nsh",
    "content": "LangString addOrReinstall ${LANG_RUSSIAN} \"Добавить/Переустановить компоненты\"\nLangString alreadyInstalled ${LANG_RUSSIAN} \"Уже установлено\"\nLangString alreadyInstalledLong ${LANG_RUSSIAN} \"${PRODUCTNAME} ${VERSION} уже установлен. Выберите действие, которое вы хотите выполнить и нажмите Далее для продолжения.\"\nLangString appRunning ${LANG_RUSSIAN} \"{{product_name}} запущен! Пожалуйста, закройте приложение и попробуйте еще раз.\"\nLangString appRunningOkKill ${LANG_RUSSIAN} \"{{product_name}} запущен!$\\nНажмите OK чтобы закрыть приложение\"\nLangString chooseMaintenanceOption ${LANG_RUSSIAN} \"Выберите действие, которое вы хотите выполнить.\"\nLangString choowHowToInstall ${LANG_RUSSIAN} \"Выберите, как вы хотите установить ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_RUSSIAN} \"Добавить ярлык на рабочий стол\"\nLangString dontUninstall ${LANG_RUSSIAN} \"Не удалять\"\nLangString dontUninstallDowngrade ${LANG_RUSSIAN} \"Не удалять (Установка более ранних версий без удаления невозможна)\"\nLangString failedToKillApp ${LANG_RUSSIAN} \"Не удалось закрыть {{product_name}}. Пожалуйста, закройте приложение и попробуйте еще раз\"\nLangString installingWebview2 ${LANG_RUSSIAN} \"Установка WebView2...\"\nLangString newerVersionInstalled ${LANG_RUSSIAN} \"Более новая версия ${PRODUCTNAME} уже установлена! Не рекомендуется устанавливать более раннюю версию. Если вы действительно хотите установить эту версию, рекомендуется сначала удалить текущую. Выберите действие, которое вы хотите выполнить и нажмите Далее для продолжения.\"\nLangString older ${LANG_RUSSIAN} \"Более ранняя\"\nLangString olderOrUnknownVersionInstalled ${LANG_RUSSIAN} \"$R4 версия ${PRODUCTNAME} уже установлена в вашей системе. Рекомендуется удалить текущую версию перед установкой. Выберите действие, которое вы хотите выполнить и нажмите Далее для продолжения.\"\nLangString silentDowngrades ${LANG_RUSSIAN} \"Установка более ранних версий в фоне невозможна, используйте установщик.$\\n\"\nLangString unableToUninstall ${LANG_RUSSIAN} \"Не удалось удалить!\"\nLangString uninstallApp ${LANG_RUSSIAN} \"Удалить ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_RUSSIAN} \"Удалить перед установкой\"\nLangString unknown ${LANG_RUSSIAN} \"Неизвестная\"\nLangString webview2AbortError ${LANG_RUSSIAN} \"Не удалось установить WebView2! Приложение не может работать без него. Попробуйте перезапустить установщик.\"\nLangString webview2DownloadError ${LANG_RUSSIAN} \"Ошибка: Не удалось загрузить WebView2 - $0\"\nLangString webview2DownloadSuccess ${LANG_RUSSIAN} \"WebView2 успешно загружен\"\nLangString webview2Downloading ${LANG_RUSSIAN} \"Загрузка WebView2...\"\nLangString webview2InstallError ${LANG_RUSSIAN} \"Ошибка: Не удалось установить WebView2, код выхода: $1\"\nLangString webview2InstallSuccess ${LANG_RUSSIAN} \"WebView2 успешно установлен\"\nLangString deleteAppData ${LANG_RUSSIAN} \"Удалить данные приложения\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/SimpChinese.nsh",
    "content": "LangString addOrReinstall ${LANG_SIMPCHINESE} \"添加/重新安装组件\"\nLangString alreadyInstalled ${LANG_SIMPCHINESE} \"已安装\"\nLangString alreadyInstalledLong ${LANG_SIMPCHINESE} \"${PRODUCTNAME} ${VERSION} 已经安装了。选择你想要执行的操作后点击下一步以继续。\"\nLangString appRunning ${LANG_SIMPCHINESE} \"{{product_name}} 正在运行！请关闭后再试。\"\nLangString appRunningOkKill ${LANG_SIMPCHINESE} \"{{product_name}} 正在运行！$\\n点击确定以终止运行。\"\nLangString chooseMaintenanceOption ${LANG_SIMPCHINESE} \"选择要执行的维护操作。\"\nLangString choowHowToInstall ${LANG_SIMPCHINESE} \"选择你想要安装 ${PRODUCTNAME} 的方式。\"\nLangString createDesktop ${LANG_SIMPCHINESE} \"创建桌面快捷方式\"\nLangString dontUninstall ${LANG_SIMPCHINESE} \"请勿卸载\"\nLangString dontUninstallDowngrade ${LANG_SIMPCHINESE} \"请勿卸载（此安装程序禁止未卸载就进行版本降级的操作）\"\nLangString failedToKillApp ${LANG_SIMPCHINESE} \"无法终止 {{product_name}}。请关闭后再试。\"\nLangString installingWebview2 ${LANG_SIMPCHINESE} \"正在安装 WebView2...\"\nLangString newerVersionInstalled ${LANG_SIMPCHINESE} \"有一个更新版本的 ${PRODUCTNAME} 已经安装了！不推荐你安装旧的版本。如果你真的想要安装这个旧的版本，推荐先卸载当前版本。选择你想要执行的操作后点击下一步以继续。\"\nLangString older ${LANG_SIMPCHINESE} \"旧的\"\nLangString olderOrUnknownVersionInstalled ${LANG_SIMPCHINESE} \"系统中已存在版本为 $R4 的 ${PRODUCTNAME}。推荐先卸载当前版本后再进行安装。选择你想要执行的操作后点击下一步以继续。\"\nLangString silentDowngrades ${LANG_SIMPCHINESE} \"降级操作在此安装程序上已禁用，无法进行安静安装，请使用图形操作界面。$\\n\"\nLangString unableToUninstall ${LANG_SIMPCHINESE} \"无法卸载！\"\nLangString uninstallApp ${LANG_SIMPCHINESE} \"卸载 ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_SIMPCHINESE} \"安装前卸载\"\nLangString unknown ${LANG_SIMPCHINESE} \"未知\"\nLangString webview2AbortError ${LANG_SIMPCHINESE} \"无法安装 WebView2！没有它，此应用就无法运行。尝试重启安装程序。\"\nLangString webview2DownloadError ${LANG_SIMPCHINESE} \"错误：无法下载 WebView2 - $0\"\nLangString webview2DownloadSuccess ${LANG_SIMPCHINESE} \"WebView2 引导程序下载成功\"\nLangString webview2Downloading ${LANG_SIMPCHINESE} \"正在下载 WebView2 引导程序...\"\nLangString webview2InstallError ${LANG_SIMPCHINESE} \"错误：安装 WebView2 时失败，错误代码：$1\"\nLangString webview2InstallSuccess ${LANG_SIMPCHINESE} \"成功安装 WebView2\"\nLangString deleteAppData ${LANG_SIMPCHINESE} \"删除应用程序数据\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Spanish.nsh",
    "content": "LangString addOrReinstall ${LANG_SPANISH} \"Añadir o reinstalar componentes\"\nLangString alreadyInstalled ${LANG_SPANISH} \"Ya está instalado\"\nLangString alreadyInstalledLong ${LANG_SPANISH} \"${PRODUCTNAME} ${VERSION} ya está instalado. Seleccione la operación que desee realizar y pulse Siguiente para continuar.\"\nLangString appRunning ${LANG_SPANISH} \"¡{{product_name}} está abierto! Por favor ciérrelo e intente de nuevo.\"\nLangString appRunningOkKill ${LANG_SPANISH} \"¡{{product_name}} está abierto!$\\nPulse Aceptar para cerrarlo.\"\nLangString chooseMaintenanceOption ${LANG_SPANISH} \"Elija la operación de mantenimiento que desee realizar.\"\nLangString choowHowToInstall ${LANG_SPANISH} \"Elija cómo desea instalar ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_SPANISH} \"Crear acceso directo en el escritorio\"\nLangString dontUninstall ${LANG_SPANISH} \"No desinstalar\"\nLangString dontUninstallDowngrade ${LANG_SPANISH} \"No desinstalar (Disminuir la versión sin desinstalar está deshabilitado para este instalador)\"\nLangString failedToKillApp ${LANG_SPANISH} \"No se ha podido cerrar {{product_name}}. Por favor ciérrelo e intente de nuevo.\"\nLangString installingWebview2 ${LANG_SPANISH} \"Instalando WebView2...\"\nLangString newerVersionInstalled ${LANG_SPANISH} \"Ya está instalada una versión más reciente de ${PRODUCTNAME}. No se recomienda que instale una versión anterior. Si realmente desea instalar esta versión anterior, es recomendable desinstalar la versión actual antes de continuar. Seleccione la operación que desee realizar y pulse Siguiente para continuar.\"\nLangString older ${LANG_SPANISH} \"anterior\"\nLangString olderOrUnknownVersionInstalled ${LANG_SPANISH} \"Una versión $R4 de ${PRODUCTNAME} está instalada en su sistema. Es recomendable desinstalar la versión actual antes de continuar. Seleccione la operación que desee realizar y pulse Siguiente para continuar.\"\nLangString silentDowngrades ${LANG_SPANISH} \"Disminuir la versión está deshabilitado para este instalador. No se puede continuar con el instalador silencioso, por favor use el instalador de interfaz gráfica.$\\n\"\nLangString unableToUninstall ${LANG_SPANISH} \"No se ha podido desinstalar.\"\nLangString uninstallApp ${LANG_SPANISH} \"Desinstalar ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_SPANISH} \"Desinstalar antes de instalar\"\nLangString unknown ${LANG_SPANISH} \"desconocida\"\nLangString webview2AbortError ${LANG_SPANISH} \"No se ha podido instalar WebView2. Intente reiniciar el instalador.\"\nLangString webview2DownloadError ${LANG_SPANISH} \"Error: No se ha podido descargar WebView2 - $0\"\nLangString webview2DownloadSuccess ${LANG_SPANISH} \"El bootstrapper de WebView2 fue descargado con éxito.\"\nLangString webview2Downloading ${LANG_SPANISH} \"Descargando el bootstrapper de WebView2...\"\nLangString webview2InstallError ${LANG_SPANISH} \"Error: La instalación de WebView2 falló con el código $1.\"\nLangString webview2InstallSuccess ${LANG_SPANISH} \"WebView2 fue instalado con éxito.\"\nLangString deleteAppData ${LANG_SPANISH} \"Eliminar los datos de aplicación\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/SpanishInternational.nsh",
    "content": "LangString addOrReinstall ${LANG_SPANISHINTERNATIONAL} \"Añadir o reinstalar componentes\"\nLangString alreadyInstalled ${LANG_SPANISHINTERNATIONAL} \"Ya está instalado\"\nLangString alreadyInstalledLong ${LANG_SPANISHINTERNATIONAL} \"${PRODUCTNAME} ${VERSION} ya está instalado. Seleccione la operación que desee realizar y pulse Siguiente para continuar.\"\nLangString appRunning ${LANG_SPANISHINTERNATIONAL} \"¡{{product_name}} está abierto! Por favor ciérrelo e intente de nuevo.\"\nLangString appRunningOkKill ${LANG_SPANISHINTERNATIONAL} \"¡{{product_name}} está abierto!$\\nPulse Aceptar para cerrarlo.\"\nLangString chooseMaintenanceOption ${LANG_SPANISHINTERNATIONAL} \"Elija la operación de mantenimiento que desee realizar.\"\nLangString choowHowToInstall ${LANG_SPANISHINTERNATIONAL} \"Elija cómo desea instalar ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_SPANISHINTERNATIONAL} \"Crear acceso directo en el escritorio\"\nLangString dontUninstall ${LANG_SPANISHINTERNATIONAL} \"No desinstalar\"\nLangString dontUninstallDowngrade ${LANG_SPANISHINTERNATIONAL} \"No desinstalar (Disminuir la versión sin desinstalar está deshabilitado para este instalador)\"\nLangString failedToKillApp ${LANG_SPANISHINTERNATIONAL} \"No se ha podido cerrar {{product_name}}. Por favor ciérrelo e intente de nuevo.\"\nLangString installingWebview2 ${LANG_SPANISHINTERNATIONAL} \"Instalando WebView2...\"\nLangString newerVersionInstalled ${LANG_SPANISHINTERNATIONAL} \"Ya está instalada una versión más reciente de ${PRODUCTNAME}. No se recomienda que instale una versión anterior. Si realmente desea instalar esta versión anterior, es recomendable desinstalar la versión actual antes de continuar. Seleccione la operación que desee realizar y pulse Siguiente para continuar.\"\nLangString older ${LANG_SPANISHINTERNATIONAL} \"anterior\"\nLangString olderOrUnknownVersionInstalled ${LANG_SPANISHINTERNATIONAL} \"Una versión $R4 de ${PRODUCTNAME} está instalada en su sistema. Es recomendable desinstalar la versión actual antes de continuar. Seleccione la operación que desee realizar y pulse Siguiente para continuar.\"\nLangString silentDowngrades ${LANG_SPANISHINTERNATIONAL} \"Disminuir la versión está deshabilitado para este instalador. No se puede continuar con el instalador silencioso, por favor use el instalador de interfaz gráfica.$\\n\"\nLangString unableToUninstall ${LANG_SPANISHINTERNATIONAL} \"No se ha podido desinstalar.\"\nLangString uninstallApp ${LANG_SPANISHINTERNATIONAL} \"Desinstalar ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_SPANISHINTERNATIONAL} \"Desinstalar antes de instalar\"\nLangString unknown ${LANG_SPANISHINTERNATIONAL} \"desconocida\"\nLangString webview2AbortError ${LANG_SPANISHINTERNATIONAL} \"No se ha podido instalar WebView2. Intente reiniciar el instalador.\"\nLangString webview2DownloadError ${LANG_SPANISHINTERNATIONAL} \"Error: No se ha podido descargar WebView2 - $0\"\nLangString webview2DownloadSuccess ${LANG_SPANISHINTERNATIONAL} \"El bootstrapper de WebView2 fue descargado con éxito.\"\nLangString webview2Downloading ${LANG_SPANISHINTERNATIONAL} \"Descargando el bootstrapper de WebView2...\"\nLangString webview2InstallError ${LANG_SPANISHINTERNATIONAL} \"Error: La instalación de WebView2 falló con el código $1.\"\nLangString webview2InstallSuccess ${LANG_SPANISHINTERNATIONAL} \"WebView2 fue instalado con éxito.\"\nLangString deleteAppData ${LANG_SPANISHINTERNATIONAL} \"Eliminar los datos de aplicación\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Swedish.nsh",
    "content": "LangString addOrReinstall ${LANG_SWEDISH} \"Lägg till/Installera om komponenter\"\nLangString alreadyInstalled ${LANG_SWEDISH}} \"Redan installerad\"\nLangString alreadyInstalledLong ${LANG_SWEDISH}} \"${PRODUCTNAME} ${VERSION} är redan installerad. Välj åtgärd och klicka på Nästa för att fortsätta.\"\nLangString appRunning ${LANG_SWEDISH} \"{{product_name}} körs! Stäng det först och försök igen.\"\nLangString appRunningOkKill ${LANG_SWEDISH} \"{{product_name}} körs!$\\nKlicka på OK för att avsluta det.\"\nLangString chooseMaintenanceOption ${LANG_SWEDISH} \"Välj underhållsåtgärd.\"\nLangString choowHowToInstall ${LANG_SWEDISH} \"Välj hur du vill installera ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_SWEDISH} \"Skapa genväg på skrivbordet\"\nLangString dontUninstall ${LANG_SWEDISH} \"Avinstallera inte\"\nLangString dontUninstallDowngrade ${LANG_SWEDISH} \"Avinstallera inte (nedgradering utan avinstallation är inaktiverad för den här installationsprogrammet)\"\nLangString failedToKillApp ${LANG_SWEDISH} \"Kunde inte avsluta {{product_name}}. Stäng det först och försök igen.\"\nLangString installingWebview2 ${LANG_SWEDISH} \"Installerar WebView2...\"\nLangString newerVersionInstalled ${LANG_SWEDISH} \"En nyare version av ${PRODUCTNAME} är redan installerad! Det rekommenderas inte att installera en äldre version. Om du verkligen vill installera denna äldre version är det bättre att avinstallera den nuvarande versionen först. Välj åtgärd och klicka på Nästa för att fortsätta.\"\nLangString older ${LANG_SWEDISH} \"äldre\"\nLangString olderOrUnknownVersionInstalled ${LANG_SWEDISH} \"En $R4-version av ${PRODUCTNAME} är installerad på ditt system. Det rekommenderas att du avinstallerar den nuvarande versionen innan du installerar. Välj åtgärd och klicka på Nästa för att fortsätta.\"\nLangString silentDowngrades ${LANG_SWEDISH} \"Nedgraderingar är inaktiverade för den här installationsprogrammet. Kan inte fortsätta med installationsprogrammet. Använd det grafiska installationsprogrammet istället.$\\n\"\nLangString unableToUninstall ${LANG_SWEDISH} \"Kan inte avinstallera!\"\nLangString uninstallApp ${LANG_SWEDISH} \"Avinstallera ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_SWEDISH} \"Avinstallera innan installation\"\nLangString unknown ${LANG_SWEDISH} \"okänd\"\nLangString webview2AbortError ${LANG_SWEDISH} \"Misslyckades med att installera WebView2! Appen kan inte köras utan det. Försök starta om installationsprogrammet.\"\nLangString webview2DownloadError ${LANG_SWEDISH} \"Fel: Nedladdning av WebView2 misslyckades - $0\"\nLangString webview2DownloadSuccess ${LANG_SWEDISH} \"WebView2 bootstrapper nedladdad framgångsrikt\"\nLangString webview2Downloading ${LANG_SWEDISH} \"Laddar ner WebView2 bootstrapper...\"\nLangString webview2InstallError ${LANG_SWEDISH} \"Fel: Installation av WebView2 misslyckades med felkod $1\"\nLangString webview2InstallSuccess ${LANG_SWEDISH} \"WebView2 installerades framgångsrikt\"\nLangString deleteAppData ${LANG_SWEDISH} \"Ta bort applikationsdata\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/TradChinese.nsh",
    "content": "LangString addOrReinstall ${LANG_TRADCHINESE} \"增加或重新安裝元件\"\nLangString alreadyInstalled ${LANG_TRADCHINESE} \"已安裝\"\nLangString alreadyInstalledLong ${LANG_TRADCHINESE} \"${PRODUCTNAME} ${VERSION} 已經安裝了。選擇你想要進行的操作並且點選下一步。\"\nLangString appRunning ${LANG_TRADCHINESE} \"{{product_name}} 正在執行中！請先關閉再進行嘗試。\"\nLangString appRunningOkKill ${LANG_TRADCHINESE} \"{{product_name}} 正在執行中！點選確定後終止。\"\nLangString chooseMaintenanceOption ${LANG_TRADCHINESE} \"請選擇你要進行的維護選項。\"\nLangString choowHowToInstall ${LANG_TRADCHINESE} \"選擇你要如何安裝 ${PRODUCTNAME}。\"\nLangString createDesktop ${LANG_TRADCHINESE} \"建立桌面捷徑\"\nLangString dontUninstall ${LANG_TRADCHINESE} \"請勿解除安裝\"\nLangString dontUninstallDowngrade ${LANG_TRADCHINESE} \"請勿解除安裝（本安裝程式不允許未解除安裝就進行版本降低的操作）\"\nLangString failedToKillApp ${LANG_TRADCHINESE} \"無法終止 {{product_name}}。請先關閉再進行嘗試。\"\nLangString installingWebview2 ${LANG_TRADCHINESE} \"WebView2 安裝中...\"\nLangString newerVersionInstalled ${LANG_TRADCHINESE} \"已安裝更新版本的 ${PRODUCTNAME}！不建議安裝舊版。如果真的想要安裝舊版的話，最好先解除安裝現在的版本。選擇你想要進行的操作後再進行下一步。\"\nLangString older ${LANG_TRADCHINESE} \"舊版\"\nLangString olderOrUnknownVersionInstalled ${LANG_TRADCHINESE} \"你的系統已經安裝 ${PRODUCTNAME} 的 $R4 版本。建議你先解除安裝現在的版本後再進行安裝。選擇你想要進行的操作後再進行下一步。\"\nLangString silentDowngrades ${LANG_TRADCHINESE} \"本安裝程式不允許未解除安裝就進行版本降低的操作，無法繼續無聲安裝，請改用圖形化介面安裝。$\\n\"\nLangString unableToUninstall ${LANG_TRADCHINESE} \"無法解除安裝！\"\nLangString uninstallApp ${LANG_TRADCHINESE} \"解除安裝 ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_TRADCHINESE} \"安裝前先解除安裝\"\nLangString unknown ${LANG_TRADCHINESE} \"未知\"\nLangString webview2AbortError ${LANG_TRADCHINESE} \"無法安裝 WebView2！這個應用程式需要安裝 WebView2 才能執行。請重新啟動安裝程式。\"\nLangString webview2DownloadError ${LANG_TRADCHINESE} \"錯誤：WebView2 下載失敗 - $0\"\nLangString webview2DownloadSuccess ${LANG_TRADCHINESE} \"WebView2 啟動載入器下載成功\"\nLangString webview2Downloading ${LANG_TRADCHINESE} \"正在下載 WebView2 啟動載入器...\"\nLangString webview2InstallError ${LANG_TRADCHINESE} \"錯誤：WebView2 安裝失敗，錯誤碼 $1\"\nLangString webview2InstallSuccess ${LANG_TRADCHINESE} \"WebView2 安裝成功\"\nLangString deleteAppData ${LANG_TRADCHINESE} \"刪除應用程式數據\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Turkish.nsh",
    "content": "LangString addOrReinstall ${LANG_TURKISH} \"Bileşen Ekle/Yeniden Yükle\"\nLangString alreadyInstalled ${LANG_TURKISH} \"Daha Önceden Yüklenmiş\"\nLangString alreadyInstalledLong ${LANG_TURKISH} \"${PRODUCTNAME} ${VERSION} daha önceden yüklenmiş. Gerçekleştirmek istediğiniz işlemi seçin ve devam etmek için İleri'ye tıklayın.\"\nLangString appRunning ${LANG_TURKISH} \"{{product_name}} çalışır durumda! Lütfen önce uygulamayı kapatın ve sonra tekrar deneyin.\"\nLangString appRunningOkKill ${LANG_TURKISH} \"{{product_name}} çalışır durumda!$\\nUygulamayı sonlandırmak için Tamam'a tıklayın.\"\nLangString chooseMaintenanceOption ${LANG_TURKISH} \"Gerçekleştirmek istediğiniz bakım seçeneğini belirleyin.\"\nLangString choowHowToInstall ${LANG_TURKISH} \"${PRODUCTNAME} uygulamasını nasıl yüklemek istediğinizi seçin.\"\nLangString createDesktop ${LANG_TURKISH} \"Masaüstü kısayolu oluştur\"\nLangString dontUninstall ${LANG_TURKISH} \"Kaldırma işlemini gerçekleştirme\"\nLangString dontUninstallDowngrade ${LANG_TURKISH} \"Kaldırma işlemini gerçekleştirme (Kaldırma işlemi yapmadan sürüm düşürme bu yükleyici için devre dışı bırakılmıştır)\"\nLangString failedToKillApp ${LANG_TURKISH} \"{{product_name}} sonlandırılamadı. Lütfen önce kapatın sonra tekrar deneyin.\"\nLangString installingWebview2 ${LANG_TURKISH} \"WebView2 yükleniyor...\"\nLangString newerVersionInstalled ${LANG_TURKISH} \"${PRODUCTNAME} uygulamasının daha yeni bir sürümü zaten yüklü! Daha eski bir sürümü yüklemeniz önerilmez. Bu eski sürümü gerçekten yüklemek istiyorsanız, önce mevcut sürümü kaldırmanız daha uygundur. Gerçekleştirmek istediğiniz işlemi seçin ve devam etmek için İleri'ye tıklayın.\"\nLangString older ${LANG_TURKISH} \"daha eski\"\nLangString olderOrUnknownVersionInstalled ${LANG_TURKISH} \"Sisteminizde ${PRODUCTNAME} uygulamasının $R4 sürümü yüklü. Yükleme işleminden önce mevcut sürümü kaldırmanız önerilir. Gerçekleştirmek istediğiniz işlemi seçin ve devam etmek için İleri'ye tıklayın.\"\nLangString silentDowngrades ${LANG_TURKISH} \"Bu yükleyici için sürüm düşürme işlemleri devre dışı bırakıldı, sessiz yükleyici ile devam edilemiyor, lütfen bunun yerine grafik arayüz yükleyicisini kullanın.$\\n\"\nLangString unableToUninstall ${LANG_TURKISH} \"Kaldırma işlemi gerçekleştirilemiyor!\"\nLangString uninstallApp ${LANG_TURKISH} \"${PRODUCTNAME}'i kaldır\"\nLangString uninstallBeforeInstalling ${LANG_TURKISH} \"Yükleme yapmadan önce kaldırın\"\nLangString unknown ${LANG_TURKISH} \"bilinmeyen\"\nLangString webview2AbortError ${LANG_TURKISH} \"WebView2 yüklenemedi! Uygulama bu bileşen olmadan çalışamaz. Yükleyiciyi yeniden başlatmayı deneyin.\"\nLangString webview2DownloadError ${LANG_TURKISH} \"Hata: WebView2 İndirmesi Başarısız - $0\"\nLangString webview2DownloadSuccess ${LANG_TURKISH} \"WebView2 önyükleyicisi başarıyla indirildi\"\nLangString webview2Downloading ${LANG_TURKISH} \"WebView2 önyükleyicisi indiriliyor...\"\nLangString webview2InstallError ${LANG_TURKISH} \"Hata: WebView2 yüklemesi $1 hata koduyla başarısız oldu.\"\nLangString webview2InstallSuccess ${LANG_TURKISH} \"WebView2 başarıyla yüklendi\"\nLangString deleteAppData ${LANG_TURKISH} \"Uygulama verilerini sil\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/languages/Ukrainian.nsh",
    "content": "LangString addOrReinstall ${LANG_UKRAINIAN} \"Додати/Перевстановити компоненти\"\nLangString alreadyInstalled ${LANG_UKRAINIAN} \"Вже встановлено\"\nLangString alreadyInstalledLong ${LANG_UKRAINIAN} \"${PRODUCTNAME} ${VERSION} вже встановлено. Виберіть дію, яку ви хочете виконати, і натисніть Далі, щоб продовжити.\"\nLangString appRunning ${LANG_UKRAINIAN} \"{{product_name}} запущено! Будь ласка, спочатку закрийте його, а потім спробуйте ще раз.\"\nLangString appRunningOkKill ${LANG_UKRAINIAN} \"{{product_name}} запущено!$\\nНатисніть ОК, щоб примусово закрити його\"\nLangString chooseMaintenanceOption ${LANG_UKRAINIAN} \"Виберіть дію, яку треба виконати.\"\nLangString choowHowToInstall ${LANG_UKRAINIAN} \"Виберіть, як ви хочете встановити ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_UKRAINIAN} \"Створити ярлик на робочому столі\"\nLangString dontUninstall ${LANG_UKRAINIAN} \"Не видаляти\"\nLangString dontUninstallDowngrade ${LANG_UKRAINIAN} \"Не видаляти (для цього встановлювача вимкнено зниження версії без видалення)\"\nLangString failedToKillApp ${LANG_UKRAINIAN} \"Не вдалося примусово закрити {{product_name}}. Будь ласка, спочатку закрийте його, а потім спробуйте ще раз\"\nLangString installingWebview2 ${LANG_UKRAINIAN} \"Встановлення WebView2...\"\nLangString newerVersionInstalled ${LANG_UKRAINIAN} \"Новіша версія ${PRODUCTNAME} вже встановлена! Встановлювати старішу версію не рекомендується. Якщо ви дійсно хочете встановити цю версію, краще спочатку видаліть поточну. Виберіть дію, яку ви хочете виконати, і натисніть Далі, щоб продовжити.\"\nLangString older ${LANG_UKRAINIAN} \"старішу\"\nLangString olderOrUnknownVersionInstalled ${LANG_UKRAINIAN} \"У вашій системі вже встановлено $R4 версію ${PRODUCTNAME}. Рекомендується видалити поточну версію перед встановленням. Виберіть дію, яку ви хочете виконати, і натисніть Далі, щоб продовжити.\"\nLangString silentDowngrades ${LANG_UKRAINIAN} \"Для цього встановлювача вимкнено зниження версій. Неможливо продовжити роботу з фоновим встановлювачем. Будь ласка, скористайтеся встановлювачем з графічним інтерфейсом.$\\n\"\nLangString unableToUninstall ${LANG_UKRAINIAN} \"Не вдалося видалити!\"\nLangString uninstallApp ${LANG_UKRAINIAN} \"Видалити ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_UKRAINIAN} \"Видалити перед встановленням\"\nLangString unknown ${LANG_UKRAINIAN} \"невідому\"\nLangString webview2AbortError ${LANG_UKRAINIAN} \"Не вдалося встановити WebView2! Без нього програма не може працювати. Спробуйте перезапустити встановлювач.\"\nLangString webview2DownloadError ${LANG_UKRAINIAN} \"Помилка: не вдалося завантажити WebView2 - $0\"\nLangString webview2DownloadSuccess ${LANG_UKRAINIAN} \"WebView2 успішно завантажено\"\nLangString webview2Downloading ${LANG_UKRAINIAN} \"Завантаження WebView2...\"\nLangString webview2InstallError ${LANG_UKRAINIAN} \"Помилка: не вдалося встановити WebView2, код виходу - $1\"\nLangString webview2InstallSuccess ${LANG_UKRAINIAN} \"WebView2 успішно встановлено \"\nLangString deleteAppData ${LANG_UKRAINIAN} \"Видалити дані програми\"\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  bundle::{\n    settings::Arch,\n    windows::{\n      sign::{should_sign, sign_command, try_sign},\n      util::{\n        download_webview2_bootstrapper, download_webview2_offline_installer,\n        NSIS_OUTPUT_FOLDER_NAME, NSIS_UPDATER_OUTPUT_FOLDER_NAME,\n      },\n    },\n  },\n  error::ErrorExt,\n  utils::{\n    http_utils::{download_and_verify, verify_file_hash, HashAlgorithm},\n    CommandExt,\n  },\n  Error, Settings,\n};\nuse tauri_utils::display_path;\n\nuse crate::error::Context;\nuse handlebars::{to_json, Handlebars};\nuse tauri_utils::config::{NSISInstallerMode, NsisCompression, WebviewInstallMode};\n\nuse std::{\n  collections::BTreeMap,\n  fs,\n  path::{Path, PathBuf},\n  process::Command,\n};\n\n// URLS for the NSIS toolchain.\n#[cfg(target_os = \"windows\")]\nconst NSIS_URL: &str =\n  \"https://github.com/tauri-apps/binary-releases/releases/download/nsis-3.11/nsis-3.11.zip\";\n#[cfg(target_os = \"windows\")]\nconst NSIS_SHA1: &str = \"EF7FF767E5CBD9EDD22ADD3A32C9B8F4500BB10D\";\nconst NSIS_TAURI_UTILS_URL: &str =\n  \"https://github.com/tauri-apps/nsis-tauri-utils/releases/download/nsis_tauri_utils-v0.5.3/nsis_tauri_utils.dll\";\nconst NSIS_TAURI_UTILS_SHA1: &str = \"75197FEE3C6A814FE035788D1C34EAD39349B860\";\n\n#[cfg(target_os = \"windows\")]\nconst NSIS_REQUIRED_FILES: &[&str] = &[\n  \"makensis.exe\",\n  \"Bin/makensis.exe\",\n  \"Stubs/lzma-x86-unicode\",\n  \"Stubs/lzma_solid-x86-unicode\",\n  \"Plugins/x86-unicode/additional/nsis_tauri_utils.dll\",\n  \"Include/MUI2.nsh\",\n  \"Include/FileFunc.nsh\",\n  \"Include/x64.nsh\",\n  \"Include/nsDialogs.nsh\",\n  \"Include/WinMessages.nsh\",\n  \"Include/Win/COM.nsh\",\n  \"Include/Win/Propkey.nsh\",\n  \"Include/Win/RestartManager.nsh\",\n];\nconst NSIS_PLUGIN_FILES: &[&str] = &[\n  \"NSISdl.dll\",\n  \"StartMenu.dll\",\n  \"System.dll\",\n  \"nsDialogs.dll\",\n  \"additional/nsis_tauri_utils.dll\",\n];\n#[cfg(not(target_os = \"windows\"))]\nconst NSIS_REQUIRED_FILES: &[&str] = &[\"Plugins/x86-unicode/additional/nsis_tauri_utils.dll\"];\n\nconst NSIS_REQUIRED_FILES_HASH: &[(&str, &str, &str, HashAlgorithm)] = &[(\n  \"Plugins/x86-unicode/additional/nsis_tauri_utils.dll\",\n  NSIS_TAURI_UTILS_URL,\n  NSIS_TAURI_UTILS_SHA1,\n  HashAlgorithm::Sha1,\n)];\n\n/// Runs all of the commands to build the NSIS installer.\n/// Returns a vector of PathBuf that shows where the NSIS installer was created.\npub fn bundle_project(settings: &Settings, updater: bool) -> crate::Result<Vec<PathBuf>> {\n  let tauri_tools_path = settings\n    .local_tools_directory()\n    .map(|d| d.join(\".tauri\"))\n    .unwrap_or_else(|| dirs::cache_dir().unwrap().join(\"tauri\"));\n\n  let nsis_toolset_path = tauri_tools_path.join(\"NSIS\");\n\n  if !nsis_toolset_path.exists() {\n    get_and_extract_nsis(&nsis_toolset_path, &tauri_tools_path)?;\n  } else if NSIS_REQUIRED_FILES\n    .iter()\n    .any(|p| !nsis_toolset_path.join(p).exists())\n  {\n    log::warn!(\"NSIS directory is missing some files. Recreating it.\");\n    std::fs::remove_dir_all(&nsis_toolset_path)?;\n    get_and_extract_nsis(&nsis_toolset_path, &tauri_tools_path)?;\n  } else {\n    let mismatched = NSIS_REQUIRED_FILES_HASH\n      .iter()\n      .filter(|(p, _, hash, hash_algorithm)| {\n        verify_file_hash(nsis_toolset_path.join(p), hash, *hash_algorithm).is_err()\n      })\n      .collect::<Vec<_>>();\n\n    if !mismatched.is_empty() {\n      log::warn!(\"NSIS directory contains mis-hashed files. Redownloading them.\");\n      for (path, url, hash, hash_algorithm) in mismatched {\n        let data = download_and_verify(url, hash, *hash_algorithm)?;\n        let out_path = nsis_toolset_path.join(path);\n        std::fs::create_dir_all(out_path.parent().context(\"output path has no parent\")?)\n          .fs_context(\"failed to create file output directory\", out_path.clone())?;\n        fs::write(&out_path, data)\n          .fs_context(\"failed to save NSIS downloaded file\", out_path.clone())?;\n      }\n    }\n  }\n\n  build_nsis_app_installer(settings, &nsis_toolset_path, &tauri_tools_path, updater)\n}\n\n// Gets NSIS and verifies the download via Sha1\nfn get_and_extract_nsis(nsis_toolset_path: &Path, _tauri_tools_path: &Path) -> crate::Result<()> {\n  log::info!(\"Verifying NSIS package\");\n\n  #[cfg(target_os = \"windows\")]\n  {\n    let data = download_and_verify(NSIS_URL, NSIS_SHA1, HashAlgorithm::Sha1)?;\n    log::info!(\"extracting NSIS\");\n    crate::utils::http_utils::extract_zip(&data, _tauri_tools_path)?;\n    fs::rename(_tauri_tools_path.join(\"nsis-3.11\"), nsis_toolset_path)?;\n  }\n\n  // download additional plugins\n  let nsis_plugins = nsis_toolset_path.join(\"Plugins\");\n\n  let data = download_and_verify(\n    NSIS_TAURI_UTILS_URL,\n    NSIS_TAURI_UTILS_SHA1,\n    HashAlgorithm::Sha1,\n  )?;\n\n  let target_folder = nsis_plugins.join(\"x86-unicode\").join(\"additional\");\n  fs::create_dir_all(&target_folder)?;\n  fs::write(target_folder.join(\"nsis_tauri_utils.dll\"), data)?;\n\n  Ok(())\n}\n\nfn try_add_numeric_build_number(version_str: &str) -> crate::Result<String> {\n  let version = semver::Version::parse(version_str)\n    .map_err(|error| Error::GenericError(format!(\"invalid app version: {error}\")))?;\n  if !version.build.is_empty() {\n    let build = version.build.parse::<u64>();\n    if build.is_ok() {\n      return Ok(format!(\n        \"{}.{}.{}.{}\",\n        version.major, version.minor, version.patch, version.build\n      ));\n    } else {\n      log::warn!(\n        \"Unable to parse version build metadata. Numeric value expected, received: `{}`. This will be replaced with `0` in `VIProductVersion` because Windows requires this field to be numeric.\",\n        version.build\n      );\n    }\n  }\n\n  Ok(format!(\n    \"{}.{}.{}.0\",\n    version.major, version.minor, version.patch,\n  ))\n}\n\nfn build_nsis_app_installer(\n  settings: &Settings,\n  #[allow(unused_variables)] nsis_toolset_path: &Path,\n  tauri_tools_path: &Path,\n  updater: bool,\n) -> crate::Result<Vec<PathBuf>> {\n  let arch = match settings.binary_arch() {\n    Arch::X86_64 => \"x64\",\n    Arch::X86 => \"x86\",\n    Arch::AArch64 => \"arm64\",\n    target => {\n      return Err(crate::Error::ArchError(format!(\n        \"unsupported architecture: {target:?}\"\n      )))\n    }\n  };\n\n  log::info!(\"Target: {}\", arch);\n\n  let output_path = settings.project_out_directory().join(\"nsis\").join(arch);\n  if output_path.exists() {\n    fs::remove_dir_all(&output_path)?;\n  }\n  fs::create_dir_all(&output_path)?;\n\n  // we make a copy of the NSIS directory if we're going to sign its DLLs\n  // because we don't want to change the DLL hashes so the cache can reuse it\n  let maybe_plugin_copy_path = if settings.windows().can_sign() {\n    // find nsis path\n    #[cfg(target_os = \"linux\")]\n    let system_nsis_toolset_path = std::env::var_os(\"NSIS_PATH\")\n      .map(PathBuf::from)\n      .unwrap_or_else(|| PathBuf::from(\"/usr/share/nsis\"));\n    #[cfg(target_os = \"macos\")]\n    let system_nsis_toolset_path = std::env::var_os(\"NSIS_PATH\")\n      .map(PathBuf::from)\n      .context(\"failed to resolve NSIS path\")\n      .or_else(|_| {\n        let mut makensis_path = which::which(\"makensis\").map_err(|error| Error::CommandFailed {\n          command: \"makensis\".to_string(),\n          error: std::io::Error::other(format!(\"failed to find makensis: {error}\")),\n        })?;\n        // homebrew installs it as a symlink\n        if makensis_path.is_symlink() {\n          // read_link might return a path relative to makensis_path so we must use join() and canonicalize\n          makensis_path = makensis_path\n            .parent()\n            .context(\"missing makensis parent\")?\n            .join(\n              std::fs::read_link(&makensis_path)\n                .fs_context(\"failed to resolve makensis symlink\", makensis_path.clone())?,\n            )\n            .canonicalize()\n            .fs_context(\n              \"failed to canonicalize makensis path\",\n              makensis_path.clone(),\n            )?;\n        }\n        // file structure:\n        // ├── bin\n        // │   ├── makensis\n        // ├── share\n        // │   ├── nsis\n        let bin_folder = makensis_path.parent().context(\"missing makensis parent\")?;\n        let root_folder = bin_folder.parent().context(\"missing makensis root\")?;\n        crate::Result::Ok(root_folder.join(\"share\").join(\"nsis\"))\n      })?;\n    #[cfg(windows)]\n    let system_nsis_toolset_path = nsis_toolset_path.to_path_buf();\n\n    let plugins_path = output_path.join(\"Plugins\");\n    // copy system plugins (we don't want to modify system installed DLLs, and on some systems there will even be permission errors if we try)\n    crate::utils::fs_utils::copy_dir(\n      &system_nsis_toolset_path.join(\"Plugins\").join(\"x86-unicode\"),\n      &plugins_path.join(\"x86-unicode\"),\n    )\n    .context(\"failed to copy system NSIS Plugins folder to local copy\")?;\n    // copy our downloaded DLLs\n    crate::utils::fs_utils::copy_dir(\n      &nsis_toolset_path\n        .join(\"Plugins\")\n        .join(\"x86-unicode\")\n        .join(\"additional\"),\n      &plugins_path.join(\"x86-unicode\").join(\"additional\"),\n    )\n    .context(\"failed to copy additional NSIS Plugins folder to local copy\")?;\n    Some(plugins_path)\n  } else {\n    // in this case plugin_copy_path can be None, we'll use the system default path\n    None\n  };\n\n  let mut data = BTreeMap::new();\n\n  let bundle_id = settings.bundle_identifier();\n  let manufacturer = settings\n    .publisher()\n    .unwrap_or_else(|| bundle_id.split('.').nth(1).unwrap_or(bundle_id));\n\n  let additional_plugins_path = maybe_plugin_copy_path\n    .clone()\n    .unwrap_or_else(|| nsis_toolset_path.join(\"Plugins\"))\n    .join(\"x86-unicode\")\n    .join(\"additional\");\n\n  data.insert(\n    \"additional_plugins_path\",\n    // either our Plugins copy (when signing) or the cache/Plugins/x86-unicode path\n    to_json(&additional_plugins_path),\n  );\n\n  data.insert(\"arch\", to_json(arch));\n  data.insert(\"bundle_id\", to_json(bundle_id));\n  data.insert(\"manufacturer\", to_json(manufacturer));\n  data.insert(\"product_name\", to_json(settings.product_name()));\n  data.insert(\"short_description\", to_json(settings.short_description()));\n  data.insert(\n    \"homepage\",\n    to_json(settings.homepage_url().unwrap_or_default()),\n  );\n  data.insert(\n    \"long_description\",\n    to_json(settings.long_description().unwrap_or_default()),\n  );\n  data.insert(\"copyright\", to_json(settings.copyright_string()));\n\n  if settings.windows().can_sign() {\n    if settings.no_sign() {\n      log::warn!(\"Skipping signing for NSIS uninstaller due to --no-sign flag.\");\n    } else {\n      let sign_cmd = format!(\"{:?}\", sign_command(\"%1\", &settings.sign_params())?);\n      data.insert(\"uninstaller_sign_cmd\", to_json(sign_cmd));\n    }\n  }\n\n  let version = settings.version_string();\n  data.insert(\"version\", to_json(version));\n  data.insert(\n    \"version_with_build\",\n    to_json(try_add_numeric_build_number(version)?),\n  );\n\n  data.insert(\n    \"allow_downgrades\",\n    to_json(settings.windows().allow_downgrades),\n  );\n\n  if let Some(license_file) = settings.license_file() {\n    let license_file = dunce::canonicalize(license_file)?;\n    let license_file_with_bom = output_path.join(\"license_file\");\n    let content = std::fs::read(license_file)?;\n    write_utf8_with_bom(&license_file_with_bom, content)?;\n    data.insert(\"license\", to_json(license_file_with_bom));\n  }\n\n  let nsis = settings.windows().nsis.as_ref();\n\n  let custom_template_path = nsis.as_ref().and_then(|n| n.template.clone());\n\n  let install_mode = nsis\n    .as_ref()\n    .map(|n| n.install_mode)\n    .unwrap_or(NSISInstallerMode::CurrentUser);\n\n  if let Some(nsis) = nsis {\n    if let Some(installer_icon) = &nsis.installer_icon {\n      data.insert(\n        \"installer_icon\",\n        to_json(dunce::canonicalize(installer_icon)?),\n      );\n    }\n\n    if let Some(header_image) = &nsis.header_image {\n      data.insert(\"header_image\", to_json(dunce::canonicalize(header_image)?));\n    }\n\n    if let Some(sidebar_image) = &nsis.sidebar_image {\n      data.insert(\n        \"sidebar_image\",\n        to_json(dunce::canonicalize(sidebar_image)?),\n      );\n    }\n\n    if let Some(installer_hooks) = &nsis.installer_hooks {\n      let installer_hooks = dunce::canonicalize(installer_hooks)?;\n      data.insert(\"installer_hooks\", to_json(installer_hooks));\n    }\n\n    if let Some(start_menu_folder) = &nsis.start_menu_folder {\n      data.insert(\"start_menu_folder\", to_json(start_menu_folder));\n    }\n    if let Some(minimum_webview2_version) = &nsis.minimum_webview2_version {\n      data.insert(\n        \"minimum_webview2_version\",\n        to_json(minimum_webview2_version),\n      );\n    }\n  }\n\n  let compression = settings\n    .windows()\n    .nsis\n    .as_ref()\n    .map(|n| n.compression)\n    .unwrap_or_default();\n  data.insert(\n    \"compression\",\n    to_json(match compression {\n      NsisCompression::Zlib => \"zlib\",\n      NsisCompression::Bzip2 => \"bzip2\",\n      NsisCompression::Lzma => \"lzma\",\n      NsisCompression::None => \"none\",\n    }),\n  );\n\n  data.insert(\n    \"install_mode\",\n    to_json(match install_mode {\n      NSISInstallerMode::CurrentUser => \"currentUser\",\n      NSISInstallerMode::PerMachine => \"perMachine\",\n      NSISInstallerMode::Both => \"both\",\n    }),\n  );\n\n  let languages = nsis\n    .and_then(|nsis| nsis.languages.clone())\n    .unwrap_or_else(|| vec![\"English\".into()]);\n  data.insert(\"languages\", to_json(languages.clone()));\n\n  data.insert(\n    \"display_language_selector\",\n    to_json(\n      nsis\n        .map(|nsis| nsis.display_language_selector && languages.len() > 1)\n        .unwrap_or(false),\n    ),\n  );\n\n  let custom_language_files = nsis.and_then(|nsis| nsis.custom_language_files.clone());\n\n  let mut language_files_paths = Vec::new();\n  for lang in &languages {\n    // if user provided a custom lang file, we rewrite it with BOM\n    if let Some(path) = custom_language_files.as_ref().and_then(|h| h.get(lang)) {\n      let path = dunce::canonicalize(path)?;\n      let path_with_bom = path\n        .file_name()\n        .map(|f| output_path.join(f))\n        .unwrap_or_else(|| output_path.join(format!(\"{lang}_custom.nsh\")));\n      let content = std::fs::read(path)?;\n      write_utf8_with_bom(&path_with_bom, content)?;\n      language_files_paths.push(path_with_bom);\n    } else {\n      // if user has not provided a custom lang file,\n      // we check our translated languages\n      if let Some((file_name, content)) = get_lang_data(lang) {\n        let path = output_path.join(file_name);\n        write_utf8_with_bom(&path, content)?;\n        language_files_paths.push(path);\n      } else {\n        log::warn!(\"Custom tauri messages for {lang} are not translated.\\nIf it is a valid language listed on <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files>, please open a Tauri feature request\\n or you can provide a custom language file for it in `tauri.conf.json > bundle > windows > nsis > custom_language_files`\");\n      }\n    }\n  }\n  data.insert(\"language_files\", to_json(language_files_paths));\n\n  let main_binary = settings.main_binary()?;\n  let main_binary_path = settings.binary_path(main_binary);\n  data.insert(\"main_binary_name\", to_json(main_binary.name()));\n  data.insert(\"main_binary_path\", to_json(&main_binary_path));\n\n  let out_file = \"nsis-output.exe\";\n  data.insert(\"out_file\", to_json(out_file));\n\n  let resources = generate_resource_data(settings)?;\n  let resources_dirs =\n    std::collections::HashSet::<PathBuf>::from_iter(resources.values().map(|r| r.0.to_owned()));\n\n  let mut resources_ancestors = resources_dirs\n    .iter()\n    .flat_map(|p| p.ancestors())\n    .collect::<Vec<_>>();\n  resources_ancestors.sort_unstable();\n  resources_ancestors.dedup();\n  resources_ancestors.sort_by_key(|p| std::cmp::Reverse(p.components().count()));\n  resources_ancestors.pop(); // Last one is always \"\"\n\n  // We need to convert / to \\ for nsis to move the files into the correct dirs\n  #[cfg(not(target_os = \"windows\"))]\n  let resources: ResourcesMap = resources\n    .into_iter()\n    .map(|(r, p)| {\n      (\n        r,\n        (\n          p.0.display().to_string().replace('/', \"\\\\\").into(),\n          p.1.display().to_string().replace('/', \"\\\\\").into(),\n        ),\n      )\n    })\n    .collect();\n  #[cfg(not(target_os = \"windows\"))]\n  let resources_ancestors: Vec<PathBuf> = resources_ancestors\n    .into_iter()\n    .map(|p| p.display().to_string().replace('/', \"\\\\\").into())\n    .collect();\n  #[cfg(not(target_os = \"windows\"))]\n  let resources_dirs: Vec<PathBuf> = resources_dirs\n    .into_iter()\n    .map(|p| p.display().to_string().replace('/', \"\\\\\").into())\n    .collect();\n\n  data.insert(\"resources_ancestors\", to_json(resources_ancestors));\n  data.insert(\"resources_dirs\", to_json(resources_dirs));\n  data.insert(\"resources\", to_json(&resources));\n\n  let binaries = generate_binaries_data(settings)?;\n  data.insert(\"binaries\", to_json(&binaries));\n\n  let estimated_size = generate_estimated_size(&main_binary_path, &binaries, &resources)?;\n  data.insert(\"estimated_size\", to_json(estimated_size));\n\n  if let Some(file_associations) = settings.file_associations() {\n    data.insert(\"file_associations\", to_json(file_associations));\n  }\n\n  if let Some(protocols) = settings.deep_link_protocols() {\n    let schemes = protocols\n      .iter()\n      .flat_map(|p| &p.schemes)\n      .collect::<Vec<_>>();\n    if !schemes.is_empty() {\n      data.insert(\"deep_link_protocols\", to_json(schemes));\n    }\n  }\n\n  let silent_webview2_install = if let WebviewInstallMode::DownloadBootstrapper { silent }\n  | WebviewInstallMode::EmbedBootstrapper { silent }\n  | WebviewInstallMode::OfflineInstaller { silent } =\n    settings.windows().webview_install_mode\n  {\n    silent\n  } else {\n    true\n  };\n\n  let webview2_install_mode = if updater {\n    WebviewInstallMode::DownloadBootstrapper {\n      silent: silent_webview2_install,\n    }\n  } else {\n    settings.windows().webview_install_mode.clone()\n  };\n\n  let webview2_installer_args = to_json(if silent_webview2_install {\n    \"/silent\"\n  } else {\n    \"\"\n  });\n\n  data.insert(\"webview2_installer_args\", to_json(webview2_installer_args));\n  data.insert(\n    \"install_webview2_mode\",\n    to_json(match webview2_install_mode {\n      WebviewInstallMode::DownloadBootstrapper { silent: _ } => \"downloadBootstrapper\",\n      WebviewInstallMode::EmbedBootstrapper { silent: _ } => \"embedBootstrapper\",\n      WebviewInstallMode::OfflineInstaller { silent: _ } => \"offlineInstaller\",\n      _ => \"\",\n    }),\n  );\n\n  match webview2_install_mode {\n    WebviewInstallMode::EmbedBootstrapper { silent: _ } => {\n      let webview2_bootstrapper_path = download_webview2_bootstrapper(tauri_tools_path)?;\n      data.insert(\n        \"webview2_bootstrapper_path\",\n        to_json(webview2_bootstrapper_path),\n      );\n    }\n    WebviewInstallMode::OfflineInstaller { silent: _ } => {\n      let webview2_installer_path =\n        download_webview2_offline_installer(&tauri_tools_path.join(arch), arch)?;\n      data.insert(\"webview2_installer_path\", to_json(webview2_installer_path));\n    }\n    _ => {}\n  }\n\n  let mut handlebars = Handlebars::new();\n  handlebars.register_helper(\"or\", Box::new(handlebars_or));\n  handlebars.register_helper(\"association-description\", Box::new(association_description));\n  handlebars.register_helper(\"no-escape\", Box::new(handlebars_no_escape));\n  handlebars.register_escape_fn(|s| {\n    let mut output = String::new();\n    for c in s.chars() {\n      match c {\n        '\\\"' => output.push_str(\"$\\\\\\\"\"),\n        '$' => output.push_str(\"$$\"),\n        '`' => output.push_str(\"$\\\\`\"),\n        '\\n' => output.push_str(\"$\\\\n\"),\n        '\\t' => output.push_str(\"$\\\\t\"),\n        '\\r' => output.push_str(\"$\\\\r\"),\n        _ => output.push(c),\n      }\n    }\n    output\n  });\n  if let Some(path) = custom_template_path {\n    handlebars\n      .register_template_string(\"installer.nsi\", std::fs::read_to_string(path)?)\n      .map_err(|e| e.to_string())\n      .expect(\"Failed to setup custom handlebar template\");\n  } else {\n    handlebars\n      .register_template_string(\"installer.nsi\", include_str!(\"./installer.nsi\"))\n      .map_err(|e| e.to_string())\n      .expect(\"Failed to setup handlebar template\");\n  }\n\n  write_utf8_with_bom(\n    output_path.join(\"FileAssociation.nsh\"),\n    include_bytes!(\"./FileAssociation.nsh\"),\n  )?;\n  write_utf8_with_bom(output_path.join(\"utils.nsh\"), include_bytes!(\"./utils.nsh\"))?;\n\n  let installer_nsi_path = output_path.join(\"installer.nsi\");\n  write_utf8_with_bom(\n    &installer_nsi_path,\n    handlebars.render(\"installer.nsi\", &data)?,\n  )?;\n\n  let package_base_name = format!(\n    \"{}_{}_{}-setup\",\n    settings.product_name(),\n    settings.version_string(),\n    arch,\n  );\n\n  let nsis_output_path = output_path.join(out_file);\n  let nsis_installer_path = settings.project_out_directory().to_path_buf().join(format!(\n    \"bundle/{}/{}.exe\",\n    if updater {\n      NSIS_UPDATER_OUTPUT_FOLDER_NAME\n    } else {\n      NSIS_OUTPUT_FOLDER_NAME\n    },\n    package_base_name\n  ));\n  fs::create_dir_all(nsis_installer_path.parent().unwrap())?;\n\n  if settings.windows().can_sign() {\n    if let Some(plugin_copy_path) = &maybe_plugin_copy_path {\n      let plugin_copy_path = plugin_copy_path.join(\"x86-unicode\");\n      log::info!(\"Signing NSIS plugins\");\n      for dll in NSIS_PLUGIN_FILES {\n        let path = plugin_copy_path.join(dll);\n        if path.exists() {\n          try_sign(&path, settings)?;\n        } else {\n          log::warn!(\"Could not find {}, skipping signing\", path.display());\n        }\n      }\n    }\n  }\n\n  log::info!(action = \"Running\"; \"makensis to produce {}\", display_path(&nsis_installer_path));\n\n  #[cfg(target_os = \"windows\")]\n  let mut nsis_cmd = Command::new(nsis_toolset_path.join(\"makensis.exe\"));\n  #[cfg(not(target_os = \"windows\"))]\n  let mut nsis_cmd = Command::new(\"makensis\");\n\n  if let Some(plugins_path) = &maybe_plugin_copy_path {\n    nsis_cmd.env(\"NSISPLUGINS\", plugins_path);\n  }\n\n  nsis_cmd\n    .args([\"-INPUTCHARSET\", \"UTF8\", \"-OUTPUTCHARSET\", \"UTF8\"])\n    .arg(match settings.log_level() {\n      log::Level::Error => \"-V1\",\n      log::Level::Warn => \"-V2\",\n      log::Level::Info => \"-V3\",\n      _ => \"-V4\",\n    })\n    .arg(installer_nsi_path)\n    .env_remove(\"NSISDIR\")\n    .env_remove(\"NSISCONFDIR\")\n    .current_dir(output_path)\n    .piped()\n    .map_err(|error| Error::CommandFailed {\n      command: \"makensis.exe\".to_string(),\n      error,\n    })?;\n\n  fs::rename(nsis_output_path, &nsis_installer_path)?;\n\n  if settings.windows().can_sign() {\n    try_sign(&nsis_installer_path, settings)?;\n  } else {\n    #[cfg(not(target_os = \"windows\"))]\n    log::warn!(\"Signing, by default, is only supported on Windows hosts, but you can specify a custom signing command in `bundler > windows > sign_command`, for now, skipping signing the installer...\");\n  }\n\n  Ok(vec![nsis_installer_path])\n}\n\nfn handlebars_or(\n  h: &handlebars::Helper<'_>,\n  _: &Handlebars<'_>,\n  _: &handlebars::Context,\n  _: &mut handlebars::RenderContext<'_, '_>,\n  out: &mut dyn handlebars::Output,\n) -> handlebars::HelperResult {\n  let param1 = h.param(0).unwrap().render();\n  let param2 = h.param(1).unwrap();\n\n  out.write(&if param1.is_empty() {\n    param2.render()\n  } else {\n    param1\n  })?;\n  Ok(())\n}\n\nfn association_description(\n  h: &handlebars::Helper<'_>,\n  _: &Handlebars<'_>,\n  _: &handlebars::Context,\n  _: &mut handlebars::RenderContext<'_, '_>,\n  out: &mut dyn handlebars::Output,\n) -> handlebars::HelperResult {\n  let description = h.param(0).unwrap().render();\n  let ext = h.param(1).unwrap();\n\n  out.write(&if description.is_empty() {\n    format!(\"{} File\", ext.render().to_uppercase())\n  } else {\n    description\n  })?;\n  Ok(())\n}\n\nfn handlebars_no_escape(\n  h: &handlebars::Helper<'_>,\n  _: &Handlebars<'_>,\n  _: &handlebars::Context,\n  _: &mut handlebars::RenderContext<'_, '_>,\n  out: &mut dyn handlebars::Output,\n) -> handlebars::HelperResult {\n  // get parameter from helper or throw an error\n  let param = h\n    .param(0)\n    .ok_or(handlebars::RenderErrorReason::ParamNotFoundForIndex(\n      \"no-escape\",\n      0,\n    ))?;\n  write!(out, \"{}\", param.render())?;\n  Ok(())\n}\n\n/// BTreeMap<OriginalPath, (ParentOfTargetPath, TargetPath)>\ntype ResourcesMap = BTreeMap<PathBuf, (PathBuf, PathBuf)>;\nfn generate_resource_data(settings: &Settings) -> crate::Result<ResourcesMap> {\n  let mut resources = ResourcesMap::new();\n\n  let cwd = std::env::current_dir()?;\n\n  let mut added_resources = Vec::new();\n\n  // Adding WebViewer2Loader.dll in case windows-gnu toolchain is used\n  if settings.target().ends_with(\"-gnu\") {\n    let loader_path =\n      dunce::simplified(&settings.project_out_directory().join(\"WebView2Loader.dll\")).to_path_buf();\n    if loader_path.exists() {\n      if settings.windows().can_sign() {\n        try_sign(&loader_path, settings)?;\n      }\n      added_resources.push(loader_path.clone());\n      resources.insert(\n        loader_path,\n        (PathBuf::new(), PathBuf::from(\"WebView2Loader.dll\")),\n      );\n    }\n  }\n\n  for resource in settings.resource_files().iter() {\n    let resource = resource?;\n\n    let src = cwd.join(resource.path());\n    let resource_path = dunce::simplified(&src).to_path_buf();\n\n    // In some glob resource paths like `assets/**/*` a file might appear twice\n    // because the `tauri_utils::resources::ResourcePaths` iterator also reads a directory\n    // when it finds one. So we must check it before processing the file.\n    if added_resources.contains(&resource_path) {\n      continue;\n    }\n    added_resources.push(resource_path.clone());\n\n    if settings.windows().can_sign() && should_sign(&resource_path)? {\n      try_sign(&resource_path, settings)?;\n    }\n\n    let target_path = resource.target();\n    resources.insert(\n      resource_path,\n      (\n        target_path\n          .parent()\n          .expect(\"Couldn't get parent of target path\")\n          .to_path_buf(),\n        target_path.to_path_buf(),\n      ),\n    );\n  }\n\n  Ok(resources)\n}\n\n/// BTreeMap<OriginalPath, TargetFileName>\ntype BinariesMap = BTreeMap<PathBuf, String>;\nfn generate_binaries_data(settings: &Settings) -> crate::Result<BinariesMap> {\n  let mut binaries = BinariesMap::new();\n  let cwd = std::env::current_dir()?;\n\n  for src in settings.external_binaries() {\n    let src = src?;\n    let binary_path = dunce::canonicalize(cwd.join(&src))?;\n    let dest_filename = src\n      .file_name()\n      .expect(\"failed to extract external binary filename\")\n      .to_string_lossy()\n      .replace(&format!(\"-{}\", settings.target()), \"\");\n    binaries.insert(binary_path, dest_filename);\n  }\n\n  for bin in settings.binaries() {\n    if !bin.main() {\n      let bin_path = settings.binary_path(bin);\n      binaries.insert(\n        bin_path.clone(),\n        bin_path\n          .file_name()\n          .expect(\"failed to extract external binary filename\")\n          .to_string_lossy()\n          .to_string(),\n      );\n    }\n  }\n\n  Ok(binaries)\n}\n\nfn generate_estimated_size(\n  main: &PathBuf,\n  binaries: &BinariesMap,\n  resources: &ResourcesMap,\n) -> crate::Result<u64> {\n  let mut size = 0;\n  for k in std::iter::once(main)\n    .chain(binaries.keys())\n    .chain(resources.keys())\n  {\n    size += std::fs::metadata(k)\n      .map_err(|error| Error::Fs {\n        context: \"when getting size of\",\n        path: k.to_path_buf(),\n        error,\n      })?\n      .len();\n  }\n  Ok(size / 1024)\n}\n\nfn get_lang_data(lang: &str) -> Option<(String, &[u8])> {\n  let path = format!(\"{lang}.nsh\");\n  let content: &[u8] = match lang.to_lowercase().as_str() {\n    \"arabic\" => include_bytes!(\"./languages/Arabic.nsh\"),\n    \"bulgarian\" => include_bytes!(\"./languages/Bulgarian.nsh\"),\n    \"dutch\" => include_bytes!(\"./languages/Dutch.nsh\"),\n    \"english\" => include_bytes!(\"./languages/English.nsh\"),\n    \"german\" => include_bytes!(\"./languages/German.nsh\"),\n    \"italian\" => include_bytes!(\"./languages/Italian.nsh\"),\n    \"japanese\" => include_bytes!(\"./languages/Japanese.nsh\"),\n    \"korean\" => include_bytes!(\"./languages/Korean.nsh\"),\n    \"portuguesebr\" => include_bytes!(\"./languages/PortugueseBR.nsh\"),\n    \"russian\" => include_bytes!(\"./languages/Russian.nsh\"),\n    \"tradchinese\" => include_bytes!(\"./languages/TradChinese.nsh\"),\n    \"simpchinese\" => include_bytes!(\"./languages/SimpChinese.nsh\"),\n    \"french\" => include_bytes!(\"./languages/French.nsh\"),\n    \"spanish\" => include_bytes!(\"./languages/Spanish.nsh\"),\n    \"spanishinternational\" => include_bytes!(\"./languages/SpanishInternational.nsh\"),\n    \"persian\" => include_bytes!(\"./languages/Persian.nsh\"),\n    \"turkish\" => include_bytes!(\"./languages/Turkish.nsh\"),\n    \"swedish\" => include_bytes!(\"./languages/Swedish.nsh\"),\n    \"portuguese\" => include_bytes!(\"./languages/Portuguese.nsh\"),\n    \"ukrainian\" => include_bytes!(\"./languages/Ukrainian.nsh\"),\n    \"norwegian\" => include_bytes!(\"./languages/Norwegian.nsh\"),\n    _ => return None,\n  };\n  Some((path, content))\n}\n\nfn write_utf8_with_bom<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, content: C) -> crate::Result<()> {\n  use std::fs::File;\n  use std::io::{BufWriter, Write};\n\n  let file = File::create(path)?;\n  let mut output = BufWriter::new(file);\n  output.write_all(&[0xEF, 0xBB, 0xBF])?; // UTF-8 BOM\n  output.write_all(content.as_ref())?;\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/nsis/utils.nsh",
    "content": "; Change shell and registry context based on running\n; architecture and chosen install mode.\n!macro SetContext\n  !if \"${INSTALLMODE}\" == \"currentUser\"\n    SetShellVarContext current\n  !else if \"${INSTALLMODE}\" == \"perMachine\"\n    SetShellVarContext all\n  !endif\n\n  ${If} ${RunningX64}\n    !if \"${ARCH}\" == \"x64\"\n      SetRegView 64\n    !else if \"${ARCH}\" == \"arm64\"\n      SetRegView 64\n    !else\n      SetRegView 32\n    !endif\n  ${EndIf}\n!macroend\n\n; Checks whether app is running or not and prompts to kill it.\n!macro CheckIfAppIsRunning executableName productName\n  !define UniqueID ${__LINE__}\n\n  ; Replace {{product_name}} placeholder in the messages with the passed product name\n  nsis_tauri_utils::StrReplace \"$(appRunning)\" \"{{product_name}}\" \"${productName}\"\n  Pop $R1\n  nsis_tauri_utils::StrReplace \"$(appRunningOkKill)\" \"{{product_name}}\" \"${productName}\"\n  Pop $R2\n  nsis_tauri_utils::StrReplace \"$(failedToKillApp)\" \"{{product_name}}\" \"${productName}\"\n  Pop $R3\n\n  !if \"${INSTALLMODE}\" == \"currentUser\"\n    nsis_tauri_utils::FindProcessCurrentUser \"${executableName}\"\n  !else\n    nsis_tauri_utils::FindProcess \"${executableName}\"\n  !endif\n  Pop $R0\n  ${If} $R0 = 0\n      IfSilent kill_${UniqueID} 0\n      ${IfThen} $PassiveMode != 1 ${|} MessageBox MB_OKCANCEL $R2 IDOK kill_${UniqueID} IDCANCEL cancel_${UniqueID} ${|}\n      kill_${UniqueID}:\n        !if \"${INSTALLMODE}\" == \"currentUser\"\n          nsis_tauri_utils::KillProcessCurrentUser \"${executableName}\"\n        !else\n          nsis_tauri_utils::KillProcess \"${executableName}\"\n        !endif\n        Pop $R0\n        Sleep 500\n        ${If} $R0 = 0\n        ${OrIf} $R0 = 2\n          Goto app_check_done_${UniqueID}\n        ${Else}\n          IfSilent silent_${UniqueID} ui_${UniqueID}\n          silent_${UniqueID}:\n            System::Call 'kernel32::AttachConsole(i -1)i.r0'\n            ${If} $0 != 0\n              System::Call 'kernel32::GetStdHandle(i -11)i.r0'\n              System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color\n              FileWrite $0 \"$R1$\\n\"\n            ${EndIf}\n            Abort\n          ui_${UniqueID}:\n            Abort $R3\n        ${EndIf}\n      cancel_${UniqueID}:\n        Abort $R1\n  ${EndIf}\n  app_check_done_${UniqueID}:\n    !undef UniqueID\n!macroend\n\n; Sets AppUserModelId on a shortcut\n!macro SetLnkAppUserModelId shortcut\n  !insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IShellLink} r0 \"\"\n  ${If} $0 P<> 0\n    ${IUnknown::QueryInterface} $0 '(\"${IID_IPersistFile}\",.r1)'\n    ${If} $1 P<> 0\n      ${IPersistFile::Load} $1 '(\"${shortcut}\", ${STGM_READWRITE})'\n      ${IUnknown::QueryInterface} $0 '(\"${IID_IPropertyStore}\",.r2)'\n      ${If} $2 P<> 0\n        System::Call 'Oleaut32::SysAllocString(w \"${BUNDLEID}\") i.r3'\n        System::Call '*${SYSSTRUCT_PROPERTYKEY}(${PKEY_AppUserModel_ID})p.r4'\n        System::Call '*${SYSSTRUCT_PROPVARIANT}(${VT_BSTR},,&i4 $3)p.r5'\n        ${IPropertyStore::SetValue} $2 '($4,$5)'\n\n        System::Call 'Oleaut32::SysFreeString($3)'\n        System::Free $4\n        System::Free $5\n        ${IPropertyStore::Commit} $2 \"\"\n        ${IUnknown::Release} $2 \"\"\n        ${IPersistFile::Save} $1 '(\"${shortcut}\",1)'\n      ${EndIf}\n      ${IUnknown::Release} $1 \"\"\n    ${EndIf}\n    ${IUnknown::Release} $0 \"\"\n  ${EndIf}\n!macroend\n\n; Deletes jump list entries and recent destinations\n!macro DeleteAppUserModelId\n  !insertmacro ComHlpr_CreateInProcInstance ${CLSID_DestinationList} ${IID_ICustomDestinationList} r1 \"\"\n  ${If} $1 P<> 0\n    ${ICustomDestinationList::DeleteList} $1 '(\"${BUNDLEID}\")'\n    ${IUnknown::Release} $1 \"\"\n  ${EndIf}\n  !insertmacro ComHlpr_CreateInProcInstance ${CLSID_ApplicationDestinations} ${IID_IApplicationDestinations} r1 \"\"\n  ${If} $1 P<> 0\n    ${IApplicationDestinations::SetAppID} $1 '(\"${BUNDLEID}\")i.r0'\n    ${If} $0 >= 0\n      ${IApplicationDestinations::RemoveAllDestinations} $1 ''\n    ${EndIf}\n    ${IUnknown::Release} $1 \"\"\n  ${EndIf}\n!macroend\n\n; Unpins a shortcut from Start menu and Taskbar\n;\n; From https://stackoverflow.com/a/42816728/16993372\n!macro UnpinShortcut shortcut\n  !insertmacro ComHlpr_CreateInProcInstance ${CLSID_StartMenuPin} ${IID_IStartMenuPinnedList} r0 \"\"\n  ${If} $0 P<> 0\n      System::Call 'SHELL32::SHCreateItemFromParsingName(ws, p0, g \"${IID_IShellItem}\", *p0r1)' \"${shortcut}\"\n      ${If} $1 P<> 0\n          ${IStartMenuPinnedList::RemoveFromList} $0 '(r1)'\n          ${IUnknown::Release} $1 \"\"\n      ${EndIf}\n      ${IUnknown::Release} $0 \"\"\n  ${EndIf}\n!macroend\n\n; Set target path for a .lnk shortcut\n!macro SetShortcutTarget shortcut target\n  !insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IShellLink} r0 \"\"\n  ${If} $0 P<> 0\n    ${IUnknown::QueryInterface} $0 '(\"${IID_IPersistFile}\",.r1)'\n    ${If} $1 P<> 0\n      ${IPersistFile::Load} $1 '(\"${shortcut}\", ${STGM_READWRITE})'\n      ${IShellLink::SetPath} $0 '(w \"${target}\")'\n      ${IPersistFile::Save} $1 '(\"${shortcut}\",1)'\n      ${IUnknown::Release} $1 \"\"\n    ${EndIf}\n    ${IUnknown::Release} $0 \"\"\n  ${EndIf}\n!macroend\n\n!define /ifndef MAX_PATH 260\n!define /ifndef SLGP_RAWPATH 0x4\n\n; Test if a .lnk shortcut's target is target,\n; use Pop to get the result, 1 is yes, 0 is no,\n; note that this macro modifies $0, $1, $2, $3\n;\n; Exmaple usage:\n;   !insertmacro \"IsShortCutTarget\" \"C:\\Users\\Public\\Desktop\\App.lnk\" \"C:\\Program Files\\App\\App.exe\"\n;   Pop $0\n;   ${If} $0 = 1\n;     MessageBox MB_OK \"shortcut target matches\"\n;   ${EndIf}\n!macro IsShortcutTarget shortcut target\n  ; $0: IShellLink\n  ; $1: IPersistFile\n  ; $2: Target path\n  ; $3: Return value\n\n  StrCpy $3 0\n  !insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IShellLink} r0 \"\"\n  ${If} $0 P<> 0\n    ${IUnknown::QueryInterface} $0 '(\"${IID_IPersistFile}\", .r1)'\n    ${If} $1 P<> 0\n      ${IPersistFile::Load} $1 '(\"${shortcut}\", ${STGM_READ})'\n      System::Alloc MAX_PATH\n      Pop $2\n      ${IShellLink::GetPath} $0 '(.r2, ${MAX_PATH}, 0, ${SLGP_RAWPATH})'\n      ${If} $2 == \"${target}\"\n        StrCpy $3 1\n      ${EndIf}\n      System::Free $2\n      ${IUnknown::Release} $1 \"\"\n    ${EndIf}\n    ${IUnknown::Release} $0 \"\"\n  ${EndIf}\n  Push $3\n!macroend\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/sign.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::bundle::settings::CustomSignCommandSettings;\n#[cfg(windows)]\nuse crate::bundle::windows::util;\nuse crate::{utils::CommandExt, Settings};\n#[cfg(windows)]\nuse std::path::PathBuf;\n#[cfg(windows)]\nuse std::sync::OnceLock;\nuse std::{path::Path, process::Command};\n\nimpl Settings {\n  pub(crate) fn sign_params(&self) -> SignParams {\n    SignParams {\n      product_name: self.product_name().into(),\n      digest_algorithm: self\n        .windows()\n        .digest_algorithm\n        .as_ref()\n        .map(|algorithm| algorithm.to_string())\n        .unwrap_or_else(|| \"sha256\".to_string()),\n      certificate_thumbprint: self\n        .windows()\n        .certificate_thumbprint\n        .clone()\n        .unwrap_or_default(),\n      timestamp_url: self\n        .windows()\n        .timestamp_url\n        .as_ref()\n        .map(|url| url.to_string()),\n      tsp: self.windows().tsp,\n      sign_command: self.windows().sign_command.clone(),\n    }\n  }\n}\n\n#[cfg_attr(not(windows), allow(dead_code))]\npub struct SignParams {\n  pub product_name: String,\n  pub digest_algorithm: String,\n  pub certificate_thumbprint: String,\n  pub timestamp_url: Option<String>,\n  pub tsp: bool,\n  pub sign_command: Option<CustomSignCommandSettings>,\n}\n\n#[cfg(windows)]\nfn signtool() -> Option<PathBuf> {\n  // sign code forked from https://github.com/forbjok/rust-codesign\n  static SIGN_TOOL: OnceLock<crate::Result<PathBuf>> = OnceLock::new();\n  SIGN_TOOL\n    .get_or_init(|| {\n      if let Some(signtool) = std::env::var_os(\"TAURI_WINDOWS_SIGNTOOL_PATH\") {\n        return Ok(PathBuf::from(signtool));\n      }\n\n      const INSTALLED_ROOTS_REGKEY_PATH: &str = r\"SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots\";\n      const KITS_ROOT_REGVALUE_NAME: &str = r\"KitsRoot10\";\n\n      // Open 32-bit HKLM \"Installed Roots\" key\n      let installed_roots_key = windows_registry::LOCAL_MACHINE\n        .open(INSTALLED_ROOTS_REGKEY_PATH)\n        .map_err(|_| crate::Error::OpenRegistry(INSTALLED_ROOTS_REGKEY_PATH.to_string()))?;\n\n      // Get the Windows SDK root path\n      let kits_root_10_path: String = installed_roots_key\n        .get_string(KITS_ROOT_REGVALUE_NAME)\n        .map_err(|_| crate::Error::GetRegistryValue(KITS_ROOT_REGVALUE_NAME.to_string()))?;\n\n      // Construct Windows SDK bin path\n      let kits_root_10_bin_path = Path::new(&kits_root_10_path).join(\"bin\");\n\n      let mut installed_kits: Vec<String> = installed_roots_key\n        .keys()\n        .map_err(|_| crate::Error::FailedToEnumerateRegKeys)?\n        .collect();\n\n      // Sort installed kits\n      installed_kits.sort();\n\n      /* Iterate through installed kit version keys in reverse (from newest to oldest),\n      adding their bin paths to the list.\n      Windows SDK 10 v10.0.15063.468 and later will have their signtools located there. */\n      let mut kit_bin_paths: Vec<PathBuf> = installed_kits\n        .iter()\n        .rev()\n        .map(|kit| kits_root_10_bin_path.join(kit))\n        .collect();\n\n      /* Add kits root bin path.\n      For Windows SDK 10 versions earlier than v10.0.15063.468, signtool will be located there. */\n      kit_bin_paths.push(kits_root_10_bin_path);\n\n      // Choose which version of SignTool to use based on OS bitness\n      let arch_dir = util::os_bitness().ok_or(crate::Error::UnsupportedBitness)?;\n\n      /* Iterate through all bin paths, checking for existence of a SignTool executable. */\n      for kit_bin_path in &kit_bin_paths {\n        /* Construct SignTool path. */\n        let signtool_path = kit_bin_path.join(arch_dir).join(\"signtool.exe\");\n\n        /* Check if SignTool exists at this location. */\n        if signtool_path.exists() {\n          // SignTool found. Return it.\n          return Ok(signtool_path);\n        }\n      }\n\n      Err(crate::Error::SignToolNotFound)\n    })\n    .as_ref()\n    .ok()\n    .cloned()\n}\n\n/// Check if binary is already signed.\n/// Used to skip sidecar binaries that are already signed.\n#[cfg(windows)]\npub fn verify(path: &Path) -> crate::Result<bool> {\n  let signtool = signtool().ok_or(crate::Error::SignToolNotFound)?;\n\n  let mut cmd = Command::new(signtool);\n  cmd.arg(\"verify\");\n  cmd.arg(\"/pa\");\n  cmd.arg(path);\n\n  Ok(cmd.status()?.success())\n}\n\npub fn sign_command_custom<P: AsRef<Path>>(\n  path: P,\n  command: &CustomSignCommandSettings,\n) -> crate::Result<Command> {\n  let path = path.as_ref();\n\n  let cwd = std::env::current_dir()?;\n\n  let mut cmd = Command::new(&command.cmd);\n  for arg in &command.args {\n    if arg == \"%1\" {\n      cmd.arg(path);\n    } else {\n      let path = Path::new(arg);\n      // turn relative paths into absolute paths - so the uninstall command can use them\n      // since the !uninstfinalize NSIS hook runs in a different directory\n      if path.exists() && path.is_relative() {\n        cmd.arg(cwd.join(path));\n      } else {\n        cmd.arg(arg);\n      }\n    }\n  }\n  Ok(cmd)\n}\n\n#[cfg(windows)]\npub fn sign_command_default<P: AsRef<Path>>(\n  path: P,\n  params: &SignParams,\n) -> crate::Result<Command> {\n  let signtool = signtool().ok_or(crate::Error::SignToolNotFound)?;\n\n  let mut cmd = Command::new(signtool);\n  cmd.arg(\"sign\");\n  cmd.args([\"/fd\", &params.digest_algorithm]);\n  cmd.args([\"/sha1\", &params.certificate_thumbprint]);\n  cmd.args([\"/d\", &params.product_name]);\n\n  if let Some(ref timestamp_url) = params.timestamp_url {\n    if params.tsp {\n      cmd.args([\"/tr\", timestamp_url]);\n      cmd.args([\"/td\", &params.digest_algorithm]);\n    } else {\n      cmd.args([\"/t\", timestamp_url]);\n    }\n  }\n\n  cmd.arg(path.as_ref());\n\n  Ok(cmd)\n}\n\npub fn sign_command<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<Command> {\n  match &params.sign_command {\n    Some(custom_command) => sign_command_custom(path, custom_command),\n    #[cfg(windows)]\n    None => sign_command_default(path, params),\n\n    // should not be reachable\n    #[cfg(not(windows))]\n    None => Ok(Command::new(\"\")),\n  }\n}\n\npub fn sign_custom<P: AsRef<Path>>(\n  path: P,\n  custom_command: &CustomSignCommandSettings,\n) -> crate::Result<()> {\n  let path = path.as_ref();\n\n  log::info!(action = \"Signing\";\"{} with a custom signing command\", tauri_utils::display_path(path));\n\n  let mut cmd = sign_command_custom(path, custom_command)?;\n\n  let output = cmd.output_ok()?;\n\n  let stdout = String::from_utf8_lossy(output.stdout.as_slice()).into_owned();\n  log::info!(action = \"Signing\";\"Output of signing command:\\n{}\", stdout.trim());\n\n  Ok(())\n}\n\n#[cfg(windows)]\npub fn sign_default<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {\n  let signtool = signtool().ok_or(crate::Error::SignToolNotFound)?;\n  let path = path.as_ref();\n\n  log::info!(action = \"Signing\"; \"{} with identity \\\"{}\\\"\", tauri_utils::display_path(path), params.certificate_thumbprint);\n\n  let mut cmd = sign_command_default(path, params)?;\n  log::debug!(\"Running signtool {:?}\", signtool);\n\n  // Execute SignTool command\n  let output = cmd.output_ok()?;\n\n  let stdout = String::from_utf8_lossy(output.stdout.as_slice()).into_owned();\n  log::info!(action = \"Signing\";\"Output of signing command:\\n{}\", stdout.trim());\n\n  Ok(())\n}\n\npub fn sign<P: AsRef<Path>>(path: P, params: &SignParams) -> crate::Result<()> {\n  match &params.sign_command {\n    Some(custom_command) => sign_custom(path, custom_command),\n    #[cfg(windows)]\n    None => sign_default(path, params),\n    // should not be reachable, as user should either use Windows\n    // or specify a custom sign_command but we succeed anyways\n    #[cfg(not(windows))]\n    None => Ok(()),\n  }\n}\n\npub fn try_sign<P: AsRef<Path>>(file_path: P, settings: &Settings) -> crate::Result<()> {\n  if settings.no_sign() {\n    log::warn!(\n      \"Skipping signing for {} due to --no-sign flag.\",\n      tauri_utils::display_path(file_path.as_ref())\n    );\n    return Ok(());\n  }\n  if settings.windows().can_sign() {\n    log::info!(action = \"Signing\"; \"{}\", tauri_utils::display_path(file_path.as_ref()));\n    sign(file_path, &settings.sign_params())?;\n  }\n  Ok(())\n}\n\n/// If the file is signable (is a binary file) and not signed already\n/// (will skip the verification if not on Windows since we can't verify it)\npub fn should_sign(file_path: &Path) -> crate::Result<bool> {\n  let is_binary = file_path\n    .extension()\n    .is_some_and(|ext| ext == \"exe\" || ext == \"dll\");\n  if !is_binary {\n    return Ok(false);\n  }\n\n  #[cfg(windows)]\n  {\n    let already_signed = verify(file_path)?;\n    Ok(!already_signed)\n  }\n  // Skip verification if not on Windows since we can't verify it\n  #[cfg(not(windows))]\n  {\n    Ok(true)\n  }\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle/windows/util.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  fs::create_dir_all,\n  path::{Path, PathBuf},\n};\nuse ureq::ResponseExt;\n\nuse crate::utils::http_utils::{base_ureq_agent, download};\n\npub const WEBVIEW2_BOOTSTRAPPER_URL: &str = \"https://go.microsoft.com/fwlink/p/?LinkId=2124703\";\npub const WEBVIEW2_OFFLINE_INSTALLER_X86_URL: &str =\n  \"https://go.microsoft.com/fwlink/?linkid=2099617\";\npub const WEBVIEW2_OFFLINE_INSTALLER_X64_URL: &str =\n  \"https://go.microsoft.com/fwlink/?linkid=2124701\";\npub const WEBVIEW2_URL_PREFIX: &str =\n  \"https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/\";\npub const NSIS_OUTPUT_FOLDER_NAME: &str = \"nsis\";\npub const NSIS_UPDATER_OUTPUT_FOLDER_NAME: &str = \"nsis-updater\";\npub const WIX_OUTPUT_FOLDER_NAME: &str = \"msi\";\npub const WIX_UPDATER_OUTPUT_FOLDER_NAME: &str = \"msi-updater\";\n\npub fn webview2_guid_path(url: &str) -> crate::Result<(String, String)> {\n  let agent = base_ureq_agent();\n  let response = agent.head(url).call().map_err(Box::new)?;\n  let final_url = response.get_uri().to_string();\n  let remaining_url = final_url.strip_prefix(WEBVIEW2_URL_PREFIX).ok_or_else(|| {\n    crate::Error::GenericError(format!(\n      \"WebView2 URL prefix mismatch. Expected `{WEBVIEW2_URL_PREFIX}`, found `{final_url}`.\"\n    ))\n  })?;\n  let (guid, filename) = remaining_url.split_once('/').ok_or_else(|| {\n    crate::Error::GenericError(format!(\n      \"WebView2 URL format mismatch. Expected `<GUID>/<FILENAME>`, found `{remaining_url}`.\"\n    ))\n  })?;\n  Ok((guid.into(), filename.into()))\n}\n\npub fn download_webview2_bootstrapper(base_path: &Path) -> crate::Result<PathBuf> {\n  let file_path = base_path.join(\"MicrosoftEdgeWebview2Setup.exe\");\n  if !file_path.exists() {\n    std::fs::write(&file_path, download(WEBVIEW2_BOOTSTRAPPER_URL)?)?;\n  }\n  Ok(file_path)\n}\n\npub fn download_webview2_offline_installer(base_path: &Path, arch: &str) -> crate::Result<PathBuf> {\n  let url = if arch == \"x64\" {\n    WEBVIEW2_OFFLINE_INSTALLER_X64_URL\n  } else {\n    WEBVIEW2_OFFLINE_INSTALLER_X86_URL\n  };\n  let (guid, filename) = webview2_guid_path(url)?;\n  let dir_path = base_path.join(guid);\n  let file_path = dir_path.join(filename);\n  if !file_path.exists() {\n    create_dir_all(dir_path)?;\n    std::fs::write(&file_path, download(url)?)?;\n  }\n  Ok(file_path)\n}\n\n#[cfg(target_os = \"windows\")]\npub fn os_bitness<'a>() -> Option<&'a str> {\n  use windows_sys::Win32::System::SystemInformation::{\n    GetNativeSystemInfo, PROCESSOR_ARCHITECTURE_AMD64, PROCESSOR_ARCHITECTURE_INTEL, SYSTEM_INFO,\n  };\n\n  let mut system_info: SYSTEM_INFO = unsafe { std::mem::zeroed() };\n  unsafe { GetNativeSystemInfo(&mut system_info) };\n  match unsafe { system_info.Anonymous.Anonymous.wProcessorArchitecture } {\n    PROCESSOR_ARCHITECTURE_INTEL => Some(\"x86\"),\n    PROCESSOR_ARCHITECTURE_AMD64 => Some(\"x64\"),\n    _ => None,\n  }\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/bundle.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nmod category;\nmod kmp;\n#[cfg(target_os = \"linux\")]\nmod linux;\n#[cfg(target_os = \"macos\")]\nmod macos;\nmod platform;\nmod settings;\nmod updater_bundle;\nmod windows;\n\nuse tauri_utils::{display_path, platform::Target as TargetPlatform};\n\nconst BUNDLE_VAR_TOKEN: &[u8] = b\"__TAURI_BUNDLE_TYPE_VAR_UNK\";\n/// Patch a binary with bundle type information\nfn patch_binary(binary: &PathBuf, package_type: &PackageType) -> crate::Result<()> {\n  #[cfg(target_os = \"linux\")]\n  let bundle_type = match package_type {\n    crate::PackageType::Deb => b\"__TAURI_BUNDLE_TYPE_VAR_DEB\",\n    crate::PackageType::Rpm => b\"__TAURI_BUNDLE_TYPE_VAR_RPM\",\n    crate::PackageType::AppImage => b\"__TAURI_BUNDLE_TYPE_VAR_APP\",\n    // NSIS installers can be built in linux using cargo-xwin\n    crate::PackageType::Nsis => b\"__TAURI_BUNDLE_TYPE_VAR_NSS\",\n    _ => {\n      return Err(crate::Error::InvalidPackageType(\n        package_type.short_name().to_owned(),\n        \"Linux\".to_owned(),\n      ))\n    }\n  };\n  #[cfg(target_os = \"windows\")]\n  let bundle_type = match package_type {\n    crate::PackageType::Nsis => b\"__TAURI_BUNDLE_TYPE_VAR_NSS\",\n    crate::PackageType::WindowsMsi => b\"__TAURI_BUNDLE_TYPE_VAR_MSI\",\n    _ => {\n      return Err(crate::Error::InvalidPackageType(\n        package_type.short_name().to_owned(),\n        \"Windows\".to_owned(),\n      ))\n    }\n  };\n  #[cfg(target_os = \"macos\")]\n  let bundle_type = match package_type {\n    // NSIS installers can be built in macOS using cargo-xwin\n    crate::PackageType::Nsis => b\"__TAURI_BUNDLE_TYPE_VAR_NSS\",\n    crate::PackageType::MacOsBundle | crate::PackageType::Dmg => {\n      // skip patching for macOS-native bundles\n      return Ok(());\n    }\n    _ => {\n      return Err(crate::Error::InvalidPackageType(\n        package_type.short_name().to_owned(),\n        \"macOS\".to_owned(),\n      ))\n    }\n  };\n\n  log::info!(\n    \"Patching {} with bundle type information: {}\",\n    display_path(binary),\n    package_type.short_name()\n  );\n\n  let mut file_data = std::fs::read(binary).expect(\"Could not read binary file.\");\n  let bundle_var_index =\n    kmp::index_of(BUNDLE_VAR_TOKEN, &file_data).ok_or(crate::Error::MissingBundleTypeVar)?;\n  file_data[bundle_var_index..bundle_var_index + BUNDLE_VAR_TOKEN.len()]\n    .copy_from_slice(bundle_type);\n\n  std::fs::write(binary, &file_data).map_err(|e| crate::Error::BinaryWriteError(e.to_string()))?;\n\n  Ok(())\n}\n\npub use self::{\n  category::AppCategory,\n  settings::{\n    AppImageSettings, BundleBinary, BundleSettings, CustomSignCommandSettings, DebianSettings,\n    DmgSettings, Entitlements, IosSettings, MacOsSettings, PackageSettings, PackageType, PlistKind,\n    Position, RpmSettings, Settings, SettingsBuilder, Size, UpdaterSettings,\n  },\n};\npub use settings::{NsisSettings, WindowsSettings, WixLanguage, WixLanguageConfig, WixSettings};\n\nuse std::{\n  fmt::Write,\n  io::{Seek, SeekFrom},\n  path::PathBuf,\n};\n\n/// Generated bundle metadata.\n#[derive(Debug)]\npub struct Bundle {\n  /// The package type.\n  pub package_type: PackageType,\n  /// All paths for this package.\n  pub bundle_paths: Vec<PathBuf>,\n}\n\n/// Bundles the project.\n/// Returns the list of paths where the bundles can be found.\npub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {\n  let mut package_types = settings.package_types()?;\n  if package_types.is_empty() {\n    return Ok(Vec::new());\n  }\n\n  package_types.sort_by_key(|a| a.priority());\n\n  let target_os = settings.target_platform();\n\n  if *target_os != TargetPlatform::current() {\n    log::warn!(\"Cross-platform compilation is experimental and does not support all features. Please use a matching host system for full compatibility.\");\n  }\n\n  // Sign windows binaries before the bundling step in case neither wix and nsis bundles are enabled\n  sign_binaries_if_needed(settings, target_os)?;\n\n  let main_binary = settings\n    .binaries()\n    .iter()\n    .find(|b| b.main())\n    .expect(\"Main binary missing in settings\");\n  let main_binary_path = settings.binary_path(main_binary);\n\n  // We make a copy of the unsigned main_binary so that we can restore it after each package_type step.\n  // This allows us to patch the binary correctly and avoids two issues:\n  //  - modifying a signed binary without updating its PE checksum can break signature verification\n  //    - codesigning tools should handle calculating+updating this, we just need to ensure\n  //      (re)signing is performed after every `patch_binary()` operation\n  //  - signing an already-signed binary can result in multiple signatures, causing verification errors\n  // TODO: change this to work on a copy while preserving the main binary unchanged\n  let mut main_binary_copy = tempfile::tempfile()?;\n  let mut main_binary_orignal = std::fs::File::open(&main_binary_path)?;\n  std::io::copy(&mut main_binary_orignal, &mut main_binary_copy)?;\n\n  let mut bundles = Vec::<Bundle>::new();\n  for package_type in &package_types {\n    // bundle was already built! e.g. DMG already built .app\n    if bundles.iter().any(|b| b.package_type == *package_type) {\n      continue;\n    }\n\n    if let Err(e) = patch_binary(&main_binary_path, package_type) {\n      log::warn!(\"Failed to add bundler type to the binary: {e}. Updater plugin may not be able to update this package. This shouldn't normally happen, please report it to https://github.com/tauri-apps/tauri/issues\");\n    }\n\n    // sign main binary for every package type after patch\n    if matches!(target_os, TargetPlatform::Windows) && settings.windows().can_sign() {\n      windows::sign::try_sign(&main_binary_path, settings)?;\n    }\n\n    let bundle_paths = match package_type {\n      #[cfg(target_os = \"macos\")]\n      PackageType::MacOsBundle => macos::app::bundle_project(settings)?,\n      #[cfg(target_os = \"macos\")]\n      PackageType::IosBundle => macos::ios::bundle_project(settings)?,\n      // dmg is dependent of MacOsBundle, we send our bundles to prevent rebuilding\n      #[cfg(target_os = \"macos\")]\n      PackageType::Dmg => {\n        let bundled = macos::dmg::bundle_project(settings, &bundles)?;\n        if !bundled.app.is_empty() {\n          bundles.push(Bundle {\n            package_type: PackageType::MacOsBundle,\n            bundle_paths: bundled.app,\n          });\n        }\n        bundled.dmg\n      }\n\n      #[cfg(target_os = \"windows\")]\n      PackageType::WindowsMsi => windows::msi::bundle_project(settings, false)?,\n      // don't restrict to windows as NSIS installers can be built in linux+macOS using cargo-xwin\n      PackageType::Nsis => windows::nsis::bundle_project(settings, false)?,\n\n      #[cfg(target_os = \"linux\")]\n      PackageType::Deb => linux::debian::bundle_project(settings)?,\n      #[cfg(target_os = \"linux\")]\n      PackageType::Rpm => linux::rpm::bundle_project(settings)?,\n      #[cfg(target_os = \"linux\")]\n      PackageType::AppImage => linux::appimage::bundle_project(settings)?,\n      _ => {\n        log::warn!(\"ignoring {}\", package_type.short_name());\n        continue;\n      }\n    };\n\n    bundles.push(Bundle {\n      package_type: package_type.to_owned(),\n      bundle_paths,\n    });\n\n    // Restore unsigned and unpatched binary\n    let mut modified_main_binary = std::fs::OpenOptions::new()\n      .write(true)\n      .truncate(true)\n      .open(&main_binary_path)?;\n    main_binary_copy.seek(SeekFrom::Start(0))?;\n    std::io::copy(&mut main_binary_copy, &mut modified_main_binary)?;\n  }\n\n  if let Some(updater) = settings.updater() {\n    if package_types.iter().any(|package_type| {\n      if updater.v1_compatible {\n        matches!(\n          package_type,\n          PackageType::AppImage\n            | PackageType::MacOsBundle\n            | PackageType::Nsis\n            | PackageType::WindowsMsi\n            | PackageType::Deb\n        )\n      } else {\n        matches!(package_type, PackageType::MacOsBundle)\n      }\n    }) {\n      let updater_paths = updater_bundle::bundle_project(settings, &bundles)?;\n      bundles.push(Bundle {\n        package_type: PackageType::Updater,\n        bundle_paths: updater_paths,\n      });\n    } else if updater.v1_compatible\n      || !package_types.iter().any(|package_type| {\n        // Self contained updater, no need to zip\n        matches!(\n          package_type,\n          PackageType::AppImage | PackageType::Nsis | PackageType::WindowsMsi | PackageType::Deb\n        )\n      })\n    {\n      log::warn!(\"The bundler was configured to create updater artifacts but no updater-enabled targets were built. Please enable one of these targets: app, appimage, msi, nsis\");\n    }\n    if updater.v1_compatible {\n      log::warn!(\"Legacy v1 compatible updater is deprecated and will be removed in v3, change bundle > createUpdaterArtifacts to true when your users are updated to the version with v2 updater plugin\");\n    }\n  }\n\n  #[cfg(target_os = \"macos\")]\n  {\n    // Clean up .app if only building dmg or updater\n    if !package_types.contains(&PackageType::MacOsBundle) {\n      if let Some(app_bundle_paths) = bundles\n        .iter()\n        .position(|b| b.package_type == PackageType::MacOsBundle)\n        .map(|i| bundles.remove(i))\n        .map(|b| b.bundle_paths)\n      {\n        for app_bundle_path in &app_bundle_paths {\n          use crate::error::ErrorExt;\n\n          log::info!(action = \"Cleaning\"; \"{}\", app_bundle_path.display());\n          match app_bundle_path.is_dir() {\n            true => std::fs::remove_dir_all(app_bundle_path),\n            false => std::fs::remove_file(app_bundle_path),\n          }\n          .fs_context(\n            \"failed to clean the app bundle\",\n            app_bundle_path.to_path_buf(),\n          )?;\n        }\n      }\n    }\n  }\n\n  if bundles.is_empty() {\n    return Ok(bundles);\n  }\n\n  let finished_bundles = bundles\n    .iter()\n    .filter(|b| b.package_type != PackageType::Updater)\n    .count();\n  let pluralised = if finished_bundles == 1 {\n    \"bundle\"\n  } else {\n    \"bundles\"\n  };\n\n  let mut printable_paths = String::new();\n  for bundle in &bundles {\n    for path in &bundle.bundle_paths {\n      let note = if bundle.package_type == crate::PackageType::Updater {\n        \" (updater)\"\n      } else {\n        \"\"\n      };\n      let path_display = display_path(path);\n      writeln!(printable_paths, \"        {path_display}{note}\").unwrap();\n    }\n  }\n\n  log::info!(action = \"Finished\"; \"{finished_bundles} {pluralised} at:\\n{printable_paths}\");\n\n  Ok(bundles)\n}\n\nfn sign_binaries_if_needed(settings: &Settings, target_os: &TargetPlatform) -> crate::Result<()> {\n  if matches!(target_os, TargetPlatform::Windows) {\n    if settings.windows().can_sign() {\n      if settings.no_sign() {\n        log::warn!(\"Skipping binary signing due to --no-sign flag.\");\n        return Ok(());\n      }\n\n      for bin in settings.binaries() {\n        if bin.main() {\n          // we will sign the main binary after patching per \"package type\"\n          continue;\n        }\n        let bin_path = settings.binary_path(bin);\n        windows::sign::try_sign(&bin_path, settings)?;\n      }\n\n      // Sign the sidecar binaries\n      for bin in settings.external_binaries() {\n        let path = bin?;\n        let skip = std::env::var(\"TAURI_SKIP_SIDECAR_SIGNATURE_CHECK\").is_ok_and(|v| v == \"true\");\n        if skip {\n          continue;\n        }\n\n        #[cfg(windows)]\n        if windows::sign::verify(&path)? {\n          log::info!(\n            \"sidecar at \\\"{}\\\" already signed. Skipping...\",\n            path.display()\n          );\n          continue;\n        }\n\n        windows::sign::try_sign(&path, settings)?;\n      }\n    } else {\n      #[cfg(not(target_os = \"windows\"))]\n      log::warn!(\"Signing, by default, is only supported on Windows hosts, but you can specify a custom signing command in `bundler > windows > sign_command`, for now, skipping signing the installer...\");\n    }\n  }\n\n  Ok(())\n}\n\n/// Check to see if there are icons in the settings struct\npub fn check_icons(settings: &Settings) -> crate::Result<bool> {\n  // make a peekable iterator of the icon_files\n  let mut iter = settings.icon_files().peekable();\n\n  // if iter's first value is a None then there are no Icon files in the settings struct\n  if iter.peek().is_none() {\n    Ok(false)\n  } else {\n    Ok(true)\n  }\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/error.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  fmt::Display,\n  io, num,\n  path::{self, PathBuf},\n};\nuse thiserror::Error as DeriveError;\n\n/// Errors returned by the bundler.\n#[derive(Debug, DeriveError)]\n#[non_exhaustive]\npub enum Error {\n  /// Error with context. Created by the [`Context`] trait.\n  #[error(\"{0}: {1}\")]\n  Context(String, Box<Self>),\n  /// File system error.\n  #[error(\"{context} {path}: {error}\")]\n  Fs {\n    /// Context of the error.\n    context: &'static str,\n    /// Path that was accessed.\n    path: PathBuf,\n    /// Error that occurred.\n    error: io::Error,\n  },\n  /// Child process error.\n  #[error(\"failed to run command {command}: {error}\")]\n  CommandFailed {\n    /// Command that failed.\n    command: String,\n    /// Error that occurred.\n    error: io::Error,\n  },\n  /// Error running tauri_utils API.\n  #[error(\"{0}\")]\n  Resource(#[from] tauri_utils::Error),\n  /// Bundler error.\n  ///\n  /// This variant is no longer used as this crate no longer uses anyhow.\n  // TODO(v3): remove this variant\n  #[error(\"{0:#}\")]\n  BundlerError(#[from] anyhow::Error),\n  /// I/O error.\n  #[error(\"`{0}`\")]\n  IoError(#[from] io::Error),\n  /// Image error.\n  #[error(\"`{0}`\")]\n  ImageError(#[from] image::ImageError),\n  /// Error walking directory.\n  #[error(\"`{0}`\")]\n  WalkdirError(#[from] walkdir::Error),\n  /// Strip prefix error.\n  #[error(\"`{0}`\")]\n  StripError(#[from] path::StripPrefixError),\n  /// Number parse error.\n  #[error(\"`{0}`\")]\n  ConvertError(#[from] num::TryFromIntError),\n  /// Zip error.\n  #[error(\"`{0}`\")]\n  ZipError(#[from] zip::result::ZipError),\n  /// Hex error.\n  #[error(\"`{0}`\")]\n  HexError(#[from] hex::FromHexError),\n  /// Handlebars template error.\n  #[error(\"`{0}`\")]\n  HandleBarsError(#[from] handlebars::RenderError),\n  /// JSON error.\n  #[error(\"`{0}`\")]\n  JsonError(#[from] serde_json::error::Error),\n  /// Regex error.\n  #[cfg(any(target_os = \"macos\", windows))]\n  #[error(\"`{0}`\")]\n  RegexError(#[from] regex::Error),\n  /// Failed to perform HTTP request.\n  #[error(\"`{0}`\")]\n  HttpError(#[from] Box<ureq::Error>),\n  /// Invalid glob pattern.\n  #[cfg(windows)]\n  #[error(\"{0}\")]\n  GlobPattern(#[from] glob::PatternError),\n  /// Failed to use glob pattern.\n  #[cfg(windows)]\n  #[error(\"`{0}`\")]\n  Glob(#[from] glob::GlobError),\n  /// Failed to parse the URL\n  #[error(\"`{0}`\")]\n  UrlParse(#[from] url::ParseError),\n  /// Failed to validate downloaded file hash.\n  #[error(\"hash mismatch of downloaded file\")]\n  HashError,\n  /// Failed to parse binary\n  #[error(\"Binary parse error: `{0}`\")]\n  BinaryParseError(#[from] goblin::error::Error),\n  /// Package type is not supported by target platform\n  #[error(\"Wrong package type {0} for platform {1}\")]\n  InvalidPackageType(String, String),\n  /// Bundle type symbol missing in binary\n  #[error(\"__TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date\")]\n  MissingBundleTypeVar,\n  /// Failed to write binary file changed\n  #[error(\"Failed to write binary file changes: `{0}`\")]\n  BinaryWriteError(String),\n  /// Invalid offset while patching binary file\n  #[deprecated]\n  #[error(\"Invalid offset while patching binary file\")]\n  BinaryOffsetOutOfRange,\n  /// Unsupported architecture.\n  #[error(\"Architecture Error: `{0}`\")]\n  ArchError(String),\n  /// Couldn't find icons.\n  #[error(\"Could not find Icon paths.  Please make sure they exist in the tauri config JSON file\")]\n  IconPathError,\n  /// Couldn't find background file.\n  #[error(\"Could not find background file. Make sure it exists in the tauri config JSON file and extension is png/jpg/gif\")]\n  BackgroundPathError,\n  /// Error on path util operation.\n  #[error(\"Path Error:`{0}`\")]\n  PathUtilError(String),\n  /// Error on shell script.\n  #[error(\"Shell Scripting Error:`{0}`\")]\n  ShellScriptError(String),\n  /// Generic error.\n  #[error(\"`{0}`\")]\n  GenericError(String),\n  /// No bundled project found for the updater.\n  #[error(\"Unable to find a bundled project for the updater\")]\n  UnableToFindProject,\n  /// String is not UTF-8.\n  #[error(\"string is not UTF-8\")]\n  Utf8(#[from] std::str::Utf8Error),\n  /// Windows SignTool not found.\n  #[error(\"SignTool not found\")]\n  SignToolNotFound,\n  /// Failed to open Windows registry.\n  #[error(\"failed to open registry {0}\")]\n  OpenRegistry(String),\n  /// Failed to get registry value.\n  #[error(\"failed to get {0} value on registry\")]\n  GetRegistryValue(String),\n  /// Failed to enumerate registry keys.\n  #[error(\"failed to enumerate registry keys\")]\n  FailedToEnumerateRegKeys,\n  /// Unsupported OS bitness.\n  #[error(\"unsupported OS bitness\")]\n  UnsupportedBitness,\n  /// Failed to sign application.\n  #[error(\"failed to sign app: {0}\")]\n  Sign(String),\n  /// time error.\n  #[cfg(target_os = \"macos\")]\n  #[error(\"`{0}`\")]\n  TimeError(#[from] time::error::Error),\n  /// Plist error.\n  #[cfg(target_os = \"macos\")]\n  #[error(transparent)]\n  Plist(#[from] plist::Error),\n  /// Rpm error.\n  #[cfg(target_os = \"linux\")]\n  #[error(\"{0}\")]\n  RpmError(#[from] rpm::Error),\n  /// Failed to notarize application.\n  #[cfg(target_os = \"macos\")]\n  #[error(\"failed to notarize app: {0}\")]\n  AppleNotarization(#[from] NotarizeAuthError),\n  /// Failed to codesign application.\n  #[cfg(target_os = \"macos\")]\n  #[error(\"failed codesign application: {0}\")]\n  AppleCodesign(#[from] Box<tauri_macos_sign::Error>),\n  /// Handlebars template error.\n  #[error(transparent)]\n  Template(#[from] handlebars::TemplateError),\n  /// Semver error.\n  #[error(\"`{0}`\")]\n  SemverError(#[from] semver::Error),\n}\n\n#[cfg(target_os = \"macos\")]\n#[allow(clippy::enum_variant_names)]\n#[derive(Debug, thiserror::Error)]\npub enum NotarizeAuthError {\n  #[error(\n    \"The team ID is now required for notarization with app-specific password as authentication. Please set the `APPLE_TEAM_ID` environment variable. You can find the team ID in https://developer.apple.com/account#MembershipDetailsCard.\"\n  )]\n  MissingTeamId,\n  #[error(\"could not find API key file. Please set the APPLE_API_KEY_PATH environment variables to the path to the {file_name} file\")]\n  MissingApiKey { file_name: String },\n  #[error(\"no APPLE_ID & APPLE_PASSWORD & APPLE_TEAM_ID or APPLE_API_KEY & APPLE_API_ISSUER & APPLE_API_KEY_PATH environment variables found\")]\n  MissingCredentials,\n}\n\n/// Convenient type alias of Result type.\npub type Result<T> = std::result::Result<T, Error>;\n\npub trait Context<T> {\n  // Required methods\n  fn context<C>(self, context: C) -> Result<T>\n  where\n    C: Display + Send + Sync + 'static;\n  fn with_context<C, F>(self, f: F) -> Result<T>\n  where\n    C: Display + Send + Sync + 'static,\n    F: FnOnce() -> C;\n}\n\nimpl<T> Context<T> for Result<T> {\n  fn context<C>(self, context: C) -> Result<T>\n  where\n    C: Display + Send + Sync + 'static,\n  {\n    self.map_err(|e| Error::Context(context.to_string(), Box::new(e)))\n  }\n\n  fn with_context<C, F>(self, f: F) -> Result<T>\n  where\n    C: Display + Send + Sync + 'static,\n    F: FnOnce() -> C,\n  {\n    self.map_err(|e| Error::Context(f().to_string(), Box::new(e)))\n  }\n}\n\nimpl<T> Context<T> for Option<T> {\n  fn context<C>(self, context: C) -> Result<T>\n  where\n    C: Display + Send + Sync + 'static,\n  {\n    self.ok_or_else(|| Error::GenericError(context.to_string()))\n  }\n\n  fn with_context<C, F>(self, f: F) -> Result<T>\n  where\n    C: Display + Send + Sync + 'static,\n    F: FnOnce() -> C,\n  {\n    self.ok_or_else(|| Error::GenericError(f().to_string()))\n  }\n}\n\npub trait ErrorExt<T> {\n  fn fs_context(self, context: &'static str, path: impl Into<PathBuf>) -> Result<T>;\n}\n\nimpl<T> ErrorExt<T> for std::result::Result<T, std::io::Error> {\n  fn fs_context(self, context: &'static str, path: impl Into<PathBuf>) -> Result<T> {\n    self.map_err(|error| Error::Fs {\n      context,\n      path: path.into(),\n      error,\n    })\n  }\n}\n\n#[allow(unused)]\nmacro_rules! bail {\n   ($msg:literal $(,)?) => {\n      return Err(crate::Error::GenericError($msg.into()))\n   };\n    ($err:expr $(,)?) => {\n       return Err(crate::Error::GenericError($err))\n    };\n   ($fmt:expr, $($arg:tt)*) => {\n     return Err(crate::Error::GenericError(format!($fmt, $($arg)*)))\n   };\n}\n\n#[allow(unused)]\npub(crate) use bail;\n"
  },
  {
    "path": "crates/tauri-bundler/src/lib.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The Tauri bundler is a tool that generates installers or app bundles for executables.\n//! It supports auto updating through [tauri](https://docs.rs/tauri).\n//!\n//! # Platform support\n//! - macOS\n//!   - DMG and App bundles\n//! - Linux\n//!   - Appimage, Debian and RPM packages\n//! - Windows\n//!   - MSI using WiX\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n#![warn(missing_docs, rust_2018_idioms)]\n\n/// The bundle API.\npub mod bundle;\nmod error;\nmod utils;\npub use bundle::*;\npub use error::{Error, Result};\n"
  },
  {
    "path": "crates/tauri-bundler/src/utils/fs_utils.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  fs::{self, File},\n  io::{self, BufWriter},\n  path::Path,\n};\n\n/// Creates a new file at the given path, creating any parent directories as\n/// needed.\npub fn create_file(path: &Path) -> crate::Result<BufWriter<File>> {\n  if let Some(parent) = path.parent() {\n    fs::create_dir_all(parent)?;\n  }\n  let file = File::create(path)?;\n  Ok(BufWriter::new(file))\n}\n\n/// Creates the given directory path,\n/// erasing it first if specified.\n#[allow(dead_code)]\npub fn create_dir(path: &Path, erase: bool) -> crate::Result<()> {\n  if erase && path.exists() {\n    remove_dir_all(path)?;\n  }\n  Ok(fs::create_dir(path)?)\n}\n\n/// Creates all of the directories of the specified path,\n/// erasing it first if specified.\n#[allow(dead_code)]\npub fn create_dir_all(path: &Path, erase: bool) -> crate::Result<()> {\n  if erase && path.exists() {\n    remove_dir_all(path)?;\n  }\n  Ok(fs::create_dir_all(path)?)\n}\n\n/// Removes the directory and its contents if it exists.\n#[allow(dead_code)]\npub fn remove_dir_all(path: &Path) -> crate::Result<()> {\n  if path.exists() {\n    Ok(fs::remove_dir_all(path)?)\n  } else {\n    Ok(())\n  }\n}\n\n/// Makes a symbolic link to a directory.\n#[cfg(unix)]\n#[allow(dead_code)]\nfn symlink_dir(src: &Path, dst: &Path) -> io::Result<()> {\n  std::os::unix::fs::symlink(src, dst)\n}\n\n/// Makes a symbolic link to a directory.\n#[cfg(windows)]\nfn symlink_dir(src: &Path, dst: &Path) -> io::Result<()> {\n  std::os::windows::fs::symlink_dir(src, dst)\n}\n\n/// Makes a symbolic link to a file.\n#[cfg(unix)]\n#[allow(dead_code)]\nfn symlink_file(src: &Path, dst: &Path) -> io::Result<()> {\n  std::os::unix::fs::symlink(src, dst)\n}\n\n/// Makes a symbolic link to a file.\n#[cfg(windows)]\nfn symlink_file(src: &Path, dst: &Path) -> io::Result<()> {\n  std::os::windows::fs::symlink_file(src, dst)\n}\n\n/// Copies a regular file from one path to another, creating any parent\n/// directories of the destination path as necessary. Fails if the source path\n/// is a directory or doesn't exist.\npub fn copy_file(from: &Path, to: &Path) -> crate::Result<()> {\n  if !from.exists() {\n    return Err(crate::Error::GenericError(format!(\n      \"{from:?} does not exist\"\n    )));\n  }\n  if !from.is_file() {\n    return Err(crate::Error::GenericError(format!(\n      \"{from:?} is not a file\"\n    )));\n  }\n  let dest_dir = to.parent().expect(\"No data in parent\");\n  fs::create_dir_all(dest_dir)?;\n  fs::copy(from, to)?;\n  Ok(())\n}\n\n/// Recursively copies a directory file from one path to another, creating any\n/// parent directories of the destination path as necessary.  Fails if the\n/// source path is not a directory or doesn't exist, or if the destination path\n/// already exists.\n#[allow(dead_code)]\npub fn copy_dir(from: &Path, to: &Path) -> crate::Result<()> {\n  if !from.exists() {\n    return Err(crate::Error::GenericError(format!(\n      \"{from:?} does not exist\"\n    )));\n  }\n  if !from.is_dir() {\n    return Err(crate::Error::GenericError(format!(\n      \"{from:?} is not a Directory\"\n    )));\n  }\n  let parent = to.parent().expect(\"No data in parent\");\n  fs::create_dir_all(parent)?;\n  for entry in walkdir::WalkDir::new(from) {\n    let entry = entry?;\n    debug_assert!(entry.path().starts_with(from));\n    let rel_path = entry.path().strip_prefix(from)?;\n    let dest_path = to.join(rel_path);\n    if entry.file_type().is_symlink() {\n      let target = fs::read_link(entry.path())?;\n      if entry.path().is_dir() {\n        symlink_dir(&target, &dest_path)?;\n      } else {\n        symlink_file(&target, &dest_path)?;\n      }\n    } else if entry.file_type().is_dir() {\n      fs::create_dir_all(dest_path)?;\n    } else {\n      fs::copy(entry.path(), dest_path)?;\n    }\n  }\n  Ok(())\n}\n\n/// Copies user-defined files specified in the configuration file to the package.\n///\n/// The configuration object maps the path in the package to the path of the file on the filesystem,\n/// relative to the tauri.conf.json file.\n///\n/// Expects a HashMap of PathBuf entries, representing destination and source paths,\n/// and also a path of a directory. The files will be stored with respect to this directory.\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"netbsd\",\n  target_os = \"openbsd\"\n))]\npub fn copy_custom_files(\n  files_map: &std::collections::HashMap<std::path::PathBuf, std::path::PathBuf>,\n  data_dir: &Path,\n) -> crate::Result<()> {\n  for (pkg_path, path) in files_map.iter() {\n    let pkg_path = if pkg_path.is_absolute() {\n      pkg_path.strip_prefix(\"/\").unwrap()\n    } else {\n      pkg_path\n    };\n    if path.is_file() {\n      copy_file(path, &data_dir.join(pkg_path))?;\n    } else {\n      copy_dir(path, &data_dir.join(pkg_path))?;\n    }\n  }\n  Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n  use super::create_file;\n  use std::io::Write;\n\n  #[test]\n  fn create_file_with_parent_dirs() {\n    let tmp = tempfile::tempdir().expect(\"Unable to create temp dir\");\n    assert!(!tmp.path().join(\"parent\").exists());\n    {\n      let mut file =\n        create_file(&tmp.path().join(\"parent/file.txt\")).expect(\"Failed to create file\");\n      writeln!(file, \"Hello, world!\").expect(\"unable to write file\");\n    }\n    assert!(tmp.path().join(\"parent\").is_dir());\n    assert!(tmp.path().join(\"parent/file.txt\").is_file());\n  }\n\n  #[cfg(not(windows))]\n  #[test]\n  fn copy_dir_with_symlinks() {\n    use std::path::PathBuf;\n\n    // Create a directory structure that looks like this:\n    //   ${TMP}/orig/\n    //       sub/\n    //           file.txt\n    //       link -> sub/file.txt\n    let tmp = tempfile::tempdir().expect(\"unable to create tempdir\");\n    {\n      let mut file =\n        create_file(&tmp.path().join(\"orig/sub/file.txt\")).expect(\"Unable to create file\");\n      writeln!(file, \"Hello, world!\").expect(\"Unable to write to file\");\n    }\n    super::symlink_file(\n      &PathBuf::from(\"sub/file.txt\"),\n      &tmp.path().join(\"orig/link\"),\n    )\n    .expect(\"Failed to create symlink\");\n    assert_eq!(\n      std::fs::read(tmp.path().join(\"orig/link\"))\n        .expect(\"Failed to read file\")\n        .as_slice(),\n      b\"Hello, world!\\n\"\n    );\n    // Copy ${TMP}/orig to ${TMP}/parent/copy, and make sure that the\n    // directory structure, file, and symlink got copied correctly.\n    super::copy_dir(&tmp.path().join(\"orig\"), &tmp.path().join(\"parent/copy\"))\n      .expect(\"Failed to copy dir\");\n    assert!(tmp.path().join(\"parent/copy\").is_dir());\n    assert!(tmp.path().join(\"parent/copy/sub\").is_dir());\n    assert!(tmp.path().join(\"parent/copy/sub/file.txt\").is_file());\n    assert_eq!(\n      std::fs::read(tmp.path().join(\"parent/copy/sub/file.txt\"))\n        .expect(\"Failed to read file\")\n        .as_slice(),\n      b\"Hello, world!\\n\"\n    );\n    assert!(tmp.path().join(\"parent/copy/link\").exists());\n    assert_eq!(\n      std::fs::read_link(tmp.path().join(\"parent/copy/link\")).expect(\"Failed to read from symlink\"),\n      PathBuf::from(\"sub/file.txt\")\n    );\n    assert_eq!(\n      std::fs::read(tmp.path().join(\"parent/copy/link\"))\n        .expect(\"Failed to read from file\")\n        .as_slice(),\n      b\"Hello, world!\\n\"\n    );\n  }\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/utils/http_utils.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  fs::{create_dir_all, File},\n  io::{Cursor, Read, Write},\n  path::Path,\n};\n\nuse regex::Regex;\nuse sha2::Digest;\nuse url::Url;\nuse zip::ZipArchive;\n\nconst BUNDLER_USER_AGENT: &str = concat!(env!(\"CARGO_PKG_NAME\"), \"/\", env!(\"CARGO_PKG_VERSION\"),);\n\nfn generate_github_mirror_url_from_template(github_url: &str) -> Option<String> {\n  std::env::var(\"TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE\")\n    .ok()\n    .and_then(|template| {\n      let re =\n        Regex::new(r\"https://github.com/([^/]+)/([^/]+)/releases/download/([^/]+)/(.*)\").unwrap();\n      re.captures(github_url).map(|caps| {\n        template\n          .replace(\"<owner>\", &caps[1])\n          .replace(\"<repo>\", &caps[2])\n          .replace(\"<version>\", &caps[3])\n          .replace(\"<asset>\", &caps[4])\n      })\n    })\n}\n\nfn generate_github_mirror_url_from_base(github_url: &str) -> Option<String> {\n  std::env::var(\"TAURI_BUNDLER_TOOLS_GITHUB_MIRROR\")\n    .ok()\n    .and_then(|cdn| Url::parse(&cdn).ok())\n    .map(|mut cdn| {\n      cdn.set_path(github_url);\n      cdn.to_string()\n    })\n}\n\nfn generate_github_alternative_url(url: &str) -> Option<(ureq::Agent, String)> {\n  if !url.starts_with(\"https://github.com/\") {\n    return None;\n  }\n\n  generate_github_mirror_url_from_template(url)\n    .or_else(|| generate_github_mirror_url_from_base(url))\n    .map(|alt_url| {\n      (\n        ureq::Agent::config_builder()\n          .user_agent(BUNDLER_USER_AGENT)\n          .build()\n          .into(),\n        alt_url,\n      )\n    })\n}\n\nfn create_agent_and_url(url: &str) -> (ureq::Agent, String) {\n  generate_github_alternative_url(url).unwrap_or((base_ureq_agent(), url.to_owned()))\n}\n\npub(crate) fn base_ureq_agent() -> ureq::Agent {\n  #[allow(unused_mut)]\n  let mut config_builder = ureq::Agent::config_builder()\n    .user_agent(BUNDLER_USER_AGENT)\n    .proxy(ureq::Proxy::try_from_env());\n\n  #[cfg(feature = \"platform-certs\")]\n  {\n    config_builder = config_builder.tls_config(\n      ureq::tls::TlsConfig::builder()\n        .root_certs(ureq::tls::RootCerts::PlatformVerifier)\n        .build(),\n    );\n  }\n\n  config_builder.build().into()\n}\n\n#[allow(dead_code)]\npub fn download(url: &str) -> crate::Result<Vec<u8>> {\n  let (agent, final_url) = create_agent_and_url(url);\n\n  log::info!(action = \"Downloading\"; \"{}\", final_url);\n\n  let response = agent.get(&final_url).call().map_err(Box::new)?;\n  let mut bytes = Vec::new();\n  response.into_body().into_reader().read_to_end(&mut bytes)?;\n  Ok(bytes)\n}\n\n#[allow(dead_code)]\n#[derive(Clone, Copy)]\npub enum HashAlgorithm {\n  #[cfg(target_os = \"windows\")]\n  Sha256,\n  Sha1,\n}\n\n/// Function used to download a file and checks SHA256 to verify the download.\n#[allow(dead_code)]\npub fn download_and_verify(\n  url: &str,\n  hash: &str,\n  hash_algorithm: HashAlgorithm,\n) -> crate::Result<Vec<u8>> {\n  let data = download(url)?;\n  log::info!(\"validating hash\");\n  verify_hash(&data, hash, hash_algorithm)?;\n  Ok(data)\n}\n\n#[allow(dead_code)]\npub fn verify_hash(data: &[u8], hash: &str, hash_algorithm: HashAlgorithm) -> crate::Result<()> {\n  match hash_algorithm {\n    #[cfg(target_os = \"windows\")]\n    HashAlgorithm::Sha256 => {\n      let hasher = sha2::Sha256::new();\n      verify_data_with_hasher(data, hash, hasher)\n    }\n    HashAlgorithm::Sha1 => {\n      let hasher = sha1::Sha1::new();\n      verify_data_with_hasher(data, hash, hasher)\n    }\n  }\n}\n\nfn verify_data_with_hasher(data: &[u8], hash: &str, mut hasher: impl Digest) -> crate::Result<()> {\n  hasher.update(data);\n\n  let url_hash = hasher.finalize().to_vec();\n  let expected_hash = hex::decode(hash)?;\n  if expected_hash == url_hash {\n    Ok(())\n  } else {\n    Err(crate::Error::HashError)\n  }\n}\n\n#[allow(dead_code)]\npub fn verify_file_hash<P: AsRef<Path>>(\n  path: P,\n  hash: &str,\n  hash_algorithm: HashAlgorithm,\n) -> crate::Result<()> {\n  let data = std::fs::read(path)?;\n  verify_hash(&data, hash, hash_algorithm)\n}\n\n/// Extracts the zips from memory into a usable path.\n#[allow(dead_code)]\npub fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> {\n  let cursor = Cursor::new(data);\n\n  let mut zipa = ZipArchive::new(cursor)?;\n\n  for i in 0..zipa.len() {\n    let mut file = zipa.by_index(i)?;\n\n    if let Some(name) = file.enclosed_name() {\n      let dest_path = path.join(name);\n      if file.is_dir() {\n        create_dir_all(&dest_path)?;\n        continue;\n      }\n\n      let parent = dest_path.parent().expect(\"Failed to get parent\");\n\n      if !parent.exists() {\n        create_dir_all(parent)?;\n      }\n\n      let mut buff: Vec<u8> = Vec::new();\n      file.read_to_end(&mut buff)?;\n      let mut fileout = File::create(dest_path).expect(\"Failed to open file\");\n\n      fileout.write_all(&buff)?;\n    }\n  }\n\n  Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n  use super::generate_github_mirror_url_from_template;\n  use std::env;\n\n  const GITHUB_ASSET_URL: &str =\n    \"https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip\";\n  const NON_GITHUB_ASSET_URL: &str = \"https://someotherwebsite.com/somefile.zip\";\n\n  #[test]\n  fn test_generate_mirror_url_no_env_var() {\n    env::remove_var(\"TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE\");\n\n    assert!(generate_github_mirror_url_from_template(GITHUB_ASSET_URL).is_none());\n  }\n\n  #[test]\n  fn test_generate_mirror_url_non_github_url() {\n    env::set_var(\n      \"TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE\",\n      \"https://mirror.example.com/<owner>/<repo>/releases/download/<version>/<asset>\",\n    );\n\n    assert!(generate_github_mirror_url_from_template(NON_GITHUB_ASSET_URL).is_none());\n  }\n\n  struct TestCase {\n    template: &'static str,\n    expected_url: &'static str,\n  }\n\n  #[test]\n  fn test_generate_mirror_url_correctly() {\n    let test_cases = vec![\n            TestCase {\n                template: \"https://mirror.example.com/<owner>/<repo>/releases/download/<version>/<asset>\",\n                expected_url: \"https://mirror.example.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip\",\n            },\n            TestCase {\n                template: \"https://mirror.example.com/<asset>\",\n                expected_url: \"https://mirror.example.com/wix311-binaries.zip\",\n            },\n        ];\n\n    for case in test_cases {\n      env::set_var(\"TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE\", case.template);\n      assert_eq!(\n        generate_github_mirror_url_from_template(GITHUB_ASSET_URL),\n        Some(case.expected_url.to_string())\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-bundler/src/utils/mod.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  ffi::OsStr,\n  io::{BufRead, BufReader},\n  path::Path,\n  process::{Command, ExitStatus, Output, Stdio},\n  sync::{Arc, Mutex},\n};\n\npub mod fs_utils;\npub mod http_utils;\n\n/// Returns true if the path has a filename indicating that it is a high-density\n/// \"retina\" icon.  Specifically, returns true the file stem ends with\n/// \"@2x\" (a convention specified by the [Apple developer docs](\n/// <https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html>)).\n#[allow(dead_code)]\npub fn is_retina(path: &Path) -> bool {\n  path\n    .file_stem()\n    .and_then(OsStr::to_str)\n    .map(|stem| stem.ends_with(\"@2x\"))\n    .unwrap_or(false)\n}\n\npub trait CommandExt {\n  // The `pipe` function sets the stdout and stderr to properly\n  // show the command output in the Node.js wrapper.\n  fn piped(&mut self) -> std::io::Result<ExitStatus>;\n  fn output_ok(&mut self) -> crate::Result<Output>;\n}\n\nimpl CommandExt for Command {\n  fn piped(&mut self) -> std::io::Result<ExitStatus> {\n    self.stdin(os_pipe::dup_stdin()?);\n    self.stdout(os_pipe::dup_stdout()?);\n    self.stderr(os_pipe::dup_stderr()?);\n    let program = self.get_program().to_string_lossy().into_owned();\n    log::debug!(action = \"Running\"; \"Command `{} {}`\", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!(\"{acc} {arg}\")));\n\n    self.status()\n  }\n\n  fn output_ok(&mut self) -> crate::Result<Output> {\n    let program = self.get_program().to_string_lossy().into_owned();\n    log::debug!(action = \"Running\"; \"Command `{} {}`\", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!(\"{acc} {arg}\")));\n\n    self.stdout(Stdio::piped());\n    self.stderr(Stdio::piped());\n\n    let mut child = self.spawn()?;\n\n    let mut stdout = child.stdout.take().map(BufReader::new).unwrap();\n    let stdout_lines = Arc::new(Mutex::new(Vec::new()));\n    let stdout_lines_ = stdout_lines.clone();\n    std::thread::spawn(move || {\n      let mut line = String::new();\n      let mut lines = stdout_lines_.lock().unwrap();\n      loop {\n        line.clear();\n        match stdout.read_line(&mut line) {\n          Ok(0) => break,\n          Ok(_) => {\n            log::debug!(action = \"stdout\"; \"{}\", line.trim_end());\n            lines.extend(line.as_bytes().to_vec());\n          }\n          Err(_) => (),\n        }\n      }\n    });\n\n    let mut stderr = child.stderr.take().map(BufReader::new).unwrap();\n    let stderr_lines = Arc::new(Mutex::new(Vec::new()));\n    let stderr_lines_ = stderr_lines.clone();\n    std::thread::spawn(move || {\n      let mut line = String::new();\n      let mut lines = stderr_lines_.lock().unwrap();\n      loop {\n        line.clear();\n        match stderr.read_line(&mut line) {\n          Ok(0) => break,\n          Ok(_) => {\n            log::debug!(action = \"stderr\"; \"{}\", line.trim_end());\n            lines.extend(line.as_bytes().to_vec());\n          }\n          Err(_) => (),\n        }\n      }\n    });\n\n    let status = child.wait()?;\n    let output = Output {\n      status,\n      stdout: std::mem::take(&mut *stdout_lines.lock().unwrap()),\n      stderr: std::mem::take(&mut *stderr_lines.lock().unwrap()),\n    };\n\n    if output.status.success() {\n      Ok(output)\n    } else {\n      Err(crate::Error::GenericError(format!(\n        \"failed to run {program}\"\n      )))\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use std::path::{Path, PathBuf};\n\n  use tauri_utils::resources::resource_relpath;\n\n  use super::is_retina;\n\n  #[test]\n  fn retina_icon_paths() {\n    assert!(!is_retina(Path::new(\"data/icons/512x512.png\")));\n    assert!(is_retina(Path::new(\"data/icons/512x512@2x.png\")));\n  }\n\n  #[test]\n  fn resource_relative_paths() {\n    assert_eq!(\n      resource_relpath(Path::new(\"./data/images/button.png\")),\n      PathBuf::from(\"data/images/button.png\")\n    );\n    assert_eq!(\n      resource_relpath(Path::new(\"../../images/wheel.png\")),\n      PathBuf::from(\"_up_/_up_/images/wheel.png\")\n    );\n    assert_eq!(\n      resource_relpath(Path::new(\"/home/ferris/crab.png\")),\n      PathBuf::from(\"_root_/home/ferris/crab.png\")\n    );\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.10.1]\n\n### Bug Fixes\n\n- [`35c35f27a`](https://www.github.com/tauri-apps/tauri/commit/35c35f27aedc430b602ec74059b271128c15ad36) ([#14931](https://www.github.com/tauri-apps/tauri/pull/14931) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Support comma-separated list of Cargo features on all commands.\n- [`0d1cb83ba`](https://www.github.com/tauri-apps/tauri/commit/0d1cb83bab2aa482c7d73116893fd7ff6aa56283) ([#14932](https://www.github.com/tauri-apps/tauri/pull/14932) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix missing Cargo args when running mobile dev and build commands.\n- [`33754ae5e`](https://www.github.com/tauri-apps/tauri/commit/33754ae5e3740d022483b6164511c5c001a3c24b) ([#15022](https://www.github.com/tauri-apps/tauri/pull/15022) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix updater signing private keys generated using `tauri signer generate` with empty password can't be used (The keys generated during tauri were broken between v2.9.3 and v2.10.0, you'll need to regenerate them)\n\n### What's Changed\n\n- [`7be58a1c6`](https://www.github.com/tauri-apps/tauri/commit/7be58a1c643a7ed6d0f484a7e1134022618eb2b2) ([#14894](https://www.github.com/tauri-apps/tauri/pull/14894) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Log patching bundle type information again\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.3`\n- Upgraded to `tauri-bundler@2.8.1`\n\n## \\[2.10.0]\n\n### Enhancements\n\n- [`f82594410`](https://www.github.com/tauri-apps/tauri/commit/f82594410cd57d6f794f58d4afea0ed335aa796f) ([#13253](https://www.github.com/tauri-apps/tauri/pull/13253) by [@Armaldio](https://www.github.com/tauri-apps/tauri/../../Armaldio)) Allow electron to run the CLI directly\n- [`2d28e3143`](https://www.github.com/tauri-apps/tauri/commit/2d28e3143ee3d97d7570ea03877aa00a0d6e47d0) ([#14632](https://www.github.com/tauri-apps/tauri/pull/14632) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Small code refactors for improved code readability. No user facing changes.\n- [`a2abe2e6b`](https://www.github.com/tauri-apps/tauri/commit/a2abe2e6bcb9e1eed8484240dfdb76a5bc28ae58) ([#14607](https://www.github.com/tauri-apps/tauri/pull/14607) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Simplified internal representation of `features: Option<Vec<String>>` with `Vec<String>`, no user facing changes\n- [`84b04c4a8`](https://www.github.com/tauri-apps/tauri/commit/84b04c4a8d3310b7a7091d10e36244bf94996e51) ([#14759](https://www.github.com/tauri-apps/tauri/pull/14759) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added new environment variables for `tauri signer sign` command, to align with existing environment variables used in `tauri build`, `tauri bundle` and `tauri signer generate`\n\n  - `TAURI_SIGNING_PRIVATE_KEY`\n  - `TAURI_SIGNING_PRIVATE_KEY_PATH`\n  - `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`\n\n  The old environment variables are deprecated and will be removed in a future release.\n\n  - `TAURI_PRIVATE_KEY`\n  - `TAURI_PRIVATE_KEY_PATH`\n  - `TAURI_PRIVATE_KEY_PASSWORD`\n\n### Bug Fixes\n\n- [`62aa13a12`](https://www.github.com/tauri-apps/tauri/commit/62aa13a124ef46bb5ce9887a2a574dd35ef86d4f) ([#14629](https://www.github.com/tauri-apps/tauri/pull/14629) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `android build`'s `--aab` and `--apk` flags requiring a value to be provided.\n- [`eccff9758`](https://www.github.com/tauri-apps/tauri/commit/eccff97588232055bd0cafd83e6ee03d11a501fb) ([#14779](https://www.github.com/tauri-apps/tauri/pull/14779) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix empty associated-domains entitlements when domains are not configured for deep links.\n- [`ea31b07f1`](https://www.github.com/tauri-apps/tauri/commit/ea31b07f19e0aa467ed0f921f60575cfe09809c8) ([#14789](https://www.github.com/tauri-apps/tauri/pull/14789) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fixed the command description for `tauri inspect`\n- [`7fca58230`](https://www.github.com/tauri-apps/tauri/commit/7fca58230f97c3e6834134419514a0c7dbbe784b) ([#14830](https://www.github.com/tauri-apps/tauri/pull/14830) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Updated `nsis_tauri_utils` to 0.5.3:\n\n  - Use an alternative method `CreateProcessWithTokenW` to run programs as user, this fixed a problem that the program launched with the previous method can't query its own handle\n- [`53611c4d7`](https://www.github.com/tauri-apps/tauri/commit/53611c4d7bdaf89b9a5d7c46a9c4bf4e34216148) ([#14747](https://www.github.com/tauri-apps/tauri/pull/14747) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Only watch dependent workspace members when running `tauri dev` instead of watching on all members\n- [`1b0e335d3`](https://www.github.com/tauri-apps/tauri/commit/1b0e335d3f3445948d6590f7e074275d97cd9859) ([#14713](https://www.github.com/tauri-apps/tauri/pull/14713) by [@wasuaje](https://www.github.com/tauri-apps/tauri/../../wasuaje)) `tauri signer sign` doesn't work for files without an extension\n\n### What's Changed\n\n- [`e3fdcb500`](https://www.github.com/tauri-apps/tauri/commit/e3fdcb5002b362b46cde2a1971e4e7f2a1161208) ([#14836](https://www.github.com/tauri-apps/tauri/pull/14836) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Continued refactors of tauri-cli, fix too weak atomics.\n- [`0575dd287`](https://www.github.com/tauri-apps/tauri/commit/0575dd287e021b61d2aedf64d62ae84a2c925fb4) ([#14521](https://www.github.com/tauri-apps/tauri/pull/14521) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Change the way bundle type information is added to binary files. Instead of looking up the value of a variable we simply look for the default value.\n- [`7f7d9aac2`](https://www.github.com/tauri-apps/tauri/commit/7f7d9aac214e22d9492490543f7a9bcae0a6659e) ([#14668](https://www.github.com/tauri-apps/tauri/pull/14668) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Refactored internal use of static on config and directory resolvings, no user facing changes, please report any regressions if you encounter any\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.2`\n- Upgraded to `tauri-macos-sign@2.3.3`\n- Upgraded to `tauri-bundler@2.8.0`\n\n## \\[2.9.6]\n\n### What's Changed\n\n- [`7b1b3514d`](https://www.github.com/tauri-apps/tauri/commit/7b1b3514df771e6e9859b9f54fa4df332433948e) ([#14621](https://www.github.com/tauri-apps/tauri/pull/14621) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Errors like `Error Failed to parse version 2 for for NPM package tauri` when there was no `package-lock.json` file present yet or when using ones like `link:./tauri` are now only logged in `--verbose` mode.\n\n### Dependencies\n\n- Upgraded to `tauri-macos-sign@2.3.2`\n- Upgraded to `tauri-bundler@2.7.5`\n\n## \\[2.9.5]\n\n### Bug Fixes\n\n- [`f022b2d1a`](https://www.github.com/tauri-apps/tauri/commit/f022b2d1ae57612e39c75782926f2f341d9034a8) ([#14582](https://www.github.com/tauri-apps/tauri/pull/14582) by [@hrzlgnm](https://www.github.com/tauri-apps/tauri/../../hrzlgnm)) Fixed an issue that caused the cli to error out with missing private key, in case the option `--no-sign` was requested and the `tauri.config` has signing key set and the plugin `tauri-plugin-updater` is used.\n- [`f855caf8a`](https://www.github.com/tauri-apps/tauri/commit/f855caf8a3830aa5dd6d0b039312866a5d9c3606) ([#14481](https://www.github.com/tauri-apps/tauri/pull/14481) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fixed the mismatched tauri package versions check didn't work for pnpm\n- [`79a7d9ec0`](https://www.github.com/tauri-apps/tauri/commit/79a7d9ec01be1a371b8e923848140fea75e9caed) ([#14468](https://www.github.com/tauri-apps/tauri/pull/14468) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the cli to print errors like `Error Failed to parse version 2 for crate tauri` when there was no `Cargo.lock` file present yet. This will still be logged in `--verbose` mode.\n\n### Performance Improvements\n\n- [`ce98d87ce`](https://www.github.com/tauri-apps/tauri/commit/ce98d87ce0aaa907285852eb80691197424e03c3) ([#14474](https://www.github.com/tauri-apps/tauri/pull/14474) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) refactor: remove needless collect. No user facing changes.\n- [`ee3cc4a91`](https://www.github.com/tauri-apps/tauri/commit/ee3cc4a91bf1315ecaefe90f423ffd55ef6c40db) ([#14475](https://www.github.com/tauri-apps/tauri/pull/14475) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) perf: remove needless clones in various files for improved performance. No user facing changes.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.7.4`\n- Upgraded to `tauri-macos-sign@2.3.1`\n- Upgraded to `tauri-utils@2.8.1`\n\n## \\[2.9.4]\n\n### Bug Fixes\n\n- [`b586ecf1f`](https://www.github.com/tauri-apps/tauri/commit/b586ecf1f4b3b087f9aa6c4668c2c18b1b7925f4) ([#14416](https://www.github.com/tauri-apps/tauri/pull/14416) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Premultiply Alpha before Resizing which gets rid of the gray fringe around the icons for svg images.\n\n## \\[2.9.3]\n\n### Enhancements\n\n- [`22edc65aa`](https://www.github.com/tauri-apps/tauri/commit/22edc65aad0b3e45515008e8e0866112da70c8a1) ([#14408](https://www.github.com/tauri-apps/tauri/pull/14408) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Set user-agent in bundler and cli http requests when fetching build tools.\n- [`779612ac8`](https://www.github.com/tauri-apps/tauri/commit/779612ac8425a787626da4cefdb9eaf7d63bea18) ([#14379](https://www.github.com/tauri-apps/tauri/pull/14379) by [@moubctez](https://www.github.com/tauri-apps/tauri/../../moubctez)) Properly read the `required-features` field of binaries in Cargo.toml to prevent bundling issues when the features weren't enabled.\n\n### Bug Fixes\n\n- [`fd8c30b4f`](https://www.github.com/tauri-apps/tauri/commit/fd8c30b4f1bca8dd7165c5c0ebe7fbfd17662153) ([#14353](https://www.github.com/tauri-apps/tauri/pull/14353) by [@ChaseKnowlden](https://www.github.com/tauri-apps/tauri/../../ChaseKnowlden)) Premultiply Alpha before Resizing which gets rid of the gray fringe around the icons.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.7.3`\n\n## \\[2.9.2]\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.7.2`\n\n## \\[2.9.1]\n\n### Dependencies\n\n- Upgraded to `tauri-macos-sign@2.3.0`\n- Upgraded to `tauri-bundler@2.7.1`\n\n## \\[2.9.0]\n\n### New Features\n\n- [`f5851ee00`](https://www.github.com/tauri-apps/tauri/commit/f5851ee00d6d1f4d560a220ca5a728fedd525092) ([#14089](https://www.github.com/tauri-apps/tauri/pull/14089)) Adds the `scrollBarStyle` option to the window configuration.\n- [`2a06d1006`](https://www.github.com/tauri-apps/tauri/commit/2a06d10066a806e392efe8bfb16d943ee0b0b61d) ([#14052](https://www.github.com/tauri-apps/tauri/pull/14052)) Add a `--no-sign` flag to the `tauri build` and `tauri bundle` commands to skip the code signing step, improving the developer experience for local testing and development without requiring code signing keys.\n- [`3b4fac201`](https://www.github.com/tauri-apps/tauri/commit/3b4fac2017832d426dd07c5e24e26684eda57f7b) ([#14194](https://www.github.com/tauri-apps/tauri/pull/14194)) Add `tauri.conf.json > bundle > android > autoIncrementVersionCode` config option to automatically increment the Android version code.\n- [`673867aa0`](https://www.github.com/tauri-apps/tauri/commit/673867aa0e1ccd766ee879ffe96aba58c758613c) ([#14094](https://www.github.com/tauri-apps/tauri/pull/14094)) Try to detect ANDROID_HOME and NDK_HOME environment variables from default system locations and install them if needed using the Android Studio command line tools.\n- [`3d6868d09`](https://www.github.com/tauri-apps/tauri/commit/3d6868d09c323d68a152f3c3f8c7256311bd020a) ([#14128](https://www.github.com/tauri-apps/tauri/pull/14128)) Added support to defining the content type of the declared file association on macOS (maps to LSItemContentTypes property).\n- [`3d6868d09`](https://www.github.com/tauri-apps/tauri/commit/3d6868d09c323d68a152f3c3f8c7256311bd020a) ([#14128](https://www.github.com/tauri-apps/tauri/pull/14128)) Added support to defining the metadata for custom types declared in `tauri.conf.json > bundle > fileAssociations > exportedType` via the `UTExportedTypeDeclarations` Info.plist property.\n- [`ed7c9a410`](https://www.github.com/tauri-apps/tauri/commit/ed7c9a4100e08c002212265549d12130d021ad1e) ([#14108](https://www.github.com/tauri-apps/tauri/pull/14108)) Added `bundle > macOS > infoPlist` and `bundle > iOS > infoPlist` configurations to allow defining custom Info.plist extensions.\n- [`75082cc5b`](https://www.github.com/tauri-apps/tauri/commit/75082cc5b340e30e2c4b4cd4bd6a1fe5382164aa) ([#14120](https://www.github.com/tauri-apps/tauri/pull/14120)) Added `ios run` and `android run` commands to run the app in production mode.\n- [`cc8c0b531`](https://www.github.com/tauri-apps/tauri/commit/cc8c0b53171173dbd1d01781a50de1a3ea159031) ([#14031](https://www.github.com/tauri-apps/tauri/pull/14031)) Added support to universal app links on macOS with the `plugins > deep-link > desktop > domains` configuration.\n\n### Enhancements\n\n- [`94cbd40fc`](https://www.github.com/tauri-apps/tauri/commit/94cbd40fc733e08c0bccd48149d22a0e9c2f1e5c) ([#14223](https://www.github.com/tauri-apps/tauri/pull/14223)) Add support for Android's adaptive and themed icons.\n- [`b5aa01870`](https://www.github.com/tauri-apps/tauri/commit/b5aa018702bf45dc98297698f9b7d238705865a6) ([#14268](https://www.github.com/tauri-apps/tauri/pull/14268)) Update cargo-mobile2 to 0.21, enhancing error messages and opening Xcode when multiple apps are installed.\n- [`55453e845`](https://www.github.com/tauri-apps/tauri/commit/55453e8453d927b8197f1ba9f26fd944482938f7) ([#14262](https://www.github.com/tauri-apps/tauri/pull/14262)) Check mismatched versions in `tauri info`\n- [`1a6627ee7`](https://www.github.com/tauri-apps/tauri/commit/1a6627ee7d085a4e66784e2705254714d68c7244) ([#14122](https://www.github.com/tauri-apps/tauri/pull/14122)) Set a default log level filter when running `tauri add log`.\n- [`b06b3bd09`](https://www.github.com/tauri-apps/tauri/commit/b06b3bd091b0fed26cdcfb23cacb0462a7a9cc2d) ([#14126](https://www.github.com/tauri-apps/tauri/pull/14126)) Improve error messages with more context.\n- [`f6622a3e3`](https://www.github.com/tauri-apps/tauri/commit/f6622a3e342f5dd5fb3cf6e0f79fb309a10e9b3d) ([#14129](https://www.github.com/tauri-apps/tauri/pull/14129)) Prompt to install the iOS platform if it isn't installed yet.\n- [`6bbb530fd`](https://www.github.com/tauri-apps/tauri/commit/6bbb530fd5edfc07b180a4f3782b8566872ca3b1) ([#14105](https://www.github.com/tauri-apps/tauri/pull/14105)) Warn if productName is empty when initializing mobile project.\n\n### Bug Fixes\n\n- [`19fb6f7cb`](https://www.github.com/tauri-apps/tauri/commit/19fb6f7cb0d702cb2f25f6f2d1e11014d9dada5d) ([#14146](https://www.github.com/tauri-apps/tauri/pull/14146)) Strip Windows-only extensions from the binary path so an Android project initialized on Windows can be used on UNIX systems.\n- [`19fb6f7cb`](https://www.github.com/tauri-apps/tauri/commit/19fb6f7cb0d702cb2f25f6f2d1e11014d9dada5d) ([#14146](https://www.github.com/tauri-apps/tauri/pull/14146)) Enhance Android build script usage on Windows by attempting to run cmd, bat and exe formats.\n- [`28a2f9bc5`](https://www.github.com/tauri-apps/tauri/commit/28a2f9bc55f658eb71ef1a970ff9f791346f7682) ([#14101](https://www.github.com/tauri-apps/tauri/pull/14101)) Fix iOS CLI usage after modifying the package name.\n- [`d2938486e`](https://www.github.com/tauri-apps/tauri/commit/d2938486e9d974debd90c15d7160b8a17bf4d763) ([#14261](https://www.github.com/tauri-apps/tauri/pull/14261)) Replaced the non-standard nerd font character with `  ⱼₛ ` in `tarui info`\n- [`25e920e16`](https://www.github.com/tauri-apps/tauri/commit/25e920e169db900ca4f07c2bb9eb290e9f9f2c7d) ([#14298](https://www.github.com/tauri-apps/tauri/pull/14298)) Wait for dev server to exit before exiting the CLI when the app is closed on `tauri dev --no-watch`.\n- [`b0012424c`](https://www.github.com/tauri-apps/tauri/commit/b0012424c5f432debfa42ba145e2672966d5f6d5) ([#14115](https://www.github.com/tauri-apps/tauri/pull/14115)) Resolve local IP address when `tauri.conf.json > build > devUrl` host is `0.0.0.0`.\n- [`abf7e8850`](https://www.github.com/tauri-apps/tauri/commit/abf7e8850ba41e7173e9e9a3fdd6dfb8f357d72d) ([#14118](https://www.github.com/tauri-apps/tauri/pull/14118)) Fixes mobile project initialization when using `pnpx` or `pnpm dlx`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.0`\n- Upgraded to `tauri-bundler@2.7.0`\n\n## \\[2.8.4]\n\n### Enhancements\n\n- [`f70b28529`](https://www.github.com/tauri-apps/tauri/commit/f70b28529d226a2dec2f41709d8934f8f5adab25) ([#14093](https://www.github.com/tauri-apps/tauri/pull/14093) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ensure Rust targets for mobile are installed when running the dev and build commands (previously only checked on init).\n- [`a9b342125`](https://www.github.com/tauri-apps/tauri/commit/a9b342125d5ac1bc9a4b2e8b5f73e8ca3cbcb8b2) ([#14114](https://www.github.com/tauri-apps/tauri/pull/14114) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS dev and build targeting the simulator on Intel machines.\n- [`61b9b681e`](https://www.github.com/tauri-apps/tauri/commit/61b9b681e88067a53b79d2318ae005dc25addcd6) ([#14111](https://www.github.com/tauri-apps/tauri/pull/14111) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Retain `RUST_*` environment variables when running the mobile commands.\n- [`c23bec62d`](https://www.github.com/tauri-apps/tauri/commit/c23bec62d6d5724798869681aa1534423aae28e2) ([#14083](https://www.github.com/tauri-apps/tauri/pull/14083) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Tauri now ignores `macOS.minimumSystemVersion` in `tauri dev` to prevent forced rebuilds of macOS specific dependencies when using something like `rust-analyzer` at the same time as `tauri dev`.\n\n### Bug Fixes\n\n- [`c37a29833`](https://www.github.com/tauri-apps/tauri/commit/c37a298331d6d744b15d32d55a2db83c884a3d6a) ([#14112](https://www.github.com/tauri-apps/tauri/pull/14112) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix usage with Deno failing with `ReferenceError: require is not defined`.\n- [`bcf000c0a`](https://www.github.com/tauri-apps/tauri/commit/bcf000c0a8607eedf488fb949b982f519abda43d) ([#14110](https://www.github.com/tauri-apps/tauri/pull/14110) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes running `ios` commands with `deno` crashing due to incorrect current working directory resolution.\n- [`7db7142f9`](https://www.github.com/tauri-apps/tauri/commit/7db7142f9ff7dc2f5719602e199b77129ceb19d3) ([#14119](https://www.github.com/tauri-apps/tauri/pull/14119) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes empty device name when using an Android emulator causing the emulator to never be detected as running.\n- [`956b4fd6f`](https://www.github.com/tauri-apps/tauri/commit/956b4fd6ffbb4312123b107ca96c87a001359b9d) ([#14106](https://www.github.com/tauri-apps/tauri/pull/14106) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Use the correct export method on Xcode < 15.4.\n\n## \\[2.8.3]\n\n### Bug Fixes\n\n- [`0ac89d3b6`](https://www.github.com/tauri-apps/tauri/commit/0ac89d3b6c8c4a4826a4c42726e4f4a8941b3fde) ([#14078](https://www.github.com/tauri-apps/tauri/pull/14078) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Updated `cargo-mobile2` to allow running on iOS simulators that have a higher version than the XCode SDK. This fixes compatiblity issues with Apple's recent \"iOS 18.5 + iOS 18.6 Simulator\" platform support component.\n\n## \\[2.8.1]\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.6.1`\n\n## \\[2.8.0]\n\n### New Features\n\n- [`91508c0b8`](https://www.github.com/tauri-apps/tauri/commit/91508c0b8d16ec61c7706e93b711c5a85aaffb4a) ([#13881](https://www.github.com/tauri-apps/tauri/pull/13881) by [@pepperoni505](https://www.github.com/tauri-apps/tauri/../../pepperoni505)) Introduces a new configuration option that allows you to specify custom folders to watch for changes when running `tauri dev`.\n- [`bc4afe7dd`](https://www.github.com/tauri-apps/tauri/commit/bc4afe7dd4780f02c2d4b1f07d97185fbc5d2bba) ([#13993](https://www.github.com/tauri-apps/tauri/pull/13993) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Check installed plugin NPM/crate versions for incompatible releases.\n- [`a9ec12843`](https://www.github.com/tauri-apps/tauri/commit/a9ec12843aa7d0eb774bd3a53e2e63da12cfa77b) ([#13521](https://www.github.com/tauri-apps/tauri/pull/13521) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added a `--skip-stapling` option to make `tauri build|bundle` *not* wait for notarization to finish on macOS.\n- [`0c402bfb6`](https://www.github.com/tauri-apps/tauri/commit/0c402bfb6bd0bec24d928fcabe2ffef1f5cff19a) ([#13997](https://www.github.com/tauri-apps/tauri/pull/13997) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Increase default iOS deployment target iOS to 14.0.\n- [`d6d5f3707`](https://www.github.com/tauri-apps/tauri/commit/d6d5f3707768a094ff7e961ae75ba0398d772655) ([#13358](https://www.github.com/tauri-apps/tauri/pull/13358) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `--root-certificate-path` option to `android dev` and `ios dev` to be able to connect to HTTPS dev servers.\n\n### Enhancements\n\n- [`8b465a12b`](https://www.github.com/tauri-apps/tauri/commit/8b465a12ba73e94d7a3995defd9cc362d15eeebe) ([#13913](https://www.github.com/tauri-apps/tauri/pull/13913) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler now pulls the latest AppImage linuxdeploy plugin instead of using the built-in one. This should remove the libfuse requirement.\n- [`390cb9c36`](https://www.github.com/tauri-apps/tauri/commit/390cb9c36a4e2416891b64514e7ad5fc0a85ccf2) ([#13953](https://www.github.com/tauri-apps/tauri/pull/13953) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Reduced the log level of the binary patcher crate `goblin` to only show its debug logs in `-vv` and above.\n- [`4475e93e1`](https://www.github.com/tauri-apps/tauri/commit/4475e93e136e9e2bd5f3c7817fa2040924f630f6) ([#13824](https://www.github.com/tauri-apps/tauri/pull/13824) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler and cli will now read TLS Certificates installed on the system when downloading tools and checking versions.\n\n### Bug Fixes\n\n- [`f0dcf9637`](https://www.github.com/tauri-apps/tauri/commit/f0dcf9637cc0d42eda05fed7dd6c5ff98bbf19ae) ([#13980](https://www.github.com/tauri-apps/tauri/pull/13980) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix the generated plugin init code of `tauri add` for `tauri-plugin-autostart` and `tauri-plugin-single-instance`\n- [`4d270a96a`](https://www.github.com/tauri-apps/tauri/commit/4d270a96a891ae83f7df751abcbe12b7072212d5) ([#13943](https://www.github.com/tauri-apps/tauri/pull/13943) by [@acx0](https://www.github.com/tauri-apps/tauri/../../acx0)) Fix codesigning verification failures caused by binary-patching during bundling\n- [`b21d86a8a`](https://www.github.com/tauri-apps/tauri/commit/b21d86a8a3ef29f16628b7d4de17ce1214e9bf49) ([#13981](https://www.github.com/tauri-apps/tauri/pull/13981) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `tauri permission add` could add duplicated permissions to the capability files\n- [`9c938be45`](https://www.github.com/tauri-apps/tauri/commit/9c938be4520fce9204361f3b59439844bc5c91e8) ([#13912](https://www.github.com/tauri-apps/tauri/pull/13912) by [@takecchi](https://www.github.com/tauri-apps/tauri/../../takecchi)) Properly migrate svelte to v5 in the plugin example template\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.7.0`\n- Upgraded to `tauri-bundler@2.6.0`\n- Upgraded to `tauri-macos-sign@2.2.0`\n\n## \\[2.7.1]\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.5.2`\n\n## \\[2.7.0]\n\n### New Features\n\n- [`33d079392`](https://www.github.com/tauri-apps/tauri/commit/33d079392ac4a5a153b7d8a6d82fefd6f54a2bdf) ([#13811](https://www.github.com/tauri-apps/tauri/pull/13811) by [@mhbagheri-99](https://www.github.com/tauri-apps/tauri/../../mhbagheri-99)) Allow runner configuration to be an object with cmd, cwd, and args properties. The runner can now be configured as `{ \"cmd\": \"my_runner\", \"cwd\": \"/path\", \"args\": [\"--quiet\"] }` while maintaining backwards compatibility with the existing string format.\n\n### Enhancements\n\n- [`232265c70`](https://www.github.com/tauri-apps/tauri/commit/232265c70e1c213bbb3f84b5541ddc07d330fce1) ([#13209](https://www.github.com/tauri-apps/tauri/pull/13209) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Binaries are patched before bundling to add the type of a bundle they will placed in. This information will be used during update process to select the correct target.\n\n### Bug Fixes\n\n- [`916aeaa48`](https://www.github.com/tauri-apps/tauri/commit/916aeaa48646a483a78e51cfe1633800ee62c37c) ([#13781](https://www.github.com/tauri-apps/tauri/pull/13781) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes Android dev and build commands reading `tauri.ios.conf.json` instead of `tauri.android.conf.json` to merge platform-specific configuration.\n- [`acd757428`](https://www.github.com/tauri-apps/tauri/commit/acd7574284056f9c00894cdce6c07f948fd80c87) ([#13743](https://www.github.com/tauri-apps/tauri/pull/13743) by [@owjs3901](https://www.github.com/tauri-apps/tauri/../../owjs3901)) Fix type of CFBundleVersion generated by `tauri ios init` when `bundleVersion` is a single number (for example `1` instead of `1.0.0`).\n- [`0f248b111`](https://www.github.com/tauri-apps/tauri/commit/0f248b111ffb8af934eaf64bd8f4591e628da786) ([#13799](https://www.github.com/tauri-apps/tauri/pull/13799) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Install iOS dependencies when needed.\n- [`7a6fd5b75`](https://www.github.com/tauri-apps/tauri/commit/7a6fd5b75d61071e2771f6277c0376ec206d302a) ([#13863](https://www.github.com/tauri-apps/tauri/pull/13863) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The AppImage bundler now pulls the AppRun binaries from our GitHub mirror, fixing 404 errors.\n- [`bda830410`](https://www.github.com/tauri-apps/tauri/commit/bda8304107da7ca60caaba5674faa793491898c6) ([#13833](https://www.github.com/tauri-apps/tauri/pull/13833) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fail with an error when trying to migrate from v2 alpha\n- [`bda830410`](https://www.github.com/tauri-apps/tauri/commit/bda8304107da7ca60caaba5674faa793491898c6) ([#13833](https://www.github.com/tauri-apps/tauri/pull/13833) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Use v2 stable instead of v2-rc when migrating from v2-beta\n\n### What's Changed\n\n- [`cfc5bb819`](https://www.github.com/tauri-apps/tauri/commit/cfc5bb819637a97141cbda3285c5d772cfc0ebca) ([#13780](https://www.github.com/tauri-apps/tauri/pull/13780) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Enable edge to edge in `tauri android init` template\n- [`12e359061`](https://www.github.com/tauri-apps/tauri/commit/12e3590613c7b4370aeddc16db7e29335e3a7684) ([#13759](https://www.github.com/tauri-apps/tauri/pull/13759) by [@owjs3901](https://www.github.com/tauri-apps/tauri/../../owjs3901)) Update compileSdk, targetSdk in android template to 36\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.5.1`\n- Upgraded to `tauri-utils@2.6.0`\n\n## \\[2.6.2]\n\n### Bug Fixes\n\n- [`cbd962972`](https://www.github.com/tauri-apps/tauri/commit/cbd9629729ed6eb208ba2234d014c11c4e9f1c8c) ([#13730](https://www.github.com/tauri-apps/tauri/pull/13730) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Load `--config` arguments when running the Xcode and Android Studio build scripts.\n\n## \\[2.6.1]\n\n### Bug Fixes\n\n- [`4b7370e9e`](https://www.github.com/tauri-apps/tauri/commit/4b7370e9e0ba299361c8ad96c08882337e44f091) ([#13710](https://www.github.com/tauri-apps/tauri/pull/13710) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue in the plugin template (`tauri plugin init`) that prevented the js build command to fail.\n\n## \\[2.6.0]\n\n### New Features\n\n- [`414619c36`](https://www.github.com/tauri-apps/tauri/commit/414619c36e94e21939534dd72c0438b93da75546) ([#13536](https://www.github.com/tauri-apps/tauri/pull/13536) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) Added support for the `bundleName` property in the macOS bundler configuration. This allows specifying the `CFBundleName` value for generated macOS bundles.\n- [`3242e1c94`](https://www.github.com/tauri-apps/tauri/commit/3242e1c946c441b58665ba5d612f3a3f1eafe0b6) ([#13659](https://www.github.com/tauri-apps/tauri/pull/13659) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow passing Cargo arguments to mobile dev and build commands.\n- [`d1ce9af62`](https://www.github.com/tauri-apps/tauri/commit/d1ce9af62881e3f7d86a495c9c40df5b7f9d1c04) ([#13660](https://www.github.com/tauri-apps/tauri/pull/13660) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow passing `--config` arguments to the `ios init` and `android init` commands to tweak the configuration used to initialize the mobile projects.\n- [`7322f0579`](https://www.github.com/tauri-apps/tauri/commit/7322f057923aaec88960ad5556776774b745762f) ([#13502](https://www.github.com/tauri-apps/tauri/pull/13502) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Allow using `CheckIfAppIsRunning` macro inside NSIS hooks, for example `!insertmacro CheckIfAppIsRunning \"another-executable.exe\" \"Another Executable\"`.\n- [`4a880ca69`](https://www.github.com/tauri-apps/tauri/commit/4a880ca697bab6d63a2a51ea94e1988cc8c4ea4a) ([#13658](https://www.github.com/tauri-apps/tauri/pull/13658) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Synchronize Tauri config productName changes with the iOS Xcode project.\n- [`8ee14a864`](https://www.github.com/tauri-apps/tauri/commit/8ee14a86480510c15823586cf28084e615cb7a9c) ([#13618](https://www.github.com/tauri-apps/tauri/pull/13618) by [@Sky-walkerX](https://www.github.com/tauri-apps/tauri/../../Sky-walkerX)) Warn the user that the app id shouldn't end in `.app` because it conflicts with the application bundle extension on macOS\n\n### Bug Fixes\n\n- [`574a4d4d3`](https://www.github.com/tauri-apps/tauri/commit/574a4d4d36762b5b09dc3fcfcbcae3a0df0b6d89) ([#13426](https://www.github.com/tauri-apps/tauri/pull/13426) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `dev`, `build` and `bundle` commands always take 2 seconds to start\n- [`35aa7e121`](https://www.github.com/tauri-apps/tauri/commit/35aa7e1218f34d0805e280e3ec32529d0cb0d733) ([#13294](https://www.github.com/tauri-apps/tauri/pull/13294) by [@kingsword09](https://www.github.com/tauri-apps/tauri/../../kingsword09)) fix: allow the target directory to be inside frontendDir as long as it is not the Rust target directory inside frontendDir.\n- [`ec6065fa4`](https://www.github.com/tauri-apps/tauri/commit/ec6065fa4a6427266ecfb0c0f62f008574bb7880) ([#13625](https://www.github.com/tauri-apps/tauri/pull/13625) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes Android and iOS dev/build commands not working when the app identifier is being modified by the `--config` option.\n- [`5a5291d66`](https://www.github.com/tauri-apps/tauri/commit/5a5291d66cb8a955c9d4f8e975782646ac0cc6e7) ([#13483](https://www.github.com/tauri-apps/tauri/pull/13483) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix simulator build detection on Xcode.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.5.0`\n- Upgraded to `tauri-utils@2.5.0`\n- [`9c16eefa3`](https://www.github.com/tauri-apps/tauri/commit/9c16eefa319b4697bac1d1019bbb5f93eca63173) ([#13629](https://www.github.com/tauri-apps/tauri/pull/13629) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Update html5ever to 0.29 and kuchikiki to version 0.8.8-speedreader.\n\n## \\[2.5.0]\n\n### New Features\n\n- [`0aa48fb9e`](https://www.github.com/tauri-apps/tauri/commit/0aa48fb9e4b9d7b5bf3522000a76ebc1836394ed) ([#13030](https://www.github.com/tauri-apps/tauri/pull/13030)) Added `bundleVersion` to iOS and macOS configuration to support specifying a `CFBundleVersion`.\n\n### Enhancements\n\n- [`ad3fd3890`](https://www.github.com/tauri-apps/tauri/commit/ad3fd3890f1fa26a9f9be04ff1bc156d6dd2a8bc) ([#13152](https://www.github.com/tauri-apps/tauri/pull/13152)) Detect package manager from environment variable `npm_config_user_agent` first\n- [`82406c61e`](https://www.github.com/tauri-apps/tauri/commit/82406c61e0fbb775ef00791ccab45349325bdd45) ([#13231](https://www.github.com/tauri-apps/tauri/pull/13231)) Improve iOS simulator usage, checking if Xcode iOS SDK is installed and allowing usage of Simulator for older iOS releases (previously only supported when running on Xcode via `ios dev --open`).\n\n### Bug Fixes\n\n- [`2dccfab53`](https://www.github.com/tauri-apps/tauri/commit/2dccfab5321fef55d45f3a4c674b6151b1c4424a) ([#13236](https://www.github.com/tauri-apps/tauri/pull/13236)) Fix `fileAssociations` missing `LSHandlerRank` on macOS.\n- [`080252903`](https://www.github.com/tauri-apps/tauri/commit/0802529031c4fd309edff374a8694e93ddec161d) ([#13210](https://www.github.com/tauri-apps/tauri/pull/13210)) Fixes iOS dev not working on Xcode 16.3 simulators. To apply the fix, either regenerate the Xcode project with `rm -r src-tauri/gen/apple && tauri ios init` or remove the `arm64-sim` architecture from the Xcode project.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.4.0`\n- Upgraded to `tauri-bundler@2.4.0`\n\n## \\[2.4.1]\n\n### Enhancements\n\n- [`f805061d1`](https://www.github.com/tauri-apps/tauri/commit/f805061d1152bc4790dbdb9475a506afcdd1de75) ([#13079](https://www.github.com/tauri-apps/tauri/pull/13079) by [@Pietagorh](https://www.github.com/tauri-apps/tauri/../../Pietagorh)) Add support for passing TOML and JSON5 config files to `--config` arg\n\n### Bug Fixes\n\n- [`30beb6fee`](https://www.github.com/tauri-apps/tauri/commit/30beb6fee7b052e588ee72c238e315a557b5d6f2) ([#13096](https://www.github.com/tauri-apps/tauri/pull/13096) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `tauri info` can't find the latest version for rust crates\n- [`30beb6fee`](https://www.github.com/tauri-apps/tauri/commit/30beb6fee7b052e588ee72c238e315a557b5d6f2) ([#13096](https://www.github.com/tauri-apps/tauri/pull/13096) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `tauri info` logging network operations in `info` level instead of `debug`\n- [`794af778e`](https://www.github.com/tauri-apps/tauri/commit/794af778e4915ffb6a4fe9bae8fba04bc880503d) ([#13117](https://www.github.com/tauri-apps/tauri/pull/13117) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix setting merge config value to null with `--config` arg no longer works\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.3.1`\n- Upgraded to `tauri-utils@2.3.1`\n\n## \\[2.4.0]\n\n### New Features\n\n- [`d91bfa5cb`](https://www.github.com/tauri-apps/tauri/commit/d91bfa5cb921a078758edd45ef3eaff71358d1eb) ([#12970](https://www.github.com/tauri-apps/tauri/pull/12970) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow merging multiple configuration values on `tauri dev`, `tauri build`, `tauri bundle`, `tauri android dev`, `tauri android build`, `tauri ios dev` and `tauri ios build`.\n- [`013f8f652`](https://www.github.com/tauri-apps/tauri/commit/013f8f652302f2d49c5ec0a075582033d8b074fb) ([#12890](https://www.github.com/tauri-apps/tauri/pull/12890) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Reads `build > removeUnusedCommands` from the config file and pass in the environment variables on the build command to trigger the build scripts and macros to remove unused commands based on the capabilities you defined. For this to work on inlined plugins you must add a `#![plugin(<insert_plugin_name>)]` inside the `tauri::generate_handler![]` usage and the app manifest must be set.\n- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `trafficLightPosition` window configuration to set the traffic light buttons position on macOS.\n\n### Enhancements\n\n- [`f981a5ee8`](https://www.github.com/tauri-apps/tauri/commit/f981a5ee8b292b9ea09329f60cecc7f688dda734) ([#12602](https://www.github.com/tauri-apps/tauri/pull/12602) by [@kxxt](https://www.github.com/tauri-apps/tauri/../../kxxt)) Add basic support for linux riscv64 platform.\n\n### Bug Fixes\n\n- [`0c4700e99`](https://www.github.com/tauri-apps/tauri/commit/0c4700e9907f242eabe579eb6149a1d75174185c) ([#12985](https://www.github.com/tauri-apps/tauri/pull/12985) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The cli will now accept `--bundles updater` again. It's still no-op as it has been for all v2 versions. If you want to build updater artifacts, enable `createUpdaterArtifacts` in `tauri.conf.json`.\n- [`eec08a18b`](https://www.github.com/tauri-apps/tauri/commit/eec08a18b66525f5544cd30144d0553260ee3a70) ([#12998](https://www.github.com/tauri-apps/tauri/pull/12998) by [@jason89521](https://www.github.com/tauri-apps/tauri/../../jason89521)) For bun's lockfile, check both `bun.lock` and `bun.lockb`.\n- [`b83921226`](https://www.github.com/tauri-apps/tauri/commit/b83921226cb3084992bb5357e7e39a09ea97843e) ([#12977](https://www.github.com/tauri-apps/tauri/pull/12977) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `tauri ios` commands using the wrong working directory with `bun@>1.2`.\n- [`f268b3dbd`](https://www.github.com/tauri-apps/tauri/commit/f268b3dbdf313484c85b4a1f69cd7cec63049f35) ([#12871](https://www.github.com/tauri-apps/tauri/pull/12871) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ignore parent .gitignore files on the Tauri project path detection.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.0`\n- Upgraded to `tauri-bundler@2.3.0`\n\n## \\[2.3.1]\n\n### Bug Fixes\n\n- [`22e9bf74a`](https://www.github.com/tauri-apps/tauri/commit/22e9bf74a4684c827279a85bb66548e83c1ea5cf) ([#12538](https://www.github.com/tauri-apps/tauri/pull/12538) by [@DeTeam](https://www.github.com/tauri-apps/tauri/../../DeTeam)) Set initialViewController for LaunchScreen (iOS).\n\n## \\[2.3.0]\n\n### Enhancements\n\n- [`a2d36b8c3`](https://www.github.com/tauri-apps/tauri/commit/a2d36b8c34a8dcfc6736797ca5cd4665faf75e7e) ([#12181](https://www.github.com/tauri-apps/tauri/pull/12181) by [@bastiankistner](https://www.github.com/tauri-apps/tauri/../../bastiankistner)) Add an option to change the default background throttling policy (currently for WebKit only).\n- [`6e417c943`](https://www.github.com/tauri-apps/tauri/commit/6e417c9435d8fae0eca9e8d42d6215e887a28d98) ([#12786](https://www.github.com/tauri-apps/tauri/pull/12786) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Added RPM to the list of package types for which signature file will be generated.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.2.0`\n- Upgraded to `tauri-bundler@2.2.4`\n- Upgraded to `tauri-macos-sign@2.1.0`\n\n## \\[2.2.7]\n\n### Bug Fixes\n\n- [`8e9134c4a`](https://www.github.com/tauri-apps/tauri/commit/8e9134c4a2047329be0dbb868b7ae061a9d3f190) ([#12511](https://www.github.com/tauri-apps/tauri/pull/12511) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused `tauri dev` to fail because of an incorrect `--bins` flag.\n\n## \\[2.2.6]\n\n### Enhancements\n\n- [`1a86974aa`](https://www.github.com/tauri-apps/tauri/commit/1a86974aa3d09957c6b1142a17bbfed9998798fd) ([#12406](https://www.github.com/tauri-apps/tauri/pull/12406) by [@bradleat](https://www.github.com/tauri-apps/tauri/../../bradleat)) `ios build --open` will now let xcode start the rust build process.\n- [`9a30bed98`](https://www.github.com/tauri-apps/tauri/commit/9a30bed98c2d8501328006fad5840eb9d533e1c2) ([#12423](https://www.github.com/tauri-apps/tauri/pull/12423) by [@tr3ysmith](https://www.github.com/tauri-apps/tauri/../../tr3ysmith)) Added conditional logic to MacOS codesigning where only executables get the entitlements file when being signed. This solves an issue where the app may not launch when using 3rd party frameworks if certain entitlements are added. Ex: multicast support (must be applied for through apple developer, and the framework would not have that capability).\n- [`0b79af711`](https://www.github.com/tauri-apps/tauri/commit/0b79af711430934362602fb950c3e4cb5b59cf9c) ([#12438](https://www.github.com/tauri-apps/tauri/pull/12438) by [@3lpsy](https://www.github.com/tauri-apps/tauri/../../3lpsy)) Log the command used to start the rust app in development.\n\n### Bug Fixes\n\n- [`bc43c738b`](https://www.github.com/tauri-apps/tauri/commit/bc43c738baf686353690d3d9259b4976881718c8) ([#12442](https://www.github.com/tauri-apps/tauri/pull/12442) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that prevented `tauri add` to work for the `clipboard-manager` plugin.\n- [`27096cdc0`](https://www.github.com/tauri-apps/tauri/commit/27096cdc05d89b61b2372b4e4a3018c87f240ab8) ([#12445](https://www.github.com/tauri-apps/tauri/pull/12445) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused Tauri's CLI to enable tauri's `native-tls` feature even though it wasn't needed. Moved `reqwest` to a mobile-only dependency in `tauri` and enabled its `rustls-tls` feature flag.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.2.3`\n\n## \\[2.2.5]\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.2.2`\n\n## \\[2.2.4]\n\n### Bug Fixes\n\n- [`cad550445`](https://www.github.com/tauri-apps/tauri/commit/cad5504455ffa53e297cebff473c113b1afa5d29) ([#12354](https://www.github.com/tauri-apps/tauri/pull/12354) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed and issue that caused `tauri add` to try to install incorrect npm packages.\n\n## \\[2.2.3]\n\n### Enhancements\n\n- [`a0f2c84d5`](https://www.github.com/tauri-apps/tauri/commit/a0f2c84d51f5086c5055867d6f61ea90c463a26c) ([#12204](https://www.github.com/tauri-apps/tauri/pull/12204) by [@pjf-dev](https://www.github.com/tauri-apps/tauri/../../pjf-dev)) Enhance `tauri icon` command by including 64x64 png size in default icon sizes.\n\n### Bug Fixes\n\n- [`98f62e65a`](https://www.github.com/tauri-apps/tauri/commit/98f62e65a27a375272c6b4d9f34c23e142b9d3a6) ([#12246](https://www.github.com/tauri-apps/tauri/pull/12246) by [@marcomq](https://www.github.com/tauri-apps/tauri/../../marcomq)) Properly add NPM packages for community plugins when using the `tauri add` command.\n- [`b9a99a5c6`](https://www.github.com/tauri-apps/tauri/commit/b9a99a5c69d8a2a1a3ff30e500b46872258dca15) ([#12297](https://www.github.com/tauri-apps/tauri/pull/12297) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the built-in dev server to constantly refresh on Linux. This only affected users who do not have `devUrl` point to a URL.\n- [`ef21ed9ac`](https://www.github.com/tauri-apps/tauri/commit/ef21ed9ac1c045c38b0c04e3d71a441694abc257) ([#12290](https://www.github.com/tauri-apps/tauri/pull/12290) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS build failing when the development team contains spaces.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.2.1`\n\n## \\[2.2.2]\n\n### Bug Fixes\n\n- [`26fc9558f`](https://www.github.com/tauri-apps/tauri/commit/26fc9558fe7b2fe649f61926da88f36110dd5707) ([#12178](https://www.github.com/tauri-apps/tauri/pull/12178) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the `tauri dev` file watcher to exit after detecting file changes.\n\n## \\[2.2.1]\n\n### Bug Fixes\n\n- [`881729448`](https://www.github.com/tauri-apps/tauri/commit/881729448c9abd0d0c7941a8a31c94119ce827af) ([#12164](https://www.github.com/tauri-apps/tauri/pull/12164) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused `tauri dev` to crash before showing the app on Linux.\n\n## \\[2.2.0]\n\n### New Features\n\n- [`cccb308c7`](https://www.github.com/tauri-apps/tauri/commit/cccb308c7b559b0838138d6cea280665f060c925) ([#11562](https://www.github.com/tauri-apps/tauri/pull/11562) by [@jLynx](https://www.github.com/tauri-apps/tauri/../../jLynx)) Generate signature for `.deb` packages when `createUpdaterArtifacts` option is enabled.\n- [`74212d40d`](https://www.github.com/tauri-apps/tauri/commit/74212d40d80dba4501b3d4ae30104fa3d447bdf9) ([#11653](https://www.github.com/tauri-apps/tauri/pull/11653) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Include Linux destkop environment and session type in `tauri info` command.\n\n### Enhancements\n\n- [`93a3a043d`](https://www.github.com/tauri-apps/tauri/commit/93a3a043d39cc96515d51d98beeb14261d3a246b) ([#11727](https://www.github.com/tauri-apps/tauri/pull/11727) by [@Kiyozz](https://www.github.com/tauri-apps/tauri/../../Kiyozz)) Add support for `Portuguese` language for NSIS windows installer.\n\n### Bug Fixes\n\n- [`c8700656b`](https://www.github.com/tauri-apps/tauri/commit/c8700656be3001a0cc6e087f23aebd482430a85b) ([#11985](https://www.github.com/tauri-apps/tauri/pull/11985) by [@ShaunSHamilton](https://www.github.com/tauri-apps/tauri/../../ShaunSHamilton)) Fix `tauri remove` from removing object type (`{}`) permissions.\n- [`0ae06c5ca`](https://www.github.com/tauri-apps/tauri/commit/0ae06c5ca89cecd24154affdc69668f5e1e67d85) ([#11914](https://www.github.com/tauri-apps/tauri/pull/11914) by [@wtto00](https://www.github.com/tauri-apps/tauri/../../wtto00)) Fix the exclude path in file `Cargo.toml` of plugin template generated by cli. Path changed in [#9346](https://github.com/tauri-apps/tauri/pull/9346)\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.2.0`\n- Upgraded to `tauri-utils@2.1.1`\n\n## \\[2.1.0]\n\n### New Features\n\n- [`1b6b2cfaa`](https://www.github.com/tauri-apps/tauri/commit/1b6b2cfaa14ab1d418c676cedbf942a812377a30) ([#11521](https://www.github.com/tauri-apps/tauri/pull/11521) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Process `bundle > windows > wix > fragmentPaths` with Handlebars to interpolate expressions within it.\n- [`6bf917941`](https://www.github.com/tauri-apps/tauri/commit/6bf917941ff0fcc49e86b3ba427340b75f3ce49c) ([#11322](https://www.github.com/tauri-apps/tauri/pull/11322) by [@ShaunSHamilton](https://www.github.com/tauri-apps/tauri/../../ShaunSHamilton)) Add `tauri remove` to remove plugins from projects.\n- [`058c0db72`](https://www.github.com/tauri-apps/tauri/commit/058c0db72f43fbe1574d0db654560e693755cd7e) ([#11584](https://www.github.com/tauri-apps/tauri/pull/11584) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > rpm > compression` config option to control RPM bundle compression type and level.\n\n### Enhancements\n\n- [`1f311832a`](https://www.github.com/tauri-apps/tauri/commit/1f311832ab5b2d62a533dfcf9b1d78bddf249ae8) ([#11405](https://www.github.com/tauri-apps/tauri/pull/11405) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add more context for errors when decoding secret and public keys for signing updater artifacts.\n- [`e0d1307d3`](https://www.github.com/tauri-apps/tauri/commit/e0d1307d3f78987d0059921a5ab01ea4b26e0ef1) ([#11414](https://www.github.com/tauri-apps/tauri/pull/11414) by [@Czxck001](https://www.github.com/tauri-apps/tauri/../../Czxck001)) Migrate the `$schema` Tauri configuration to the v2 format.\n- [`c43d5df15`](https://www.github.com/tauri-apps/tauri/commit/c43d5df15828ecffa606482ea2b60350c488c981) ([#11512](https://www.github.com/tauri-apps/tauri/pull/11512) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Associate a newly created capability file with the `main` window on the `tauri add` and `tauri permission add` commands.\n\n### Bug Fixes\n\n- [`7af01ff2c`](https://www.github.com/tauri-apps/tauri/commit/7af01ff2ce623d727cd13a4c8a549c1c80031882) ([#11523](https://www.github.com/tauri-apps/tauri/pull/11523) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `tauri migrate` failing to install NPM depenencies when running from Deno.\n- [`100a4455a`](https://www.github.com/tauri-apps/tauri/commit/100a4455aa48df508510bbc08273215bdf70c012) ([#11529](https://www.github.com/tauri-apps/tauri/pull/11529) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix detecting yarn berry (v2 and higher) in various tauri cli commands.\n- [`60e86d5f6`](https://www.github.com/tauri-apps/tauri/commit/60e86d5f6e0f0c769d34ef368cd8801a918d796d) ([#11624](https://www.github.com/tauri-apps/tauri/pull/11624) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Use the public network IP address on `android dev` by default on Windows.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.0`\n- Upgraded to `tauri-bundler@2.1.0`\n\n## \\[2.0.4]\n\n### Enhancements\n\n- [`e4c9268b1`](https://www.github.com/tauri-apps/tauri/commit/e4c9268b19c614dc9ebb0895448fd16de7efee80) ([#11258](https://www.github.com/tauri-apps/tauri/pull/11258) by [@regexident](https://www.github.com/tauri-apps/tauri/../../regexident)) Support custom project directory structure where the Tauri app folder is not a subfolder of the frontend project.\n  The frontend and Tauri app project paths can be set with the `TAURI_FRONTEND_PATH` and the `TAURI_APP_PATH` environment variables respectively.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.4`\n\n## \\[2.0.3]\n\n### New Features\n\n- [`eda5713ea`](https://www.github.com/tauri-apps/tauri/commit/eda5713eab78d28182071ea25ceca5f1994f37ea) ([#11242](https://www.github.com/tauri-apps/tauri/pull/11242) by [@alex-sandri](https://www.github.com/tauri-apps/tauri/../../alex-sandri)) Add `Italian` to supported NSIS installer languages\n- [`b3563e3d6`](https://www.github.com/tauri-apps/tauri/commit/b3563e3d6ae8dc90ee68f25f575cd5538ab1915b) ([#11304](https://www.github.com/tauri-apps/tauri/pull/11304) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add Deno support in tauri-cli operations.\n\n### Bug Fixes\n\n- [`d609bef9f`](https://www.github.com/tauri-apps/tauri/commit/d609bef9fd7cd6eeb2bd701558100bd9cfb6e6f6) ([#11314](https://www.github.com/tauri-apps/tauri/pull/11314) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix android invalid proguard file when using an `identifier` that contains a component that is a reserved kotlin keyword, like `in`, `class`, etc\n- [`069c05e44`](https://www.github.com/tauri-apps/tauri/commit/069c05e44fd6f30083fdc00dd6c0001278898592) ([#11315](https://www.github.com/tauri-apps/tauri/pull/11315) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix CLI crashing and failing to find a `.ico` file when `bundle > icon` option is using globs and doesn't have a string that ends with `.ico`.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.3`\n\n## \\[2.0.2]\n\n### What's Changed\n\n- [`4475fbb50`](https://www.github.com/tauri-apps/tauri/commit/4475fbb502c5ffb3cea4de6bef1c7869be39bed6) ([#11208](https://www.github.com/tauri-apps/tauri/pull/11208) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Update cargo-mobile2 to 0.17.3, fixing lib name validation.\n- [`a49a19ffa`](https://www.github.com/tauri-apps/tauri/commit/a49a19ffa304f031fb1a04d31a567cc7f42a380a) ([#11218](https://www.github.com/tauri-apps/tauri/pull/11218)) Fix bundling `appimage`, `deb` and `rpm` bundles failing to open when using `mainBinaryName` with spaces.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.2`\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.1`\n- Upgraded to `tauri-macos-sign@2.0.1`\n- Upgraded to `tauri-bundler@2.0.1`\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78.\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0`\n- Upgraded to `tauri-macos-sign@2.0.0`\n- Upgraded to `tauri-bundler@2.0.0`\n\n## \\[2.0.0-rc.18]\n\n### Enhancements\n\n- [`a08e6ffa6`](https://www.github.com/tauri-apps/tauri/commit/a08e6ffa6fe499553be3c4c620726d6031cd6dd3) ([#11185](https://www.github.com/tauri-apps/tauri/pull/11185) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Enhance port forwarding on `android dev` to be more resilient and tolerate delays when booting up devices.\n- [`6cfe7edf6`](https://www.github.com/tauri-apps/tauri/commit/6cfe7edf63636fdf66c429efdeb7bc9a0f404e9f) ([#11186](https://www.github.com/tauri-apps/tauri/pull/11186) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Retain logger verbosity on the `android-studio-script` and `xcode-script` commands.\n- [`60a5aea53`](https://www.github.com/tauri-apps/tauri/commit/60a5aea53db02ae6af325812ab97555f2c013d70) ([#11181](https://www.github.com/tauri-apps/tauri/pull/11181) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Set the `TRUNK_SERVE_ADDRESS` environment variable when running on iOS physical devices to support Trunk.\n\n### Bug Fixes\n\n- [`f5d61822b`](https://www.github.com/tauri-apps/tauri/commit/f5d61822bf5988827776dd58bed75c19364e86bd) ([#11184](https://www.github.com/tauri-apps/tauri/pull/11184) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS application not including the provided capabilities (entitlements).\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-rc.15`\n- Upgraded to `tauri-macos-sign@0.1.1-rc.1`\n\n## \\[2.0.0-rc.17]\n\n### New Features\n\n- [`06718b456`](https://www.github.com/tauri-apps/tauri/commit/06718b4569577a004daed9b8455e852a50b4b80f) ([#11096](https://www.github.com/tauri-apps/tauri/pull/11096) by [@thep0y](https://www.github.com/tauri-apps/tauri/../../thep0y)) Add the `TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE` environment variable to specify a more accessible mirror template, facilitating companies, organizations, or individuals who cannot access GitHub to download the necessary files through their own mirror servers.\n- [`a944b9b05`](https://www.github.com/tauri-apps/tauri/commit/a944b9b05bc5ae6125ff451e86c5b207c511f3d7) ([#11118](https://www.github.com/tauri-apps/tauri/pull/11118) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `--github-workflows` flag for `tauri plugin new/init`.\n- [`f57a729cd`](https://www.github.com/tauri-apps/tauri/commit/f57a729cd8f7e10d8daf0b9d5b85f9c7ad530496) ([#11039](https://www.github.com/tauri-apps/tauri/pull/11039) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `tauri inspect wix-upgrade-code` to print default Upgrade Code for your MSI installer derived from `productName`.\n\n### Enhancements\n\n- [`67b8a9a17`](https://www.github.com/tauri-apps/tauri/commit/67b8a9a17a0315ccba681a646678dc1922f5ebf2) ([#10940](https://www.github.com/tauri-apps/tauri/pull/10940) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Generate `.sig` signature files for installers and bundles when `createUpdaterArtifacts` is set to `v1Compatible`\n\n### Bug Fixes\n\n- [`62b52f60a`](https://www.github.com/tauri-apps/tauri/commit/62b52f60a22ef84c4a2a2d9e662038b49f58e16c) ([#11064](https://www.github.com/tauri-apps/tauri/pull/11064) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `tauri add` failing to add NPM depenency with `npm` package manager.\n- [`56e087471`](https://www.github.com/tauri-apps/tauri/commit/56e087471a347f6bee7422221a956925c60b17e3) ([#11100](https://www.github.com/tauri-apps/tauri/pull/11100) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS xcode-script usage with `bun`.\n- [`b88e22a5f`](https://www.github.com/tauri-apps/tauri/commit/b88e22a5fe4e2e4376d6cad64d1e74d104ca8927) ([#11063](https://www.github.com/tauri-apps/tauri/pull/11063) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The cli now only sets the iOS deployment target environment variable when building for iOS.\n- [`8d22c0c81`](https://www.github.com/tauri-apps/tauri/commit/8d22c0c814e7227d5e56ce9a08929045ccea1a1b) ([#11101](https://www.github.com/tauri-apps/tauri/pull/11101) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Only modify the iOS Xcode project \"sign style\" if we need to enforce manual signing.\n- [`df24cb944`](https://www.github.com/tauri-apps/tauri/commit/df24cb944249ee398f6c8ba8c19757b398eec701) ([#11168](https://www.github.com/tauri-apps/tauri/pull/11168) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes Xcode pbxproj file parsing not expecting `_` in build configuration IDs.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-rc.14`\n- Upgraded to `tauri-utils@2.0.0-rc.13`\n\n### Breaking Changes\n\n- [`a944b9b05`](https://www.github.com/tauri-apps/tauri/commit/a944b9b05bc5ae6125ff451e86c5b207c511f3d7) ([#11118](https://www.github.com/tauri-apps/tauri/pull/11118) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) `tauri plugin init/new` will no longer generate a `.github` directory with workflows by default, instead use the new `--github-workflows` flag.\n\n## \\[2.0.0-rc.16]\n\n### New Features\n\n- [`9bb8fc618`](https://www.github.com/tauri-apps/tauri/commit/9bb8fc6189a93bcb811588b36e710d0f7818a1f9) ([#11030](https://www.github.com/tauri-apps/tauri/pull/11030) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `--no-example` flag for `tauri plugin new` and `tauri plugin init` to disable creation of an example project.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.12`\n- Upgraded to `tauri-bundler@2.0.1-rc.13`\n\n## \\[2.0.0-rc.15]\n\n### Enhancements\n\n- [`5a0e922d4`](https://www.github.com/tauri-apps/tauri/commit/5a0e922d40dc3b7d9a8e3a65ccaf76d09f026cb8) ([#11007](https://www.github.com/tauri-apps/tauri/pull/11007) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Automatically discover the `src-tauri/src/main.rs` binary when it is not explicitly defined in the Cargo manifest bin array.\n\n### Bug Fixes\n\n- [`94e9d476e`](https://www.github.com/tauri-apps/tauri/commit/94e9d476ef506b1b8c09f55b81620c7839f98086) ([#11011](https://www.github.com/tauri-apps/tauri/pull/11011) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `main_binary_name` in custom wix and nsis templates including `.exe`\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-rc.12`\n\n## \\[2.0.0-rc.13]\n\n### New Features\n\n- [`656618225`](https://www.github.com/tauri-apps/tauri/commit/65661822580c31eb10a44be45842e259c598374c) ([#10866](https://www.github.com/tauri-apps/tauri/pull/10866) by [@thep0y](https://www.github.com/tauri-apps/tauri/../../thep0y)) Add `TAURI_BUNDLER_TOOLS_GITHUB_MIRROR` environment variable to specify a GitHub mirror to download files and tools used by tauri bundler. This is designed for areas like Mainland China where GitHub access can be unreliable.\n- [`35bd9dd3d`](https://www.github.com/tauri-apps/tauri/commit/35bd9dd3dc3d8972bbc4aa5f4a6c6fa14354e9bf) ([#10977](https://www.github.com/tauri-apps/tauri/pull/10977) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `mainBinaryName` config option to set the file name for the main binary.\n\n### Enhancements\n\n- [`6c5340f8b`](https://www.github.com/tauri-apps/tauri/commit/6c5340f8b2549dfe89f19656304e65cd670afc92) ([#11004](https://www.github.com/tauri-apps/tauri/pull/11004) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added the `log` plugin to the app template, which is required to visualize logs on Android and iOS.\n- [`3ad2427dc`](https://www.github.com/tauri-apps/tauri/commit/3ad2427dc08f12c61edc726b587acce32eca1080) ([#10961](https://www.github.com/tauri-apps/tauri/pull/10961) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Only render app logs on iOS unless `-vv` is provided to the `ios dev` command.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-rc.11`\n- Upgraded to `tauri-utils@2.0.0-rc.11`\n\n## \\[2.0.0-rc.12]\n\n### Bug Fixes\n\n- [`a5848af65`](https://www.github.com/tauri-apps/tauri/commit/a5848af65b10d89686314cf737b7fd9d91f99dd8) ([#10944](https://www.github.com/tauri-apps/tauri/pull/10944) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Synchronize app version (`tauri.conf.json > version` or `Cargo.toml > package > version`) with the `CFBundleVersion` and `CFBundleShortVersionString` Info.plist values.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.10`\n- Upgraded to `tauri-bundler@2.0.1-rc.10`\n\n## \\[2.0.0-rc.11]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.9`\n- Upgraded to `tauri-bundler@2.0.1-rc.9`\n\n## \\[2.0.0-rc.10]\n\n### Enhancements\n\n- [`9c9644d15`](https://www.github.com/tauri-apps/tauri/commit/9c9644d155818d9efcad65b60aa985a59e767922) ([#10845](https://www.github.com/tauri-apps/tauri/pull/10845) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Enhance iOS library validation, checking libs built with link time optimization.\n\n### Bug Fixes\n\n- [`b42683592`](https://www.github.com/tauri-apps/tauri/commit/b42683592d446f25c2005b59e9e3ec551175906d) ([#10847](https://www.github.com/tauri-apps/tauri/pull/10847) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes `ios build --target [aarch64-sim | x86_64]` failing to generate the app bundle for the iOS simulator.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.8`\n- Upgraded to `tauri-bundler@2.0.1-rc.8`\n\n## \\[2.0.0-rc.9]\n\n### Bug Fixes\n\n- [`6faa03276`](https://www.github.com/tauri-apps/tauri/commit/6faa032766b23cd161503905d4c79365ff6c50d1) ([#10854](https://www.github.com/tauri-apps/tauri/pull/10854) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes iOS code signing failing on CI due to a missing development certificate.\n\n## \\[2.0.0-rc.9]\n\n### Bug Fixes\n\n- [`5af1f5dec`](https://www.github.com/tauri-apps/tauri/commit/5af1f5dec1bb98f335169df8c5e30c19a24cae07) ([#10851](https://www.github.com/tauri-apps/tauri/pull/10851) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes `ios build` failing to build iOS app in CI when using an API key for automatic signing.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-rc.7`\n\n## \\[2.0.0-rc.8]\n\n### New Features\n\n- [`91e9e784a`](https://www.github.com/tauri-apps/tauri/commit/91e9e784aa59634e3fe6359f8b78d071d76a9e42) ([#10729](https://www.github.com/tauri-apps/tauri/pull/10729) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add plugins information in `tauri info` output\n- [`09e9dc1aa`](https://www.github.com/tauri-apps/tauri/commit/09e9dc1aab1b66aa6a3a009d5873db586abe76a0) ([#10752](https://www.github.com/tauri-apps/tauri/pull/10752) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow Xcode to manage iOS code sign and provisioning profiles by default.\n  On CI, the `APPLE_API_KEY`, `APPLE_API_ISSUER` and `APPLE_API_KEY_PATH` environment variables must be provided for authentication.\n\n### Enhancements\n\n- [`3a4972b39`](https://www.github.com/tauri-apps/tauri/commit/3a4972b394c65c32eefebfb2181ba56b0cfc08f7) ([#10793](https://www.github.com/tauri-apps/tauri/pull/10793) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Include architecture in the `tauri info` output.\n- [`fd68b7fde`](https://www.github.com/tauri-apps/tauri/commit/fd68b7fdea3890d9f0a373a252a3682bd9d04138) ([#10785](https://www.github.com/tauri-apps/tauri/pull/10785) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Remove the `.cargo/config` file creation that used to fix mobile build caches.\n- [`f67a9eb6d`](https://www.github.com/tauri-apps/tauri/commit/f67a9eb6de4567c2374b8cdbabadcf0ca44d28fb) ([#10802](https://www.github.com/tauri-apps/tauri/pull/10802) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Synchronize identifier, development team and lib name with the iOS Xcode project.\n\n### Bug Fixes\n\n- [`83ed090bf`](https://www.github.com/tauri-apps/tauri/commit/83ed090bfa58a1784495f474d93b16a568be513f) ([#10790](https://www.github.com/tauri-apps/tauri/pull/10790) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Do not quit `ios dev` and `android dev` process when we fail to attach the logger.\n- [`2d31aef75`](https://www.github.com/tauri-apps/tauri/commit/2d31aef759f496f3afe46b7697176e61a8570511) ([#10751](https://www.github.com/tauri-apps/tauri/pull/10751) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ensure gradlew is executable and does not use CRLF so it can be used on UNIX systems.\n- [`02b2f964a`](https://www.github.com/tauri-apps/tauri/commit/02b2f964a70c61ff08b5052bd9fcde472d706d9c) ([#10795](https://www.github.com/tauri-apps/tauri/pull/10795) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix the `add` command NPM version specifier for known plugins from `2.0.0-rc` (unknown version requirement) to `^2.0.0-rc`.\n- [`84070bae9`](https://www.github.com/tauri-apps/tauri/commit/84070bae92d234bc3630e795cfaf79f869f3a751) ([#10792](https://www.github.com/tauri-apps/tauri/pull/10792) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `tauri plugin ios init` not generating the iOS folder.\n- [`edb2ca31f`](https://www.github.com/tauri-apps/tauri/commit/edb2ca31f70a39004b6a09ae53425f22e243318e) ([#10794](https://www.github.com/tauri-apps/tauri/pull/10794) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Migrate v1 plugins NPM packages.\n- [`9718dc9e8`](https://www.github.com/tauri-apps/tauri/commit/9718dc9e8c9bc91d9a5d9e0e06a7afab62492152) ([#10791](https://www.github.com/tauri-apps/tauri/pull/10791) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Reintroduce the `targetSdk` value in the Android application template.\n\n### What's Changed\n\n- [`fb6bf3142`](https://www.github.com/tauri-apps/tauri/commit/fb6bf314252c88dd49af74bdbb8499df370836ae) ([#10763](https://www.github.com/tauri-apps/tauri/pull/10763) by [@rdlabo](https://www.github.com/tauri-apps/tauri/../../rdlabo)) Update plugin template Android code to match documentation on Android package ID usage.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.7`\n- Upgraded to `tauri-bundler@2.0.1-rc.6`\n\n### Breaking Changes\n\n- [`073bb4f45`](https://www.github.com/tauri-apps/tauri/commit/073bb4f459a923541b94970dfa7e087bccaa2cfd) ([#10772](https://www.github.com/tauri-apps/tauri/pull/10772) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Removed the deprecated `webview_fixed_runtime_path` config option, use the `webview_install_mode` instead.\n\n## \\[2.0.0-rc.7]\n\n### Enhancements\n\n- [`da8c9a7d3`](https://www.github.com/tauri-apps/tauri/commit/da8c9a7d3069398c26826aeb082caa44b7c92809) ([#10669](https://www.github.com/tauri-apps/tauri/pull/10669) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Modify both ExportOptions.plist and project.pbxproj to reflect changes for the `IOS_CERTIFICATE`, `IOS_CERTIFICATE_PASSWORD` and `IOS_MOBILE_PROVISION` environment variables.\n\n### Bug Fixes\n\n- [`793ee0531`](https://www.github.com/tauri-apps/tauri/commit/793ee0531730597e6008c9c0dedabbab7a2bef53) ([#10700](https://www.github.com/tauri-apps/tauri/pull/10700) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow hyphens and underscores on app identifiers.\n- [`da8c9a7d3`](https://www.github.com/tauri-apps/tauri/commit/da8c9a7d3069398c26826aeb082caa44b7c92809) ([#10669](https://www.github.com/tauri-apps/tauri/pull/10669) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Synchronize Xcode project changes with the ExportOptions.plist file so `ios build` calls can work with code signing changes made in Xcode.\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.6`\n- Upgraded to `tauri-bundler@2.0.1-rc.5`\n\n### Breaking Changes\n\n- [`da8c9a7d3`](https://www.github.com/tauri-apps/tauri/commit/da8c9a7d3069398c26826aeb082caa44b7c92809) ([#10669](https://www.github.com/tauri-apps/tauri/pull/10669) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) The `IOS_CERTIFICATE`, `IOS_CERTIFICATE_PASSWORD` and `IOS_MOBILE_PROVISION` environment variables are now read by the `ios build` command instead of `ios init`.\n\n## \\[2.0.0-rc.6]\n\n### New Features\n\n- [`da381e07f`](https://www.github.com/tauri-apps/tauri/commit/da381e07f3770988fe6d0859a02331b87cc6723f) ([#10696](https://www.github.com/tauri-apps/tauri/pull/10696) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Inject configured resources on mobile apps.\n\n### Bug Fixes\n\n- [`1a60822a4`](https://www.github.com/tauri-apps/tauri/commit/1a60822a4220b6dbb1ad7295a2e37d6c3004edad) ([#10699](https://www.github.com/tauri-apps/tauri/pull/10699) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Changed the `add` command to use a version requirement that matches the CLI's stable and prerelease numbers.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.5`\n- Upgraded to `tauri-bundler@2.0.1-rc.4`\n\n## \\[2.0.0-rc.5]\n\n### New Features\n\n- [`8d148a9e2`](https://www.github.com/tauri-apps/tauri/commit/8d148a9e2566edebfea2d75f32df7c9396d765a4) ([#10634](https://www.github.com/tauri-apps/tauri/pull/10634) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) Custom sign command with object notation for whitespaces in the command path and arguments.\n\n### Bug Fixes\n\n- [`8ae52a615`](https://www.github.com/tauri-apps/tauri/commit/8ae52a615a11d934930001da63ce6ac8442c7efc) ([#10676](https://www.github.com/tauri-apps/tauri/pull/10676) by [@rdlabo](https://www.github.com/tauri-apps/tauri/../../rdlabo)) Change plugin template call to `register_ios_plugin` params to snake case\n- [`7796a8fc6`](https://www.github.com/tauri-apps/tauri/commit/7796a8fc649cd7397a67048c71f8d1fbf822122a) ([#10687](https://www.github.com/tauri-apps/tauri/pull/10687) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix Swift plugin compilation on older versions.\n- [`9b99ebab1`](https://www.github.com/tauri-apps/tauri/commit/9b99ebab17d6a043d82a7aeecfb76c56a995c287) ([#10431](https://www.github.com/tauri-apps/tauri/pull/10431) by [@mrguiman](https://www.github.com/tauri-apps/tauri/../../mrguiman)) Do not include the target arch when building and archiving the iOS application,\n  which makes Xcode project modifications more flexible.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.4`\n- Upgraded to `tauri-bundler@2.0.1-rc.3`\n\n## \\[2.0.0-rc.4]\n\n### New Features\n\n- [`78e22bedc`](https://www.github.com/tauri-apps/tauri/commit/78e22bedcab5096f1a4e667321fc8b2817b79214) ([#10602](https://www.github.com/tauri-apps/tauri/pull/10602) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add necessary options to `AndroidManifest.xml` in android template to support AndroidTV.\n- [`3bec7b159`](https://www.github.com/tauri-apps/tauri/commit/3bec7b1595e28630a22b9fb16540beafd5eb7969) ([#10544](https://www.github.com/tauri-apps/tauri/pull/10544) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) v1 migrate script now migrates Svelte and Vue.js code.\n\n### Enhancements\n\n- [`bba1a4419`](https://www.github.com/tauri-apps/tauri/commit/bba1a441917defcdf9e88221e9b0e1cdd744e77a) ([#10457](https://www.github.com/tauri-apps/tauri/pull/10457) by [@mmvanheusden](https://www.github.com/tauri-apps/tauri/../../mmvanheusden)) Added `--no-fmt` option to the `add` command to skip formatting the code after applying changes.\n- [`71d00646a`](https://www.github.com/tauri-apps/tauri/commit/71d00646a9b7c52311ba087820e52fd19861b3d8) ([#10504](https://www.github.com/tauri-apps/tauri/pull/10504) by [@fu050409](https://www.github.com/tauri-apps/tauri/../../fu050409)) Improve the `init` command behavior by detecting the project NPM package manager.\n- [`8deb1966a`](https://www.github.com/tauri-apps/tauri/commit/8deb1966ace93d1350f271d525a878ba4b0879ce) ([#10652](https://www.github.com/tauri-apps/tauri/pull/10652) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Infer macOS codesign identity from the `APPLE_CERTIFICATE` environment variable when provided, meaning the identity no longer needs to be provided when signing on CI using that option. If the imported certificate name does not match a provided signingIdentity configuration, an error is returned.\n- [`f35bcda28`](https://www.github.com/tauri-apps/tauri/commit/f35bcda2895a1350df31853da76a051783b9fd3f) ([#10598](https://www.github.com/tauri-apps/tauri/pull/10598) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) `permission add` and `add` commands now check if the plugin is known and if it is either desktop or mobile only\n  we add the permission to a target-specific capability.\n\n### Bug Fixes\n\n- [`f712f31d1`](https://www.github.com/tauri-apps/tauri/commit/f712f31d1d21e85fab99194530702c70e45c63fc) ([#10639](https://www.github.com/tauri-apps/tauri/pull/10639) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Include notarization error output in the error message if it fails.\n- [`9f75d0622`](https://www.github.com/tauri-apps/tauri/commit/9f75d06228fcb7036cf7a4e215abc7bc8d1a0a56) ([#10604](https://www.github.com/tauri-apps/tauri/pull/10604) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes `android dev` port forward failing under some conditions, add better logging and error handling.\n- [`2d47352a0`](https://www.github.com/tauri-apps/tauri/commit/2d47352a07a7d742e62291a5e6810aed79fc8b50) ([#10418](https://www.github.com/tauri-apps/tauri/pull/10418) by [@samkearney](https://www.github.com/tauri-apps/tauri/../../samkearney)) CLI commands will now consistently search for the `app_dir` (the directory containing `package.json`) from the current working directory of the command invocation.\n- [`f4cd68f04`](https://www.github.com/tauri-apps/tauri/commit/f4cd68f040635f019ff989667289cfe9061c7dfb) ([#10600](https://www.github.com/tauri-apps/tauri/pull/10600) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes `android dev` not working when using the builtin dev server.\n- [`9089d9763`](https://www.github.com/tauri-apps/tauri/commit/9089d97637e49bebbe7dba8adc6351e04b53a44d) ([#10605](https://www.github.com/tauri-apps/tauri/pull/10605) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes `[android|ios] build --config <config>` failing to resolve.\n- [`712f1049f`](https://www.github.com/tauri-apps/tauri/commit/712f1049fae74bfda5f360adcee7210cea92fe63) ([#10569](https://www.github.com/tauri-apps/tauri/pull/10569) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes running `ios dev` and `ios build` using `bun`.\n- [`3998570fd`](https://www.github.com/tauri-apps/tauri/commit/3998570fd3d03c1bb282bd060a4aafb4ab5437f9) ([#10540](https://www.github.com/tauri-apps/tauri/pull/10540) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes v1 migration of Cargo.toml dependencies and features.\n- [`3beba92b5`](https://www.github.com/tauri-apps/tauri/commit/3beba92b5bdc62ed00c9f6a9b8f8c05cfa78f8dc) ([#10542](https://www.github.com/tauri-apps/tauri/pull/10542) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes v1 frontend code migration when using plugin default imports.\n- [`10fb027b7`](https://www.github.com/tauri-apps/tauri/commit/10fb027b7590cf2c020b5c220328b9051c05adca) ([#10656](https://www.github.com/tauri-apps/tauri/pull/10656) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Migrate v1 plugins to their v2 releases.\n- [`10fb027b7`](https://www.github.com/tauri-apps/tauri/commit/10fb027b7590cf2c020b5c220328b9051c05adca) ([#10656](https://www.github.com/tauri-apps/tauri/pull/10656) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Prevent duplicate permissions on v1 migration.\n- [`b160f9359`](https://www.github.com/tauri-apps/tauri/commit/b160f9359d6f661d280185d2a2a4bdf280b8e72c) ([#10638](https://www.github.com/tauri-apps/tauri/pull/10638) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Only validate the output iOS library on debug builds.\n- [`4bfe4880f`](https://www.github.com/tauri-apps/tauri/commit/4bfe4880fbef42d1a115f840e712d4a2f59c8ab3) ([#10550](https://www.github.com/tauri-apps/tauri/pull/10550) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) fails to build universal fat binary if main bin is renamed to another name in `Cargo.toml`\n- [`f3837d5b9`](https://www.github.com/tauri-apps/tauri/commit/f3837d5b98f0caebc3337f9a9e8127e7b96c3fc5) ([#10539](https://www.github.com/tauri-apps/tauri/pull/10539) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Improve migration tooling by supporting TOML configs, handle nulls and properly check for updater migration.\n\n### What's Changed\n\n- [`794cf8234`](https://www.github.com/tauri-apps/tauri/commit/794cf8234f8b620c74cbd23cc4b81be9b2edc386) ([#10571](https://www.github.com/tauri-apps/tauri/pull/10571) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Change iOS template default export method from deprecated `development` to `debugging`.\n- [`bfc49cc7a`](https://www.github.com/tauri-apps/tauri/commit/bfc49cc7a1d43e3378e93865b9b37ce4bddfa6e6) ([#10558](https://www.github.com/tauri-apps/tauri/pull/10558) by [@ahqsoftwares](https://www.github.com/tauri-apps/tauri/../../ahqsoftwares)) Remove targetSdk from gradle files\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.3`\n- Upgraded to `tauri-bundler@2.0.1-rc.2`\n- Upgraded to `tauri-macos-sign@0.1.1-rc.0`\n\n## \\[2.0.0-rc.3]\n\n### Enhancements\n\n- [`5f56cb0a8`](https://www.github.com/tauri-apps/tauri/commit/5f56cb0a8b9c6f695bc6439a8db997c98b3a3997) ([#10507](https://www.github.com/tauri-apps/tauri/pull/10507) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Update gradle to 8.9 and the gradle android plugin to 8.5.1 in the android templates (requires latest Android Studio). This should add support for Java 21 but Java 17 keeps being the recommended version.\n\n### Bug Fixes\n\n- [`f5dfc0280`](https://www.github.com/tauri-apps/tauri/commit/f5dfc02800dbd3bdee671b032454c49ac7102fb4) ([#10533](https://www.github.com/tauri-apps/tauri/pull/10533) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue causing `tauri ios init` to fail if `iOS.minimumSystemVersion` was not configured explicitly.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.2`\n- Upgraded to `tauri-bundler@2.0.1-rc.1`\n\n## \\[2.0.0-rc.2]\n\n### New Features\n\n- [`8dc81b6cc`](https://www.github.com/tauri-apps/tauri/commit/8dc81b6cc2b8235b11f74a971d6aa3a5df5e9f68) ([#10496](https://www.github.com/tauri-apps/tauri/pull/10496) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `bundle > ios > template` configuration option for custom Xcode project YML Handlebars template using XcodeGen.\n- [`02c00abc6`](https://www.github.com/tauri-apps/tauri/commit/02c00abc63cf86e9bf9179cbb143d5145a9397b6) ([#10495](https://www.github.com/tauri-apps/tauri/pull/10495) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `bundle > ios > minimumSystemVersion` configuration option.\n\n### Enhancements\n\n- [`8e1e15304`](https://www.github.com/tauri-apps/tauri/commit/8e1e15304e9dc98d7f875fc8dceb7d4ce19adc47) ([#10483](https://www.github.com/tauri-apps/tauri/pull/10483) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Check if the Rust library contains the symbols required at runtime for Android and iOS apps.\n- [`ca6868956`](https://www.github.com/tauri-apps/tauri/commit/ca68689564cbc8dfa9a5220d3daf81a44ef81fcc) ([#10479](https://www.github.com/tauri-apps/tauri/pull/10479) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Check if identifier or lib name changed when running mobile commands.\n\n### Bug Fixes\n\n- [`2e8ab7bac`](https://www.github.com/tauri-apps/tauri/commit/2e8ab7bac12046d734fb07a1b4fe5e03004b305e) ([#10481](https://www.github.com/tauri-apps/tauri/pull/10481) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Migration from v1 to v2 now adds the updater plugin when it is active.\n\n### What's Changed\n\n- [`a3cd9779a`](https://www.github.com/tauri-apps/tauri/commit/a3cd9779a47428e306a628d658740669faf69ccd) ([#10480](https://www.github.com/tauri-apps/tauri/pull/10480) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Removed the `[android|ios] open` command. It is recommended to use `[android|ios] dev --open` or `[android|ios] build --open` instead.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-rc.0`\n- Upgraded to `tauri-utils@2.0.0-rc.1`\n\n## \\[2.0.0-rc.1]\n\n### Bug Fixes\n\n- [`fb1933f17`](https://www.github.com/tauri-apps/tauri/commit/fb1933f17442674e53374578e57a8cad241ac3c6) ([#10467](https://www.github.com/tauri-apps/tauri/pull/10467) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes running `android dev --open`.\n- [`206914fe8`](https://www.github.com/tauri-apps/tauri/commit/206914fe8d97eb61a2ff2a80e94e65e7a42bcea5) ([#10466](https://www.github.com/tauri-apps/tauri/pull/10466) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes running `adb reverse` in Node.js context.\n\n## \\[2.0.0-rc.0]\n\n### New Features\n\n- [`d5511c311`](https://www.github.com/tauri-apps/tauri/commit/d5511c3117b1a117cb0b7359c5fa09aa4795122b) ([#10395](https://www.github.com/tauri-apps/tauri/pull/10395)) Added migration from `2.0.0-beta` to `2.0.0-rc`.\n- [`a5bfbaa62`](https://www.github.com/tauri-apps/tauri/commit/a5bfbaa62b8cd0aacbb33f730d4e30b43c461fe1)([#9962](https://www.github.com/tauri-apps/tauri/pull/9962)) Added `bundle > iOS > frameworks` configuration to define a list of frameworks that are linked to the Xcode project when it is generated.\n\n### Enhancements\n\n- [`a0841d509`](https://www.github.com/tauri-apps/tauri/commit/a0841d509abc43b62bb7c755e8727f3f461862d1) ([#10421](https://www.github.com/tauri-apps/tauri/pull/10421)) Changes the default behavior of the `dev` command to only expose to localhost (`127.0.0.1`) instead of the default system interface.\n\n### Security fixes\n\n- [`289ae5555`](https://www.github.com/tauri-apps/tauri/commit/289ae5555da3802741018015bfe4927729a2eb33) ([#10386](https://www.github.com/tauri-apps/tauri/pull/10386)) Re-enable TLS checks that were previously disabled to support an insecure HTTPS custom protocol on Android which is no longer used.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.0`\n- Upgraded to `tauri-bundler@2.0.0-rc.0`\n\n### Breaking Changes\n\n- [`758d28c8a`](https://www.github.com/tauri-apps/tauri/commit/758d28c8a2d5c9567158e339326b765f72da983e) ([#10390](https://www.github.com/tauri-apps/tauri/pull/10390)) Core plugin permissions are now prefixed with `core:`, the `core:default` permission set can now be used and the `core` plugin name is reserved.\n  The `tauri migrate` tool will automate the migration process, which involves prefixing all `app`, `event`, `image`, `menu`, `path`, `resources`, `tray`, `webview` and `window` permissions with `core:`.\n- [`7ba67b4ac`](https://www.github.com/tauri-apps/tauri/commit/7ba67b4aca8d3f3b1aa5ad08819605029d36e6b4)([#10437](https://www.github.com/tauri-apps/tauri/pull/10437)) `ios dev` and `android dev` now uses localhost for the development server unless running on an iOS device,\n  which still requires connecting to the public network address. To conditionally check this on your frontend\n  framework's configuration you can check for the existence of the `TAURI_DEV_HOST`\n  environment variable instead of checking if the target is iOS or Android (previous recommendation).\n\n## \\[2.0.0-beta.23]\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-beta.19`\n\n## \\[2.0.0-beta.22]\n\n### New Features\n\n- [`c734b9e3c`](https://www.github.com/tauri-apps/tauri/commit/c734b9e3cd6e5a22dfd84ec8a779c2ee9591751b) ([#10072](https://www.github.com/tauri-apps/tauri/pull/10072) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Upgraded the WiX version to 3.14 which fixes vulnerability issues and adds support for Arm targets.\n- [`7c7fa0964`](https://www.github.com/tauri-apps/tauri/commit/7c7fa0964db3403037fdb9a34de2b877ddb8df1c) ([#9963](https://www.github.com/tauri-apps/tauri/pull/9963) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `--method` argument for `ios build` to select the export options' method.\n- [`7c7fa0964`](https://www.github.com/tauri-apps/tauri/commit/7c7fa0964db3403037fdb9a34de2b877ddb8df1c) ([#9963](https://www.github.com/tauri-apps/tauri/pull/9963) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Setup iOS signing by reading `IOS_CERTIFICATE`, `IOS_CERTIFICATE_PASSWORD` and `IOS_MOBILE_PROVISION` environment variables.\n\n### Enhancements\n\n- [`c01e87ad4`](https://www.github.com/tauri-apps/tauri/commit/c01e87ad46e2a5b3fb8d018739e724ef932008d7) ([#10198](https://www.github.com/tauri-apps/tauri/pull/10198) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Enhance `tauri migrate` to also migrate variables like `appWindow`:\n\n  ```ts\n  import { appWindow } from '@tauri-apps/api/window'\n  ```\n\n  will become:\n\n  ```ts\n  import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'\n  const appWindow = getCurrentWebviewWindow()\n  ```\n\n### Bug Fixes\n\n- [`94136578b`](https://www.github.com/tauri-apps/tauri/commit/94136578bc89e4b973c471050ae9c2d83ffcb7c6) ([#10186](https://www.github.com/tauri-apps/tauri/pull/10186) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `migrate` command, migrating incorrect permissions for `clipboard`.\n- [`c01e87ad4`](https://www.github.com/tauri-apps/tauri/commit/c01e87ad46e2a5b3fb8d018739e724ef932008d7) ([#10198](https://www.github.com/tauri-apps/tauri/pull/10198) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `tauri migrate` incorrectly migrating `@tauri-apps/api/tauri` module to just `core` and `@tauri-apps/api/window` to just `webviewWindow`.\n- [`15e125996`](https://www.github.com/tauri-apps/tauri/commit/15e12599667b749c3d7cd2259e6cf7c7b5c6e2be) ([#10234](https://www.github.com/tauri-apps/tauri/pull/10234) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix cli failing to detect the correct cargo target directory when using cargo `--target-dir` flag with `tauri build` or `tauri dev`\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-beta.18`\n- Upgraded to `tauri-macos-sign@0.1.0-beta.0`\n- Upgraded to `tauri-utils@2.0.0-beta.19`\n\n## \\[2.0.0-beta.21]\n\n### New Features\n\n- [`656a64974`](https://www.github.com/tauri-apps/tauri/commit/656a64974468bc207bf39537e02ae179bdee9b83) ([#9318](https://www.github.com/tauri-apps/tauri/pull/9318)) Added a configuration option to disable hardened runtime on macOS codesign.\n\n### Enhancements\n\n- [`f44a2ec47`](https://www.github.com/tauri-apps/tauri/commit/f44a2ec47c13243d472fa08a9df8b20d8490d79f) ([#10030](https://www.github.com/tauri-apps/tauri/pull/10030)) Enhance the plugin template to include `permissions/default.toml` and default capabilities file for the example application.\n\n### Bug Fixes\n\n- [`019a74e97`](https://www.github.com/tauri-apps/tauri/commit/019a74e970958d29cf69a6f24669d603399dcbb3) ([#9931](https://www.github.com/tauri-apps/tauri/pull/9931)) Fix wrong migration of `clipboard` and `globalShortcut` modules\n- [`27838365a`](https://www.github.com/tauri-apps/tauri/commit/27838365a6841b0d3fa645ba2528221d23d4aeb2) ([#10135](https://www.github.com/tauri-apps/tauri/pull/10135)) Fix parsing of cargo profile when using `--profile=<profile>` syntax.\n- [`79542f4d4`](https://www.github.com/tauri-apps/tauri/commit/79542f4d4542bd97451da7605de16e8464d6a06c) ([#10039](https://www.github.com/tauri-apps/tauri/pull/10039)) Fixed an issue that prevented `tauri icon` from rendering `<text>` nodes in SVG files.\n- [`40c0f44e1`](https://www.github.com/tauri-apps/tauri/commit/40c0f44e1c74c18ed0d6c645724d650637725456) ([#9971](https://www.github.com/tauri-apps/tauri/pull/9971)) Changed the deployment target of plugin iOS Xcode project to 13.0 so it works on older iOS releases.\n- [`f56cdc9e3`](https://www.github.com/tauri-apps/tauri/commit/f56cdc9e391c4d55e4d7e935203d0f891864f22d) ([#10016](https://www.github.com/tauri-apps/tauri/pull/10016)) Add missing dependency `libayatana-appindicator3.so.1` for rpm package.\n- [`1601da5b5`](https://www.github.com/tauri-apps/tauri/commit/1601da5b525de05cb813002d611f22ea4217a4fb) ([#10114](https://www.github.com/tauri-apps/tauri/pull/10114)) Removed alpha channel from default icons in iOS template to comply with Apple's human interface guideline\n  (https://developer.apple.com/design/human-interface-guidelines/app-icons), because\n  transparent icons with alpha channel are not allowed, and will be rejected\n  upon upload to Apple appstore.\n\n### What's Changed\n\n- [`3cca5c2be`](https://www.github.com/tauri-apps/tauri/commit/3cca5c2be88bbd52139e7dda371e88510d28bc8e) ([#9924](https://www.github.com/tauri-apps/tauri/pull/9924)) Migrate to new Android buildFeatures.buildConfig format.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-beta.17`\n- Upgraded to `tauri-utils@2.0.0-beta.18`\n- [`f955f7b49`](https://www.github.com/tauri-apps/tauri/commit/f955f7b4903bcea376c0a8b430736f66c8cebf56) ([#9929](https://www.github.com/tauri-apps/tauri/pull/9929)) Switch from `dirs_next` to `dirs` as `dirs_next` is now unmaintained while `dirs` is\n\n### Breaking Changes\n\n- [`911242f09`](https://www.github.com/tauri-apps/tauri/commit/911242f0928e0a2add3595fa9de27850fb875fa6) ([#9883](https://www.github.com/tauri-apps/tauri/pull/9883)) Move updater target from `bundle > targets` to a separate field `bundle > createUpdaterArtifacts`\n\n## \\[2.0.0-beta.20]\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-beta.16`\n\n## \\[2.0.0-beta.19]\n\n### New Features\n\n- [`8a1ae2dea`](https://www.github.com/tauri-apps/tauri/commit/8a1ae2deaf3086e531ada25b1627f900e2e421fb)([#9843](https://www.github.com/tauri-apps/tauri/pull/9843)) Added an option to use a Xcode project for the iOS plugin instead of a plain SwiftPM project.\n- [`9e4b2253f`](https://www.github.com/tauri-apps/tauri/commit/9e4b2253f6ddaccd0f5c88734287bd5c84d4936a)([#9734](https://www.github.com/tauri-apps/tauri/pull/9734)) Add `tauri bundle` subcommand which runs the bundle phase only, best paired with `tauri build --no-bundle`\n\n### Enhancements\n\n- [`8b032c3cf`](https://www.github.com/tauri-apps/tauri/commit/8b032c3cf638e64e50df9d9cf8bc789c7e285987)([#9896](https://www.github.com/tauri-apps/tauri/pull/9896)) Add a blank LaunchScreen.storyboard to the iOS project init template to pass the App Store validation.\n- [`71a5e2ba2`](https://www.github.com/tauri-apps/tauri/commit/71a5e2ba24017b0b9a0e84e053167265b7eddcce)([#9799](https://www.github.com/tauri-apps/tauri/pull/9799)) On Android, allow using Kotlin keywords as identifiers and escape them in templates.\n- [`9970d88be`](https://www.github.com/tauri-apps/tauri/commit/9970d88becee1560a4b2a7ffc1fe65991a42a8c9)([#9892](https://www.github.com/tauri-apps/tauri/pull/9892)) Update to latest gradle.\n\n### What's Changed\n\n- [`80aa50498`](https://www.github.com/tauri-apps/tauri/commit/80aa504987dd9cfa59aa5848c4d7960e1d58d0e6)([#9870](https://www.github.com/tauri-apps/tauri/pull/9870)) Updated Android target SDK to 34.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-beta.15`\n- Upgraded to `tauri-utils@2.0.0-beta.17`\n\n### Breaking Changes\n\n- [`265c23886`](https://www.github.com/tauri-apps/tauri/commit/265c23886ee5efbcc6d7188ff5c84cb32fa82aea)([#9375](https://www.github.com/tauri-apps/tauri/pull/9375)) Avoid renaming main binary to product name and perserve the name generated by cargo.\n- [`1df5cdeb0`](https://www.github.com/tauri-apps/tauri/commit/1df5cdeb06f5464e0eec4055e21b7b7bc8739eed)([#9858](https://www.github.com/tauri-apps/tauri/pull/9858)) Use `tauri.conf.json > identifier` to set the `PackageName` in Android and `BundleId` in iOS.\n\n## \\[2.0.0-beta.18]\n\n### Bug Fixes\n\n- [`beda18bce`](https://www.github.com/tauri-apps/tauri/commit/beda18bce95fd6e10543b2d8f1eca5fb7ca0655b)([#9855](https://www.github.com/tauri-apps/tauri/pull/9855)) Fixed an issue that caused `tauri add` to fail for multiple rust-only and platform-specific plugins.\n- [`4a33bc6a6`](https://www.github.com/tauri-apps/tauri/commit/4a33bc6a62d2ed9371191c8a7f78ff3f33930455)([#9553](https://www.github.com/tauri-apps/tauri/pull/9553)) Fixes `pnpm` detection when initializing and running a mobile project.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-beta.14`\n- Upgraded to `tauri-utils@2.0.0-beta.16`\n\n## \\[2.0.0-beta.17]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.15`\n- Upgraded to `tauri-bundler@2.0.1-beta.13`\n\n## \\[2.0.0-beta.16]\n\n### Bug Fixes\n\n- [`97ec422f2`](https://www.github.com/tauri-apps/tauri/commit/97ec422f22d069b9570931834241c7e47bc68cc3)([#9638](https://www.github.com/tauri-apps/tauri/pull/9638)) Exit `tauri icon` with non-zero code when it fails.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.14`\n- Upgraded to `tauri-bundler@2.0.1-beta.12`\n\n## \\[2.0.0-beta.15]\n\n### Bug Fixes\n\n- [`3f0805488`](https://www.github.com/tauri-apps/tauri/commit/3f0805488506e013e15cfb3cc1cfc8c2c5f84bd2)([#9603](https://www.github.com/tauri-apps/tauri/pull/9603)) Use `windows-sys` crate instead of `winapi` which fixes installing the published cli from crates.io using `cargo install tauri-cli --version \"^2.0.0-beta\"`.\n\n## \\[2.0.0-beta.14]\n\n### Enhancements\n\n- [`8a63ceb4f`](https://www.github.com/tauri-apps/tauri/commit/8a63ceb4f31c422311b0f7dff173a9c8c0e1a604)([#9473](https://www.github.com/tauri-apps/tauri/pull/9473)) Ignore `.DS_Store` by default for `tauri dev` hot reloads.\n\n### Bug Fixes\n\n- [`e64b8f1dc`](https://www.github.com/tauri-apps/tauri/commit/e64b8f1dcedad3222f46755bf6f30392a7ec2f90)([#9479](https://www.github.com/tauri-apps/tauri/pull/9479)) Upgrade `heck` to v0.5 to better support Chinese and Japanese product name, because Chinese do not have word separation.\n- [`aaa332c6e`](https://www.github.com/tauri-apps/tauri/commit/aaa332c6e78c956debd11efda021a0406621a01d)([#9540](https://www.github.com/tauri-apps/tauri/pull/9540)) Fix `tauri migrate` trying to migrate to a non-existing plugin.\n- [`e64b8f1dc`](https://www.github.com/tauri-apps/tauri/commit/e64b8f1dcedad3222f46755bf6f30392a7ec2f90)([#9479](https://www.github.com/tauri-apps/tauri/pull/9479)) Fixed an issue causing the `build.runner` and `build.features` configs to not take effect.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-beta.10`\n- Upgraded to `tauri-utils@2.0.0-beta.13`\n\n## \\[2.0.0-beta.13]\n\n### Bug Fixes\n\n- [`73c1c2d33`](https://www.github.com/tauri-apps/tauri/commit/73c1c2d33872651c32c761c838714b684980c668)([#9457](https://www.github.com/tauri-apps/tauri/pull/9457)) Gracefully handle Non-UTF8 files when using `tauri migrate`\n- [`9331435a5`](https://www.github.com/tauri-apps/tauri/commit/9331435a50cc3769720bd2671da8510699d28671)([#9412](https://www.github.com/tauri-apps/tauri/pull/9412)) Fix `tauri info` crashing when Node.js is not installed.\n\n### What's Changed\n\n- [`8f4b1050c`](https://www.github.com/tauri-apps/tauri/commit/8f4b1050c4de0e9194680408ff3a6902b67045f8)([#9459](https://www.github.com/tauri-apps/tauri/pull/9459)) Show full expected path of `frontendDist` when if can't be found.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-beta.9`\n- Upgraded to `tauri-utils@2.0.0-beta.12`\n\n## \\[2.0.0-beta.12]\n\n### New Features\n\n- [`93e0e1392`](https://www.github.com/tauri-apps/tauri/commit/93e0e1392ec341fcadf696c03e78f0ca1e73c941) Support specifying a version for `tauri add` subcommand, for example: `tauri add window-state@2.0.0-beta.2`\n\n### Enhancements\n\n- [`6703b7cbc`](https://www.github.com/tauri-apps/tauri/commit/6703b7cbca3ade84e534c121c63fb22c5f7abbfd)([#9310](https://www.github.com/tauri-apps/tauri/pull/9310)) Use `$CARGO_MANIFEST_DIR` when including templates at build-time.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-beta.8`\n- Upgraded to `tauri-utils@2.0.0-beta.11`\n\n## \\[2.0.0-beta.11]\n\n### Enhancements\n\n- [`ac76a22f3`](https://www.github.com/tauri-apps/tauri/commit/ac76a22f383028d9bacdedebeb41d3fca5ec9dac)([#9183](https://www.github.com/tauri-apps/tauri/pull/9183)) Allow empty responses for `devUrl`, `beforeDevCommand` and `beforeBuildCommands` questions in `tauri init`.\n- [`b525ddadf`](https://www.github.com/tauri-apps/tauri/commit/b525ddadf7e7588c3e195cf0f821c9862c545d06)([#9237](https://www.github.com/tauri-apps/tauri/pull/9237)) `openssl` is no longer a required dependency on macOS.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-beta.7`\n\n## \\[2.0.0-beta.10]\n\n### New Features\n\n- [`7213b9e47`](https://www.github.com/tauri-apps/tauri/commit/7213b9e47242bef814aa7257e0bf84631bf5fe7e)([#9124](https://www.github.com/tauri-apps/tauri/pull/9124)) Add default permission for a plugin to capabilities when using `tauri add <plugin>`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.10`\n- Upgraded to `tauri-bundler@2.0.1-beta.6`\n\n## \\[2.0.0-beta.9]\n\n### Bug Fixes\n\n- [`c3ea3a2b7`](https://www.github.com/tauri-apps/tauri/commit/c3ea3a2b7d2fe3085f05b63dd1feb962beb4b7b3)([#9126](https://www.github.com/tauri-apps/tauri/pull/9126)) Fix bundling when `plugins > updater > windows > installerArgs` are set in `tauri.conf.json`\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.9`\n- Upgraded to `tauri-bundler@2.0.1-beta.5`\n\n## \\[2.0.0-beta.8]\n\n### Enhancements\n\n- [`3e472d0af`](https://www.github.com/tauri-apps/tauri/commit/3e472d0afcd67545dd6d9f18d304580a3b2759a8)([#9115](https://www.github.com/tauri-apps/tauri/pull/9115)) Changed the permission and capability platforms to be optional.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.8`\n- Upgraded to `tauri-bundler@2.0.1-beta.4`\n\n## \\[2.0.0-beta.7]\n\n### Enhancements\n\n- [`c68218b36`](https://www.github.com/tauri-apps/tauri/commit/c68218b362c417b62e56c7a2b5b32c13fe035a83)([#8990](https://www.github.com/tauri-apps/tauri/pull/8990)) Add `--no-bundle` flag for `tauri build` command to skip bundling. Previously `none` was used to skip bundling, it will now be treated as invalid format and a warning will be emitted instead.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.7`\n- Upgraded to `tauri-bundler@2.0.1-beta.3`\n- [`4f7894176`](https://www.github.com/tauri-apps/tauri/commit/4f789417630b8a32dcf9c0daec448ea8182daca1)([#9034](https://www.github.com/tauri-apps/tauri/pull/9034)) Update dependencies, fix `log` compilation issue.\n\n## \\[2.0.0-beta.6]\n\n### Bug Fixes\n\n- [`f5f3ed5f`](https://www.github.com/tauri-apps/tauri/commit/f5f3ed5f6faa0b51e83244acc15e9006299a03ba)([#9009](https://www.github.com/tauri-apps/tauri/pull/9009)) Fixes Android and iOS project initialization when the Tauri CLI is on a different disk partition.\n- [`d7d03c71`](https://www.github.com/tauri-apps/tauri/commit/d7d03c7197212f3a5bebe08c929417d60927eb89)([#9017](https://www.github.com/tauri-apps/tauri/pull/9017)) Fixes dev watcher on mobile dev.\n- [`b658ded6`](https://www.github.com/tauri-apps/tauri/commit/b658ded614cfc169228cb22ad5bfc64478dfe161)([#9015](https://www.github.com/tauri-apps/tauri/pull/9015)) Fixes truncation of existing BuildTask.kt when running `tauri android init`.\n\n### What's Changed\n\n- [`3657ad82`](https://www.github.com/tauri-apps/tauri/commit/3657ad82f88ce528551d032d521c52eed3f396b4)([#9008](https://www.github.com/tauri-apps/tauri/pull/9008)) Updates to new ACL manifest path.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.6`\n- Upgraded to `tauri-bundler@2.0.1-beta.2`\n\n## \\[2.0.0-beta.5]\n\n### New Features\n\n- [`06d63d67`](https://www.github.com/tauri-apps/tauri/commit/06d63d67a061459dd533ddcae755922427a6dfc5)([#8827](https://www.github.com/tauri-apps/tauri/pull/8827)) Add new subcommands for managing permissions and cababilities:\n\n  - `tauri permission new`\n  - `tauri permission add`\n  - `tauri permission rm`\n  - `tauri permission ls`\n  - `tauri capability new`\n\n### Breaking Changes\n\n- [`b9e6a018`](https://www.github.com/tauri-apps/tauri/commit/b9e6a01879d9233040f3d3fab11c59e70563da7e)([#8937](https://www.github.com/tauri-apps/tauri/pull/8937)) The `custom-protocol` Cargo feature is no longer required on your application and is now ignored. To check if running on production, use `#[cfg(not(dev))]` instead of `#[cfg(feature = \"custom-protocol\")]`.\n\n### Enhancements\n\n- [`9be314f0`](https://www.github.com/tauri-apps/tauri/commit/9be314f07a4ca5d14433d41919492f3e91b5536a)([#8951](https://www.github.com/tauri-apps/tauri/pull/8951)) Add plugins to `Cargo.toml` when using `tauri migrate`\n\n### Bug Fixes\n\n- [`cbd9755e`](https://www.github.com/tauri-apps/tauri/commit/cbd9755e0926a7e47e59deb50f4bb93d621791a5)([#8977](https://www.github.com/tauri-apps/tauri/pull/8977)) Fixes process logs not showing on `ios dev`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.5`\n- Upgraded to `tauri-bundler@2.0.1-beta.1`\n\n## \\[2.0.0-beta.4]\n\n### Bug Fixes\n\n- [`e538ba58`](https://www.github.com/tauri-apps/tauri/commit/e538ba586c5b8b50955586c8ef2704adb5d7cc43)([#8949](https://www.github.com/tauri-apps/tauri/pull/8949)) Fixes android and iOS process spawning not working on Node.js.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.1-beta.0`\n- Upgraded to `tauri-utils@2.0.0-beta.4`\n\n### Breaking Changes\n\n- [`a76fb118`](https://www.github.com/tauri-apps/tauri/commit/a76fb118ce2de22e1bdb4216bf0ac01dfc3e5799)([#8950](https://www.github.com/tauri-apps/tauri/pull/8950)) Changed the capability format to allow configuring both `remote: { urls: Vec<String> }` and `local: bool (default: true)` instead of choosing one on the `context` field.\n\n## \\[2.0.0-beta.3]\n\n### Enhancements\n\n- [`a029b9f7`](https://www.github.com/tauri-apps/tauri/commit/a029b9f77e432533a403c292940fa3efba68692c)([#8910](https://www.github.com/tauri-apps/tauri/pull/8910)) Setting up code signing is no longer required on iOS when using the simulator.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.3`\n- Upgraded to `tauri-bundler@2.0.0-beta.3`\n\n## \\[2.0.0-beta.2]\n\n### Enhancements\n\n- [`83a68deb`](https://www.github.com/tauri-apps/tauri/commit/83a68deb5676d39cd4728d2e140f6b46d5f787ed)([#8797](https://www.github.com/tauri-apps/tauri/pull/8797)) Update app template following capabilities configuration change.\n\n### Bug Fixes\n\n- [`aa06a053`](https://www.github.com/tauri-apps/tauri/commit/aa06a0534cf224038866e0ddd6910ea873b2574d)([#8810](https://www.github.com/tauri-apps/tauri/pull/8810)) Fix `tauri plugin android init` printing invalid code that has a missing closing `\"`.\n- [`3cee26a5`](https://www.github.com/tauri-apps/tauri/commit/3cee26a58ab44639a12c7816f4096655daa327a4)([#8865](https://www.github.com/tauri-apps/tauri/pull/8865)) On Windows, fixed `tauri info` fails to detect the build tool when the system language is CJK.\n- [`052e8b43`](https://www.github.com/tauri-apps/tauri/commit/052e8b4311d9f0f963a2866163be27bfd8f70c60)([#8838](https://www.github.com/tauri-apps/tauri/pull/8838)) Downgrade minisign dependency fixing updater signing key bug and prevent it from happening in the future.\n- [`fb0d9971`](https://www.github.com/tauri-apps/tauri/commit/fb0d997117367e3387896bcd0fba004579475c40)([#8783](https://www.github.com/tauri-apps/tauri/pull/8783)) Fixes a regression on the `--config` argument not accepting file paths.\n- [`baca704d`](https://www.github.com/tauri-apps/tauri/commit/baca704d4b5fae239fc320d10140f35bd705bfbb)([#8768](https://www.github.com/tauri-apps/tauri/pull/8768)) Do not migrate updater configuration if the active flag is set to false.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.2`\n- Upgraded to `tauri-bundler@2.0.0-beta.2`\n\n## \\[2.0.0-beta.1]\n\n### Enhancements\n\n- [`4e101f80`](https://www.github.com/tauri-apps/tauri/commit/4e101f801657e7d01ce8c22f9c6468067d0caab2)([#8756](https://www.github.com/tauri-apps/tauri/pull/8756)) Moved the capability JSON schema to the `src-tauri/gen` folder so it's easier to track changes on the `capabilities` folder.\n- [`4e101f80`](https://www.github.com/tauri-apps/tauri/commit/4e101f801657e7d01ce8c22f9c6468067d0caab2)([#8756](https://www.github.com/tauri-apps/tauri/pull/8756)) Update app and plugin templates following generated files change from tauri-build and tauri-plugin.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.1`\n- Upgraded to `tauri-bundler@2.0.0-beta.1`\n\n## \\[2.0.0-beta.0]\n\n### New Features\n\n- [`7fcc0bcd`](https://www.github.com/tauri-apps/tauri/commit/7fcc0bcd3482bbc8771e330942ef6cd78cc8ec35)([#8490](https://www.github.com/tauri-apps/tauri/pull/8490)) Add plugin initialization rust code when using `tauri add`\n- [`1878766f`](https://www.github.com/tauri-apps/tauri/commit/1878766f7f81a03b0f0b87ec33ee113d7aa7a902)([#8667](https://www.github.com/tauri-apps/tauri/pull/8667)) Migrate the allowlist config to the new capability file format.\n\n### Enhancements\n\n- [`d6c7568c`](https://www.github.com/tauri-apps/tauri/commit/d6c7568c27445653edf570f3969163bc358ba2ba)([#8720](https://www.github.com/tauri-apps/tauri/pull/8720)) Add `files` option to the AppImage Configuration.\n- [`b3209bb2`](https://www.github.com/tauri-apps/tauri/commit/b3209bb28bb379d5046d577c7e42319d6e76ced0)([#8688](https://www.github.com/tauri-apps/tauri/pull/8688)) Ignore global `.gitignore` when searching for tauri directory.\n- [`e691208e`](https://www.github.com/tauri-apps/tauri/commit/e691208e7b39bb8e3ffc9bf66cff731a5025ef16)([#7837](https://www.github.com/tauri-apps/tauri/pull/7837)) Prevent unneeded double Cargo.toml rewrite on `dev` and `build`.\n- [`f492efd7`](https://www.github.com/tauri-apps/tauri/commit/f492efd7144fdd8d25cac0c4d2389a95ab75fb02)([#8666](https://www.github.com/tauri-apps/tauri/pull/8666)) Update app and plugin template following the new access control permission model.\n\n### Bug Fixes\n\n- [`9cb9aa79`](https://www.github.com/tauri-apps/tauri/commit/9cb9aa7978f231f7da238b33d6ab33fdd2d2c842)([#8672](https://www.github.com/tauri-apps/tauri/pull/8672)) Allow license field in Cargo.toml to be `{ workspace = true }`\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.0`\n- Upgraded to `tauri-bundler@2.0.0-beta.0`\n\n### Breaking Changes\n\n- [`8de308d1`](https://www.github.com/tauri-apps/tauri/commit/8de308d1bf6a855d7a26af58bd0e744938ba47d8)([#8723](https://www.github.com/tauri-apps/tauri/pull/8723)) Restructured Tauri config per [RFC#5](https://github.com/tauri-apps/rfcs/blob/f3e82a6b0c5390401e855850d47dc7b7d9afd684/texts/0005-tauri-config-restructure.md):\n\n  - Moved `package.productName`, `package.version` and `tauri.bundle.identifier` fields to the top-level.\n  - Removed `package` object.\n  - Renamed `tauri` object to `app`.\n  - Moved `tauri.bundle` object to the top-level.\n  - Renamed `build.distDir` field to `frontendDist`.\n  - Renamed `build.devPath` field to `devUrl` and will no longer accepts paths, it will only accept URLs.\n  - Moved `tauri.pattern` to `app.security.pattern`.\n  - Removed `tauri.bundle.updater` object, and its fields have been moved to the updater plugin under `plugins.updater` object.\n  - Moved `build.withGlobalTauri` to `app.withGlobalTauri`.\n  - Moved `tauri.bundle.dmg` object to `bundle.macOS.dmg`.\n  - Moved `tauri.bundle.deb` object to `bundle.linux.deb`.\n  - Moved `tauri.bundle.appimage` object to `bundle.linux.appimage`.\n  - Removed all license fields from each bundle configuration object and instead added `bundle.license` and `bundle.licenseFile`.\n  - Renamed `AppUrl` to `FrontendDist` and refactored its variants to be more explicit.\n\n## \\[2.0.0-alpha.21]\n\n### New Features\n\n- [`27bad32d`](https://www.github.com/tauri-apps/tauri/commit/27bad32d4d4acca8155b20225d529d540fb9aaf4)([#7798](https://www.github.com/tauri-apps/tauri/pull/7798)) Add `files` object on the `tauri > bundle > macOS` configuration option.\n- [`0ec28c39`](https://www.github.com/tauri-apps/tauri/commit/0ec28c39f478de7199a66dd75e8642e1aa1344e6)([#8529](https://www.github.com/tauri-apps/tauri/pull/8529)) Include tauri-build on the migration script.\n\n### Enhancements\n\n- [`091100ac`](https://www.github.com/tauri-apps/tauri/commit/091100acbb507b51de39fb1446f685926f888fd2)([#5202](https://www.github.com/tauri-apps/tauri/pull/5202)) Add RPM packaging\n\n### Bug Fixes\n\n- [`4f73057e`](https://www.github.com/tauri-apps/tauri/commit/4f73057e6fd4c137bc112367fb91f5fc0c8a39f6)([#8486](https://www.github.com/tauri-apps/tauri/pull/8486)) Prevent `Invalid target triple` warnings and correctly set `TAURI_ENV_` vars when target triple contains 4 components.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.0-alpha.14`\n- Upgraded to `tauri-utils@2.0.0-alpha.13`\n\n### Breaking Changes\n\n- [`4f73057e`](https://www.github.com/tauri-apps/tauri/commit/4f73057e6fd4c137bc112367fb91f5fc0c8a39f6)([#8486](https://www.github.com/tauri-apps/tauri/pull/8486)) Removed `TAURI_ENV_PLATFORM_TYPE` which will not be set for CLI hook commands anymore, use `TAURI_ENV_PLATFORM` instead. Also Changed value of `TAURI_ENV_PLATFORM` and `TAURI_ENV_ARCH` values to match the target triple more accurately:\n\n  - `darwin` and `androideabi` are no longer replaced with `macos` and `android` in `TAURI_ENV_PLATFORM`.\n  - `i686` and `i586` are no longer replaced with `x86` in `TAURI_ENV_ARCH`.\n\n## \\[2.0.0-alpha.20]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.12`\n- Upgraded to `tauri-bundler@2.0.0-alpha.13`\n\n## \\[2.0.0-alpha.19]\n\n### Enhancements\n\n- [`803c3a79`](https://www.github.com/tauri-apps/tauri/commit/803c3a794d96c1f0b3361ca98f56e8bcf5038ede)([#8327](https://www.github.com/tauri-apps/tauri/pull/8327)) Read the following env vars when using the `tauri signer sign` command to make it easier to use in CI.\n\n  - `TAURI_PRIVATE_KEY`\n  - `TAURI_PRIVATE_KEY_PASSWORD`\n  - `TAURI_PRIVATE_KEY_PATH`\n\n## \\[2.0.0-alpha.18]\n\n### New Features\n\n- [`50f7ccbb`](https://www.github.com/tauri-apps/tauri/commit/50f7ccbbf3467f33cc7dd1cca53125fec6eda1c6)([#6444](https://www.github.com/tauri-apps/tauri/pull/6444)) Add suport to SVG input image for the `tauri icon` command.\n- [`25e5f91d`](https://www.github.com/tauri-apps/tauri/commit/25e5f91dae7fe2bbc1ba4317d5d829402bfd1d50)([#8200](https://www.github.com/tauri-apps/tauri/pull/8200)) Merge `src-tauri/Info.plist` and `src-tauri/Info.ios.plist` with the iOS project plist file.\n\n### Enhancements\n\n- [`01a7a983`](https://www.github.com/tauri-apps/tauri/commit/01a7a983aba2946b455a608b8a6a4b08cb25fc11)([#8128](https://www.github.com/tauri-apps/tauri/pull/8128)) Transform paths to relative to the mobile project for the IDE script runner script.\n\n### Bug Fixes\n\n- [`88dac86f`](https://www.github.com/tauri-apps/tauri/commit/88dac86f3b301d1919df6473a9e20f46b560f29b)([#8149](https://www.github.com/tauri-apps/tauri/pull/8149)) Ensure `tauri add` prints `rust_code` with plugin name in snake case.\n- [`977d0e52`](https://www.github.com/tauri-apps/tauri/commit/977d0e52f14b1ad01c86371765ef25b36572459e)([#8202](https://www.github.com/tauri-apps/tauri/pull/8202)) Fixes `android build --open` and `ios build --open` IDE failing to read CLI options.\n- [`bfbbefdb`](https://www.github.com/tauri-apps/tauri/commit/bfbbefdb9e13ed1f42f6db7fa9ceaa84db1267e9)([#8161](https://www.github.com/tauri-apps/tauri/pull/8161)) Fix invalid plugin template.\n- [`92b50a3a`](https://www.github.com/tauri-apps/tauri/commit/92b50a3a398c9d55b6992a8f5c2571e4d72bdaaf)([#8209](https://www.github.com/tauri-apps/tauri/pull/8209)) Added support to Xcode's archive. This requires regenerating the Xcode project.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.0-alpha.12`\n- Upgraded to `tauri-utils@2.0.0-alpha.11`\n\n## \\[2.0.0-alpha.17]\n\n### Enhancements\n\n- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.0-alpha.11`\n- Upgraded to `tauri-utils@2.0.0-alpha.10`\n\n### Breaking Changes\n\n- [`198abe3c`](https://www.github.com/tauri-apps/tauri/commit/198abe3c2cae06dacab860b3a93f715dcf529a95)([#8076](https://www.github.com/tauri-apps/tauri/pull/8076)) Updated the mobile plugin templates following the tauri v2.0.0-alpha.17 changes.\n\n## \\[2.0.0-alpha.16]\n\n### New Features\n\n- [`8b166e9b`](https://www.github.com/tauri-apps/tauri/commit/8b166e9bf82e69ddb3200a3a825614980bd8d433)([#7949](https://www.github.com/tauri-apps/tauri/pull/7949)) Add `--no-dev-server-wait` option to skip waiting for the dev server to start when using `tauri dev`.\n- [`880266a7`](https://www.github.com/tauri-apps/tauri/commit/880266a7f697e1fe58d685de3bb6836ce5251e92)([#8031](https://www.github.com/tauri-apps/tauri/pull/8031)) Bump the MSRV to 1.70.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.9`\n- Upgraded to `tauri-bundler@2.0.0-alpha.10`\n\n### Breaking Changes\n\n- [`8b166e9b`](https://www.github.com/tauri-apps/tauri/commit/8b166e9bf82e69ddb3200a3a825614980bd8d433)([#7949](https://www.github.com/tauri-apps/tauri/pull/7949)) Changed a number of environment variables used by tauri CLI for consistency and clarity:\n\n  - `TAURI_PRIVATE_KEY` -> `TAURI_SIGNING_PRIVATE_KEY`\n  - `TAURI_KEY_PASSWORD` -> `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`\n  - `TAURI_SKIP_DEVSERVER_CHECK` -> `TAURI_CLI_NO_DEV_SERVER_WAIT`\n  - `TAURI_DEV_SERVER_PORT` -> `TAURI_CLI_PORT`\n  - `TAURI_PATH_DEPTH` -> `TAURI_CLI_CONFIG_DEPTH`\n  - `TAURI_FIPS_COMPLIANT` -> `TAURI_BUNDLER_WIX_FIPS_COMPLIANT`\n  - `TAURI_DEV_WATCHER_IGNORE_FILE` -> `TAURI_CLI_WATCHER_IGNORE_FILENAME`\n  - `TAURI_TRAY` -> `TAURI_LINUX_AYATANA_APPINDICATOR`\n  - `TAURI_APPLE_DEVELOPMENT_TEAM` -> `APPLE_DEVELOPMENT_TEAM`\n- [`4caa1cca`](https://www.github.com/tauri-apps/tauri/commit/4caa1cca990806f2c2ef32d5dabaf56e82f349e6)([#7990](https://www.github.com/tauri-apps/tauri/pull/7990)) The `tauri plugin` subcommand is receving a couple of consitency and quality of life improvements:\n\n  - Renamed `tauri plugin android/ios add` command to `tauri plugin android/ios init` to match the `tauri plugin init` command.\n  - Removed the `-n/--name` argument from the `tauri plugin init`, `tauri plugin android/ios init`, and is now parsed from the first positional argument.\n  - Added `tauri plugin new` to create a plugin in a new directory.\n  - Changed `tauri plugin init` to initalize a plugin in an existing directory (defaults to current directory) instead of creating a new one.\n  - Changed `tauri plugin init` to NOT generate mobile projects by default, you can opt-in to generate them using `--android` and `--ios` flags or `--mobile` flag or initalize them later using `tauri plugin android/ios init`.\n- [`8b166e9b`](https://www.github.com/tauri-apps/tauri/commit/8b166e9bf82e69ddb3200a3a825614980bd8d433)([#7949](https://www.github.com/tauri-apps/tauri/pull/7949)) Removed checking for a new version of the CLI.\n- [`ebcc21e4`](https://www.github.com/tauri-apps/tauri/commit/ebcc21e4b95f4e8c27639fb1bca545b432f52d5e)([#8057](https://www.github.com/tauri-apps/tauri/pull/8057)) Renamed the beforeDevCommand, beforeBuildCommand and beforeBundleCommand hooks environment variables from `TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG` to `TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG` to differentiate the prefix with other CLI environment variables.\n\n## \\[2.0.0-alpha.15]\n\n### New Features\n\n- [`b2f17723`](https://www.github.com/tauri-apps/tauri/commit/b2f17723a415f04c2620132a6305eb138d7cb47f)([#7971](https://www.github.com/tauri-apps/tauri/pull/7971)) Use `devicectl` to connect to iOS 17+ devices on macOS 14+.\n\n### Bug Fixes\n\n- [`100d9ede`](https://www.github.com/tauri-apps/tauri/commit/100d9ede35995d9db21d2087dd5606adfafb89a5)([#7802](https://www.github.com/tauri-apps/tauri/pull/7802)) Properly read platform-specific configuration files for mobile targets.\n- [`228e5a4c`](https://www.github.com/tauri-apps/tauri/commit/228e5a4c76ad5f97409c912d07699b49ba4bb162)([#7902](https://www.github.com/tauri-apps/tauri/pull/7902)) Fixes `icon` command not writing files to the correct Android project folders.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.8`\n- Upgraded to `tauri-bundler@2.0.0-alpha.9`\n\n## \\[2.0.0-alpha.14]\n\n### Breaking Changes\n\n- [`d5074af5`](https://www.github.com/tauri-apps/tauri/commit/d5074af562b2b5cb6c5711442097c4058af32db6)([#7801](https://www.github.com/tauri-apps/tauri/pull/7801)) The custom protocol on Android now uses the `http` scheme instead of `https`.\n\n## \\[2.0.0-alpha.13]\n\n### Breaking Changes\n\n- [`4cb51a2d`](https://www.github.com/tauri-apps/tauri/commit/4cb51a2d56cfcae0749062c79ede5236bd8c02c2)([#7779](https://www.github.com/tauri-apps/tauri/pull/7779)) The custom protocol on Windows now uses the `http` scheme instead of `https`.\n- [`974e38b4`](https://www.github.com/tauri-apps/tauri/commit/974e38b4ddc198530aa977ec77d513b76013b9f3)([#7744](https://www.github.com/tauri-apps/tauri/pull/7744)) Renamed the `plugin add` command to `add`.\n\n## \\[2.0.0-alpha.12]\n\n### Bug Fixes\n\n- [`b75a1210`](https://www.github.com/tauri-apps/tauri/commit/b75a1210bed589187678861d7314ae6279bf7c87)([#7762](https://www.github.com/tauri-apps/tauri/pull/7762)) Fixes a regression on alpha.11 where iOS logs aren't being displayed when using `ios dev` with a real device.\n- [`8faa5a4a`](https://www.github.com/tauri-apps/tauri/commit/8faa5a4a1238a44ca7b54d2084aaed553ac2a1ba)([#7765](https://www.github.com/tauri-apps/tauri/pull/7765)) Ensure asset directory exists on the iOS project.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@2.0.0-alpha.8`\n\n## \\[2.0.0-alpha.11]\n\n### New Features\n\n- [`522de0e7`](https://www.github.com/tauri-apps/tauri/commit/522de0e78891d0bdf6387a5118985fc41a11baeb)([#7447](https://www.github.com/tauri-apps/tauri/pull/7447)) Expose an environment variable `TAURI_${PLUGIN_NAME}_PLUGIN_CONFIG` for each defined plugin configuration object.\n- [`c7dacca4`](https://www.github.com/tauri-apps/tauri/commit/c7dacca4661c6ddf937c1a3dd3ace896d5baf40c)([#7446](https://www.github.com/tauri-apps/tauri/pull/7446)) Expose the `TAURI_IOS_PROJECT_PATH` and `TAURI_IOS_APP_NAME` environment variables when using `ios` commands.\n- [`aa94f719`](https://www.github.com/tauri-apps/tauri/commit/aa94f7197e4345a7cab1617272b10895859674f9)([#7445](https://www.github.com/tauri-apps/tauri/pull/7445)) Generate empty entitlements file for the iOS project.\n- [`d010bc07`](https://www.github.com/tauri-apps/tauri/commit/d010bc07b81116bef769b64cdc19b23dff762d48)([#7554](https://www.github.com/tauri-apps/tauri/pull/7554)) Set the iOS project PRODUCT_NAME value to the string under `tauri.conf.json > package > productName` if it is set.\n- [`8af24974`](https://www.github.com/tauri-apps/tauri/commit/8af2497496f11ee481472dfdc3125757346c1a3e)([#7561](https://www.github.com/tauri-apps/tauri/pull/7561)) The `migrate` command now automatically reads all JavaScript files and updates `@tauri-apps/api` import paths and install the missing plugins.\n\n### Enhancements\n\n- [`fbeb5b91`](https://www.github.com/tauri-apps/tauri/commit/fbeb5b9185baeda19e865228179e3e44c165f1d9)([#7170](https://www.github.com/tauri-apps/tauri/pull/7170)) Update migrate command to update the configuration CSP to include `ipc:` on the `connect-src` directive, needed by the new IPC using custom protocols.\n\n### Bug Fixes\n\n- [`5eb85543`](https://www.github.com/tauri-apps/tauri/commit/5eb85543313eaf7ca2289898652b1b11dc776608)([#7282](https://www.github.com/tauri-apps/tauri/pull/7282)) Fix `tauri info` failing when there is no available iOS code signing certificate.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.7`\n- Upgraded to `tauri-bundler@2.0.0-alpha.7`\n\n## \\[2.0.0-alpha.10]\n\n### New Features\n\n- [`7e5905ae`](https://www.github.com/tauri-apps/tauri/commit/7e5905ae1d56b920de0e821be28036cbbe302518)([#7023](https://www.github.com/tauri-apps/tauri/pull/7023)) Added `tauri plugin add` command to add a plugin to the Tauri project.\n- [`b0f94775`](https://www.github.com/tauri-apps/tauri/commit/b0f947752a315b7b89c5979de50157f997f1dd6e)([#7008](https://www.github.com/tauri-apps/tauri/pull/7008)) Added `migrate` command.\n\n### Enhancements\n\n- [`aa6c9164`](https://www.github.com/tauri-apps/tauri/commit/aa6c9164e63b5316d690f25b1c118f1b12310570)([#7007](https://www.github.com/tauri-apps/tauri/pull/7007)) Don't build library files when building desktop targets.\n- [`a28fdf7e`](https://www.github.com/tauri-apps/tauri/commit/a28fdf7ec71bf6db2498569004de83318b6d25ac)([#7044](https://www.github.com/tauri-apps/tauri/pull/7044)) Skip Rust target installation if they are already installed.\n- [`735db1ce`](https://www.github.com/tauri-apps/tauri/commit/735db1ce839a16ba998c9e6786c441e3bf6c90b3)([#7044](https://www.github.com/tauri-apps/tauri/pull/7044)) Add `--skip-targets-install` flag for `tauri android init` and `tauri ios init` to skip installing needed rust targets vie rustup.\n\n### Bug Fixes\n\n- [`3f4c4ce8`](https://www.github.com/tauri-apps/tauri/commit/3f4c4ce88b071e4e59f03887beb4dfe76f66e11b)([#7028](https://www.github.com/tauri-apps/tauri/pull/7028)) Fix `--split-per-abi` not building any targets unless specified by `--target` flag.\n- [`1ed2600d`](https://www.github.com/tauri-apps/tauri/commit/1ed2600da67715908af857255305eaeb293d8791)([#6771](https://www.github.com/tauri-apps/tauri/pull/6771)) Set current directory to tauri directory before reading config file.\n- [`4847b87b`](https://www.github.com/tauri-apps/tauri/commit/4847b87b1067dd8c6e73986059f51e6eee1f1121)([#7209](https://www.github.com/tauri-apps/tauri/pull/7209)) Fix `tauri (android|ios) (dev|build)` failing when using `npx tauri`\n- [`655c714e`](https://www.github.com/tauri-apps/tauri/commit/655c714e4100f69d4265c5ea3f08f5bc11709446)([#7240](https://www.github.com/tauri-apps/tauri/pull/7240)) Fixes panic when exiting the `ios dev` command with Ctrl + C.\n- [`6252380f`](https://www.github.com/tauri-apps/tauri/commit/6252380f4447c66913b0f06611e5949005b1eec2)([#7241](https://www.github.com/tauri-apps/tauri/pull/7241)) Exit `beforeDevCommand` process if the android or iOS `dev` command fails.\n\n## \\[2.0.0-alpha.9]\n\n- [`19cd0e49`](https://www.github.com/tauri-apps/tauri/commit/19cd0e49603ad3500cd2180bfa16e1649e3a771a)([#6811](https://www.github.com/tauri-apps/tauri/pull/6811)) Add `key.properties` file to android's `.gitignore`.\n- [`124d5c5a`](https://www.github.com/tauri-apps/tauri/commit/124d5c5adf67f0b68d2e41c7ddb07d9cb63f1996)([#6788](https://www.github.com/tauri-apps/tauri/pull/6788)) On mobile, fix regression introduced in `tauri-cli` version `2.0.0-alpha.3` where library not found error was thrown.\n- [`31444ac1`](https://www.github.com/tauri-apps/tauri/commit/31444ac196add770f2ad18012d7c18bce7538f22)([#6725](https://www.github.com/tauri-apps/tauri/pull/6725)) Update mobile template to `wry@0.28`\n- [`6d1fa49f`](https://www.github.com/tauri-apps/tauri/commit/6d1fa49fce3a03965ce7c656390e682ce5b776e3)([#6881](https://www.github.com/tauri-apps/tauri/pull/6881)) Clear Android plugin JSON file before building Rust library to ensure removed plugins are propagated to the Android project.\n- [`5a9307d1`](https://www.github.com/tauri-apps/tauri/commit/5a9307d11c1643221bc2a280feb00024f8fa6030)([#6890](https://www.github.com/tauri-apps/tauri/pull/6890)) Update android template to gradle 8.0\n- [`73c803a5`](https://www.github.com/tauri-apps/tauri/commit/73c803a561181137f20366f5d52511392a619f2b)([#6837](https://www.github.com/tauri-apps/tauri/pull/6837)) Inject Tauri configuration in the Android assets.\n- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Moved the updater configuration to the `BundleConfig`.\n- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65.\n- [`2969d1cb`](https://www.github.com/tauri-apps/tauri/commit/2969d1cbba39301f9cc611d9f7d7051d80eef846)([#6773](https://www.github.com/tauri-apps/tauri/pull/6773)) Use absolute path to each Android plugin project instead of copying the files to enhance developer experience.\n- [`d48aaa15`](https://www.github.com/tauri-apps/tauri/commit/d48aaa150a1ceeb65ec0ba18f1e3795f70c838e3)([#6894](https://www.github.com/tauri-apps/tauri/pull/6894)) Add Cargo manifest files for the plugin example templates.\n- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Removed the allowlist configuration.\n\n## \\[2.0.0-alpha.8]\n\n- Do not gitignore the Android project's `buildSrc` folder by default since we removed absolute paths from it.\n  - [ee2d3b97](https://www.github.com/tauri-apps/tauri/commit/ee2d3b971df6d3630b8d935394fb4febcfa3a909) fix(cli): remove buildSrc from Android project gitignored paths ([#6702](https://www.github.com/tauri-apps/tauri/pull/6702)) on 2023-04-13\n- Fixes iOS build script using the wrong path for the app library file.\n  - [abc5f91f](https://www.github.com/tauri-apps/tauri/commit/abc5f91fa3569efc9dfdee46d1c501eda8755944) fix(cli): iOS Xcode script using incorrect library path ([#6699](https://www.github.com/tauri-apps/tauri/pull/6699)) on 2023-04-13\n\n## \\[2.0.0-alpha.7]\n\n- Add `--release` flag for `tauri android dev` however you will need to sign your Android app, see https://next--tauri.netlify.app/next/guides/distribution/sign-android\n  - [63f088e5](https://www.github.com/tauri-apps/tauri/commit/63f088e5fc9701fd7fb329dad7ffb27a2d8fd5aa) feat(cli): add `--release` for `android dev` ([#6638](https://www.github.com/tauri-apps/tauri/pull/6638)) on 2023-04-05\n- Build only specified rust targets for `tauri android build` instead of all.\n  - [d03e47d1](https://www.github.com/tauri-apps/tauri/commit/d03e47d141c3917520975be9081775dbc4e9d4fd) fix: only build specified rust targets for aab/apk build ([#6625](https://www.github.com/tauri-apps/tauri/pull/6625)) on 2023-04-05\n- Use local ip address for built-in dev server on mobile.\n  - [7fec0f08](https://www.github.com/tauri-apps/tauri/commit/7fec0f083c932dc63ccb8716080d97e2ab985b25) fix(cli): use local ip addr for built-in server on mobile, closes [#6454](https://www.github.com/tauri-apps/tauri/pull/6454) ([#6631](https://www.github.com/tauri-apps/tauri/pull/6631)) on 2023-04-04\n- Change minimum Android SDK version to 21 for the plugin library.\n  - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07\n- Readd the Cargo.toml file to the plugin template.\n  - [5288a386](https://www.github.com/tauri-apps/tauri/commit/5288a386f1bf8ac11f991350463c3f5c20983f43) fix(cli): readd Cargo.toml to the plugin template ([#6637](https://www.github.com/tauri-apps/tauri/pull/6637)) on 2023-04-04\n\n## \\[2.0.0-alpha.6]\n\n- Use Ubuntu 20.04 to compile the CLI for cargo-binstall, increasing the minimum libc required.\n- Automatically enable the `rustls-tls` tauri feature on mobile and `native-tls` on desktop if `rustls-tls` is not enabled.\n  - [cfdee00f](https://www.github.com/tauri-apps/tauri/commit/cfdee00f2b1455a9719bc44823fdaeabbe4c1cb2) refactor(core): fix tls features, use rustls on mobile ([#6591](https://www.github.com/tauri-apps/tauri/pull/6591)) on 2023-03-30\n\n## \\[2.0.0-alpha.5]\n\n- Fixes the iOS project script to build the Rust library.\n  - [6e3e4c22](https://www.github.com/tauri-apps/tauri/commit/6e3e4c22be51500bec7856d90dcb2e40ef7fe1b4) fix(cli): use correct variable on script to build Rust iOS code ([#6581](https://www.github.com/tauri-apps/tauri/pull/6581)) on 2023-03-29\n- Fix `tauri android build/dev` crashing when used with standalone `pnpm` executable on Windows.\n  - [39df2c98](https://www.github.com/tauri-apps/tauri/commit/39df2c982e5e2ee8617b40f829a2f2e4abfce412) fix(cli/android): fallback to `${program}.cmd` ([#6576](https://www.github.com/tauri-apps/tauri/pull/6576)) on 2023-03-29\n\n## \\[2.0.0-alpha.4]\n\n- Fix android project build crashing when using `pnpm` caused by extra `--`.\n  - [c787f749](https://www.github.com/tauri-apps/tauri/commit/c787f749de01b79d891615aad8c37b23037fff4c) fix(cli): only add `--` to generated android template for npm ([#6508](https://www.github.com/tauri-apps/tauri/pull/6508)) on 2023-03-21\n- Fixes the Android build gradle plugin implementation on Windows.\n  - [00241fa9](https://www.github.com/tauri-apps/tauri/commit/00241fa92d104870068a701519340633cc35b716) fix(cli): append .cmd on the gradle plugin binary on Windows, fix [#6502](https://www.github.com/tauri-apps/tauri/pull/6502) ([#6503](https://www.github.com/tauri-apps/tauri/pull/6503)) on 2023-03-21\n\n## \\[2.0.0-alpha.3]\n\n- Added `plugin android add` and `plugin ios add` commands to add mobile plugin functionality to existing projects.\n  - [14d03d42](https://www.github.com/tauri-apps/tauri/commit/14d03d426e86d966950a790926c04560c76203b3) refactor(cli): enhance plugin commands for mobile ([#6289](https://www.github.com/tauri-apps/tauri/pull/6289)) on 2023-02-16\n- Add `--port` to specify the port used for static files dev server. It can also be specified through `TAURI_DEV_SERVER_PORT` env var.\n  - [b7a2ce2c](https://www.github.com/tauri-apps/tauri/commit/b7a2ce2c633c8383851ec9ec3c2cafda39f19745) feat(cli): add --port, closes [#6186](https://www.github.com/tauri-apps/tauri/pull/6186) ([#6283](https://www.github.com/tauri-apps/tauri/pull/6283)) on 2023-03-16\n- Auto select an external IP for mobile development and fallback to prompting the user. Use `--force-ip-prompt` to force prompting.\n  - [ec007ef0](https://www.github.com/tauri-apps/tauri/commit/ec007ef0d0852d8ee6e3247049916285c98a945f) feat: use `local_ip()` and fallback to prompt ([#6290](https://www.github.com/tauri-apps/tauri/pull/6290)) on 2023-02-16\n  - [4d090744](https://www.github.com/tauri-apps/tauri/commit/4d09074454068ae282ccd4670ae0b1df30510a3a) feat(cli): add `--force-ip-prompt` ([#6406](https://www.github.com/tauri-apps/tauri/pull/6406)) on 2023-03-16\n- Add commands to add native Android and iOS functionality to plugins.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n- In mobile commands, correctly detect when nodejs binary has the version in its name, for example `node-18`\n  - [58d4709f](https://www.github.com/tauri-apps/tauri/commit/58d4709f7eba07e77d3afd59c0df47c085df9e2c) fix: update nodejs detection in mobile commands ([#6451](https://www.github.com/tauri-apps/tauri/pull/6451)) on 2023-03-16\n- Use temp file instead of environment variable to pass CLI IPC websocket address to the IDE.\n  - [894a8d06](https://www.github.com/tauri-apps/tauri/commit/894a8d060c12a482a0fc5b3714f3848189b809de) refactor(cli): use temp file to communicate IPC websocket address ([#6219](https://www.github.com/tauri-apps/tauri/pull/6219)) on 2023-02-08\n- Change the Android template to enable minification on release and pull ProGuard rules from proguard-tauri.pro.\n  - [bef4ef51](https://www.github.com/tauri-apps/tauri/commit/bef4ef51bc2c633b88db121c2087a38dddb7d6bf) feat(android): enable minify on release, add proguard rules ([#6257](https://www.github.com/tauri-apps/tauri/pull/6257)) on 2023-02-13\n- Print an error if the Android project was generated with an older bundle identifier or package name.\n  - [79eb0542](https://www.github.com/tauri-apps/tauri/commit/79eb054292b04bb089a0e4df401a5986b33b691e) feat(cli): handle Android package identifier change ([#6314](https://www.github.com/tauri-apps/tauri/pull/6314)) on 2023-02-19\n- Fixes the generated mobile build script when using an NPM runner.\n  - [62f15265](https://www.github.com/tauri-apps/tauri/commit/62f152659204ce1218178596f463f0bcfbd4e6dc) fix(cli): generate build script using NPM runner if it was used ([#6233](https://www.github.com/tauri-apps/tauri/pull/6233)) on 2023-02-10\n- Resolve Android package name from single word bundle identifiers.\n  - [60a8b07d](https://www.github.com/tauri-apps/tauri/commit/60a8b07dc7c56c9c45331cb57d9afb410e7eadf3) fix: handle single word bundle identifier when resolving Android domain ([#6313](https://www.github.com/tauri-apps/tauri/pull/6313)) on 2023-02-19\n- Update Android project template with fix to crash on orientation change.\n  - [947eb391](https://www.github.com/tauri-apps/tauri/commit/947eb391ca41cebdb11abd9ffaec642baffbf44a) fix(android): crash on orientation change due to activity recreation ([#6261](https://www.github.com/tauri-apps/tauri/pull/6261)) on 2023-02-13\n- Added `--ios-color` option to the `tauri icon` command.\n  - [67755425](https://www.github.com/tauri-apps/tauri/commit/677554257e40e05b1af0dd61c982d6be8a8a033c) feat(cli): add `--ios-color` option to set iOS icon background color ([#6247](https://www.github.com/tauri-apps/tauri/pull/6247)) on 2023-02-12\n- Fixes HMR on mobile when devPath is configured to load a filesystem path.\n  - [4a82da29](https://www.github.com/tauri-apps/tauri/commit/4a82da2919e0564ec993b2005dc65b5b49407b36) fix(cli): use local ip address for reload ([#6285](https://www.github.com/tauri-apps/tauri/pull/6285)) on 2023-02-16\n- Ignore the `gen` folder on the dev watcher.\n  - [cab4ff95](https://www.github.com/tauri-apps/tauri/commit/cab4ff95b98aeac88401c1fed2d8b8940e4180cb) fix(cli): ignore the `gen` folder on the dev watcher ([#6232](https://www.github.com/tauri-apps/tauri/pull/6232)) on 2023-02-09\n- Correctly pass arguments from `npm run` to `tauri`.\n  - [1b343bd1](https://www.github.com/tauri-apps/tauri/commit/1b343bd11686f47f24a87298d8192097c66250f6) fix(cli): use `npm run tauri -- foo` for correctly passing args to tauri ([#6448](https://www.github.com/tauri-apps/tauri/pull/6448)) on 2023-03-16\n- Changed the `--api` flag on `plugin init` to `--no-api`.\n  - [14d03d42](https://www.github.com/tauri-apps/tauri/commit/14d03d426e86d966950a790926c04560c76203b3) refactor(cli): enhance plugin commands for mobile ([#6289](https://www.github.com/tauri-apps/tauri/pull/6289)) on 2023-02-16\n\n## \\[2.0.0-alpha.2]\n\n- Fixes `TAURI_*` environment variables for hook scripts on mobile commands.\n  - [1af9be90](https://www.github.com/tauri-apps/tauri/commit/1af9be904a309138b9f79dc741391000b1652c75) feat(cli): properly fill target for TAURI\\_ env vars on mobile ([#6116](https://www.github.com/tauri-apps/tauri/pull/6116)) on 2023-01-23\n- Force colored logs on mobile commands.\n  - [2c4a0bbd](https://www.github.com/tauri-apps/tauri/commit/2c4a0bbd1fbe15d7500264e6490772397e1917ed) feat(cli): force colored logs on mobile commands ([#5934](https://www.github.com/tauri-apps/tauri/pull/5934)) on 2022-12-28\n- Keep the process alive even when the iOS application is closed.\n  - [dee9460f](https://www.github.com/tauri-apps/tauri/commit/dee9460f9c9bc92e9c638e7691e616849ac2085b) feat: keep CLI alive when iOS app exits, show logs, closes [#5855](https://www.github.com/tauri-apps/tauri/pull/5855) ([#5902](https://www.github.com/tauri-apps/tauri/pull/5902)) on 2022-12-27\n- Show all application logs on iOS.\n  - [dee9460f](https://www.github.com/tauri-apps/tauri/commit/dee9460f9c9bc92e9c638e7691e616849ac2085b) feat: keep CLI alive when iOS app exits, show logs, closes [#5855](https://www.github.com/tauri-apps/tauri/pull/5855) ([#5902](https://www.github.com/tauri-apps/tauri/pull/5902)) on 2022-12-27\n- Print log output for all tags on Android development.\n  - [8cc11149](https://www.github.com/tauri-apps/tauri/commit/8cc111494d74161e489152e52191e1442dd99759) fix(cli): print Android logs for all tags on 2023-01-17\n- Add support to custom and kebab case library names for mobile apps.\n  - [50f6dd87](https://www.github.com/tauri-apps/tauri/commit/50f6dd87b1ac2c99f8794b055f1acba4ef7d34d3) feat: improvements to support hyphens in crate name ([#5989](https://www.github.com/tauri-apps/tauri/pull/5989)) on 2023-01-06\n- Bump the MSRV to 1.64.\n  - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30\n- Fix target directory detection when compiling for Android.\n  - [e873bae0](https://www.github.com/tauri-apps/tauri/commit/e873bae09f0f27517f720a753f51c1dcb903f883) fix(cli): Cargo target dir detection on Android, closes [#5865](https://www.github.com/tauri-apps/tauri/pull/5865) ([#5932](https://www.github.com/tauri-apps/tauri/pull/5932)) on 2022-12-28\n\n## \\[2.0.0-alpha.1]\n\n- Fixes running on device using Xcode 14.\n  - [1e4a6758](https://www.github.com/tauri-apps/tauri/commit/1e4a675843c486bddc11292d09fb766e98758514) fix(cli): run on iOS device on Xcode 14 ([#5807](https://www.github.com/tauri-apps/tauri/pull/5807)) on 2022-12-12\n- Improve local IP address detection with user selection.\n  - [76204b89](https://www.github.com/tauri-apps/tauri/commit/76204b893846a04552f8f8b87ad2c9b55e1b417f) feat(cli): improve local IP detection ([#5817](https://www.github.com/tauri-apps/tauri/pull/5817)) on 2022-12-12\n\n## \\[2.0.0-alpha.0]\n\n- Added `android build` command.\n  - [4c9ea450](https://www.github.com/tauri-apps/tauri/commit/4c9ea450c3b47c6b8c825ba32e9837909945ccd7) feat(cli): add `android build` command ([#4999](https://www.github.com/tauri-apps/tauri/pull/4999)) on 2022-08-22\n- Added `ios build` command.\n  - [403859d4](https://www.github.com/tauri-apps/tauri/commit/403859d47e1a9bf978b353fa58e4b971e66337a3) feat(cli): add `ios build` command ([#5002](https://www.github.com/tauri-apps/tauri/pull/5002)) on 2022-08-22\n- Added `android dev` and `ios dev` commands.\n  - [6f061504](https://www.github.com/tauri-apps/tauri/commit/6f0615044d09ec58393a7ebca5e45bb175e20db3) feat(cli): add `android dev` and `ios dev` commands ([#4982](https://www.github.com/tauri-apps/tauri/pull/4982)) on 2022-08-20\n- Added `android init` and `ios init` commands.\n  - [d44f67f7](https://www.github.com/tauri-apps/tauri/commit/d44f67f7afd30a81d53a973ec603b2a253150bde) feat: add `android init` and `ios init` commands ([#4942](https://www.github.com/tauri-apps/tauri/pull/4942)) on 2022-08-15\n- Added `android open` and `ios open` commands.\n  - [a9c8e565](https://www.github.com/tauri-apps/tauri/commit/a9c8e565c6495961940877df7090f307be16b554) feat: add `android open` and `ios open` commands ([#4946](https://www.github.com/tauri-apps/tauri/pull/4946)) on 2022-08-15\n- First mobile alpha release!\n  - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08\n\n## \\[1.5.11]\n\n### Bug Fixes\n\n- [`97a05145f`](https://www.github.com/tauri-apps/tauri/commit/97a05145fbb24533526eba6589594f03046e11df)([#9119](https://www.github.com/tauri-apps/tauri/pull/9119)) Fix compilation error due to dependency on unstable features of `log` crate.\n- [`b15948b11`](https://www.github.com/tauri-apps/tauri/commit/b15948b11c0e362eea7ef57a4606f15f7dbd886b)([#8903](https://www.github.com/tauri-apps/tauri/pull/8903)) Fix `.taurignore` failing to ignore in some cases.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@1.5.1`\n\n## \\[1.5.10]\n\n### New Features\n\n- [`89911296`](https://www.github.com/tauri-apps/tauri/commit/89911296e475d5c36f3486b9b75232505846e767)([#8259](https://www.github.com/tauri-apps/tauri/pull/8259)) On macOS, support for signing nested .dylib, .app, .xpc and .framework under predefined directories inside the bundled frameworks (\"MacOS\", \"Frameworks\", \"Plugins\", \"Helpers\", \"XPCServices\" and \"Libraries\").\n\n### Bug Fixes\n\n- [`b0f27814`](https://www.github.com/tauri-apps/tauri/commit/b0f27814b90ded2f1ed44b7852080eedbff0d9e4)([#8776](https://www.github.com/tauri-apps/tauri/pull/8776)) Fix `fail to rename app` when using `--profile dev`.\n- [`0bff8c32`](https://www.github.com/tauri-apps/tauri/commit/0bff8c325d004fdead2023f58e0f5fd73a9c22ba)([#8697](https://www.github.com/tauri-apps/tauri/pull/8697)) Fix the built-in dev server failing to serve files when URL had queries `?` and other url components.\n- [`67d7877f`](https://www.github.com/tauri-apps/tauri/commit/67d7877f27f265c133a70d48a46c83ffff31d571)([#8520](https://www.github.com/tauri-apps/tauri/pull/8520)) The cli now also watches cargo workspace members if the tauri folder is the workspace root.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@1.5.0`\n\n## \\[1.5.9]\n\n### Bug Fixes\n\n- [`0a2175ea`](https://www.github.com/tauri-apps/tauri/commit/0a2175eabb736b2a4cd01ab682e08be0b5ebb2b9)([#8439](https://www.github.com/tauri-apps/tauri/pull/8439)) Expand glob patterns in workspace member paths so the CLI would watch all matching pathhs.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@1.4.8`\n- Upgraded to `tauri-utils@1.5.2`\n\n## \\[1.5.8]\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@1.4.7`\n\n## \\[1.5.7]\n\n### Bug Fixes\n\n- [`1d5aa38a`](https://www.github.com/tauri-apps/tauri/commit/1d5aa38ae418ea31f593590b6d32cf04d3bfd8c1)([#8162](https://www.github.com/tauri-apps/tauri/pull/8162)) Fixes errors on command output, occuring when the output stream contains an invalid UTF-8 character, or ends with a multi-bytes UTF-8 character.\n- [`f26d9f08`](https://www.github.com/tauri-apps/tauri/commit/f26d9f0884f63f61b9f4d4fac15e6b251163793e)([#8263](https://www.github.com/tauri-apps/tauri/pull/8263)) Fixes an issue in the NSIS installer which caused the uninstallation to leave empty folders on the system if the `resources` feature was used.\n- [`92bc7d0e`](https://www.github.com/tauri-apps/tauri/commit/92bc7d0e16157434330a1bcf1eefda6f0f1e5f85)([#8233](https://www.github.com/tauri-apps/tauri/pull/8233)) Fixes an issue in the NSIS installer which caused the installation to take much longer than expected when many `resources` were added to the bundle.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@1.4.6`\n\n## \\[1.5.6]\n\n### Bug Fixes\n\n- [`5264e41d`](https://www.github.com/tauri-apps/tauri/commit/5264e41db3763e4c2eb0c3c21bd423fb7bece3e2)([#8082](https://www.github.com/tauri-apps/tauri/pull/8082)) Downgraded `rust-minisign` to `0.7.3` to fix signing updater bundles with empty passwords.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@1.4.5`\n\n## \\[1.5.5]\n\n### Enhancements\n\n- [`9bead42d`](https://www.github.com/tauri-apps/tauri/commit/9bead42dbca0fb6dd7ea0b6bfb2f2308a5c5f992)([#8059](https://www.github.com/tauri-apps/tauri/pull/8059)) Allow rotating the updater private key.\n\n### Bug Fixes\n\n- [`be8e5aa3`](https://www.github.com/tauri-apps/tauri/commit/be8e5aa3071d9bc5d0bd24647e8168f312d11c8d)([#8042](https://www.github.com/tauri-apps/tauri/pull/8042)) Fixes duplicated newlines on command outputs.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@1.4.4`\n\n## \\[1.5.4]\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@1.4.3`\n\n## \\[1.5.3]\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@1.4.2`\n\n## \\[1.5.2]\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@1.4.1`\n\n## \\[1.5.1]\n\n### Bug Fixes\n\n- [`d6eb46cf`](https://www.github.com/tauri-apps/tauri/commit/d6eb46cf1116d147121f6b6db9d390b5e2fb238d)([#7934](https://www.github.com/tauri-apps/tauri/pull/7934)) On macOS, fix the `apple-id` option name when using `notarytools submit`.\n\n## \\[1.5.0]\n\n### New Features\n\n- [`e1526626`](https://www.github.com/tauri-apps/tauri/commit/e152662687ece7a62d383923a50751cc0dd34331)([#7723](https://www.github.com/tauri-apps/tauri/pull/7723)) Support Bun package manager in CLI\n\n### Enhancements\n\n- [`13279917`](https://www.github.com/tauri-apps/tauri/commit/13279917d4cae071d0ce3a686184d48af079f58a)([#7713](https://www.github.com/tauri-apps/tauri/pull/7713)) Add version of Rust Tauri CLI installed with Cargo to `tauri info` command.\n\n### Bug Fixes\n\n- [`dad4f54e`](https://www.github.com/tauri-apps/tauri/commit/dad4f54eec9773d2ea6233a7d9fd218741173823)([#7277](https://www.github.com/tauri-apps/tauri/pull/7277)) Removed the automatic version check of the CLI that ran after `tauri` commands which caused various issues.\n\n### Dependencies\n\n- Upgraded to `tauri-bundler@1.4.0`\n- Upgraded to `tauri-utils@1.5.0`\n\n## \\[1.4.0]\n\n### New Features\n\n- [`0ddbb3a1`](https://www.github.com/tauri-apps/tauri/commit/0ddbb3a1dc1961ba5c6c1a60081513c1380c8af1)([#7015](https://www.github.com/tauri-apps/tauri/pull/7015)) Provide prebuilt CLIs for Windows ARM64 targets.\n- [`35cd751a`](https://www.github.com/tauri-apps/tauri/commit/35cd751adc6fef1f792696fa0cfb471b0bf99374)([#5176](https://www.github.com/tauri-apps/tauri/pull/5176)) Added the `desktop_template` option on `tauri.conf.json > tauri > bundle > deb`.\n- [`6c5ade08`](https://www.github.com/tauri-apps/tauri/commit/6c5ade08d97844bb685789d30e589400bbe3e04c)([#4537](https://www.github.com/tauri-apps/tauri/pull/4537)) Added `tauri completions` to generate shell completions scripts.\n- [`29488205`](https://www.github.com/tauri-apps/tauri/commit/2948820579d20dfaa0861c2f0a58bd7737a7ffd1)([#6867](https://www.github.com/tauri-apps/tauri/pull/6867)) Allow specifying custom language files of Tauri's custom messages for the NSIS installer\n- [`e092f799`](https://www.github.com/tauri-apps/tauri/commit/e092f799469ff32c7d1595d0f07d06fd2dab5c29)([#6887](https://www.github.com/tauri-apps/tauri/pull/6887)) Add `nsis > template` option to specify custom NSIS installer template.\n\n### Enhancements\n\n- [`d75c1b82`](https://www.github.com/tauri-apps/tauri/commit/d75c1b829bd96d9e3a672bcc79120597d5ada4a0)([#7181](https://www.github.com/tauri-apps/tauri/pull/7181)) Print a useful error when `updater` bundle target is specified without an updater-enabled target.\n- [`52474e47`](https://www.github.com/tauri-apps/tauri/commit/52474e479d695865299d8c8d868fb98b99731020)([#7141](https://www.github.com/tauri-apps/tauri/pull/7141)) Enhance injection of Cargo features.\n- [`2659ca1a`](https://www.github.com/tauri-apps/tauri/commit/2659ca1ab4799a5bda65c229c149e98bd01eb1ee)([#6900](https://www.github.com/tauri-apps/tauri/pull/6900)) Add `rustls` as default Cargo feature.\n- [`c7056d1b`](https://www.github.com/tauri-apps/tauri/commit/c7056d1b202927d025aa479818c2fb20bf5d6113)([#6982](https://www.github.com/tauri-apps/tauri/pull/6982)) Improve Visual Studio installation detection in `tauri info` command to check for the necessary components instead of whole workloads. This also fixes the detection of minimal installations and auto-installations done by `rustup`.\n\n### Bug Fixes\n\n- [`3cb7a3e6`](https://www.github.com/tauri-apps/tauri/commit/3cb7a3e642bb10ee90dc1d24daa48b8c8c15c9ce)([#6997](https://www.github.com/tauri-apps/tauri/pull/6997)) Fix built-in devserver adding hot-reload code to non-html files.\n- [`fd3b5a16`](https://www.github.com/tauri-apps/tauri/commit/fd3b5a16b13d62f62c850e68763401916dde26f2)([#6954](https://www.github.com/tauri-apps/tauri/pull/6954)) Fix building with a custom cargo profile\n- [`1253bbf7`](https://www.github.com/tauri-apps/tauri/commit/1253bbf7ae11a87887e0b3bd98cc26dbb98c8130)([#7013](https://www.github.com/tauri-apps/tauri/pull/7013)) Fixes Cargo.toml feature rewriting.\n\n## \\[1.3.1]\n\n- Correctly escape XML for resource files in WiX bundler.\n  - Bumped due to a bump in tauri-bundler.\n  - [6a6b1388](https://www.github.com/tauri-apps/tauri/commit/6a6b1388ea5787aea4c0e0b0701a4772087bbc0f) fix(bundler): correctly escape resource xml, fixes [#6853](https://www.github.com/tauri-apps/tauri/pull/6853) ([#6855](https://www.github.com/tauri-apps/tauri/pull/6855)) on 2023-05-04\n\n- Added the following languages to the NSIS bundler:\n\n- `Spanish`\n\n- `SpanishInternational`\n\n- Bumped due to a bump in tauri-bundler.\n\n- [422b4817](https://www.github.com/tauri-apps/tauri/commit/422b48179856504e980a156500afa8e22c44d357) Add Spanish and SpanishInternational languages ([#6871](https://www.github.com/tauri-apps/tauri/pull/6871)) on 2023-05-06\n\n- Correctly escape arguments in NSIS script to fix bundling apps that use non-default WebView2 install modes.\n  - Bumped due to a bump in tauri-bundler.\n  - [2915bd06](https://www.github.com/tauri-apps/tauri/commit/2915bd068ed40dc01a363b69212c6b6f2d3ec01e) fix(bundler): Fix webview install modes in NSIS bundler ([#6854](https://www.github.com/tauri-apps/tauri/pull/6854)) on 2023-05-04\n\n## \\[1.3.0]\n\n- Look for available port when using the built-in dev server for static files.\n  - [a7ee5ca7](https://www.github.com/tauri-apps/tauri/commit/a7ee5ca7c348d33bdfdc1213b6850bcf5c39d6e6) fix(cli): look for available ports for built-in dev server, closes [#6511](https://www.github.com/tauri-apps/tauri/pull/6511) ([#6514](https://www.github.com/tauri-apps/tauri/pull/6514)) on 2023-03-31\n- Add `--port` to specify the port used for static files dev server. It can also be specified through `TAURI_DEV_SERVER_PORT` env var.\n  - [b7a2ce2c](https://www.github.com/tauri-apps/tauri/commit/b7a2ce2c633c8383851ec9ec3c2cafda39f19745) feat(cli): add --port, closes [#6186](https://www.github.com/tauri-apps/tauri/pull/6186) ([#6283](https://www.github.com/tauri-apps/tauri/pull/6283)) on 2023-03-16\n- Fix `tauri info` panicking when parsing crates version on a newly created project without a `Cargo.lock` file.\n  - [c2608423](https://www.github.com/tauri-apps/tauri/commit/c2608423b6eec5eb91d0ffc861714c011ad3988b) fix(cli): don't panic when a crate version couldn't be parsed ([#5873](https://www.github.com/tauri-apps/tauri/pull/5873)) on 2022-12-26\n- Improve the error message when `rustc` couldn't be found.\n  - [7aab3e20](https://www.github.com/tauri-apps/tauri/commit/7aab3e2076272c14c78a563e288a1b04ed3cfd41) fix(cli.rs): improve `rustc` not found error msg ([#6021](https://www.github.com/tauri-apps/tauri/pull/6021)) on 2023-01-17\n- Add `--ci` flag and respect the `CI` environment variable on the `signer generate` command. In this case the default password will be an empty string and the CLI will not prompt for a value.\n  - [8fb1df8a](https://www.github.com/tauri-apps/tauri/commit/8fb1df8aa65a52cdb4a7e1bb9dda9b912a7a2895) feat(cli): add `--ci` flag to `signer generate`, closes [#6089](https://www.github.com/tauri-apps/tauri/pull/6089) ([#6097](https://www.github.com/tauri-apps/tauri/pull/6097)) on 2023-01-19\n- Fix Outdated Github Actions in the Plugin Templates `with-api` and `backend`\n  - [a926b49a](https://www.github.com/tauri-apps/tauri/commit/a926b49a01925ca757d391994bfac3beea29599b) Fix Github Actions of Tauri Plugin with-api template ([#6603](https://www.github.com/tauri-apps/tauri/pull/6603)) on 2023-04-03\n- Do not crash on Cargo.toml watcher.\n  - [e8014a7f](https://www.github.com/tauri-apps/tauri/commit/e8014a7f612a1094461ddad63aacc498a2682ff5) fix(cli): do not crash on watcher ([#6303](https://www.github.com/tauri-apps/tauri/pull/6303)) on 2023-02-17\n- On Windows, printing consistent paths on Windows with backslashs only.\n  - [9da99607](https://www.github.com/tauri-apps/tauri/commit/9da996073ff07d4b59668a5315d40e9bc578e340) fix(cli): fix printing paths on Windows ([#6137](https://www.github.com/tauri-apps/tauri/pull/6137)) on 2023-01-26\n- Add `--png` option for the `icon` command to generate custom icon sizes.\n  - [9d214412](https://www.github.com/tauri-apps/tauri/commit/9d2144128fc5fad67d8404bce95f82297ebb0e4a) feat(cli): add option to make custom icon sizes, closes [#5121](https://www.github.com/tauri-apps/tauri/pull/5121) ([#5246](https://www.github.com/tauri-apps/tauri/pull/5246)) on 2022-12-27\n- Skip the password prompt on the build command when `TAURI_KEY_PASSWORD` environment variable is empty and the `--ci` argument is provided or the `CI` environment variable is set.\n  - [d4f89af1](https://www.github.com/tauri-apps/tauri/commit/d4f89af18d69fd95a4d8a1ede8442547c6a6d0ee) feat: skip password prompt on the build command if CI is set fixes [#6089](https://www.github.com/tauri-apps/tauri/pull/6089) on 2023-01-18\n- Fix `default-run` not deserialized.\n  - [57c6bf07](https://www.github.com/tauri-apps/tauri/commit/57c6bf07bb380847abdf27c3fff9891d99c1c98c) fix(cli): fix default-run not deserialized ([#6584](https://www.github.com/tauri-apps/tauri/pull/6584)) on 2023-03-30\n- Fixes HTML serialization removing template tags on the dev server.\n  - [314f0e21](https://www.github.com/tauri-apps/tauri/commit/314f0e212fd2b9e452bfe3424cdce2b0bf37b5d7) fix(cli): web_dev_server html template serialization (fix [#6165](https://www.github.com/tauri-apps/tauri/pull/6165)) ([#6166](https://www.github.com/tauri-apps/tauri/pull/6166)) on 2023-01-29\n- Use escaping on Handlebars templates.\n  - [6d6b6e65](https://www.github.com/tauri-apps/tauri/commit/6d6b6e653ea70fc02794f723092cdc860995c259) feat: configure escaping on handlebars templates ([#6678](https://www.github.com/tauri-apps/tauri/pull/6678)) on 2023-05-02\n- Fix building apps with unicode characters in their `productName`.\n  - [72621892](https://www.github.com/tauri-apps/tauri/commit/72621892fe8195bad67b4237467ebd7e89f6af7f) fix(cli): use `unicode` feature for `heck` crate, closes [#5860](https://www.github.com/tauri-apps/tauri/pull/5860) ([#5872](https://www.github.com/tauri-apps/tauri/pull/5872)) on 2022-12-26\n- Bump minimum supported Rust version to 1.60.\n  - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31\n- Add initial support for building `nsis` bundles on non-Windows platforms.\n  - [60e6f6c3](https://www.github.com/tauri-apps/tauri/commit/60e6f6c3f1605f3064b5bb177992530ff788ccf0) feat(bundler): Add support for creating NSIS bundles on unix hosts ([#5788](https://www.github.com/tauri-apps/tauri/pull/5788)) on 2023-01-19\n- Add `nsis` bundle target\n  - [c94e1326](https://www.github.com/tauri-apps/tauri/commit/c94e1326a7c0767a13128a8b1d327a00156ece12) feat(bundler): add `nsis`, closes [#4450](https://www.github.com/tauri-apps/tauri/pull/4450), closes [#2319](https://www.github.com/tauri-apps/tauri/pull/2319) ([#4674](https://www.github.com/tauri-apps/tauri/pull/4674)) on 2023-01-03\n- Remove default features from Cargo.toml template.\n  - [b08ae637](https://www.github.com/tauri-apps/tauri/commit/b08ae637a0f58b38cbce9b8a1fa0b6c5dc0cfd05) fix(cli): remove default features from template ([#6074](https://www.github.com/tauri-apps/tauri/pull/6074)) on 2023-01-17\n- Added support for Cargo's workspace inheritance for package information. The cli now also detects inherited `tauri` and `tauri-build` dependencies and disables manifest rewrites accordingly.\n  - [cd8c074a](https://www.github.com/tauri-apps/tauri/commit/cd8c074ae6592303d3f6844a4fb6d262eae913b2) feat(cli): add support for Cargo's workspace inheritance for the package version, closes [#5070](https://www.github.com/tauri-apps/tauri/pull/5070) ([#5775](https://www.github.com/tauri-apps/tauri/pull/5775)) on 2022-12-14\n  - [d20a7288](https://www.github.com/tauri-apps/tauri/commit/d20a728892eee1858ab525ab6216cd721f473ab5) feat: Further improve workspace inheritance, closes [#6122](https://www.github.com/tauri-apps/tauri/pull/6122), [#5070](https://www.github.com/tauri-apps/tauri/pull/5070) ([#6144](https://www.github.com/tauri-apps/tauri/pull/6144)) on 2023-01-26\n- Use Ubuntu 20.04 to compile the CLI for cargo-binstall, increasing the minimum libc required.\n\n## \\[1.2.3]\n\n- Pin `ignore` to `=0.4.18`.\n  - [adcb082b](https://www.github.com/tauri-apps/tauri/commit/adcb082b1651ecb2a6208b093e12f4185aa3fc98) chore(deps): pin `ignore` to =0.4.18 on 2023-01-17\n\n## \\[1.2.2]\n\n- Detect SvelteKit and Vite for the init and info commands.\n  - [9d872ab8](https://www.github.com/tauri-apps/tauri/commit/9d872ab8728b1b121909af434adcd5936e5afb7d) feat(cli): detect SvelteKit and Vite ([#5742](https://www.github.com/tauri-apps/tauri/pull/5742)) on 2022-12-02\n- Detect SolidJS and SolidStart for the init and info commands.\n  - [9e7ce0a8](https://www.github.com/tauri-apps/tauri/commit/9e7ce0a8eef4bf3536645976e3e09162fbf772ab) feat(cli): detect SolidJS and SolidStart ([#5758](https://www.github.com/tauri-apps/tauri/pull/5758)) on 2022-12-08\n- Use older icon types to work around a macOS bug resulting in corrupted 16x16px and 32x32px icons in bundled apps.\n  - [2d545eff](https://www.github.com/tauri-apps/tauri/commit/2d545eff58734ec70f23f11a429d35435cdf090e) fix(cli): corrupted icons in bundled macOS icons ([#5698](https://www.github.com/tauri-apps/tauri/pull/5698)) on 2022-11-28\n- Add `--no-dev-server` flag to the cli to disable the dev server for static files in dev mode.\n  - [c0989848](https://www.github.com/tauri-apps/tauri/commit/c0989848b9421fb19070ae652a89a5d5675deab8) feat(cli/dev): add `--no-dev-server`, ref [#5708](https://www.github.com/tauri-apps/tauri/pull/5708) ([#5722](https://www.github.com/tauri-apps/tauri/pull/5722)) on 2022-11-30\n\n## \\[1.2.1]\n\n- Fixes injection of Cargo features defined in the configuration file.\n  - [1ecaeb29](https://www.github.com/tauri-apps/tauri/commit/1ecaeb29aa798f591f6488dc6c3a7a8d22f6073e) fix(cli): inject config feature flags when features arg is not provided on 2022-11-18\n\n## \\[1.2.0]\n\n- Keep `tauri dev` watcher alive when the configuration is invalid.\n  - [cc186c7a](https://www.github.com/tauri-apps/tauri/commit/cc186c7a0eab1c364f8b58101f86979ae4ed3d03) fix(cli): keep dev watcher alive if config is incorrect, closes [#5173](https://www.github.com/tauri-apps/tauri/pull/5173) ([#5495](https://www.github.com/tauri-apps/tauri/pull/5495)) on 2022-10-28\n- Ignore workspace members in dev watcher if they are ignored by `.taurignore`\n  - [9417ce40](https://www.github.com/tauri-apps/tauri/commit/9417ce401c4985e97245ce02d3b7cc31fb4bf59e) fix(cli): apply `.taurignore` rules to workspace members, closes [#5355](https://www.github.com/tauri-apps/tauri/pull/5355) ([#5460](https://www.github.com/tauri-apps/tauri/pull/5460)) on 2022-10-28\n- Detect JSON5 and TOML configuration files in the dev watcher.\n  - [e7ccbd85](https://www.github.com/tauri-apps/tauri/commit/e7ccbd8573f6b9124e80c0b67fa2365729c3c196) feat(cli): detect JSON5 and TOML configuration files in the dev watcher ([#5439](https://www.github.com/tauri-apps/tauri/pull/5439)) on 2022-10-19\n- Fix cli passing `--no-default-features` to the app instead of the runner (Cargo).\n  - [a3a70218](https://www.github.com/tauri-apps/tauri/commit/a3a70218f3cc438b4875a046a182ca44dab357ae) fix(cli): pass `--no-default-features` to runner instead of app, closes [#5415](https://www.github.com/tauri-apps/tauri/pull/5415) ([#5474](https://www.github.com/tauri-apps/tauri/pull/5474)) on 2022-10-25\n- Validate `package > productName` in the tauri config and produce errors if it contains one of the following characters `/\\:*?\\\"<>|`\n  - [b9316a64](https://www.github.com/tauri-apps/tauri/commit/b9316a64eaa9348c79efafb8b94960d9b4d5b27a) fix(cli): validate `productName` in config, closes [#5233](https://www.github.com/tauri-apps/tauri/pull/5233) ([#5262](https://www.github.com/tauri-apps/tauri/pull/5262)) on 2022-09-28\n- Hot-reload the frontend when `tauri.conf.json > build > devPath` points to a directory.\n  - [54c337e0](https://www.github.com/tauri-apps/tauri/commit/54c337e06f3bc624c4780cf002bc54790f446c90) feat(cli): hotreload support for frontend static files, closes [#2173](https://www.github.com/tauri-apps/tauri/pull/2173) ([#5256](https://www.github.com/tauri-apps/tauri/pull/5256)) on 2022-09-28\n- Expose `TAURI_TARGET_TRIPLE` to `beforeDevCommand`, `beforeBuildCommand` and `beforeBundleCommand`\n  - [a4aec9f0](https://www.github.com/tauri-apps/tauri/commit/a4aec9f0a864ec6d7712db8bd50989d9c2e2fd2e) feat(cli): expose `TAURI_TARGET_TRIPLE` to before\\*Commands, closes [#5091](https://www.github.com/tauri-apps/tauri/pull/5091) ([#5101](https://www.github.com/tauri-apps/tauri/pull/5101)) on 2022-10-03\n- Log dev watcher file change detection.\n  - [9076d5d2](https://www.github.com/tauri-apps/tauri/commit/9076d5d2e76d432aef475ba403e9ab5bd3b9d2b0) feat(cli): add prompt information when file changing detected, closes [#5417](https://www.github.com/tauri-apps/tauri/pull/5417) ([#5428](https://www.github.com/tauri-apps/tauri/pull/5428)) on 2022-10-19\n- Set `TAURI_PLATFORM_TYPE`, `TAURI_FAMILY`, `TAURI_ARCH` and `TAURI_PLATFORM` env vars for hook commands to based on the app not the cli.\n  - [a4aec9f0](https://www.github.com/tauri-apps/tauri/commit/a4aec9f0a864ec6d7712db8bd50989d9c2e2fd2e) feat(cli): expose `TAURI_TARGET_TRIPLE` to before\\*Commands, closes [#5091](https://www.github.com/tauri-apps/tauri/pull/5091) ([#5101](https://www.github.com/tauri-apps/tauri/pull/5101)) on 2022-10-03\n- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28\n- Add `tauri.conf.json > bundle > publisher` field to specify the app publisher.\n  - [628285c1](https://www.github.com/tauri-apps/tauri/commit/628285c1cf43f03ed62378f3b6cc0c991317526f) feat(bundler): add `publisher` field, closes [#5273](https://www.github.com/tauri-apps/tauri/pull/5273) ([#5283](https://www.github.com/tauri-apps/tauri/pull/5283)) on 2022-09-28\n- Changed the project template to not enable all APIs by default.\n  - [582c25a0](https://www.github.com/tauri-apps/tauri/commit/582c25a0f0fa2725d786ec4edd0defe7811ad6e8) refactor(cli): disable api-all on templates ([#5538](https://www.github.com/tauri-apps/tauri/pull/5538)) on 2022-11-03\n\n## \\[1.1.1]\n\n- Fix wrong cli metadata that caused new projects (created through `tauri init`) fail to build\n  - [db26aaf2](https://www.github.com/tauri-apps/tauri/commit/db26aaf2b44ce5335c9223c571ef2b2175e0cd6d) fix: fix wrong cli metadata ([#5214](https://www.github.com/tauri-apps/tauri/pull/5214)) on 2022-09-16\n\n## \\[1.1.0]\n\n- Allow adding `build > beforeBundleCommand` in tauri.conf.json to run a shell command before the bundling phase.\n  - [57ab9847](https://www.github.com/tauri-apps/tauri/commit/57ab9847eb2d8c9a5da584b873b7c072e9ee26bf) feat(cli): add `beforeBundleCommand`, closes [#4879](https://www.github.com/tauri-apps/tauri/pull/4879) ([#4893](https://www.github.com/tauri-apps/tauri/pull/4893)) on 2022-08-09\n- Change `before_dev_command` and `before_build_command` config value to allow configuring the current working directory.\n  - [d6f7d3cf](https://www.github.com/tauri-apps/tauri/commit/d6f7d3cfe8a7ec8d68c8341016c4e0a3103da587) Add cwd option to `before` commands, add wait option to dev [#4740](https://www.github.com/tauri-apps/tauri/pull/4740) [#3551](https://www.github.com/tauri-apps/tauri/pull/3551) ([#4834](https://www.github.com/tauri-apps/tauri/pull/4834)) on 2022-08-02\n- Allow configuring the `before_dev_command` to force the CLI to wait for the command to finish before proceeding.\n  - [d6f7d3cf](https://www.github.com/tauri-apps/tauri/commit/d6f7d3cfe8a7ec8d68c8341016c4e0a3103da587) Add cwd option to `before` commands, add wait option to dev [#4740](https://www.github.com/tauri-apps/tauri/pull/4740) [#3551](https://www.github.com/tauri-apps/tauri/pull/3551) ([#4834](https://www.github.com/tauri-apps/tauri/pull/4834)) on 2022-08-02\n- Check if the default build target is set in the Cargo configuration.\n  - [436f3d8d](https://www.github.com/tauri-apps/tauri/commit/436f3d8d66727f5b64165522f0b55f4ab54bd1ae) feat(cli): load Cargo configuration to check default build target ([#4990](https://www.github.com/tauri-apps/tauri/pull/4990)) on 2022-08-21\n- Add support to cargo-binstall.\n  - [90d5929f](https://www.github.com/tauri-apps/tauri/commit/90d5929fea6df575a2aa3c0a749374358f1ddb9b) feat(cli.rs): add support to cargo-binstall, closes [#4651](https://www.github.com/tauri-apps/tauri/pull/4651) ([#4817](https://www.github.com/tauri-apps/tauri/pull/4817)) on 2022-08-02\n- Use `cargo metadata` to detect the workspace root and target directory.\n  - [fea70eff](https://www.github.com/tauri-apps/tauri/commit/fea70effad219c0794d919f8834fa1a1ffd204c7) refactor(cli): Use `cargo metadata` to detect the workspace root and target directory, closes [#4632](https://www.github.com/tauri-apps/tauri/pull/4632), [#4928](https://www.github.com/tauri-apps/tauri/pull/4928). ([#4932](https://www.github.com/tauri-apps/tauri/pull/4932)) on 2022-08-21\n- Prompt for `beforeDevCommand` and `beforeBuildCommand` in `tauri init`.\n  - [6d4945c9](https://www.github.com/tauri-apps/tauri/commit/6d4945c9f06cd1f7018e1c48686ba682aae817df) feat(cli): prompt for before\\*Command, closes [#4691](https://www.github.com/tauri-apps/tauri/pull/4691) ([#4721](https://www.github.com/tauri-apps/tauri/pull/4721)) on 2022-07-25\n- Add `icon` command to generate icons.\n  - [12e9d811](https://www.github.com/tauri-apps/tauri/commit/12e9d811e69813e7e9db5344b422101ddc09589f) feat(cli): Add `icon` command (tauricon) ([#4992](https://www.github.com/tauri-apps/tauri/pull/4992)) on 2022-09-03\n- Added support to configuration files in TOML format (Tauri.toml file).\n  - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02\n- Automatically use any `.taurignore` file as ignore rules for dev watcher and app path finder.\n  - [596fa08d](https://www.github.com/tauri-apps/tauri/commit/596fa08d48e371c7bd29e1ef799119ac8fca0d0b) feat(cli): automatically use `.taurignore`, ref [#4617](https://www.github.com/tauri-apps/tauri/pull/4617) ([#4623](https://www.github.com/tauri-apps/tauri/pull/4623)) on 2022-07-28\n- Enable WiX FIPS compliance when the `TAURI_FIPS_COMPLIANT` environment variable is set to `true`.\n  - [d88b9de7](https://www.github.com/tauri-apps/tauri/commit/d88b9de7aaeaaa2e42e4795dbc2b8642b5ae7a50) feat(core): add `fips_compliant` wix config option, closes [#4541](https://www.github.com/tauri-apps/tauri/pull/4541) ([#4843](https://www.github.com/tauri-apps/tauri/pull/4843)) on 2022-08-04\n- Fixes dev watcher incorrectly exiting the CLI when sequential file updates are detected.\n  - [47fab680](https://www.github.com/tauri-apps/tauri/commit/47fab6809a1e23b3b9a84695e2d91ff0826ba79a) fix(cli): dev watcher incorrectly killing process on multiple file write ([#4684](https://www.github.com/tauri-apps/tauri/pull/4684)) on 2022-07-25\n- Set the `MACOSX_DEPLOYMENT_TARGET` environment variable with the configuration `minimum_system_version` value.\n  - [fa23310f](https://www.github.com/tauri-apps/tauri/commit/fa23310f23cb9e6a02ec2524f1ef394a5b42990e) fix(cli): set MACOSX_DEPLOYMENT_TARGET env var, closes [#4704](https://www.github.com/tauri-apps/tauri/pull/4704) ([#4842](https://www.github.com/tauri-apps/tauri/pull/4842)) on 2022-08-02\n- Added `--no-watch` argument to the `dev` command to disable the file watcher.\n  - [0983d7ce](https://www.github.com/tauri-apps/tauri/commit/0983d7ce7f24ab43f9ae7b5e1177ff244d8885a8) feat(cli): add `--no-watch` argument to the dev command, closes [#4617](https://www.github.com/tauri-apps/tauri/pull/4617) ([#4793](https://www.github.com/tauri-apps/tauri/pull/4793)) on 2022-07-29\n- Validate updater signature matches configured public key.\n  - [b2a8930b](https://www.github.com/tauri-apps/tauri/commit/b2a8930b3c4b72c50ce72e73575f42c9cbe91bad) feat(cli): validate updater private key when signing ([#4754](https://www.github.com/tauri-apps/tauri/pull/4754)) on 2022-07-25\n\n## \\[1.0.5]\n\n- Correctly fill the architecture when building Debian packages targeting ARM64 (aarch64).\n  - Bumped due to a bump in tauri-bundler.\n  - [635f23b8](https://www.github.com/tauri-apps/tauri/commit/635f23b88adbb8726d628f67840709cd870836dc) fix(bundler): correctly set debian architecture for aarch64 ([#4700](https://www.github.com/tauri-apps/tauri/pull/4700)) on 2022-07-17\n\n## \\[1.0.4]\n\n- Do not capture and force colors of `cargo build` output.\n  - [c635a0da](https://www.github.com/tauri-apps/tauri/commit/c635a0dad437860d54109adffaf245b7c21bc684) refactor(cli): do not capture and force colors of cargo build output ([#4627](https://www.github.com/tauri-apps/tauri/pull/4627)) on 2022-07-12\n- Reduce the amount of allocations when converting cases.\n  - [bc370e32](https://www.github.com/tauri-apps/tauri/commit/bc370e326810446e15b1f50fb962b980114ba16b) feat: reduce the amount of `heck`-related allocations ([#4634](https://www.github.com/tauri-apps/tauri/pull/4634)) on 2022-07-11\n\n## \\[1.0.3]\n\n- Changed the app template to not set the default app menu as it is now set automatically on macOS which is the platform that needs a menu to function properly.\n  - [91055883](https://www.github.com/tauri-apps/tauri/commit/9105588373cc8401bd9ad79bdef26f509b2d76b7) feat: add implicit default menu for macOS only, closes [#4551](https://www.github.com/tauri-apps/tauri/pull/4551) ([#4570](https://www.github.com/tauri-apps/tauri/pull/4570)) on 2022-07-04\n- Improved bundle identifier validation showing the exact source of the configuration value.\n  - [8e3e7fc6](https://www.github.com/tauri-apps/tauri/commit/8e3e7fc64641afc7a6833bc93205e6f525562545) feat(cli): improve bundle identifier validation, closes [#4589](https://www.github.com/tauri-apps/tauri/pull/4589) ([#4596](https://www.github.com/tauri-apps/tauri/pull/4596)) on 2022-07-05\n- Improve configuration deserialization error messages.\n  - [9170c920](https://www.github.com/tauri-apps/tauri/commit/9170c9207044fa561535f624916dfdbaa41ff79d) feat(core): improve config deserialization error messages ([#4607](https://www.github.com/tauri-apps/tauri/pull/4607)) on 2022-07-06\n- Skip the static link of the `vcruntime140.dll` if the `STATIC_VCRUNTIME` environment variable is set to `false`.\n  - [2e61abaa](https://www.github.com/tauri-apps/tauri/commit/2e61abaa9ae5d7a41ca1fa6505b5d6c368625ce5) feat(cli): allow dynamic link vcruntime, closes [#4565](https://www.github.com/tauri-apps/tauri/pull/4565) ([#4601](https://www.github.com/tauri-apps/tauri/pull/4601)) on 2022-07-06\n- The `TAURI_CONFIG` environment variable now represents the configuration to be merged instead of the entire JSON.\n  - [fa028ebf](https://www.github.com/tauri-apps/tauri/commit/fa028ebf3c8ca7b43a70d283a01dbea86217594f) refactor: do not pass entire config from CLI to core, send patch instead ([#4598](https://www.github.com/tauri-apps/tauri/pull/4598)) on 2022-07-06\n- Watch for Cargo workspace members in the `dev` file watcher.\n  - [dbb8c87b](https://www.github.com/tauri-apps/tauri/commit/dbb8c87b96dec9942b1bf877b29bafb8246514d4) feat(cli): watch Cargo workspaces in the dev command, closes [#4222](https://www.github.com/tauri-apps/tauri/pull/4222) ([#4572](https://www.github.com/tauri-apps/tauri/pull/4572)) on 2022-07-03\n\n## \\[1.0.2]\n\n- Fixes a crash on the `signer sign` command.\n  - [8e808fec](https://www.github.com/tauri-apps/tauri/commit/8e808fece95f2e506acf2c446d37b9913fd67d50) fix(cli.rs): conflicts_with arg doesn't exist closes  ([#4538](https://www.github.com/tauri-apps/tauri/pull/4538)) on 2022-06-30\n\n## \\[1.0.1]\n\n- No longer adds the `pkg-config` dependency to `.deb` packages when the `systemTray` is used.\n  This only works with recent versions of `libappindicator-sys` (including https://github.com/tauri-apps/libappindicator-rs/pull/38),\n  so a `cargo update` may be necessary if you create `.deb` bundles and use the tray feature.\n  - [0e6edeb1](https://www.github.com/tauri-apps/tauri/commit/0e6edeb14f379af1e02a7cebb4e3a5c9e87ebf7f) fix(cli): Don't add `pkg-config` to `deb` ([#4508](https://www.github.com/tauri-apps/tauri/pull/4508)) on 2022-06-29\n- AppImage bundling will now prefer bundling correctly named appindicator library (including `.1` version suffix). With a symlink for compatibility with the old naming.\n  - [bf45ca1d](https://www.github.com/tauri-apps/tauri/commit/bf45ca1df6691c05bdf72c5716cc01e89a7791d4) fix(cli,bundler): prefer AppImage libraries with ABI version ([#4505](https://www.github.com/tauri-apps/tauri/pull/4505)) on 2022-06-29\n- Improve error message when `cargo` is not installed.\n  - [e0e5f772](https://www.github.com/tauri-apps/tauri/commit/e0e5f772430f6349ec99ba891e601331e376e3c7) feat(cli): improve `cargo not found` error message, closes [#4428](https://www.github.com/tauri-apps/tauri/pull/4428) ([#4430](https://www.github.com/tauri-apps/tauri/pull/4430)) on 2022-06-21\n- The app template now only sets the default menu on macOS.\n  - [5105b428](https://www.github.com/tauri-apps/tauri/commit/5105b428c4726b2179cd4b3244350d1a1ee73734) feat(cli): change app template to only set default menu on macOS ([#4518](https://www.github.com/tauri-apps/tauri/pull/4518)) on 2022-06-29\n- Warn if updater is enabled but not in the bundle target list.\n  - [31c15cd2](https://www.github.com/tauri-apps/tauri/commit/31c15cd2bd94dbe39fb94982a15cbe02ac5d8925) docs(config): enhance documentation for bundle targets, closes [#3251](https://www.github.com/tauri-apps/tauri/pull/3251) ([#4418](https://www.github.com/tauri-apps/tauri/pull/4418)) on 2022-06-21\n- Check if target exists and is installed on dev and build commands.\n  - [13b8a240](https://www.github.com/tauri-apps/tauri/commit/13b8a2403d1353a8c3a643fbc6b6e862af68376e) feat(cli): validate target argument ([#4458](https://www.github.com/tauri-apps/tauri/pull/4458)) on 2022-06-24\n- Fixes the covector configuration on the plugin templates.\n  - [b8a64d01](https://www.github.com/tauri-apps/tauri/commit/b8a64d01bab11f955b7bbdf323d0afa1a3db4b64) fix(cli): add prepublish scripts to the plugin templates on 2022-06-19\n- Set the binary name to the product name in development.\n  - [b025b9f5](https://www.github.com/tauri-apps/tauri/commit/b025b9f581ac1a6ae0a26789c2be1e9928fb0282) refactor(cli): set binary name on dev ([#4447](https://www.github.com/tauri-apps/tauri/pull/4447)) on 2022-06-23\n- Allow registering a `.gitignore` file to skip watching some project files and directories via the `TAURI_DEV_WATCHER_IGNORE_FILE` environment variable.\n  - [83186dd8](https://www.github.com/tauri-apps/tauri/commit/83186dd89768407984db35fb67c3cc51f50ea8f5) Read extra ignore file for dev watcher, closes [#4406](https://www.github.com/tauri-apps/tauri/pull/4406) ([#4409](https://www.github.com/tauri-apps/tauri/pull/4409)) on 2022-06-20\n- Fix shebang for `kill-children.sh`.\n  - [35dd51db](https://www.github.com/tauri-apps/tauri/commit/35dd51db6826ec1eed7b90082b9eb6b2a699b627) fix(cli): add shebang for kill-children.sh, closes [#4262](https://www.github.com/tauri-apps/tauri/pull/4262) ([#4416](https://www.github.com/tauri-apps/tauri/pull/4416)) on 2022-06-22\n- Update plugin templates to use newer `tauri-apps/create-pull-request` GitHub action.\n  - [07f90795](https://www.github.com/tauri-apps/tauri/commit/07f9079532a42f3517d96faeaf46cad6176b31ac) chore(cli): update plugin template tauri-apps/create-pull-request on 2022-06-19\n- Use UNIX path separator on the init `$schema` field.\n  - [01053045](https://www.github.com/tauri-apps/tauri/commit/010530459ef62c48eed68ca965f2688accabcf69) chore(cli): use unix path separator on $schema ([#4384](https://www.github.com/tauri-apps/tauri/pull/4384)) on 2022-06-19\n- The `info` command now can check the Cargo lockfile on workspaces.\n  - [12f65219](https://www.github.com/tauri-apps/tauri/commit/12f65219ea75a51ebd38659ddce1563e015a036c) fix(cli): read lockfile from workspace on the info command, closes [#4232](https://www.github.com/tauri-apps/tauri/pull/4232) ([#4423](https://www.github.com/tauri-apps/tauri/pull/4423)) on 2022-06-21\n- Preserve the `Cargo.toml` formatting when the features array is not changed.\n  - [6650e5d6](https://www.github.com/tauri-apps/tauri/commit/6650e5d6720c215530ca1fdccd19bd2948dd6ca3) fix(cli): preserve Cargo manifest formatting when possible ([#4431](https://www.github.com/tauri-apps/tauri/pull/4431)) on 2022-06-21\n- Change the updater signature metadata to include the file name instead of its full path.\n  - [094b3eb3](https://www.github.com/tauri-apps/tauri/commit/094b3eb352bcf5de28414015e7c44290d619ea8c) fix(cli): file name instead of path on updater sig comment, closes [#4467](https://www.github.com/tauri-apps/tauri/pull/4467) ([#4484](https://www.github.com/tauri-apps/tauri/pull/4484)) on 2022-06-27\n- Validate bundle identifier as it must only contain alphanumeric characters, hyphens and periods.\n  - [0674a801](https://www.github.com/tauri-apps/tauri/commit/0674a80129d7c31bc93257849afc0a5069129fed) fix: assert config.bundle.identifier to be only alphanumeric, hyphens or dots. closes [#4359](https://www.github.com/tauri-apps/tauri/pull/4359) ([#4363](https://www.github.com/tauri-apps/tauri/pull/4363)) on 2022-06-17\n\n## \\[1.0.0]\n\n- Upgrade to `stable`!\n  - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15\n\n## \\[1.0.0-rc.16]\n\n- Use the default window menu in the app template.\n  - [4c4acc30](https://www.github.com/tauri-apps/tauri/commit/4c4acc3094218dd9cee0f1ad61810c979e0b41fa) feat: implement `Default` for `Menu`, closes [#2398](https://www.github.com/tauri-apps/tauri/pull/2398) ([#4291](https://www.github.com/tauri-apps/tauri/pull/4291)) on 2022-06-15\n\n## \\[1.0.0-rc.15]\n\n- Removed the tray icon from the Debian and AppImage bundles since they are embedded in the binary now.\n  - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14\n\n## \\[1.0.0-rc.14]\n\n- Set the `TRAY_LIBRARY_PATH` environment variable to make the bundle copy the appindicator library to the AppImage.\n  - [34552444](https://www.github.com/tauri-apps/tauri/commit/3455244436578003a5fbb447b039e5c8971152ec) feat(cli): bundle appindicator library in the AppImage, closes [#3859](https://www.github.com/tauri-apps/tauri/pull/3859) ([#4267](https://www.github.com/tauri-apps/tauri/pull/4267)) on 2022-06-07\n- Set the `APPIMAGE_BUNDLE_GSTREAMER` environment variable to make the bundler copy additional gstreamer files to the AppImage.\n  - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10\n- Configure the AppImage bundler to copy the `/usr/bin/xdg-open` binary if it exists and the shell `open` API is enabled.\n  - [2322ac11](https://www.github.com/tauri-apps/tauri/commit/2322ac11cf6290c6bf65413048a049c8072f863b) fix(bundler): bundle `/usr/bin/xdg-open` in appimage if open API enabled ([#4265](https://www.github.com/tauri-apps/tauri/pull/4265)) on 2022-06-04\n- Fixes multiple occurrences handling of the `bundles` and `features` arguments.\n  - [f685df39](https://www.github.com/tauri-apps/tauri/commit/f685df399a5a05480b6e4f5d92da71f3b87895ef) fix(cli): parsing of arguments with multiple values, closes [#4231](https://www.github.com/tauri-apps/tauri/pull/4231) ([#4233](https://www.github.com/tauri-apps/tauri/pull/4233)) on 2022-05-29\n- Log command output in real time instead of waiting for it to finish.\n  - [76d1eaae](https://www.github.com/tauri-apps/tauri/commit/76d1eaaebda5c8f6b0d41bf6587945e98cd441f3) feat(cli): debug command output in real time ([#4318](https://www.github.com/tauri-apps/tauri/pull/4318)) on 2022-06-12\n- Configure the `STATIC_VCRUNTIME` environment variable so `tauri-build` statically links it on the build command.\n  - [d703d27a](https://www.github.com/tauri-apps/tauri/commit/d703d27a707edc028f13b35603205da1133fcc2b) fix(build): statically link VC runtime only on `tauri build` ([#4292](https://www.github.com/tauri-apps/tauri/pull/4292)) on 2022-06-07\n- Use the `TAURI_TRAY` environment variable to determine which package should be added to the Debian `depends` section. Possible values are `ayatana` and `gtk`.\n  - [6216eb49](https://www.github.com/tauri-apps/tauri/commit/6216eb49e72863bfb6d4c9edb8827b21406ac393) refactor(core): drop `ayatana-tray` and `gtk-tray` Cargo features ([#4247](https://www.github.com/tauri-apps/tauri/pull/4247)) on 2022-06-02\n\n## \\[1.0.0-rc.13]\n\n- Check if `$CWD/src-tauri/tauri.conf.json` exists before walking through the file tree to find the tauri dir in case the whole project is gitignored.\n  - [bd8f3e29](https://www.github.com/tauri-apps/tauri/commit/bd8f3e298a0cb71809f2e93cc3ebc8e6e5b6a626) fix(cli): manual config lookup to handle gitignored folders, fixes [#3527](https://www.github.com/tauri-apps/tauri/pull/3527) ([#4224](https://www.github.com/tauri-apps/tauri/pull/4224)) on 2022-05-26\n- Statically link the Visual C++ runtime instead of using a merge module on the installer.\n  - [bb061509](https://www.github.com/tauri-apps/tauri/commit/bb061509fb674bef86ecbc1de3aa8f3e367a9907) refactor(core): statically link vcruntime, closes [#4122](https://www.github.com/tauri-apps/tauri/pull/4122) ([#4227](https://www.github.com/tauri-apps/tauri/pull/4227)) on 2022-05-27\n\n## \\[1.0.0-rc.12]\n\n- Properly fetch the NPM dependency information when using Yarn 2+.\n  - [cdfa6255](https://www.github.com/tauri-apps/tauri/commit/cdfa62551115586725bd3e4c04f12c5256f20790) fix(cli): properly read info when using yarn 2+, closes [#4106](https://www.github.com/tauri-apps/tauri/pull/4106) ([#4193](https://www.github.com/tauri-apps/tauri/pull/4193)) on 2022-05-23\n\n## \\[1.0.0-rc.11]\n\n- Allow configuring the display options for the MSI execution allowing quieter updates.\n  - [9f2c3413](https://www.github.com/tauri-apps/tauri/commit/9f2c34131952ea83c3f8e383bc3cec7e1450429f) feat(core): configure msiexec display options, closes [#3951](https://www.github.com/tauri-apps/tauri/pull/3951) ([#4061](https://www.github.com/tauri-apps/tauri/pull/4061)) on 2022-05-15\n\n## \\[1.0.0-rc.10]\n\n- Resolve binary file extension from target triple instead of compile-time checks to allow cross compilation.\n  - [4562e671](https://www.github.com/tauri-apps/tauri/commit/4562e671e4795e9386429348bf738f7078706945) fix(build): append .exe binary based on target triple instead of running OS, closes [#3870](https://www.github.com/tauri-apps/tauri/pull/3870) ([#4032](https://www.github.com/tauri-apps/tauri/pull/4032)) on 2022-05-03\n- Fixes text overflow on `tauri dev` on Windows.\n  - [094534d1](https://www.github.com/tauri-apps/tauri/commit/094534d138a9286e4746b61adff2da616e3b6a61) fix(cli): dev command stderr text overflow on Windows, closes [#3995](https://www.github.com/tauri-apps/tauri/pull/3995) ([#4000](https://www.github.com/tauri-apps/tauri/pull/4000)) on 2022-04-29\n- Improve CLI's logging output, making use of the standard rust `log` system.\n  - [35f21471](https://www.github.com/tauri-apps/tauri/commit/35f2147161e6697cbd2824681eeaf870b5a991c2) feat(cli): Improve CLI logging ([#4060](https://www.github.com/tauri-apps/tauri/pull/4060)) on 2022-05-07\n- Don't override the default keychain on macOS while code signing.\n  - [a4fcaf1d](https://www.github.com/tauri-apps/tauri/commit/a4fcaf1d04aafc3b4d42186f0fb386797d959a9d) fix: don't override default keychain, closes [#4008](https://www.github.com/tauri-apps/tauri/pull/4008) ([#4053](https://www.github.com/tauri-apps/tauri/pull/4053)) on 2022-05-05\n- - Remove startup delay in `tauri dev` caused by checking for a newer cli version. The check is now done upon process exit.\n- Add `TAURI_SKIP_UPDATE_CHECK` env variable to skip checking for a newer CLI version.\n- [bbabc8cd](https://www.github.com/tauri-apps/tauri/commit/bbabc8cd1ea2c1f6806610fd2d533c99305d320c) fix(cli.rs): remove startup delay in `tauri dev` ([#3999](https://www.github.com/tauri-apps/tauri/pull/3999)) on 2022-04-29\n- Fix `tauri info` panic when a package isn't installed.\n  - [4f0f3187](https://www.github.com/tauri-apps/tauri/commit/4f0f3187c9e69262ef28350331b368c831ab930a) fix(cli.rs): fix `tauri info` panic when a package isn't installed, closes [#3985](https://www.github.com/tauri-apps/tauri/pull/3985) ([#3996](https://www.github.com/tauri-apps/tauri/pull/3996)) on 2022-04-29\n- Added `$schema` support to `tauri.conf.json`.\n  - [715cbde3](https://www.github.com/tauri-apps/tauri/commit/715cbde3842a916c4ebeab2cab348e1774b5c192) feat(config): add `$schema` to `tauri.conf.json`, closes [#3464](https://www.github.com/tauri-apps/tauri/pull/3464) ([#4031](https://www.github.com/tauri-apps/tauri/pull/4031)) on 2022-05-03\n- **Breaking change:** The `dev` command now reads the custom config file from CWD instead of the Tauri folder.\n  - [a1929c6d](https://www.github.com/tauri-apps/tauri/commit/a1929c6dacccd00af4cdbcc4d29cfb98d8428f55) fix(cli): always read custom config file from CWD, closes [#4067](https://www.github.com/tauri-apps/tauri/pull/4067) ([#4074](https://www.github.com/tauri-apps/tauri/pull/4074)) on 2022-05-07\n- Fixes a Powershell crash when sending SIGINT to the dev command.\n  - [32048486](https://www.github.com/tauri-apps/tauri/commit/320484866b83ecabb01eb58d158e0fedd9dd08be) fix(cli): powershell crashing on SIGINT, closes [#3997](https://www.github.com/tauri-apps/tauri/pull/3997) ([#4007](https://www.github.com/tauri-apps/tauri/pull/4007)) on 2022-04-29\n- Prevent building when the bundle identifier is the default `com.tauri.dev`.\n  - [95726ebb](https://www.github.com/tauri-apps/tauri/commit/95726ebb6180d371be44bff9f16ca1eee049006a) feat(cli): prevent default bundle identifier from building, closes [#4041](https://www.github.com/tauri-apps/tauri/pull/4041) ([#4042](https://www.github.com/tauri-apps/tauri/pull/4042)) on 2022-05-04\n\n## \\[1.0.0-rc.9]\n\n- Exit CLI when Cargo returns a non-compilation error in `tauri dev`.\n  - [b5622882](https://www.github.com/tauri-apps/tauri/commit/b5622882cf3748e1e5a90915f415c0cd922aaaf8) fix(cli): exit on non-compilation Cargo errors, closes [#3930](https://www.github.com/tauri-apps/tauri/pull/3930) ([#3942](https://www.github.com/tauri-apps/tauri/pull/3942)) on 2022-04-22\n- Notify CLI update when running `tauri dev`.\n  - [a649aad7](https://www.github.com/tauri-apps/tauri/commit/a649aad7ad26d4578699370d6e63d80edeca1f97) feat(cli): check and notify about updates on `tauri dev`, closes [#3789](https://www.github.com/tauri-apps/tauri/pull/3789) ([#3960](https://www.github.com/tauri-apps/tauri/pull/3960)) on 2022-04-25\n- The CLI will not automatically run `strip` on release binaries anymore. Use the \\[`strip`]\\[strip] profile setting stabilized with Cargo 1.59.\n\n[`strip`]: https://doc.rust-lang.org/cargo/reference/profiles.html#strip\n\n- [62106224](https://www.github.com/tauri-apps/tauri/commit/621062246d065f21800d340126cf58315177f97e) refactor: drop strip from build command. closes [#3559](https://www.github.com/tauri-apps/tauri/pull/3559) ([#3863](https://www.github.com/tauri-apps/tauri/pull/3863)) on 2022-04-06\n- Kill the `beforeDevCommand` and app processes if the dev command returns an error.\n  - [485c9743](https://www.github.com/tauri-apps/tauri/commit/485c97438ac956d86bcf3794ceaa626bef968a4e) fix(cli): kill beforeDevCommand if dev code returns an error ([#3907](https://www.github.com/tauri-apps/tauri/pull/3907)) on 2022-04-19\n- Fix `info` command showing outdated text for latest versions.\n  - [73a4b74a](https://www.github.com/tauri-apps/tauri/commit/73a4b74aea8544e6fda51c1f6697630b0768072c) fix(cli.rs/info):  don't show outdated text for latest versions ([#3829](https://www.github.com/tauri-apps/tauri/pull/3829)) on 2022-04-02\n- **Breaking change:** Enable default Cargo features except `tauri/custom-protocol` on the dev command.\n  - [f2a30d8b](https://www.github.com/tauri-apps/tauri/commit/f2a30d8bc54fc3ba49e16f69a413eca5f61a9b1f) refactor(core): use ayatana appindicator by default, keep option to use gtk ([#3916](https://www.github.com/tauri-apps/tauri/pull/3916)) on 2022-04-19\n- Kill the `beforeDevCommand` process recursively on Unix.\n  - [e251e1b0](https://www.github.com/tauri-apps/tauri/commit/e251e1b0991d26ab10aea33cfb228f3e7f0f85b5) fix(cli): kill before dev command recursively on Unix, closes [#2794](https://www.github.com/tauri-apps/tauri/pull/2794) ([#3848](https://www.github.com/tauri-apps/tauri/pull/3848)) on 2022-04-03\n\n## \\[1.0.0-rc.8]\n\n- Allows the `tauri.conf.json` file to be git ignored on the path lookup function.\n  - [cc7c2d77](https://www.github.com/tauri-apps/tauri/commit/cc7c2d77da2e4a39ec2a97b080d41a719e6d0161) feat(cli): allow conf path to be gitignored, closes [#3636](https://www.github.com/tauri-apps/tauri/pull/3636) ([#3683](https://www.github.com/tauri-apps/tauri/pull/3683)) on 2022-03-13\n- Remove `minimumSystemVersion: null` from the application template configuration.\n  - [c81534eb](https://www.github.com/tauri-apps/tauri/commit/c81534ebd873c358e0346c7949aeb171803149a5) feat(cli): use default macOS minimum system version when it is empty ([#3658](https://www.github.com/tauri-apps/tauri/pull/3658)) on 2022-03-13\n- Improve readability of the `info` subcommand output.\n  - [49d2f13f](https://www.github.com/tauri-apps/tauri/commit/49d2f13fc07d763d5de9bf4b19d00c901776c11d) feat(cli): colorful cli ([#3635](https://www.github.com/tauri-apps/tauri/pull/3635)) on 2022-03-08\n- Properly terminate the `beforeDevCommand` process.\n  - [94d78efb](https://www.github.com/tauri-apps/tauri/commit/94d78efbe542e7be3c00e3b2355c22803816715f) fix(cli.rs): terminate the beforeDevCommand, closes [#2794](https://www.github.com/tauri-apps/tauri/pull/2794) ([#2883](https://www.github.com/tauri-apps/tauri/pull/2883)) on 2022-03-27\n- Fixes DMG bundling on macOS 12.3.\n  - [348a1ab5](https://www.github.com/tauri-apps/tauri/commit/348a1ab59d2697478a594016016f1fccbf1ac054) fix(bundler): DMG bundling on macOS 12.3 cannot use bless, closes [#3719](https://www.github.com/tauri-apps/tauri/pull/3719) ([#3721](https://www.github.com/tauri-apps/tauri/pull/3721)) on 2022-03-18\n- Fixes resources bundling on Windows when the path is on the root of the Tauri folder.\n  - [4c84559e](https://www.github.com/tauri-apps/tauri/commit/4c84559e1f3019e7aa2666b10a1a0bd97bb09d24) fix(cli): root resource bundling on Windows, closes [#3539](https://www.github.com/tauri-apps/tauri/pull/3539) ([#3685](https://www.github.com/tauri-apps/tauri/pull/3685)) on 2022-03-13\n\n## \\[1.0.0-rc.7]\n\n- Added `tsp` config option under `tauri > bundle > windows`, which enables Time-Stamp Protocol (RFC 3161) for the timestamping\n  server under code signing on Windows if set to `true`.\n  - [bdd5f7c2](https://www.github.com/tauri-apps/tauri/commit/bdd5f7c2f03af4af8b60a9527e55bb18525d989b) fix: add support for Time-Stamping Protocol for Windows codesigning (fix [#3563](https://www.github.com/tauri-apps/tauri/pull/3563)) ([#3570](https://www.github.com/tauri-apps/tauri/pull/3570)) on 2022-03-07\n- Change the `plugin init` templates to use the new `tauri::plugin::Builder` syntax.\n  - [f7acb061](https://www.github.com/tauri-apps/tauri/commit/f7acb061e4d1ecdbfe182793587632d7ba6d8eff) feat(cli): use plugin::Builder syntax on the plugin template ([#3606](https://www.github.com/tauri-apps/tauri/pull/3606)) on 2022-03-03\n\n## \\[1.0.0-rc.6]\n\n- Improve \"waiting for your dev server to start\" message.\n  - [5999379f](https://www.github.com/tauri-apps/tauri/commit/5999379fb06052a115f04f99274ab46d1eefd659) chore(cli): improve \"waiting for dev server\" message, closes [#3491](https://www.github.com/tauri-apps/tauri/pull/3491) ([#3504](https://www.github.com/tauri-apps/tauri/pull/3504)) on 2022-02-18\n- Do not panic if the updater private key password is wrong.\n  - [17f17a80](https://www.github.com/tauri-apps/tauri/commit/17f17a80f818bcc20c387583a6ff00a8e07ec533) fix(cli): do not panic if private key password is wrong, closes [#3449](https://www.github.com/tauri-apps/tauri/pull/3449) ([#3495](https://www.github.com/tauri-apps/tauri/pull/3495)) on 2022-02-17\n- Check the current folder before checking the directories on the app and tauri dir path lookup function.\n  - [a06de376](https://www.github.com/tauri-apps/tauri/commit/a06de3760184caa71acfe7a2fe2189a033b565f5) fix(cli): path lookup should not check subfolder before the current one ([#3465](https://www.github.com/tauri-apps/tauri/pull/3465)) on 2022-02-15\n- Fixes the signature of the `signer sign` command to not have duplicated short flags.\n  - [a9755514](https://www.github.com/tauri-apps/tauri/commit/a975551461f3698db3f3b6afa5101189aaeeada9) fix(cli): duplicated short flag for `signer sign`, closes [#3483](https://www.github.com/tauri-apps/tauri/pull/3483) ([#3492](https://www.github.com/tauri-apps/tauri/pull/3492)) on 2022-02-17\n\n## \\[1.0.0-rc.5]\n\n- Allow passing arguments to the `build` runner (`tauri build -- <ARGS>...`).\n  - [679fe1fe](https://www.github.com/tauri-apps/tauri/commit/679fe1fedd6ed016ab1140c8087c2d1404504bfb) feat(cli.rs): allow passing arguments to the build runner, closes [#3398](https://www.github.com/tauri-apps/tauri/pull/3398) ([#3431](https://www.github.com/tauri-apps/tauri/pull/3431)) on 2022-02-13\n- Improve error message when the dev runner command fails.\n  - [759d1afb](https://www.github.com/tauri-apps/tauri/commit/759d1afb86f3657f6071a2ae39c9be21e20ed22c) feat(cli): improve error message when dev runner command fails ([#3447](https://www.github.com/tauri-apps/tauri/pull/3447)) on 2022-02-13\n- Increase `tauri.conf.json` directory lookup depth to `3` and allow changing it with the `TAURI_PATH_DEPTH` environment variable.\n  - [c6031c70](https://www.github.com/tauri-apps/tauri/commit/c6031c7070c6bb7539bbfdfe42cb73012829c910) feat(cli): increase lookup depth, add env var option ([#3451](https://www.github.com/tauri-apps/tauri/pull/3451)) on 2022-02-13\n- Added `tauri-build`, `tao` and `wry` version to the `info` command output.\n  - [16f1173f](https://www.github.com/tauri-apps/tauri/commit/16f1173f456b1db543d0160df2c9828708bfc68a) feat(cli): add tao and wry version to the `info` output ([#3443](https://www.github.com/tauri-apps/tauri/pull/3443)) on 2022-02-13\n- **Breaking change:** The extra arguments passed to `tauri dev` using `-- <ARGS>...` are now propagated to the runner (defaults to cargo). To pass arguments to your binary using Cargo, you now need to run `tauri dev -- -- <ARGS-TO-YOUR-BINARY>...` (notice the double `--`).\n  - [679fe1fe](https://www.github.com/tauri-apps/tauri/commit/679fe1fedd6ed016ab1140c8087c2d1404504bfb) feat(cli.rs): allow passing arguments to the build runner, closes [#3398](https://www.github.com/tauri-apps/tauri/pull/3398) ([#3431](https://www.github.com/tauri-apps/tauri/pull/3431)) on 2022-02-13\n- Change the `init` template configuration to disable CSP for better usability for new users.\n  - [102a5e9b](https://www.github.com/tauri-apps/tauri/commit/102a5e9bb83c5d8388dc9aedc7f03cc57bdae8cb) refactor(cli.rs): change template config CSP to null, closes [#3427](https://www.github.com/tauri-apps/tauri/pull/3427) ([#3429](https://www.github.com/tauri-apps/tauri/pull/3429)) on 2022-02-13\n\n## \\[1.0.0-rc.4]\n\n- Change default value for the `freezePrototype` configuration to `false`.\n  - Bumped due to a bump in tauri-utils.\n  - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12\n\n## \\[1.0.0-rc.3]\n\n- Fixes Tauri path resolution on projects without Git or a `.gitignore` file.\n  - [d8acbe11](https://www.github.com/tauri-apps/tauri/commit/d8acbe11492bd990e6983c7e63e0f1a8f1ea5c7c) fix(cli.rs): app path resolution on projects without git, closes [#3409](https://www.github.com/tauri-apps/tauri/pull/3409) ([#3410](https://www.github.com/tauri-apps/tauri/pull/3410)) on 2022-02-11\n\n## \\[1.0.0-rc.2]\n\n- Fix `init` command prompting for values even if the argument has been provided on the command line.\n  - [def76840](https://www.github.com/tauri-apps/tauri/commit/def76840257a1447723ecda13c807cf0c881f083) fix(cli.rs): do not prompt for `init` values if arg set ([#3400](https://www.github.com/tauri-apps/tauri/pull/3400)) on 2022-02-11\n  - [41052dee](https://www.github.com/tauri-apps/tauri/commit/41052deeda2a00ee2b8ec2041c9c87c11de82ab2) fix(covector): add cli.js to change files on 2022-02-11\n- Fixes CLI freezing when running `light.exe` on Windows without the `--verbose` flag.\n  - [8beab636](https://www.github.com/tauri-apps/tauri/commit/8beab6363491e2a8757cc9fc0fa1eccc98ece916) fix(cli): build freezing on Windows, closes [#3399](https://www.github.com/tauri-apps/tauri/pull/3399) ([#3402](https://www.github.com/tauri-apps/tauri/pull/3402)) on 2022-02-11\n- Respect `.gitignore` configuration when looking for the folder with the `tauri.conf.json` file.\n  - [9c6c5a8c](https://www.github.com/tauri-apps/tauri/commit/9c6c5a8c52c6460d0b0a1a55300e1828262994ba) perf(cli.rs): improve performance of tauri dir lookup reading .gitignore ([#3405](https://www.github.com/tauri-apps/tauri/pull/3405)) on 2022-02-11\n  - [41052dee](https://www.github.com/tauri-apps/tauri/commit/41052deeda2a00ee2b8ec2041c9c87c11de82ab2) fix(covector): add cli.js to change files on 2022-02-11\n\n## \\[1.0.0-rc.1]\n\n- Include `vswhere.exe` on the published package.\n  - [3227502e](https://www.github.com/tauri-apps/tauri/commit/3227502e8c9f137e5783cba2e0c692473cc5456d) fix(cli.rs): package `vswhere.exe` on 2022-02-10\n\n## \\[1.0.0-rc.0]\n\n- Do not force Tauri application code on `src-tauri` folder and use a glob pattern to look for a subfolder with a `tauri.conf.json` file.\n  - [a8cff6b3](https://www.github.com/tauri-apps/tauri/commit/a8cff6b3bc3288a53d7cdc5b3cb95d371309d2d6) feat(cli): do not enforce `src-tauri` folder structure, closes [#2643](https://www.github.com/tauri-apps/tauri/pull/2643) ([#2654](https://www.github.com/tauri-apps/tauri/pull/2654)) on 2021-09-27\n- Define `TAURI_PLATFORM`, `TAURI_ARCH`, `TAURI_FAMILY`, `TAURI_PLATFORM_TYPE`, `TAURI_PLATFORM_VERSION` and `TAURI_DEBUG` environment variables for the `beforeDevCommand` and `beforeBuildCommand` scripts.\n  - [8599313a](https://www.github.com/tauri-apps/tauri/commit/8599313a0f56d9777d335426467e79ba687be1d4) feat(cli.rs): env vars for beforeDev/beforeBuild commands, closes [#2610](https://www.github.com/tauri-apps/tauri/pull/2610) ([#2655](https://www.github.com/tauri-apps/tauri/pull/2655)) on 2021-09-26\n  - [b5ee03a1](https://www.github.com/tauri-apps/tauri/commit/b5ee03a13a1c0d0ff677cf9c8d7ef28516fffa5b) feat(cli.rs): expose debug flag to beforeDev/beforeBuild commands ([#2727](https://www.github.com/tauri-apps/tauri/pull/2727)) on 2021-10-08\n  - [9bb68973](https://www.github.com/tauri-apps/tauri/commit/9bb68973dd10f3cb98d2a95e5432bfc765d77064) fix(cli.rs): prefix the \"before script\" env vars with `TAURI_` ([#3274](https://www.github.com/tauri-apps/tauri/pull/3274)) on 2022-01-24\n- Allow `config` arg to be a path to a JSON file on the `dev` and `build` commands.\n  - [7b81e5b8](https://www.github.com/tauri-apps/tauri/commit/7b81e5b82e665fe0562b91ac33b63a871af9e111) feat(cli.rs): allow config argument to be a path to a JSON file ([#2938](https://www.github.com/tauri-apps/tauri/pull/2938)) on 2021-11-22\n- Add `rustup` version and active rust toolchain to the `info` command output.\n  - [28aaec87](https://www.github.com/tauri-apps/tauri/commit/28aaec87e2f6445859e9dbaaf2231d02d1e1d4b5) feat(cli.rs): add active toolchain and rustup to `tauri info`, closes [#2730](https://www.github.com/tauri-apps/tauri/pull/2730) ([#2986](https://www.github.com/tauri-apps/tauri/pull/2986)) on 2021-12-09\n- Add `Visual Studio Build Tools` installed versions to the `info` command output.\n  - [d5f07d14](https://www.github.com/tauri-apps/tauri/commit/d5f07d14f31954fe89ff5045fd4ef72cfc2b9ac1) feat(cli.rs): build tools info ([#2618](https://www.github.com/tauri-apps/tauri/pull/2618)) on 2021-09-21\n- The inferred development server port for Svelte is now `8080` (assumes latest Svelte with `sirv-cli >= 2.0.0`).\n  - [de0543f3](https://www.github.com/tauri-apps/tauri/commit/de0543f3e052d2981a95ce3baa8470592740a1a2) feat(cli.rs): change inferred dev server port to 8080 for Svelte apps on 2022-02-05\n- Detect if tauri is used from git in the `info` command.\n  - [65ad5b5e](https://www.github.com/tauri-apps/tauri/commit/65ad5b5ef923bf1e6b6f078d794d071a04fcdf57) feat(cli.rs/info): detect if tauri is used from git ([#3309](https://www.github.com/tauri-apps/tauri/pull/3309)) on 2022-02-05\n- Drop the `dialoguer` soft fork and use the published version instead.\n  - [b1f5c6d7](https://www.github.com/tauri-apps/tauri/commit/b1f5c6d7ac48c7407f28402afef0d3e521314127) refactor(cli.rs): drop `dialoguer` and `console` soft fork ([#2790](https://www.github.com/tauri-apps/tauri/pull/2790)) on 2021-10-22\n- Fix `build` command when executed on a 32-bit Windows machine when pulling from the `binary-releases` repo.\n  - [35588b2e](https://www.github.com/tauri-apps/tauri/commit/35588b2e04d5be8e5708583bdc52a012341bc75e) fix(cli.rs): check default arch at runtime, closes [#3067](https://www.github.com/tauri-apps/tauri/pull/3067) ([#3078](https://www.github.com/tauri-apps/tauri/pull/3078)) on 2021-12-27\n- The `generate` and `sign` commands are now available under a `signer` subcommand.\n  - [1458ab3c](https://www.github.com/tauri-apps/tauri/commit/1458ab3c535637ada996ab0ff3494cd75fe40bf7) refactor(cli.rs): `signer` and `plugin` subcommands, use new clap derive syntax ([#2928](https://www.github.com/tauri-apps/tauri/pull/2928)) on 2021-12-09\n- Use `tauri-utils` to get the `Config` types.\n  - [4de285c3](https://www.github.com/tauri-apps/tauri/commit/4de285c3967d32250d73acdd5d171a6fd332d2b3) feat(core): validate Cargo features matching allowlist \\[TRI-023] on 2022-01-09\n- Print warning and exit if `distDir` contains `node_modules`, `src-tauri` or `target` folders.\n  - [7ed3f3b7](https://www.github.com/tauri-apps/tauri/commit/7ed3f3b7e4268708bbe8f83c45653e5d6704824b) feat(cli.rs): validate `distDir`, closes [#2554](https://www.github.com/tauri-apps/tauri/pull/2554) ([#2701](https://www.github.com/tauri-apps/tauri/pull/2701)) on 2021-10-04\n- Fix `tauri build` failing on Windows if `tauri.conf.json > tauri > bundle > Windows > wix > license` is used.\n  - [17a1ad68](https://www.github.com/tauri-apps/tauri/commit/17a1ad682363e51365b57899c8d7557b1b65201c) fix(cli.rs): ensure `target/release/wix` exists, closes [#2927](https://www.github.com/tauri-apps/tauri/pull/2927) ([#2987](https://www.github.com/tauri-apps/tauri/pull/2987)) on 2021-12-07\n- Added `dev_csp` to the `security` configuration object.\n  - [cf54dcf9](https://www.github.com/tauri-apps/tauri/commit/cf54dcf9c81730e42c9171daa9c8aa474c95b522) feat: improve `CSP` security with nonces and hashes, add `devCsp` \\[TRI-004] ([#8](https://www.github.com/tauri-apps/tauri/pull/8)) on 2022-01-09\n- Kill process if `beforeDevCommand` exits with a non-zero status code.\n  - [a2d5929a](https://www.github.com/tauri-apps/tauri/commit/a2d5929a8f1f6310d186199cc54246fcb0a01b46) feat(cli.rs): wait for dev URL to be reachable, exit if command fails ([#3358](https://www.github.com/tauri-apps/tauri/pull/3358)) on 2022-02-08\n- Fixes output directory detection when using Cargo workspaces.\n  - [8d630bc8](https://www.github.com/tauri-apps/tauri/commit/8d630bc8c494cba6ac1604b7777b89b763044471) fix(cli.rs): fix workspace detection, fixes [#2614](https://www.github.com/tauri-apps/tauri/pull/2614), closes [#2515](https://www.github.com/tauri-apps/tauri/pull/2515) ([#2644](https://www.github.com/tauri-apps/tauri/pull/2644)) on 2021-09-23\n- Allow using a fixed version for the Webview2 runtime via the `tauri > bundle > windows > webviewFixedRuntimePath` config option.\n  - [85df94f2](https://www.github.com/tauri-apps/tauri/commit/85df94f2b0d40255812b42c5e32a70c4b45392df) feat(core): config for fixed webview2 runtime version path ([#27](https://www.github.com/tauri-apps/tauri/pull/27)) on 2021-11-02\n- Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension.\n\nHere is the logic flow that determines if JSON or JSON5 will be used to parse the config:\n\n1. Check if `tauri.conf.json` exists\n   a. Parse it with `serde_json`\n   b. Parse it with `json5` if `serde_json` fails\n   c. Return original `serde_json` error if all above steps failed\n2. Check if `tauri.conf.json5` exists\n   a. Parse it with `json5`\n   b. Return error if all above steps failed\n3. Return error if all above steps failed\n\n- [995de57a](https://www.github.com/tauri-apps/tauri/commit/995de57a76cf51215277673e526d7ec32b86b564) Add seamless support for using JSON5 in the config file ([#47](https://www.github.com/tauri-apps/tauri/pull/47)) on 2022-02-03\n- Added `$ tauri plugin init` command, which initializes a Tauri plugin.\n  - [ac8e69a9](https://www.github.com/tauri-apps/tauri/commit/ac8e69a98ca1d6f646344ffdef1876cc9274323a) feat(cli.rs): add `init plugin` command, bootstraps a plugin project ([#2669](https://www.github.com/tauri-apps/tauri/pull/2669)) on 2021-09-27\n  - [db275f0b](https://www.github.com/tauri-apps/tauri/commit/db275f0b633f44fb2f85755d32929dfb7893b1e0) refactor(cli.rs): rename `init plugin` subcommand to `plugin init` ([#2885](https://www.github.com/tauri-apps/tauri/pull/2885)) on 2021-11-13\n- **Breaking change:** Add `macos-private-api` feature flag, enabled via `tauri.conf.json > tauri > macOSPrivateApi`.\n  - [6ac21b3c](https://www.github.com/tauri-apps/tauri/commit/6ac21b3cef7f14358df38cc69ea3d277011accaf) feat: add private api feature flag ([#7](https://www.github.com/tauri-apps/tauri/pull/7)) on 2022-01-09\n- Move the copying of resources and sidecars from `cli.rs` to `tauri-build` so using the Cargo CLI directly processes the files for the application execution in development.\n  - [5eb72c24](https://www.github.com/tauri-apps/tauri/commit/5eb72c24deddf5a01093bea96b90c0d8806afc3f) refactor: copy resources and sidecars on the Cargo build script ([#3357](https://www.github.com/tauri-apps/tauri/pull/3357)) on 2022-02-08\n- The minimum Rust version is now `1.56`.\n  - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22\n- Automatically `strip` the built binary on Linux and macOS if `--debug` is not specified.\n  - [2f3a582c](https://www.github.com/tauri-apps/tauri/commit/2f3a582c69994d66f2035bbe62825eafc869d90f) feat(cli.rs): strip release binaries \\[TRI-031] ([#22](https://www.github.com/tauri-apps/tauri/pull/22)) on 2022-01-09\n- Fixes pnpm error when running `pnpm tauri info`.\n  - [2026134f](https://www.github.com/tauri-apps/tauri/commit/2026134f471a212aea9b227a873ecc937dda1acc) fix(cli.rs): pnpm tauri info exits with error (fix [#2509](https://www.github.com/tauri-apps/tauri/pull/2509)) ([#2510](https://www.github.com/tauri-apps/tauri/pull/2510)) on 2021-08-24\n- Add support to building Universal macOS Binaries through the virtual target `universal-apple-darwin` (run `tauri build --target universal-apple-darwin`).\n  - [83f52fdb](https://www.github.com/tauri-apps/tauri/commit/83f52fdbe3a9ffd98dffc752e5f9e14322b56e60) feat: Add `universal-darwin-macos` build target, closes [#3317](https://www.github.com/tauri-apps/tauri/pull/3317) ([#3318](https://www.github.com/tauri-apps/tauri/pull/3318)) on 2022-02-04\n- Wait for `devPath` URL to be reachable before starting the application. Skipped if the `TAURI_SKIP_DEVSERVER_CHECK` environment variable is set to `true`.\n  - [a2d5929a](https://www.github.com/tauri-apps/tauri/commit/a2d5929a8f1f6310d186199cc54246fcb0a01b46) feat(cli.rs): wait for dev URL to be reachable, exit if command fails ([#3358](https://www.github.com/tauri-apps/tauri/pull/3358)) on 2022-02-08\n- On Windows, Fix `beforeDevCommand` and `beforeBuildCommand` not executing the expected command if it contains quotes. This is done by executing them with `CMD /S /C {command}` instead of `CMD /C {command}` on Windows.\n  - [52e9a6d8](https://www.github.com/tauri-apps/tauri/commit/52e9a6d81a701a66a8cf6a67c2b37d135583543f) fix: Make CMD handle quotes `\"` properly. ([#3334](https://www.github.com/tauri-apps/tauri/pull/3334)) on 2022-02-06\n- Allow setting the localization file for WiX.\n  - [af329f27](https://www.github.com/tauri-apps/tauri/commit/af329f2722d6194c6d70e976fc970dc2c9e4de2b) feat(bundler): wix localization, closes [#3174](https://www.github.com/tauri-apps/tauri/pull/3174) ([#3179](https://www.github.com/tauri-apps/tauri/pull/3179)) on 2022-02-05\n\n## \\[1.0.0-beta.7]\n\n- Update cli.yml to pass clap ArgSettings::MultipleValues assertion.\n  - [0391ac3d](https://www.github.com/tauri-apps/tauri/commit/0391ac3dc96d9c74c34a957e4cb70da88a0a85b7) fix: Update cli.yml to pass clap ArgSettings::MultipleValues assertion. ([#2506](https://www.github.com/tauri-apps/tauri/pull/2506)) ([#2507](https://www.github.com/tauri-apps/tauri/pull/2507)) on 2021-08-22\n\n## \\[1.0.0-beta.6]\n\n- Added `APPLE_SIGNING_IDENTITY` as supported environment variable for the bundler.\n  - [44f6ee4c](https://www.github.com/tauri-apps/tauri/commit/44f6ee4cfdfad5fb21d96e69f8776c0e68685682) chore(ci): add step to detect code signing ([#2245](https://www.github.com/tauri-apps/tauri/pull/2245)) on 2021-08-08\n- Added configuration for the WiX banner icon under `tauri.conf.json > tauri > bundle > windows > wix > bannerPath`.\n  - [13003ec7](https://www.github.com/tauri-apps/tauri/commit/13003ec761b1530705d6129519dc4e226eb992c7) feat(bundler): add config for WiX banner path, closes [#2175](https://www.github.com/tauri-apps/tauri/pull/2175) ([#2448](https://www.github.com/tauri-apps/tauri/pull/2448)) on 2021-08-16\n- Added configuration for the WiX dialog background bitmap under `tauri.conf.json > tauri > bundle > windows > wix > dialogImagePath`.\n  - [9bfdeb42](https://www.github.com/tauri-apps/tauri/commit/9bfdeb42effeeec27aa15bbc5b05040eadfda5ba) feat(bundler): add config for WiX dialog image path ([#2449](https://www.github.com/tauri-apps/tauri/pull/2449)) on 2021-08-16\n- Only convert package name and binary name to kebab-case, keeping the `.desktop` `Name` field with the original configured value.\n  - [3f039cb8](https://www.github.com/tauri-apps/tauri/commit/3f039cb8a308b0f18deaa37d7cfb1cc50d308d0e) fix: keep original `productName` for .desktop `Name` field, closes [#2295](https://www.github.com/tauri-apps/tauri/pull/2295) ([#2384](https://www.github.com/tauri-apps/tauri/pull/2384)) on 2021-08-10\n- Merge platform-specific `tauri.linux.conf.json`, `tauri.windows.conf.json` and `tauri.macos.conf.json` into the config JSON from `tauri.conf.json`.\n  - [71d687b7](https://www.github.com/tauri-apps/tauri/commit/71d687b787cd722c60879adda543897826bf42c9) feat(cli.rs): platform-specific conf.json ([#2309](https://www.github.com/tauri-apps/tauri/pull/2309)) on 2021-07-28\n- Update minimum Rust version to 1.54.0.\n  - [a5394716](https://www.github.com/tauri-apps/tauri/commit/a53947160985a4f5b0ad1fbb4aa6865d6f852c66) chore: update rust to 1.54.0 ([#2434](https://www.github.com/tauri-apps/tauri/pull/2434)) on 2021-08-15\n\n## \\[1.0.0-beta.5]\n\n- Run powershell commands with `-NoProfile` flag\n  - [3e6f3416](https://www.github.com/tauri-apps/tauri/commit/3e6f34160deab4f774d90aba28122e5b6b6f9db2) fix(cli.rs): run powershell kill command without profile ([#2130](https://www.github.com/tauri-apps/tauri/pull/2130)) on 2021-06-30\n- Adds `release` argument to the `dev` command. Allowing to run the backend in release mode during development.\n  - [7ee2dc8b](https://www.github.com/tauri-apps/tauri/commit/7ee2dc8b690703f509ab2d6ecdf9dafd6b72cd0b) feat(cli.rs): add release argument to the dev command ([#2192](https://www.github.com/tauri-apps/tauri/pull/2192)) on 2021-07-12\n- Fixes `center` and `focus` not being allowed in `tauri.conf.json > tauri > windows` and ignored in `WindowBuilderWrapper`.\n  - [bc2c331d](https://www.github.com/tauri-apps/tauri/commit/bc2c331dec3dec44c79e659b082b5fb6b65cc5ea) fix: center and focus not being allowed in config ([#2199](https://www.github.com/tauri-apps/tauri/pull/2199)) on 2021-07-12\n\n## \\[1.0.0-beta.4]\n\n- Improve error message when the product name is invalid.\n  - [1a41e9f0](https://www.github.com/tauri-apps/tauri/commit/1a41e9f040cfa18b6cc1380dfe21251d56e3f973) feat(cli.rs): improve error message on app rename, closes [#2101](https://www.github.com/tauri-apps/tauri/pull/2101) ([#2114](https://www.github.com/tauri-apps/tauri/pull/2114)) on 2021-06-28\n\n## \\[1.0.0-beta.3]\n\n- Properly detect target platform's architecture.\n  - [628a53eb](https://www.github.com/tauri-apps/tauri/commit/628a53eb6176f811d22d7730f08a99e5c370dbf4) fix(cli): properly detect target architecture, closes [#2040](https://www.github.com/tauri-apps/tauri/pull/2040) ([#2102](https://www.github.com/tauri-apps/tauri/pull/2102)) on 2021-06-28\n- Fixes `build` command when the `target` arg is set.\n  - [8e238701](https://www.github.com/tauri-apps/tauri/commit/8e2387018940e9e1421948d74a82156661ce2e4b) fix(cli.rs): fix out dir detection when target arg is set, closes [#2040](https://www.github.com/tauri-apps/tauri/pull/2040) ([#2098](https://www.github.com/tauri-apps/tauri/pull/2098)) on 2021-06-27\n\n## \\[1.0.0-beta.2]\n\n- Support `cargo tauri build` on Apple M1 chip.\n  - [3bf853d7](https://www.github.com/tauri-apps/tauri/commit/3bf853d782b491ad4965a1da25d19337eeac161f) feat(cli.rs): support tauri build on M1 chip ([#1915](https://www.github.com/tauri-apps/tauri/pull/1915)) on 2021-05-29\n- Infer `app name` and `window title` from `package.json > productName` or `package.json > name`.\n  Infer `distDir` and `devPath` by reading the package.json and trying to determine the UI framework (Vue.js, Angular, React, Svelte and some UI frameworks).\n  - [21a971c3](https://www.github.com/tauri-apps/tauri/commit/21a971c3b76bf0c26d00b2520b4976fa526738f5) feat(cli.rs): infer devPath/distDir/appName from package.json ([#1930](https://www.github.com/tauri-apps/tauri/pull/1930)) on 2021-05-31\n- Watch workspace crates on `dev` command.\n  - [86a23ff3](https://www.github.com/tauri-apps/tauri/commit/86a23ff30b4f18effa39c87b7cae6b7e324d131c) added support for cargo workspaces for `dev` command ([#1827](https://www.github.com/tauri-apps/tauri/pull/1827)) on 2021-05-13\n- Adds `features` argument to the `dev` and `build` commands.\n  - [6ec8e84d](https://www.github.com/tauri-apps/tauri/commit/6ec8e84d9172c090ee1549db56c98c66f12436ff) feat(cli.rs): add `features` arg to dev/build ([#1828](https://www.github.com/tauri-apps/tauri/pull/1828)) on 2021-05-13\n- Fixes the libwebkit2gtk package name.\n  - [e08065d7](https://www.github.com/tauri-apps/tauri/commit/e08065d7fe8398b41180b3a64854ec8e71174d42) fix: deb installation error ([#1844](https://www.github.com/tauri-apps/tauri/pull/1844)) on 2021-05-18\n- Properly keep all `tauri` features that are not managed by the CLI.\n  - [17c7c439](https://www.github.com/tauri-apps/tauri/commit/17c7c4396ff2d5e13fc8726c2965b4e810fad6b9) refactor(core): use `attohttpc` by default ([#1861](https://www.github.com/tauri-apps/tauri/pull/1861)) on 2021-05-19\n- Copy resources and binaries to `OUT_DIR` on `tauri dev` command.\n  - [8f29a260](https://www.github.com/tauri-apps/tauri/commit/8f29a260e67aa111f6aeb262bd846a46d2858ce9) fix(cli.rs): copy resources and binaries on dev, closes [#1298](https://www.github.com/tauri-apps/tauri/pull/1298) ([#1946](https://www.github.com/tauri-apps/tauri/pull/1946)) on 2021-06-04\n- Read cargo features from `tauri.conf.json > build > features` and propagate them on `dev` and `build`.\n  - [2b814e9c](https://www.github.com/tauri-apps/tauri/commit/2b814e9c937489af0acb56051bd01c0d7fca2413) added cargo features to tauri config ([#1824](https://www.github.com/tauri-apps/tauri/pull/1824)) on 2021-05-13\n- Fixes `tauri.conf.json > tauri > bundle > targets` not applying to the bundler.\n  - [8be35ced](https://www.github.com/tauri-apps/tauri/commit/8be35ced78658de732360e3b20d7d70108c9b32d) fix(cli.rs): `tauri.conf.json > tauri > bundle > targets` being ignored ([#1945](https://www.github.com/tauri-apps/tauri/pull/1945)) on 2021-06-04\n- Fixes `info` command not striping `\\r` from child process version output.\n  - [6a95d7ac](https://www.github.com/tauri-apps/tauri/commit/6a95d7acc378b40230bab18d00ea32de40a5818c) fix(cli.rs): `info` version checks not striping `\\r` on Windows ([#1952](https://www.github.com/tauri-apps/tauri/pull/1952)) on 2021-06-05\n- Allow setting a path to a license file for the Windows Installer (`tauri.conf.json > bundle > windows > wix > license`).\n  - [b769c7f7](https://www.github.com/tauri-apps/tauri/commit/b769c7f7da4064b6133bf39a82127863d0d35531) feat(bundler): windows installer license, closes [#2009](https://www.github.com/tauri-apps/tauri/pull/2009) ([#2027](https://www.github.com/tauri-apps/tauri/pull/2027)) on 2021-06-21\n- Change the `csp` value on the template to include `wss:` and `tauri:` to the `default-src` attribute.\n  - [463fd00d](https://www.github.com/tauri-apps/tauri/commit/463fd00d06241c734994fe8e1882788dc30cc993) fix(csp): add wss and tauri to conf template ([#1974](https://www.github.com/tauri-apps/tauri/pull/1974)) on 2021-06-15\n- Adds `tauri > bundle > windows > wix > language` config option. See https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables.\n  - [47919619](https://www.github.com/tauri-apps/tauri/commit/47919619815900fc3af47ec5873e31afb778b0ad) feat(bundler): allow setting wix language, closes [#1976](https://www.github.com/tauri-apps/tauri/pull/1976) ([#1988](https://www.github.com/tauri-apps/tauri/pull/1988)) on 2021-06-15\n\n## \\[1.0.0-beta.1]\n\n- Add `'self'` to default CSP because otherwise no joy on macOS.\n  - [12268e6](https://www.github.com/tauri-apps/tauri/commit/12268e6e69dc9a7034652f50316d3545cac687c7) fix(csp): add 'self' ([#1794](https://www.github.com/tauri-apps/tauri/pull/1794)) on 2021-05-12\n- Fix a typo that would result in bundle arg being ignored.\n  - [71f6a5e](https://www.github.com/tauri-apps/tauri/commit/71f6a5ed442a43bf1008043c95a1a90effdd2f81) fix(cli.rs/build): fix typo getting bundle arg ([#1783](https://www.github.com/tauri-apps/tauri/pull/1783)) on 2021-05-12\n\n## \\[1.0.0-beta.0]\n\n- Fixes a cargo `target/` cache issue.\n  - [79feb6a](https://www.github.com/tauri-apps/tauri/commit/79feb6a918c2b40af771b5dccc94c8f6f4176986) fix(cli.rs): cargo build failed due to cache issue, closes [#1543](https://www.github.com/tauri-apps/tauri/pull/1543) ([#1741](https://www.github.com/tauri-apps/tauri/pull/1741)) on 2021-05-07\n- Improve error logging.\n  - [5cc4b11](https://www.github.com/tauri-apps/tauri/commit/5cc4b11f5d00a1e7e580e31785b31c491c06d8d7) feat(cli.rs): add context to errors ([#1674](https://www.github.com/tauri-apps/tauri/pull/1674)) on 2021-05-01\n- Adds Webview2 version on `info` command.\n  - [2b4e2b7](https://www.github.com/tauri-apps/tauri/commit/2b4e2b7560515b76002d0c724bcca1f470ed106f) feat(cli.rs/info): get webview2 version on windows ([#1669](https://www.github.com/tauri-apps/tauri/pull/1669)) on 2021-05-04\n- Adds `--runner [PROGRAM]` argument on the `dev` and `build` command, allowing using the specified program to run and build the application (example program: `cross`).\n  - [5c1fe52](https://www.github.com/tauri-apps/tauri/commit/5c1fe52c2bd74e2a8f6c99c2870af967e6309e8d) feat(cli.rs): allow using cross instead of cargo, add target triple arg ([#1664](https://www.github.com/tauri-apps/tauri/pull/1664)) on 2021-04-30\n- Adds `--target [TARGET_TRIPLE]` option to the `build` command (example: `--target arm-unknown-linux-gnueabihf`).\n  - [5c1fe52](https://www.github.com/tauri-apps/tauri/commit/5c1fe52c2bd74e2a8f6c99c2870af967e6309e8d) feat(cli.rs): allow using cross instead of cargo, add target triple arg ([#1664](https://www.github.com/tauri-apps/tauri/pull/1664)) on 2021-04-30\n- Rename `--target` option on the `build` command to `--bundle`.\n  - [5c1fe52](https://www.github.com/tauri-apps/tauri/commit/5c1fe52c2bd74e2a8f6c99c2870af967e6309e8d) feat(cli.rs): allow using cross instead of cargo, add target triple arg ([#1664](https://www.github.com/tauri-apps/tauri/pull/1664)) on 2021-04-30\n- Automatically add Tauri dependencies to the debian package `Depends` section.\n  - [72b8048](https://www.github.com/tauri-apps/tauri/commit/72b8048b5ada7a18d71b0fd8a4a0177109b43db7) feat(cli.rs): fill debian `depends` with tauri dependencies ([#1767](https://www.github.com/tauri-apps/tauri/pull/1767)) on 2021-05-10\n- Properly kill `beforeDevCommand` process.\n  - [ac2cbcb](https://www.github.com/tauri-apps/tauri/commit/ac2cbcb131819e01074e1ed8fb6808260c56a027) fix(cli.rs): `before dev` process kill, closes [#1626](https://www.github.com/tauri-apps/tauri/pull/1626) ([#1700](https://www.github.com/tauri-apps/tauri/pull/1700)) on 2021-05-04\n- Adds support to `tauri` dependency as string and table on `Cargo.toml`.\n  - [df8bdcf](https://www.github.com/tauri-apps/tauri/commit/df8bdcf0631fd4e1e7035eb20a954574da96de66) feat(cli.rs): add support to string and table dependency, closes [#1653](https://www.github.com/tauri-apps/tauri/pull/1653) ([#1654](https://www.github.com/tauri-apps/tauri/pull/1654)) on 2021-04-29\n- Show `framework` and `bundler` on the `info` command by reading the `package.json` file and matching known dependencies.\n  - [152c755](https://www.github.com/tauri-apps/tauri/commit/152c755c4787b323ca3469c45934cc1e4d368cfa) feat(cli.rs): `framework` and `bundler` on info cmd, closes [#1681](https://www.github.com/tauri-apps/tauri/pull/1681) ([#1682](https://www.github.com/tauri-apps/tauri/pull/1682)) on 2021-05-02\n\n## \\[1.0.0-beta-rc.4]\n\n- Fixes the Message `command` name value on plugin invoke handler.\n  - Bumped due to a bump in tauri.\n  - [422dd5e](https://www.github.com/tauri-apps/tauri/commit/422dd5e2a0a03bb1556915c78f110bfab092c874) fix(core): command name on plugin invoke handler ([#1577](https://www.github.com/tauri-apps/tauri/pull/1577)) on 2021-04-21\n  - [f575aaa](https://www.github.com/tauri-apps/tauri/commit/f575aaad71f23d44b2f89cf9ee5d84817dc3bb7a) fix: change files not referencing core packages ([#1619](https://www.github.com/tauri-apps/tauri/pull/1619)) on 2021-04-25\n- The package info APIs now checks the `package` object on `tauri.conf.json`.\n  - Bumped due to a bump in tauri.\n  - [8fd1baf](https://www.github.com/tauri-apps/tauri/commit/8fd1baf69b14bb81d7be9d31605ed7f02058b392) fix(core): pull package info from tauri.conf.json if set ([#1581](https://www.github.com/tauri-apps/tauri/pull/1581)) on 2021-04-22\n  - [f575aaa](https://www.github.com/tauri-apps/tauri/commit/f575aaad71f23d44b2f89cf9ee5d84817dc3bb7a) fix: change files not referencing core packages ([#1619](https://www.github.com/tauri-apps/tauri/pull/1619)) on 2021-04-25\n\n## \\[1.0.0-beta-rc.3]\n\n- Check if distDir assets are built after running `beforeDevCommand`.\n  - [a670d3a](https://www.github.com/tauri-apps/tauri/commit/a670d3a457bc0c0135b879c746d26a5f121c87a7) fix(cli.rs): check if distDir exists after running `beforeDevCommand` ([#1586](https://www.github.com/tauri-apps/tauri/pull/1586)) on 2021-04-22\n- Fixes `tauri info` display version for the `@tauri-apps/api` package.\n  - [0012782](https://www.github.com/tauri-apps/tauri/commit/0012782e43bd4e7e49528853c226b8e0e24b8794) fix(cli.rs): `info` command `npm_package_version` parsing `beta-rc` ([#1587](https://www.github.com/tauri-apps/tauri/pull/1587)) on 2021-04-22\n- Fixes crash on usage of modifier keys on Windows when running `tauri init`.\n  - [d623d95](https://www.github.com/tauri-apps/tauri/commit/d623d95fcb67736bc0862866b347c7102cde66aa) fix(cli.rs): inliner dialoguer & console until they publish, fixes [#1492](https://www.github.com/tauri-apps/tauri/pull/1492) ([#1610](https://www.github.com/tauri-apps/tauri/pull/1610)) on 2021-04-25\n- Enable `tauri` `updater` feature when `tauri.conf.json > tauri > updater > active` is set to `true`.\n  - [9490b25](https://www.github.com/tauri-apps/tauri/commit/9490b257d2564840eb0c9167340bf444bca84699) fix(cli.rs): enable the `updater` feature on cli ([#1597](https://www.github.com/tauri-apps/tauri/pull/1597)) on 2021-04-23\n\n## \\[1.0.0-beta-rc.2]\n\n- Add missing camelcase rename for config\n  - [bdf7072](https://www.github.com/tauri-apps/tauri/commit/bdf707285e3d307ab083009c274ccb56d5053ff2) fix(cli.rs/info): add missing camelCase rename ([#1505](https://www.github.com/tauri-apps/tauri/pull/1505)) on 2021-04-14\n- Fix `tauri info`\n- Properly detect `yarn` and `npm` versions on windows.\n- Fix a panic caused by a wrong field name in `metadata.json`\n- [71666e9](https://www.github.com/tauri-apps/tauri/commit/71666e9f9cfb5499a727b3f95182e89073f67d7b) fix(cli.rs): fix panic & use `cmd` to run `yarn`&`npm` on windows ([#1511](https://www.github.com/tauri-apps/tauri/pull/1511)) on 2021-04-17\n- Sync `metadata.json` via script to update version reference to cli.js, tauri (core) and tauri-build.\n  - [1f64927](https://www.github.com/tauri-apps/tauri/commit/1f64927362ef20761d7cd3591281519eb292aa33) chore: sync cli.rs metadata.json file versions ([#1534](https://www.github.com/tauri-apps/tauri/pull/1534)) on 2021-04-19\n\n## \\[1.0.0-beta-rc.1]\n\n- Missing the `files` property in the package.json which mean that the `dist` directory was not published and used.\n  - Bumped due to a bump in api.\n  - [b2569a7](https://www.github.com/tauri-apps/tauri/commit/b2569a729a3caa88bdba62abc31f0665e1323aaa) fix(js-api): dist ([#1498](https://www.github.com/tauri-apps/tauri/pull/1498)) on 2021-04-15\n\n## \\[1.0.0-beta-rc.0]\n\n- You can now run `cargo tauri build -t none` to speed up the build if you don't need executables.\n  - [4d507f9](https://www.github.com/tauri-apps/tauri/commit/4d507f9adfb26819f9d6406b191fdaa6188145f4) feat(cli/core): add support for building without targets ([#1203](https://www.github.com/tauri-apps/tauri/pull/1203)) on 2021-02-10\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- The `dev` and `build` pipeline is now written in Rust.\n  - [3e8abe3](https://www.github.com/tauri-apps/tauri/commit/3e8abe376407bb0ca8893602590ed9edf7aa71a1) feat(cli) rewrite the core CLI in Rust ([#851](https://www.github.com/tauri-apps/tauri/pull/851)) on 2021-01-30\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Run `beforeDevCommand` and `beforeBuildCommand` in a shell.\n  - [32eb0d5](https://www.github.com/tauri-apps/tauri/commit/32eb0d562b135d8df19c78ff22aa53c73f459c76) feat(cli): run beforeDev and beforeBuild in a shell, closes [#1295](https://www.github.com/tauri-apps/tauri/pull/1295) ([#1399](https://www.github.com/tauri-apps/tauri/pull/1399)) on 2021-03-28\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Fixes `<a target=\"_blank\">` polyfill.\n  - [4ee044a](https://www.github.com/tauri-apps/tauri/commit/4ee044a3e662a0ac2be98f7e1286088d721c3307) fix(cli): use correct arg in `_blanks` links polyfill ([#1362](https://www.github.com/tauri-apps/tauri/pull/1362)) on 2021-03-17\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Update all code files to have our license header.\n  - [bf82136](https://www.github.com/tauri-apps/tauri/commit/bf8213646689175f8a158b956911f3a43e360690) feat(license): SPDX Headers ([#1449](https://www.github.com/tauri-apps/tauri/pull/1449)) on 2021-04-11\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Adds `productName` and `version` configs on `tauri.conf.json > package`.\n  - [5b3d9b2](https://www.github.com/tauri-apps/tauri/commit/5b3d9b2c07da766f81981ba7c4961cd354d51340) feat(config): allow setting product name and version on tauri.conf.json ([#1358](https://www.github.com/tauri-apps/tauri/pull/1358)) on 2021-03-22\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- The `info` command was rewritten in Rust.\n  - [c3e06ee](https://www.github.com/tauri-apps/tauri/commit/c3e06ee9e88b3631da6eeb17d61ddd41cd5c6fe9) refactor(cli): rewrite info in Rust ([#1389](https://www.github.com/tauri-apps/tauri/pull/1389)) on 2021-03-25\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- The `init` command was rewritten in Rust.\n  - [f72b93b](https://www.github.com/tauri-apps/tauri/commit/f72b93b676ba8c48fd9273c187de3dbbc410fa0f) refactor(cli): rewrite init command in Rust ([#1382](https://www.github.com/tauri-apps/tauri/pull/1382)) on 2021-03-24\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- All the arguments passed after `tauri dev --` are now propagated to the binary.\n  - [4e9d31c](https://www.github.com/tauri-apps/tauri/commit/4e9d31c70ba13f1cabe830c6519a1b5f4789fd7b) feat(cli): propagate args passed after `dev --`, closes [#1406](https://www.github.com/tauri-apps/tauri/pull/1406) ([#1407](https://www.github.com/tauri-apps/tauri/pull/1407)) on 2021-03-30\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Alpha version of tauri-updater. Please refer to the `README` for more details.\n  - [6d70c8e](https://www.github.com/tauri-apps/tauri/commit/6d70c8e1e256fe839c4a947375bb529d7b4f7301) feat(updater): Alpha version ([#643](https://www.github.com/tauri-apps/tauri/pull/643)) on 2021-04-05\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n"
  },
  {
    "path": "crates/tauri-cli/Cargo.toml",
    "content": "[package]\nname = \"tauri-cli\"\nversion = \"2.10.1\"\nauthors = [\"Tauri Programme within The Commons Conservancy\"]\nedition = \"2021\"\nrust-version = \"1.77.2\"\ncategories = [\"gui\", \"web-programming\"]\nlicense = \"Apache-2.0 OR MIT\"\nhomepage = \"https://tauri.app\"\nrepository = \"https://github.com/tauri-apps/tauri\"\ndescription = \"Command line interface for building Tauri apps\"\ninclude = [\n  \"src/\",\n  \"/templates\",\n  \"scripts/\",\n  \"*.json\",\n  \"*.rs\",\n  \"tauri.gitignore\",\n  \"tauri-dev-watcher.gitignore\",\n  \"LICENSE*\",\n]\n\n[package.metadata.binstall]\npkg-url = \"{ repo }/releases/download/tauri-cli-v{ version }/cargo-tauri-{ target }.{ archive-format }\"\nbin-dir = \"{ bin }{ binary-ext }\"\npkg-fmt = \"tgz\"\n\n[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]\npkg-fmt = \"zip\"\n\n[package.metadata.binstall.overrides.x86_64-apple-darwin]\npkg-fmt = \"zip\"\n\n[[bin]]\nname = \"cargo-tauri\"\npath = \"src/main.rs\"\n\n[target.\"cfg(any(target_os = \\\"linux\\\", target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"openbsd\\\", target_os = \\\"netbsd\\\", target_os = \\\"windows\\\", target_os = \\\"macos\\\"))\".dependencies]\ncargo-mobile2 = { version = \"0.22.3\", default-features = false }\n\n[dependencies]\njsonrpsee = { version = \"0.24\", features = [\"server\"] }\njsonrpsee-core = \"0.24\"\njsonrpsee-client-transport = { version = \"0.24\", features = [\"ws\"] }\njsonrpsee-ws-client = { version = \"0.24\", default-features = false }\nsublime_fuzzy = \"0.7\"\nclap_complete = \"4\"\nclap = { version = \"4\", features = [\"derive\", \"env\"] }\nthiserror = \"2\"\ntauri-bundler = { version = \"2.8.1\", default-features = false, path = \"../tauri-bundler\" }\ncolored = \"2\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = { version = \"1\", features = [\"preserve_order\"] }\njson5 = \"0.4\"\nnotify = \"8\"\nnotify-debouncer-full = \"0.6\"\nshared_child = \"1\"\nduct = \"1.0\"\ntoml_edit = { version = \"0.25\", features = [\"serde\"] }\njson-patch = \"3\"\ntauri-utils = { version = \"2.8.3\", path = \"../tauri-utils\", features = [\n  \"isolation\",\n  \"schema\",\n  \"config-json5\",\n  \"config-toml\",\n  \"html-manipulation\",\n] }\ntoml = \"1\"\njsonschema = { version = \"0.33\", default-features = false }\nhandlebars = \"6\"\ninclude_dir = \"0.7\"\ndirs = \"6\"\n# 0.7.4 to 0.8.0 were broken, 0.9 pulls in getrandom 0.4 with a high MSRV\n# see https://github.com/tauri-apps/tauri/pull/15022\nminisign = \"=0.7.3\"\nbase64 = \"0.22\"\nureq = { version = \"3\", default-features = false, features = [\"gzip\"] }\nos_info = \"3\"\nsemver = \"1\"\nregex = \"1\"\nheck = \"0.5\"\ndialoguer = \"0.11\"\nurl = { version = \"2\", features = [\"serde\"] }\nos_pipe = \"1\"\nignore = \"0.4\"\nctrlc = \"3\"\nlog = { version = \"0.4.21\", features = [\"kv\", \"kv_std\"] }\nenv_logger = \"0.11\"\nicns = { package = \"tauri-icns\", version = \"0.1\" }\nimage = { version = \"0.25\", default-features = false, features = [\"ico\"] }\naxum = { version = \"0.8\", features = [\"ws\"] }\nhtml5ever = \"0.29\"\nkuchiki = { package = \"kuchikiki\", version = \"=0.8.8-speedreader\" }\ntokio = { version = \"1\", features = [\"macros\", \"sync\"] }\ncommon-path = \"1\"\nserde-value = \"0.7\"\nitertools = \"0.13\"\nlocal-ip-address = \"0.6\"\ncss-color = \"0.2\"\nresvg = \"0.45.0\"\ndunce = \"1\"\nglob = \"0.3\"\n# 0.39 raised msrv to above 1.78 but 0.37+ can't compile on 1.77.2 either.\noxc_parser = \"0.36\"\noxc_span = \"0.36\"\noxc_allocator = \"0.36\"\noxc_ast = \"0.36\"\nmagic_string = \"0.3\"\nphf = { version = \"0.11\", features = [\"macros\"] }\nwalkdir = \"2\"\nelf = \"0.7\"\nmemchr = \"2\"\ntempfile = \"3\"\nuuid = { version = \"1\", features = [\"v5\"] }\nrand = \"0.9\"\nzip = { version = \"4\", default-features = false, features = [\"deflate\"] }\nwhich = \"8\"\nrayon = \"1.10\"\n\n[dev-dependencies]\ninsta = \"1\"\npretty_assertions = \"1\"\n\n[target.\"cfg(windows)\".dependencies.windows-sys]\nversion = \"0.60\"\nfeatures = [\n  \"Win32_Security\",\n  \"Win32_Storage_FileSystem\",\n  \"Win32_System_IO\",\n  \"Win32_System_Console\",\n]\n\n[target.\"cfg(unix)\".dependencies]\nlibc = \"0.2\"\n\n[target.\"cfg(target_os = \\\"macos\\\")\".dependencies]\nplist = \"1\"\ntauri-macos-sign = { version = \"2.3.3\", path = \"../tauri-macos-sign\" }\nobject = { version = \"0.36\", default-features = false, features = [\n  \"macho\",\n  \"read_core\",\n  \"std\",\n] }\nar = \"0.9\"\n\n[features]\ndefault = [\"rustls\", \"platform-certs\"]\nnative-tls = [\n  \"tauri-bundler/native-tls\",\n  \"cargo-mobile2/native-tls\",\n  \"ureq/native-tls\",\n]\nnative-tls-vendored = [\"native-tls\", \"tauri-bundler/native-tls-vendored\"]\nrustls = [\"tauri-bundler/rustls\", \"cargo-mobile2/rustls\", \"ureq/rustls\"]\nplatform-certs = [\"tauri-bundler/platform-certs\", \"ureq/platform-verifier\"]\n"
  },
  {
    "path": "crates/tauri-cli/ENVIRONMENT_VARIABLES.md",
    "content": "### Tauri's Environment Variables\n\nThis is a documentation of all environment variables used by tauri core crates and tauri CLI.\n\n### Tauri CLI\n\nThese environment variables are inputs to the CLI which may have an equivalent CLI flag.\n\n> if both environment variable and CLI flag are used, the CLI flag will have priority.\n\n- `CI` — If set, the CLI will run in CI mode and won't require any user interaction.\n- `TAURI_CLI_CONFIG_DEPTH` — Number of levels to traverse and find tauri configuration file.\n- `TAURI_CLI_PORT` — Port to use for the CLI built-in dev server.\n- `TAURI_CLI_WATCHER_IGNORE_FILENAME` — Name of a `.gitignore`-style file to control which files should be watched by the CLI in `dev` command. The CLI will look for this file name in each directory.\n- `TAURI_CLI_NO_DEV_SERVER_WAIT` — Skip waiting for the frontend dev server to start before building the tauri application.\n- `TAURI_LINUX_AYATANA_APPINDICATOR` — Set this var to `true` or `1` to force usage of `libayatana-appindicator` for system tray on Linux.\n- `TAURI_BUNDLER_WIX_FIPS_COMPLIANT` — Specify the bundler's WiX `FipsCompliant` option.\n- `TAURI_BUNDLER_TOOLS_GITHUB_MIRROR` - Specify a GitHub mirror to download files and tools used by tauri bundler.\n- `TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE` - Specify a GitHub mirror template to download files and tools used by tauri bundler, for example: `https://mirror.example.com/<owner>/<repo>/releases/download/<version>/<asset>`.\n- `TAURI_BUNDLER_DMG_IGNORE_CI` - Disable the check for `CI: true` in the `.dmg` bundler.\n- `TAURI_SKIP_SIDECAR_SIGNATURE_CHECK` - Skip signing sidecars.\n- `TAURI_SIGNING_PRIVATE_KEY` — Private key used to sign your app bundles, can be either a string or a path to the file.\n- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` — The signing private key password, see `TAURI_SIGNING_PRIVATE_KEY`.\n- `TAURI_SIGNING_RPM_KEY` — The private GPG key used to sign the RPM bundle, exported to its ASCII-armored format.\n- `TAURI_SIGNING_RPM_KEY_PASSPHRASE` — The GPG key passphrase for `TAURI_SIGNING_RPM_KEY`, if needed.\n- `TAURI_WINDOWS_SIGNTOOL_PATH` — Specify a path to `signtool.exe` used for code signing the application on Windows.\n- `APPLE_CERTIFICATE` — Base64 encoded of the `.p12` certificate for code signing. To get this value, run `openssl base64 -in MyCertificate.p12 -out MyCertificate-base64.txt`.\n- `APPLE_CERTIFICATE_PASSWORD` — The password you used to export the certificate.\n- `APPLE_ID` — The Apple ID used to notarize the application. If this environment variable is provided, `APPLE_PASSWORD` and `APPLE_TEAM_ID` must also be set. Alternatively, `APPLE_API_KEY` and `APPLE_API_ISSUER` can be used to authenticate.\n- `APPLE_PASSWORD` — The Apple password used to authenticate for application notarization. Required if `APPLE_ID` is specified. An app-specific password can be used. Alternatively to entering the password in plaintext, it may also be specified using a '@keychain:' or '@env:' prefix followed by a keychain password item name or environment variable name.\n- `APPLE_TEAM_ID`: Developer team ID. To find your Team ID, go to the [Account](https://developer.apple.com/account) page on the Apple Developer website, and check your membership details.\n- `APPLE_API_KEY` — Alternative to `APPLE_ID` and `APPLE_PASSWORD` for notarization authentication using JWT. Also an option to allow automated iOS certificate and provisioning profile management.\n  - See [creating API keys](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api) for more information.\n- `API_PRIVATE_KEYS_DIR` — Specify the directory where your AuthKey file is located. See `APPLE_API_KEY`.\n- `APPLE_API_ISSUER` — Issuer ID. Required if `APPLE_API_KEY` is specified.\n- `APPLE_API_KEY_PATH` - path to the API key `.p8` file. If not specified, for macOS apps the bundler searches the following directories in sequence for a private key file with the name of `AuthKey\\_<api_key>.p8`: `./private_keys`, `~/private_keys`, `~/.private_keys`, and `~/.appstoreconnect/private_keys`. **For iOS this variable is required**.\n- `APPLE_SIGNING_IDENTITY` — The identity used to code sign. Overwrites `tauri.conf.json > bundle > macOS > signingIdentity`. If neither are set, it is inferred from `APPLE_CERTIFICATE` when provided.\n- `APPLE_PROVIDER_SHORT_NAME` — If your Apple ID is connected to multiple teams, you have to specify the provider short name of the team you want to use to notarize your app. Overwrites `tauri.conf.json > bundle > macOS > providerShortName`.\n- `APPLE_DEVELOPMENT_TEAM` — The team ID used to code sign on iOS. Overwrites `tauri.conf.json > bundle > iOS > developmentTeam`. Can be found in https://developer.apple.com/account#MembershipDetailsCard.\n- `TAURI_WEBVIEW_AUTOMATION` — Enables webview automation (Linux Only).\n- `TAURI_ANDROID_PROJECT_PATH` — Path of the tauri android project, usually will be `<project>/src-tauri/gen/android`.\n- `TAURI_IOS_PROJECT_PATH` — Path of the tauri iOS project, usually will be `<project>/src-tauri/gen/ios`.\n\n### Tauri CLI Hook Commands\n\nThese environment variables are set for each hook command (`beforeDevCommand`, `beforeBuildCommand`, ...etc) which could be useful to conditionally build your frontend or execute a specific action.\n\n- `TAURI_ENV_DEBUG` — `true` for `dev` command or `build --debug`, `false` otherwise.\n- `TAURI_ENV_TARGET_TRIPLE` — Target triple the CLI is building.\n- `TAURI_ENV_ARCH` — Target arch, `x86_64`, `aarch64`...etc.\n- `TAURI_ENV_PLATFORM` — Target platform, `windows`, `darwin`, `linux`...etc.\n- `TAURI_ENV_PLATFORM_VERSION` — Build platform version\n- `TAURI_ENV_FAMILY` — Target platform family `unix` or `windows`.\n"
  },
  {
    "path": "crates/tauri-cli/LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/tauri-cli/LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2017 - Present Tauri Apps Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "crates/tauri-cli/README.md",
    "content": "# Tauri CLI\n\n <img align=\"right\" src=\"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\" height=\"128\" width=\"128\">\n\n[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev)\n[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri)\n[![test cli](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-cli-rs.yml?label=test%20cli&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-cli-rs.yml)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield)\n[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S)\n[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app)\n[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)\n[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri)\n\n| Component | Version                                                                                                |\n| --------- | ------------------------------------------------------------------------------------------------------ |\n| tauri-cli | [![](https://img.shields.io/crates/v/tauri-cli?style=flat-square)](https://crates.io/crates/tauri-cli) |\n\n## About Tauri\n\nTauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.\n\nTauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.\n\n## This module\n\nThis rust executable provides the full interface to all of the required activities for which the CLI is required. It will run on macOS, Windows, and Linux.\n\nTo learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.\n\n## Semver\n\n**tauri** is following [Semantic Versioning 2.0](https://semver.org/).\n\n## Licenses\n\nCode: (c) 2015 - 2021 - The Tauri Programme within The Commons Conservancy.\n\nMIT or MIT/Apache 2.0 where applicable.\n\nLogo: CC-BY-NC-ND\n\n- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)\n"
  },
  {
    "path": "crates/tauri-cli/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nfn main() {\n  println!(\"cargo:rerun-if-changed=templates/\");\n}\n"
  },
  {
    "path": "crates/tauri-cli/config.schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"$id\": \"https://schema.tauri.app/config/2.10.3\",\n  \"title\": \"Config\",\n  \"description\": \"The Tauri configuration object.\\n It is read from a file where you can define your frontend assets,\\n configure the bundler and define a tray icon.\\n\\n The configuration file is generated by the\\n [`tauri init`](https://v2.tauri.app/reference/cli/#init) command that lives in\\n your Tauri application source directory (src-tauri).\\n\\n Once generated, you may modify it at will to customize your Tauri application.\\n\\n ## File Formats\\n\\n By default, the configuration is defined as a JSON file named `tauri.conf.json`.\\n\\n Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.\\n The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.\\n The TOML file name is `Tauri.toml`.\\n\\n ## Platform-Specific Configuration\\n\\n In addition to the default configuration file, Tauri can\\n read a platform-specific configuration from `tauri.linux.conf.json`,\\n `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`\\n (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),\\n which gets merged with the main configuration object.\\n\\n ## Configuration Structure\\n\\n The configuration is composed of the following objects:\\n\\n - [`app`](#appconfig): The Tauri configuration\\n - [`build`](#buildconfig): The build configuration\\n - [`bundle`](#bundleconfig): The bundle configurations\\n - [`plugins`](#pluginconfig): The plugins configuration\\n\\n Example tauri.config.json file:\\n\\n ```json\\n {\\n   \\\"productName\\\": \\\"tauri-app\\\",\\n   \\\"version\\\": \\\"0.1.0\\\",\\n   \\\"build\\\": {\\n     \\\"beforeBuildCommand\\\": \\\"\\\",\\n     \\\"beforeDevCommand\\\": \\\"\\\",\\n     \\\"devUrl\\\": \\\"http://localhost:3000\\\",\\n     \\\"frontendDist\\\": \\\"../dist\\\"\\n   },\\n   \\\"app\\\": {\\n     \\\"security\\\": {\\n       \\\"csp\\\": null\\n     },\\n     \\\"windows\\\": [\\n       {\\n         \\\"fullscreen\\\": false,\\n         \\\"height\\\": 600,\\n         \\\"resizable\\\": true,\\n         \\\"title\\\": \\\"Tauri App\\\",\\n         \\\"width\\\": 800\\n       }\\n     ]\\n   },\\n   \\\"bundle\\\": {},\\n   \\\"plugins\\\": {}\\n }\\n ```\",\n  \"type\": \"object\",\n  \"required\": [\n    \"identifier\"\n  ],\n  \"properties\": {\n    \"$schema\": {\n      \"description\": \"The JSON schema for the Tauri config.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"productName\": {\n      \"description\": \"App name.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ],\n      \"pattern\": \"^[^/\\\\:*?\\\"<>|]+$\"\n    },\n    \"mainBinaryName\": {\n      \"description\": \"Overrides app's main binary filename.\\n\\n By default, Tauri uses the output binary from `cargo`, by setting this, we will rename that binary in `tauri-cli`'s\\n `tauri build` command, and target `tauri bundle` to it\\n\\n If possible, change the [`package name`] or set the [`name field`] instead,\\n and if that's not enough and you're using nightly, consider using the [`different-binary-name`] feature instead\\n\\n Note: this config should not include the binary extension (e.g. `.exe`), we'll add that for you\\n\\n [`package name`]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field\\n [`name field`]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-name-field\\n [`different-binary-name`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#different-binary-name\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"version\": {\n      \"description\": \"App version. It is a semver version number or a path to a `package.json` file containing the `version` field.\\n\\n If removed the version number from `Cargo.toml` is used.\\n It's recommended to manage the app versioning in the Tauri config.\\n\\n ## Platform-specific\\n\\n - **macOS**: Translates to the bundle's CFBundleShortVersionString property and is used as the default CFBundleVersion.\\n    You can set an specific bundle version using [`bundle > macOS > bundleVersion`](MacConfig::bundle_version).\\n - **iOS**: Translates to the bundle's CFBundleShortVersionString property and is used as the default CFBundleVersion.\\n    You can set an specific bundle version using [`bundle > iOS > bundleVersion`](IosConfig::bundle_version).\\n    The `tauri ios build` CLI command has a `--build-number <number>` option that lets you append a build number to the app version.\\n - **Android**: By default version 1.0 is used. You can set a version code using [`bundle > android > versionCode`](AndroidConfig::version_code).\\n\\n By default version 1.0 is used on Android.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"identifier\": {\n      \"description\": \"The application identifier in reverse domain name notation (e.g. `com.tauri.example`).\\n This string must be unique across applications since it is used in system configurations like\\n the bundle ID and path to the webview data directory.\\n This string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-),\\n and periods (.).\",\n      \"type\": \"string\"\n    },\n    \"app\": {\n      \"description\": \"The App configuration.\",\n      \"default\": {\n        \"enableGTKAppId\": false,\n        \"macOSPrivateApi\": false,\n        \"security\": {\n          \"assetProtocol\": {\n            \"enable\": false,\n            \"scope\": []\n          },\n          \"capabilities\": [],\n          \"dangerousDisableAssetCspModification\": false,\n          \"freezePrototype\": false,\n          \"pattern\": {\n            \"use\": \"brownfield\"\n          }\n        },\n        \"windows\": [],\n        \"withGlobalTauri\": false\n      },\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/AppConfig\"\n        }\n      ]\n    },\n    \"build\": {\n      \"description\": \"The build configuration.\",\n      \"default\": {\n        \"additionalWatchFolders\": [],\n        \"removeUnusedCommands\": false\n      },\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/BuildConfig\"\n        }\n      ]\n    },\n    \"bundle\": {\n      \"description\": \"The bundler configuration.\",\n      \"default\": {\n        \"active\": false,\n        \"android\": {\n          \"autoIncrementVersionCode\": false,\n          \"minSdkVersion\": 24\n        },\n        \"createUpdaterArtifacts\": false,\n        \"iOS\": {\n          \"minimumSystemVersion\": \"14.0\"\n        },\n        \"icon\": [],\n        \"linux\": {\n          \"appimage\": {\n            \"bundleMediaFramework\": false,\n            \"files\": {}\n          },\n          \"deb\": {\n            \"files\": {}\n          },\n          \"rpm\": {\n            \"epoch\": 0,\n            \"files\": {},\n            \"release\": \"1\"\n          }\n        },\n        \"macOS\": {\n          \"dmg\": {\n            \"appPosition\": {\n              \"x\": 180,\n              \"y\": 170\n            },\n            \"applicationFolderPosition\": {\n              \"x\": 480,\n              \"y\": 170\n            },\n            \"windowSize\": {\n              \"height\": 400,\n              \"width\": 660\n            }\n          },\n          \"files\": {},\n          \"hardenedRuntime\": true,\n          \"minimumSystemVersion\": \"10.13\"\n        },\n        \"targets\": \"all\",\n        \"useLocalToolsDir\": false,\n        \"windows\": {\n          \"allowDowngrades\": true,\n          \"certificateThumbprint\": null,\n          \"digestAlgorithm\": null,\n          \"nsis\": null,\n          \"signCommand\": null,\n          \"timestampUrl\": null,\n          \"tsp\": false,\n          \"webviewInstallMode\": {\n            \"silent\": true,\n            \"type\": \"downloadBootstrapper\"\n          },\n          \"wix\": null\n        }\n      },\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/BundleConfig\"\n        }\n      ]\n    },\n    \"plugins\": {\n      \"description\": \"The plugins config.\",\n      \"default\": {},\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/PluginConfig\"\n        }\n      ]\n    }\n  },\n  \"additionalProperties\": false,\n  \"definitions\": {\n    \"AppConfig\": {\n      \"description\": \"The App configuration object.\\n\\n See more: <https://v2.tauri.app/reference/config/#appconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"windows\": {\n          \"description\": \"The app windows configuration.\\n\\n ## Example:\\n\\n To create a window at app startup\\n\\n ```json\\n {\\n   \\\"app\\\": {\\n     \\\"windows\\\": [\\n       { \\\"width\\\": 800, \\\"height\\\": 600 }\\n     ]\\n   }\\n }\\n ```\\n\\n If not specified, the window's label (its identifier) defaults to \\\"main\\\",\\n you can use this label to get the window through\\n `app.get_webview_window` in Rust or `WebviewWindow.getByLabel` in JavaScript\\n\\n When working with multiple windows, each window will need an unique label\\n\\n ```json\\n {\\n   \\\"app\\\": {\\n     \\\"windows\\\": [\\n       { \\\"label\\\": \\\"main\\\", \\\"width\\\": 800, \\\"height\\\": 600 },\\n       { \\\"label\\\": \\\"secondary\\\", \\\"width\\\": 800, \\\"height\\\": 600 }\\n     ]\\n   }\\n }\\n ```\\n\\n You can also set `create` to false and use this config through the Rust APIs\\n\\n ```json\\n {\\n   \\\"app\\\": {\\n     \\\"windows\\\": [\\n       { \\\"create\\\": false, \\\"width\\\": 800, \\\"height\\\": 600 }\\n     ]\\n   }\\n }\\n ```\\n\\n and use it like this\\n\\n ```rust\\n tauri::Builder::default()\\n   .setup(|app| {\\n     tauri::WebviewWindowBuilder::from_config(app.handle(), &app.config().app.windows[0])?.build()?;\\n     Ok(())\\n   });\\n ```\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/WindowConfig\"\n          }\n        },\n        \"security\": {\n          \"description\": \"Security configuration.\",\n          \"default\": {\n            \"assetProtocol\": {\n              \"enable\": false,\n              \"scope\": []\n            },\n            \"capabilities\": [],\n            \"dangerousDisableAssetCspModification\": false,\n            \"freezePrototype\": false,\n            \"pattern\": {\n              \"use\": \"brownfield\"\n            }\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/SecurityConfig\"\n            }\n          ]\n        },\n        \"trayIcon\": {\n          \"description\": \"Configuration for app tray icon.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/TrayIconConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"macOSPrivateApi\": {\n          \"description\": \"MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"withGlobalTauri\": {\n          \"description\": \"Whether we should inject the Tauri API on `window.__TAURI__` or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"enableGTKAppId\": {\n          \"description\": \"If set to true \\\"identifier\\\" will be set as GTK app ID (on systems that use GTK).\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WindowConfig\": {\n      \"description\": \"The window configuration object.\\n\\n See more: <https://v2.tauri.app/reference/config/#windowconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"label\": {\n          \"description\": \"The window identifier. It must be alphanumeric.\",\n          \"default\": \"main\",\n          \"type\": \"string\"\n        },\n        \"create\": {\n          \"description\": \"Whether Tauri should create this window at app startup or not.\\n\\n When this is set to `false` you must manually grab the config object via `app.config().app.windows`\\n and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).\\n\\n ## Example:\\n\\n ```rust\\n tauri::Builder::default()\\n   .setup(|app| {\\n     tauri::WebviewWindowBuilder::from_config(app.handle(), &app.config().app.windows[0])?.build()?;\\n     Ok(())\\n   });\\n ```\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"url\": {\n          \"description\": \"The window webview URL.\",\n          \"default\": \"index.html\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WebviewUrl\"\n            }\n          ]\n        },\n        \"userAgent\": {\n          \"description\": \"The user agent for the webview\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dragDropEnabled\": {\n          \"description\": \"Whether the drag and drop is enabled or not on the webview. By default it is enabled.\\n\\n Disabling it is required to use HTML5 drag and drop on the frontend on Windows.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"center\": {\n          \"description\": \"Whether or not the window starts centered or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"x\": {\n          \"description\": \"The horizontal position of the window's top left corner in logical pixels\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"y\": {\n          \"description\": \"The vertical position of the window's top left corner in logical pixels\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"width\": {\n          \"description\": \"The window width in logical pixels.\",\n          \"default\": 800.0,\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"height\": {\n          \"description\": \"The window height in logical pixels.\",\n          \"default\": 600.0,\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"minWidth\": {\n          \"description\": \"The min window width in logical pixels.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"minHeight\": {\n          \"description\": \"The min window height in logical pixels.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"maxWidth\": {\n          \"description\": \"The max window width in logical pixels.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"maxHeight\": {\n          \"description\": \"The max window height in logical pixels.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"preventOverflow\": {\n          \"description\": \"Whether or not to prevent the window from overflowing the workarea\\n\\n ## Platform-specific\\n\\n - **iOS / Android:** Unsupported.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/PreventOverflowConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"resizable\": {\n          \"description\": \"Whether the window is resizable or not. When resizable is set to false, native window's maximize button is automatically disabled.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"maximizable\": {\n          \"description\": \"Whether the window's native maximize button is enabled or not.\\n If resizable is set to false, this setting is ignored.\\n\\n ## Platform-specific\\n\\n - **macOS:** Disables the \\\"zoom\\\" button in the window titlebar, which is also used to enter fullscreen mode.\\n - **Linux / iOS / Android:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"minimizable\": {\n          \"description\": \"Whether the window's native minimize button is enabled or not.\\n\\n ## Platform-specific\\n\\n - **Linux / iOS / Android:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"closable\": {\n          \"description\": \"Whether the window's native close button is enabled or not.\\n\\n ## Platform-specific\\n\\n - **Linux:** \\\"GTK+ will do its best to convince the window manager not to show a close button.\\n   Depending on the system, this function may not have any effect when called on a window that is already visible\\\"\\n - **iOS / Android:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"title\": {\n          \"description\": \"The window title.\",\n          \"default\": \"Tauri App\",\n          \"type\": \"string\"\n        },\n        \"fullscreen\": {\n          \"description\": \"Whether the window starts as fullscreen or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"focus\": {\n          \"description\": \"Whether the window will be initially focused or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"focusable\": {\n          \"description\": \"Whether the window will be focusable or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"transparent\": {\n          \"description\": \"Whether the window is transparent or not.\\n\\n Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri > macOSPrivateApi`.\\n WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"maximized\": {\n          \"description\": \"Whether the window is maximized or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"visible\": {\n          \"description\": \"Whether the window is visible or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"decorations\": {\n          \"description\": \"Whether the window should have borders and bars.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"alwaysOnBottom\": {\n          \"description\": \"Whether the window should always be below other windows.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"alwaysOnTop\": {\n          \"description\": \"Whether the window should always be on top of other windows.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"visibleOnAllWorkspaces\": {\n          \"description\": \"Whether the window should be visible on all workspaces or virtual desktops.\\n\\n ## Platform-specific\\n\\n - **Windows / iOS / Android:** Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"contentProtected\": {\n          \"description\": \"Prevents the window contents from being captured by other apps.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"skipTaskbar\": {\n          \"description\": \"If `true`, hides the window icon from the taskbar on Windows and Linux.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"windowClassname\": {\n          \"description\": \"The name of the window class created on Windows to create the window. **Windows only**.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"theme\": {\n          \"description\": \"The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Theme\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"titleBarStyle\": {\n          \"description\": \"The style of the macOS title bar.\",\n          \"default\": \"Visible\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/TitleBarStyle\"\n            }\n          ]\n        },\n        \"trafficLightPosition\": {\n          \"description\": \"The position of the window controls on macOS.\\n\\n Requires titleBarStyle: Overlay and decorations: true.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/LogicalPosition\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"hiddenTitle\": {\n          \"description\": \"If `true`, sets the window title to be hidden on macOS.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"acceptFirstMouse\": {\n          \"description\": \"Whether clicking an inactive window also clicks through to the webview on macOS.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"tabbingIdentifier\": {\n          \"description\": \"Defines the window [tabbing identifier] for macOS.\\n\\n Windows with matching tabbing identifiers will be grouped together.\\n If the tabbing identifier is not set, automatic tabbing will be disabled.\\n\\n [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"additionalBrowserArgs\": {\n          \"description\": \"Defines additional browser arguments on Windows. By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`\\n so if you use this method, you also need to disable these components by yourself if you want.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"shadow\": {\n          \"description\": \"Whether or not the window has shadow.\\n\\n ## Platform-specific\\n\\n - **Windows:**\\n   - `false` has no effect on decorated window, shadow are always ON.\\n   - `true` will make undecorated window have a 1px white border,\\n and on Windows 11, it will have a rounded corners.\\n - **Linux:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"windowEffects\": {\n          \"description\": \"Window effects.\\n\\n Requires the window to be transparent.\\n\\n ## Platform-specific:\\n\\n - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>\\n - **Linux**: Unsupported\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowEffectsConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"incognito\": {\n          \"description\": \"Whether or not the webview should be launched in incognito  mode.\\n\\n ## Platform-specific:\\n\\n - **Android**: Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"parent\": {\n          \"description\": \"Sets the window associated with this label to be the parent of the window to be created.\\n\\n ## Platform-specific\\n\\n - **Windows**: This sets the passed parent as an owner window to the window to be created.\\n   From [MSDN owned windows docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows):\\n     - An owned window is always above its owner in the z-order.\\n     - The system automatically destroys an owned window when its owner is destroyed.\\n     - An owned window is hidden when its owner is minimized.\\n - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\\n - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"proxyUrl\": {\n          \"description\": \"The proxy URL for the WebView for all network requests.\\n\\n Must be either a `http://` or a `socks5://` URL.\\n\\n ## Platform-specific\\n\\n - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"format\": \"uri\"\n        },\n        \"zoomHotkeysEnabled\": {\n          \"description\": \"Whether page zooming by hotkeys is enabled\\n\\n ## Platform-specific:\\n\\n - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.\\n - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,\\n 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission\\n\\n - **Android / iOS**: Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"browserExtensionsEnabled\": {\n          \"description\": \"Whether browser extensions can be installed for the webview process\\n\\n ## Platform-specific:\\n\\n - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)\\n - **MacOS / Linux / iOS / Android** - Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"useHttpsScheme\": {\n          \"description\": \"Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.\\n\\n ## Note\\n\\n Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.\\n\\n ## Warning\\n\\n Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"devtools\": {\n          \"description\": \"Enable web inspector which is usually called browser devtools. Enabled by default.\\n\\n This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.\\n\\n ## Platform-specific\\n\\n - macOS: This will call private functions on **macOS**.\\n - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.\\n - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"backgroundColor\": {\n          \"description\": \"Set the window and webview background color.\\n\\n ## Platform-specific:\\n\\n - **Windows**: alpha channel is ignored for the window layer.\\n - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.\\n - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored for the webview layer.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Color\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"backgroundThrottling\": {\n          \"description\": \"Change the default background throttling behaviour.\\n\\n By default, browsers use a suspend policy that will throttle timers and even unload\\n the whole tab (view) to free resources after roughly 5 minutes when a view became\\n minimized or hidden. This will pause all tasks until the documents visibility state\\n changes back from hidden to visible by bringing the view back to the foreground.\\n\\n ## Platform-specific\\n\\n - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.\\n - **iOS**: Supported since version 17.0+.\\n - **macOS**: Supported since version 14.0+.\\n\\n see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BackgroundThrottlingPolicy\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"javascriptDisabled\": {\n          \"description\": \"Whether we should disable JavaScript code execution on the webview or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"allowLinkPreview\": {\n          \"description\": \"on macOS and iOS there is a link preview on long pressing links, this is enabled by default.\\n see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"disableInputAccessoryView\": {\n          \"description\": \"Allows disabling the input accessory view on iOS.\\n\\n The accessory view is the view that appears above the keyboard when a text input element is focused.\\n It usually displays a view with \\\"Done\\\", \\\"Next\\\" buttons.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"dataDirectory\": {\n          \"description\": \"Set a custom path for the webview's data directory (localStorage, cache, etc.) **relative to [`appDataDir()`]/${label}**.\\n\\n To set absolute paths, use [`WebviewWindowBuilder::data_directory`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.data_directory)\\n\\n #### Platform-specific:\\n\\n - **Windows**: WebViews with different values for settings like `additionalBrowserArgs`, `browserExtensionsEnabled` or `scrollBarStyle` must have different data directories.\\n - **macOS / iOS**: Unsupported, use `dataStoreIdentifier` instead.\\n - **Android**: Unsupported.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dataStoreIdentifier\": {\n          \"description\": \"Initialize the WebView with a custom data store identifier. This can be seen as a replacement for `dataDirectory` which is unavailable in WKWebView.\\n See https://developer.apple.com/documentation/webkit/wkwebsitedatastore/init(foridentifier:)?language=objc\\n\\n The array must contain 16 u8 numbers.\\n\\n #### Platform-specific:\\n\\n - **iOS**: Supported since version 17.0+.\\n - **macOS**: Supported since version 14.0+.\\n - **Windows / Linux / Android**: Unsupported.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"integer\",\n            \"format\": \"uint8\",\n            \"minimum\": 0.0\n          },\n          \"maxItems\": 16,\n          \"minItems\": 16\n        },\n        \"scrollBarStyle\": {\n          \"description\": \"Specifies the native scrollbar style to use with the webview.\\n CSS styles that modify the scrollbar are applied on top of the native appearance configured here.\\n\\n Defaults to `default`, which is the browser default.\\n\\n ## Platform-specific\\n\\n - **Windows**:\\n   - `fluentOverlay` requires WebView2 Runtime version 125.0.2535.41 or higher,\\n     and does nothing on older versions.\\n   - This option must be given the same value for all webviews that target the same data directory.\\n - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.\",\n          \"default\": \"default\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/ScrollBarStyle\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WebviewUrl\": {\n      \"description\": \"An URL to open on a Tauri webview window.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An external URL. Must use either the `http` or `https` schemes.\",\n          \"type\": \"string\",\n          \"format\": \"uri\"\n        },\n        {\n          \"description\": \"The path portion of an app URL.\\n For instance, to load `tauri://localhost/users/john`,\\n you can simply provide `users/john` in this configuration.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A custom protocol url, for example, `doom://index.html`\",\n          \"type\": \"string\",\n          \"format\": \"uri\"\n        }\n      ]\n    },\n    \"PreventOverflowConfig\": {\n      \"description\": \"Prevent overflow with a margin\",\n      \"anyOf\": [\n        {\n          \"description\": \"Enable prevent overflow or not\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Enable prevent overflow with a margin\\n so that the window's size + this margin won't overflow the workarea\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/PreventOverflowMargin\"\n            }\n          ]\n        }\n      ]\n    },\n    \"PreventOverflowMargin\": {\n      \"description\": \"Enable prevent overflow with a margin\\n so that the window's size + this margin won't overflow the workarea\",\n      \"type\": \"object\",\n      \"required\": [\n        \"height\",\n        \"width\"\n      ],\n      \"properties\": {\n        \"width\": {\n          \"description\": \"Horizontal margin in physical pixels\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"height\": {\n          \"description\": \"Vertical margin in physical pixels\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Theme\": {\n      \"description\": \"System theme.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Light theme.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Light\"\n          ]\n        },\n        {\n          \"description\": \"Dark theme.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Dark\"\n          ]\n        }\n      ]\n    },\n    \"TitleBarStyle\": {\n      \"description\": \"How the window title bar should be displayed on macOS.\",\n      \"oneOf\": [\n        {\n          \"description\": \"A normal title bar.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Visible\"\n          ]\n        },\n        {\n          \"description\": \"Makes the title bar transparent, so the window background color is shown instead.\\n\\n Useful if you don't need to have actual HTML under the title bar. This lets you avoid the caveats of using `TitleBarStyle::Overlay`. Will be more useful when Tauri lets you set a custom window background color.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Transparent\"\n          ]\n        },\n        {\n          \"description\": \"Shows the title bar as a transparent overlay over the window's content.\\n\\n Keep in mind:\\n - The height of the title bar is different on different OS versions, which can lead to window the controls and title not being where you don't expect.\\n - You need to define a custom drag region to make your window draggable, however due to a limitation you can't drag the window when it's not in focus <https://github.com/tauri-apps/tauri/issues/4316>.\\n - The color of the window title depends on the system theme.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Overlay\"\n          ]\n        }\n      ]\n    },\n    \"LogicalPosition\": {\n      \"description\": \"Position coordinates struct.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"x\",\n        \"y\"\n      ],\n      \"properties\": {\n        \"x\": {\n          \"description\": \"X coordinate.\",\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"y\": {\n          \"description\": \"Y coordinate.\",\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WindowEffectsConfig\": {\n      \"description\": \"The window effects configuration object\",\n      \"type\": \"object\",\n      \"required\": [\n        \"effects\"\n      ],\n      \"properties\": {\n        \"effects\": {\n          \"description\": \"List of Window effects to apply to the Window.\\n Conflicting effects will apply the first one and ignore the rest.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/WindowEffect\"\n          }\n        },\n        \"state\": {\n          \"description\": \"Window effect state **macOS Only**\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowEffectState\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"radius\": {\n          \"description\": \"Window effect corner radius **macOS Only**\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"color\": {\n          \"description\": \"Window effect color. Affects [`WindowEffect::Blur`] and [`WindowEffect::Acrylic`] only\\n on Windows 10 v1903+. Doesn't have any effect on Windows 7 or Windows 11.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Color\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WindowEffect\": {\n      \"description\": \"Platform-specific window effects\",\n      \"oneOf\": [\n        {\n          \"description\": \"A default material appropriate for the view's effectiveAppearance. **macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"appearanceBased\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"light\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"dark\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"mediumLight\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"ultraDark\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.10+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"titlebar\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.10+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"selection\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.11+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"menu\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.11+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"popover\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.11+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"sidebar\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"headerView\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"sheet\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"windowBackground\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"hudWindow\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"fullScreenUI\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tooltip\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"contentBackground\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"underWindowBackground\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"underPageBackground\"\n          ]\n        },\n        {\n          \"description\": \"Mica effect that matches the system dark preference **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"mica\"\n          ]\n        },\n        {\n          \"description\": \"Mica effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"micaDark\"\n          ]\n        },\n        {\n          \"description\": \"Mica effect with light mode **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"micaLight\"\n          ]\n        },\n        {\n          \"description\": \"Tabbed effect that matches the system dark preference **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tabbed\"\n          ]\n        },\n        {\n          \"description\": \"Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tabbedDark\"\n          ]\n        },\n        {\n          \"description\": \"Tabbed effect with light mode **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tabbedLight\"\n          ]\n        },\n        {\n          \"description\": \"**Windows 7/10/11(22H1) Only**\\n\\n ## Notes\\n\\n This effect has bad performance when resizing/dragging the window on Windows 11 build 22621.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"blur\"\n          ]\n        },\n        {\n          \"description\": \"**Windows 10/11 Only**\\n\\n ## Notes\\n\\n This effect has bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"acrylic\"\n          ]\n        }\n      ]\n    },\n    \"WindowEffectState\": {\n      \"description\": \"Window effect state **macOS only**\\n\\n <https://developer.apple.com/documentation/appkit/nsvisualeffectview/state>\",\n      \"oneOf\": [\n        {\n          \"description\": \"Make window effect state follow the window's active state\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"followsWindowActiveState\"\n          ]\n        },\n        {\n          \"description\": \"Make window effect state always active\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"active\"\n          ]\n        },\n        {\n          \"description\": \"Make window effect state always inactive\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"inactive\"\n          ]\n        }\n      ]\n    },\n    \"Color\": {\n      \"anyOf\": [\n        {\n          \"description\": \"Color hex string, for example: #fff, #ffffff, or #ffffffff.\",\n          \"type\": \"string\",\n          \"pattern\": \"^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$\"\n        },\n        {\n          \"description\": \"Array of RGB colors. Each value has minimum of 0 and maximum of 255.\",\n          \"type\": \"array\",\n          \"items\": [\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            }\n          ],\n          \"maxItems\": 3,\n          \"minItems\": 3\n        },\n        {\n          \"description\": \"Array of RGBA colors. Each value has minimum of 0 and maximum of 255.\",\n          \"type\": \"array\",\n          \"items\": [\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            }\n          ],\n          \"maxItems\": 4,\n          \"minItems\": 4\n        },\n        {\n          \"description\": \"Object of red, green, blue, alpha color values. Each value has minimum of 0 and maximum of 255.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"blue\",\n            \"green\",\n            \"red\"\n          ],\n          \"properties\": {\n            \"red\": {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            \"green\": {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            \"blue\": {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            \"alpha\": {\n              \"default\": 255,\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            }\n          }\n        }\n      ]\n    },\n    \"BackgroundThrottlingPolicy\": {\n      \"description\": \"Background throttling policy.\",\n      \"oneOf\": [\n        {\n          \"description\": \"A policy where background throttling is disabled\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"disabled\"\n          ]\n        },\n        {\n          \"description\": \"A policy where a web view that's not in a window fully suspends tasks. This is usually the default behavior in case no policy is set.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"suspend\"\n          ]\n        },\n        {\n          \"description\": \"A policy where a web view that's not in a window limits processing, but does not fully suspend tasks.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"throttle\"\n          ]\n        }\n      ]\n    },\n    \"ScrollBarStyle\": {\n      \"description\": \"The scrollbar style to use in the webview.\\n\\n ## Platform-specific\\n\\n - **Windows**: This option must be given the same value for all webviews that target the same data directory.\",\n      \"oneOf\": [\n        {\n          \"description\": \"The scrollbar style to use in the webview.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"default\"\n          ]\n        },\n        {\n          \"description\": \"Fluent UI style overlay scrollbars. **Windows Only**\\n\\n Requires WebView2 Runtime version 125.0.2535.41 or higher, does nothing on older versions,\\n see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/?tabs=dotnetcsharp#10253541\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"fluentOverlay\"\n          ]\n        }\n      ]\n    },\n    \"SecurityConfig\": {\n      \"description\": \"Security configuration.\\n\\n See more: <https://v2.tauri.app/reference/config/#securityconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"csp\": {\n          \"description\": \"The Content Security Policy that will be injected on all HTML files on the built application.\\n If [`dev_csp`](#SecurityConfig.devCsp) is not specified, this value is also injected on dev.\\n\\n This is a really important part of the configuration since it helps you ensure your WebView is secured.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Csp\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"devCsp\": {\n          \"description\": \"The Content Security Policy that will be injected on all HTML files on development.\\n\\n This is a really important part of the configuration since it helps you ensure your WebView is secured.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Csp\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"freezePrototype\": {\n          \"description\": \"Freeze the `Object.prototype` when using the custom protocol.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"dangerousDisableAssetCspModification\": {\n          \"description\": \"Disables the Tauri-injected CSP sources.\\n\\n At compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy\\n to only allow loading of your own scripts and styles by injecting nonce and hash sources.\\n This stricts your CSP, which may introduce issues when using along with other flexing sources.\\n\\n This configuration option allows both a boolean and a list of strings as value.\\n A boolean instructs Tauri to disable the injection for all CSP injections,\\n and a list of strings indicates the CSP directives that Tauri cannot inject.\\n\\n **WARNING:** Only disable this if you know what you are doing and have properly configured the CSP.\\n Your application might be vulnerable to XSS attacks without this Tauri protection.\",\n          \"default\": false,\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/DisabledCspModificationKind\"\n            }\n          ]\n        },\n        \"assetProtocol\": {\n          \"description\": \"Custom protocol config.\",\n          \"default\": {\n            \"enable\": false,\n            \"scope\": []\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/AssetProtocolConfig\"\n            }\n          ]\n        },\n        \"pattern\": {\n          \"description\": \"The pattern to use.\",\n          \"default\": {\n            \"use\": \"brownfield\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/PatternKind\"\n            }\n          ]\n        },\n        \"capabilities\": {\n          \"description\": \"List of capabilities that are enabled on the application.\\n\\n By default (not set or empty list), all capability files from `./capabilities/` are included,\\n by setting values in this entry, you have fine grained control over which capabilities are included\\n\\n You can either reference a capability file defined in `./capabilities/` with its identifier or inline a [`Capability`]\\n\\n ### Example\\n\\n ```json\\n {\\n   \\\"app\\\": {\\n     \\\"capabilities\\\": [\\n       \\\"main-window\\\",\\n       {\\n         \\\"identifier\\\": \\\"drag-window\\\",\\n         \\\"permissions\\\": [\\\"core:window:allow-start-dragging\\\"]\\n       }\\n     ]\\n   }\\n }\\n ```\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/CapabilityEntry\"\n          }\n        },\n        \"headers\": {\n          \"description\": \"The headers, which are added to every http response from tauri to the web view\\n This doesn't include IPC Messages and error responses\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Csp\": {\n      \"description\": \"A Content-Security-Policy definition.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\",\n      \"anyOf\": [\n        {\n          \"description\": \"The entire CSP policy in a single text string.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An object mapping a directive with its sources values as a list of strings.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/CspDirectiveSources\"\n          }\n        }\n      ]\n    },\n    \"CspDirectiveSources\": {\n      \"description\": \"A Content-Security-Policy directive source list.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources>.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An inline list of CSP sources. Same as [`Self::List`], but concatenated with a space separator.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A list of CSP sources. The collection will be concatenated with a space separator for the CSP string.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"DisabledCspModificationKind\": {\n      \"description\": \"The possible values for the `dangerous_disable_asset_csp_modification` config option.\",\n      \"anyOf\": [\n        {\n          \"description\": \"If `true`, disables all CSP modification.\\n `false` is the default value and it configures Tauri to control the CSP.\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Disables the given list of CSP directives modifications.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"AssetProtocolConfig\": {\n      \"description\": \"Config for the asset custom protocol.\\n\\n See more: <https://v2.tauri.app/reference/config/#assetprotocolconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"scope\": {\n          \"description\": \"The access scope for the asset protocol.\",\n          \"default\": [],\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/FsScope\"\n            }\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Enables the asset protocol.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"FsScope\": {\n      \"description\": \"Protocol scope definition.\\n It is a list of glob patterns that restrict the API access from the webview.\\n\\n Each pattern can start with a variable that resolves to a system base directory.\\n The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,\\n `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,\\n `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$TEMP`,\\n `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A list of paths that are allowed by this scope.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A complete scope configuration.\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"allow\": {\n              \"description\": \"A list of paths that are allowed by this scope.\",\n              \"default\": [],\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            },\n            \"deny\": {\n              \"description\": \"A list of paths that are not allowed by this scope.\\n This gets precedence over the [`Self::Scope::allow`] list.\",\n              \"default\": [],\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            },\n            \"requireLiteralLeadingDot\": {\n              \"description\": \"Whether or not paths that contain components that start with a `.`\\n will require that `.` appears literally in the pattern; `*`, `?`, `**`,\\n or `[...]` will not match. This is useful because such files are\\n conventionally considered hidden on Unix systems and it might be\\n desirable to skip them when listing files.\\n\\n Defaults to `true` on Unix systems and `false` on Windows\",\n              \"type\": [\n                \"boolean\",\n                \"null\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"PatternKind\": {\n      \"description\": \"The application pattern.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Brownfield pattern.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"use\"\n          ],\n          \"properties\": {\n            \"use\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"brownfield\"\n              ]\n            }\n          }\n        },\n        {\n          \"description\": \"Isolation pattern. Recommended for security purposes.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"options\",\n            \"use\"\n          ],\n          \"properties\": {\n            \"use\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"isolation\"\n              ]\n            },\n            \"options\": {\n              \"type\": \"object\",\n              \"required\": [\n                \"dir\"\n              ],\n              \"properties\": {\n                \"dir\": {\n                  \"description\": \"The dir containing the index.html file that contains the secure isolation application.\",\n                  \"type\": \"string\"\n                }\n              }\n            }\n          }\n        }\n      ]\n    },\n    \"CapabilityEntry\": {\n      \"description\": \"A capability entry which can be either an inlined capability or a reference to a capability defined on its own file.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An inlined capability.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Capability\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Reference to a capability identifier.\",\n          \"type\": \"string\"\n        }\n      ]\n    },\n    \"Capability\": {\n      \"description\": \"A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\\n\\n It controls application windows' and webviews' fine grained access\\n to the Tauri core, application, or plugin commands.\\n If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\\n\\n This can be done to create groups of windows, based on their required system access, which can reduce\\n impact of frontend vulnerabilities in less privileged windows.\\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\\n A Window can have none, one, or multiple associated capabilities.\\n\\n ## Example\\n\\n ```json\\n {\\n   \\\"identifier\\\": \\\"main-user-files-write\\\",\\n   \\\"description\\\": \\\"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\\\",\\n   \\\"windows\\\": [\\n     \\\"main\\\"\\n   ],\\n   \\\"permissions\\\": [\\n     \\\"core:default\\\",\\n     \\\"dialog:open\\\",\\n     {\\n       \\\"identifier\\\": \\\"fs:allow-write-text-file\\\",\\n       \\\"allow\\\": [{ \\\"path\\\": \\\"$HOME/test.txt\\\" }]\\n     },\\n   ],\\n   \\\"platforms\\\": [\\\"macOS\\\",\\\"windows\\\"]\\n }\\n ```\",\n      \"type\": \"object\",\n      \"required\": [\n        \"identifier\",\n        \"permissions\"\n      ],\n      \"properties\": {\n        \"identifier\": {\n          \"description\": \"Identifier of the capability.\\n\\n ## Example\\n\\n `main-user-files-write`\",\n          \"type\": \"string\"\n        },\n        \"description\": {\n          \"description\": \"Description of what the capability is intended to allow on associated windows.\\n\\n It should contain a description of what the grouped permissions should allow.\\n\\n ## Example\\n\\n This capability allows the `main` window access to `filesystem` write related\\n commands and `dialog` commands to enable programmatic access to files selected by the user.\",\n          \"default\": \"\",\n          \"type\": \"string\"\n        },\n        \"remote\": {\n          \"description\": \"Configure remote URLs that can use the capability permissions.\\n\\n This setting is optional and defaults to not being set, as our\\n default use case is that the content is served from our local application.\\n\\n :::caution\\n Make sure you understand the security implications of providing remote\\n sources with local system access.\\n :::\\n\\n ## Example\\n\\n ```json\\n {\\n   \\\"urls\\\": [\\\"https://*.mydomain.dev\\\"]\\n }\\n ```\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/CapabilityRemote\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"local\": {\n          \"description\": \"Whether this capability is enabled for local app URLs or not. Defaults to `true`.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"windows\": {\n          \"description\": \"List of windows that are affected by this capability. Can be a glob pattern.\\n\\n If a window label matches any of the patterns in this list,\\n the capability will be enabled on all the webviews of that window,\\n regardless of the value of [`Self::webviews`].\\n\\n On multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`]\\n for a fine grained access control.\\n\\n ## Example\\n\\n `[\\\"main\\\"]`\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"webviews\": {\n          \"description\": \"List of webviews that are affected by this capability. Can be a glob pattern.\\n\\n The capability will be enabled on all the webviews\\n whose label matches any of the patterns in this list,\\n regardless of whether the webview's window label matches a pattern in [`Self::windows`].\\n\\n ## Example\\n\\n `[\\\"sub-webview-one\\\", \\\"sub-webview-two\\\"]`\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"permissions\": {\n          \"description\": \"List of permissions attached to this capability.\\n\\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\\n For commands directly implemented in the application itself only `${permission-name}`\\n is required.\\n\\n ## Example\\n\\n ```json\\n [\\n   \\\"core:default\\\",\\n   \\\"shell:allow-open\\\",\\n   \\\"dialog:open\\\",\\n   {\\n     \\\"identifier\\\": \\\"fs:allow-write-text-file\\\",\\n     \\\"allow\\\": [{ \\\"path\\\": \\\"$HOME/test.txt\\\" }]\\n   }\\n ]\\n ```\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/PermissionEntry\"\n          },\n          \"uniqueItems\": true\n        },\n        \"platforms\": {\n          \"description\": \"Limit which target platforms this capability applies to.\\n\\n By default all platforms are targeted.\\n\\n ## Example\\n\\n `[\\\"macOS\\\",\\\"windows\\\"]`\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/definitions/Target\"\n          }\n        }\n      }\n    },\n    \"CapabilityRemote\": {\n      \"description\": \"Configuration for remote URLs that are associated with the capability.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"urls\"\n      ],\n      \"properties\": {\n        \"urls\": {\n          \"description\": \"Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\\n\\n ## Examples\\n\\n - \\\"https://*.mydomain.dev\\\": allows subdomains of mydomain.dev\\n - \\\"https://mydomain.dev/api/*\\\": allows any subpath of mydomain.dev/api\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"PermissionEntry\": {\n      \"description\": \"An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`]\\n or an object that references a permission and extends its scope.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Reference a permission or permission set by identifier.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Identifier\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Reference a permission or permission set by identifier and extends its scope.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"identifier\"\n          ],\n          \"properties\": {\n            \"identifier\": {\n              \"description\": \"Identifier of the permission or permission set.\",\n              \"allOf\": [\n                {\n                  \"$ref\": \"#/definitions/Identifier\"\n                }\n              ]\n            },\n            \"allow\": {\n              \"description\": \"Data that defines what is allowed by the scope.\",\n              \"type\": [\n                \"array\",\n                \"null\"\n              ],\n              \"items\": {\n                \"$ref\": \"#/definitions/Value\"\n              }\n            },\n            \"deny\": {\n              \"description\": \"Data that defines what is denied by the scope. This should be prioritized by validation logic.\",\n              \"type\": [\n                \"array\",\n                \"null\"\n              ],\n              \"items\": {\n                \"$ref\": \"#/definitions/Value\"\n              }\n            }\n          }\n        }\n      ]\n    },\n    \"Identifier\": {\n      \"type\": \"string\"\n    },\n    \"Value\": {\n      \"description\": \"All supported ACL values.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents a null JSON value.\",\n          \"type\": \"null\"\n        },\n        {\n          \"description\": \"Represents a [`bool`].\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Represents a valid ACL [`Number`].\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Number\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Represents a [`String`].\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Represents a list of other [`Value`]s.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        },\n        {\n          \"description\": \"Represents a map of [`String`] keys to [`Value`]s.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        }\n      ]\n    },\n    \"Number\": {\n      \"description\": \"A valid ACL number.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents an [`i64`].\",\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        {\n          \"description\": \"Represents a [`f64`].\",\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      ]\n    },\n    \"Target\": {\n      \"description\": \"Platform target.\",\n      \"oneOf\": [\n        {\n          \"description\": \"MacOS.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"macOS\"\n          ]\n        },\n        {\n          \"description\": \"Windows.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"windows\"\n          ]\n        },\n        {\n          \"description\": \"Linux.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"linux\"\n          ]\n        },\n        {\n          \"description\": \"Android.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"android\"\n          ]\n        },\n        {\n          \"description\": \"iOS.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"iOS\"\n          ]\n        }\n      ]\n    },\n    \"HeaderConfig\": {\n      \"description\": \"A struct, where the keys are some specific http header names.\\n\\n If the values to those keys are defined, then they will be send as part of a response message.\\n This does not include error messages and ipc messages\\n\\n ## Example configuration\\n ```javascript\\n {\\n  //..\\n   app:{\\n     //..\\n     security: {\\n       headers: {\\n         \\\"Cross-Origin-Opener-Policy\\\": \\\"same-origin\\\",\\n         \\\"Cross-Origin-Embedder-Policy\\\": \\\"require-corp\\\",\\n         \\\"Timing-Allow-Origin\\\": [\\n           \\\"https://developer.mozilla.org\\\",\\n           \\\"https://example.com\\\",\\n         ],\\n         \\\"Access-Control-Expose-Headers\\\": \\\"Tauri-Custom-Header\\\",\\n         \\\"Tauri-Custom-Header\\\": {\\n           \\\"key1\\\": \\\"'value1' 'value2'\\\",\\n           \\\"key2\\\": \\\"'value3'\\\"\\n         }\\n       },\\n       csp: \\\"default-src 'self'; connect-src ipc: http://ipc.localhost\\\",\\n     }\\n     //..\\n   }\\n  //..\\n }\\n ```\\n In this example `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` are set to allow for the use of [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).\\n The result is, that those headers are then set on every response sent via the `get_response` function in crates/tauri/src/protocol/tauri.rs.\\n The Content-Security-Policy header is defined separately, because it is also handled separately.\\n\\n For the helloworld example, this config translates into those response headers:\\n ```http\\n access-control-allow-origin:  http://tauri.localhost\\n access-control-expose-headers: Tauri-Custom-Header\\n content-security-policy: default-src 'self'; connect-src ipc: http://ipc.localhost; script-src 'self' 'sha256-Wjjrs6qinmnr+tOry8x8PPwI77eGpUFR3EEGZktjJNs='\\n content-type: text/html\\n cross-origin-embedder-policy: require-corp\\n cross-origin-opener-policy: same-origin\\n tauri-custom-header: key1 'value1' 'value2'; key2 'value3'\\n timing-allow-origin: https://developer.mozilla.org, https://example.com\\n ```\\n Since the resulting header values are always 'string-like'. So depending on the what data type the HeaderSource is, they need to be converted.\\n  - `String`(JS/Rust): stay the same for the resulting header value\\n  - `Array`(JS)/`Vec\\\\<String\\\\>`(Rust): Item are joined by \\\", \\\" for the resulting header value\\n  - `Object`(JS)/ `Hashmap\\\\<String,String\\\\>`(Rust): Items are composed from: key + space + value. Item are then joined by \\\"; \\\" for the resulting header value\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"Access-Control-Allow-Credentials\": {\n          \"description\": \"The Access-Control-Allow-Credentials response header tells browsers whether the\\n server allows cross-origin HTTP requests to include credentials.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Access-Control-Allow-Headers\": {\n          \"description\": \"The Access-Control-Allow-Headers response header is used in response\\n to a preflight request which includes the Access-Control-Request-Headers\\n to indicate which HTTP headers can be used during the actual request.\\n\\n This header is required if the request has an Access-Control-Request-Headers header.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Access-Control-Allow-Methods\": {\n          \"description\": \"The Access-Control-Allow-Methods response header specifies one or more methods\\n allowed when accessing a resource in response to a preflight request.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Access-Control-Expose-Headers\": {\n          \"description\": \"The Access-Control-Expose-Headers response header allows a server to indicate\\n which response headers should be made available to scripts running in the browser,\\n in response to a cross-origin request.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Access-Control-Max-Age\": {\n          \"description\": \"The Access-Control-Max-Age response header indicates how long the results of a\\n preflight request (that is the information contained in the\\n Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can\\n be cached.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Cross-Origin-Embedder-Policy\": {\n          \"description\": \"The HTTP Cross-Origin-Embedder-Policy (COEP) response header configures embedding\\n cross-origin resources into the document.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Cross-Origin-Opener-Policy\": {\n          \"description\": \"The HTTP Cross-Origin-Opener-Policy (COOP) response header allows you to ensure a\\n top-level document does not share a browsing context group with cross-origin documents.\\n COOP will process-isolate your document and potential attackers can't access your global\\n object if they were to open it in a popup, preventing a set of cross-origin attacks dubbed XS-Leaks.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Cross-Origin-Resource-Policy\": {\n          \"description\": \"The HTTP Cross-Origin-Resource-Policy response header conveys a desire that the\\n browser blocks no-cors cross-origin/cross-site requests to the given resource.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Permissions-Policy\": {\n          \"description\": \"The HTTP Permissions-Policy header provides a mechanism to allow and deny the\\n use of browser features in a document or within any \\\\<iframe\\\\> elements in the document.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Service-Worker-Allowed\": {\n          \"description\": \"The HTTP Service-Worker-Allowed response header is used to broaden the path restriction for a\\n service worker's default scope.\\n\\n By default, the scope for a service worker registration is the directory where the service\\n worker script is located. For example, if the script `sw.js` is located in `/js/sw.js`,\\n it can only control URLs under `/js/` by default. Servers can use the `Service-Worker-Allowed`\\n header to allow a service worker to control URLs outside of its own directory.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Service-Worker-Allowed>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Timing-Allow-Origin\": {\n          \"description\": \"The Timing-Allow-Origin response header specifies origins that are allowed to see values\\n of attributes retrieved via features of the Resource Timing API, which would otherwise be\\n reported as zero due to cross-origin restrictions.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"X-Content-Type-Options\": {\n          \"description\": \"The X-Content-Type-Options response HTTP header is a marker used by the server to indicate\\n that the MIME types advertised in the Content-Type headers should be followed and not be\\n changed. The header allows you to avoid MIME type sniffing by saying that the MIME types\\n are deliberately configured.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Tauri-Custom-Header\": {\n          \"description\": \"A custom header field Tauri-Custom-Header, don't use it.\\n Remember to set Access-Control-Expose-Headers accordingly\\n\\n **NOT INTENDED FOR PRODUCTION USE**\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"HeaderSource\": {\n      \"description\": \"definition of a header source\\n\\n The header value to a header name\",\n      \"anyOf\": [\n        {\n          \"description\": \"string version of the header Value\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"list version of the header value. Item are joined by \\\",\\\" for the real header value\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"(Rust struct | Json | JavaScript Object) equivalent of the header value. Items are composed from: key + space + value. Item are then joined by \\\";\\\" for the real header value\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"TrayIconConfig\": {\n      \"description\": \"Configuration for application tray icon.\\n\\n See more: <https://v2.tauri.app/reference/config/#trayiconconfig>\",\n      \"type\": \"object\",\n      \"required\": [\n        \"iconPath\"\n      ],\n      \"properties\": {\n        \"id\": {\n          \"description\": \"Set an id for this tray icon so you can reference it later, defaults to `main`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"iconPath\": {\n          \"description\": \"Path to the default icon to use for the tray icon.\\n\\n Note: this stores the image in raw pixels to the final binary,\\n so keep the icon size (width and height) small\\n or else it's going to bloat your final executable\",\n          \"type\": \"string\"\n        },\n        \"iconAsTemplate\": {\n          \"description\": \"A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"menuOnLeftClick\": {\n          \"description\": \"A Boolean value that determines whether the menu should appear when the tray icon receives a left click.\\n\\n ## Platform-specific:\\n\\n - **Linux**: Unsupported.\",\n          \"default\": true,\n          \"deprecated\": true,\n          \"type\": \"boolean\"\n        },\n        \"showMenuOnLeftClick\": {\n          \"description\": \"A Boolean value that determines whether the menu should appear when the tray icon receives a left click.\\n\\n ## Platform-specific:\\n\\n - **Linux**: Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"title\": {\n          \"description\": \"Title for MacOS tray\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tooltip\": {\n          \"description\": \"Tray icon tooltip on Windows and macOS\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"BuildConfig\": {\n      \"description\": \"The Build configuration object.\\n\\n See more: <https://v2.tauri.app/reference/config/#buildconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"runner\": {\n          \"description\": \"The binary used to build and run the application.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/RunnerConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"devUrl\": {\n          \"description\": \"The URL to load in development.\\n\\n This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.\\n Most modern JavaScript bundlers like [Vite](https://vite.dev/guide/) provides a way to start a dev server by default.\\n\\n If you don't have a dev server or don't want to use one, ignore this option and use [`frontendDist`](BuildConfig::frontend_dist)\\n and point to a web assets directory, and Tauri CLI will run its built-in dev server and provide a simple hot-reload experience.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"format\": \"uri\"\n        },\n        \"frontendDist\": {\n          \"description\": \"The path to the application assets (usually the `dist` folder of your javascript bundler)\\n or a URL that could be either a custom protocol registered in the tauri app (for example: `myprotocol://`)\\n or a remote URL (for example: `https://site.com/app`).\\n\\n When a path relative to the configuration file is provided,\\n it is read recursively and all files are embedded in the application binary.\\n Tauri then looks for an `index.html` and serves it as the default entry point for your application.\\n\\n You can also provide a list of paths to be embedded, which allows granular control over what files are added to the binary.\\n In this case, all files are added to the root and you must reference it that way in your HTML files.\\n\\n When a URL is provided, the application won't have bundled assets\\n and the application will load that URL by default.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/FrontendDist\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"beforeDevCommand\": {\n          \"description\": \"A shell command to run before `tauri dev` kicks in.\\n\\n The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BeforeDevCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"beforeBuildCommand\": {\n          \"description\": \"A shell command to run before `tauri build` kicks in.\\n\\n The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HookCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"beforeBundleCommand\": {\n          \"description\": \"A shell command to run before the bundling phase in `tauri build` kicks in.\\n\\n The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HookCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"features\": {\n          \"description\": \"Features passed to `cargo` commands.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"removeUnusedCommands\": {\n          \"description\": \"Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\\n and they'll try to get all the allowed commands and remove the rest\\n\\n Note:\\n   - This won't be accounting for dynamically added ACLs when you use features from the `dynamic-acl` (currently enabled by default) feature flag, so make sure to check it when using this\\n   - This feature requires tauri-plugin 2.1 and tauri 2.4\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"additionalWatchFolders\": {\n          \"description\": \"Additional paths to watch for changes when running `tauri dev`.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"RunnerConfig\": {\n      \"description\": \"The runner configuration.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A string specifying the binary to run.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An object with advanced configuration options.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"cmd\"\n          ],\n          \"properties\": {\n            \"cmd\": {\n              \"description\": \"The binary to run.\",\n              \"type\": \"string\"\n            },\n            \"cwd\": {\n              \"description\": \"The current working directory to run the command from.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            },\n            \"args\": {\n              \"description\": \"Arguments to pass to the command.\",\n              \"type\": [\n                \"array\",\n                \"null\"\n              ],\n              \"items\": {\n                \"type\": \"string\"\n              }\n            }\n          }\n        }\n      ]\n    },\n    \"FrontendDist\": {\n      \"description\": \"Defines the URL or assets to embed in the application.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An external URL that should be used as the default application URL. No assets are embedded in the app in this case.\",\n          \"type\": \"string\",\n          \"format\": \"uri\"\n        },\n        {\n          \"description\": \"Path to a directory containing the frontend dist assets.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An array of files to embed in the app.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"BeforeDevCommand\": {\n      \"description\": \"Describes the shell command to run before `tauri dev`.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Run the given script with the default options.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Run the given script with custom options.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"script\"\n          ],\n          \"properties\": {\n            \"script\": {\n              \"description\": \"The script to execute.\",\n              \"type\": \"string\"\n            },\n            \"cwd\": {\n              \"description\": \"The current working directory.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            },\n            \"wait\": {\n              \"description\": \"Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`.\",\n              \"default\": false,\n              \"type\": \"boolean\"\n            }\n          }\n        }\n      ]\n    },\n    \"HookCommand\": {\n      \"description\": \"Describes a shell command to be executed when a CLI hook is triggered.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Run the given script with the default options.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Run the given script with custom options.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"script\"\n          ],\n          \"properties\": {\n            \"script\": {\n              \"description\": \"The script to execute.\",\n              \"type\": \"string\"\n            },\n            \"cwd\": {\n              \"description\": \"The current working directory.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"BundleConfig\": {\n      \"description\": \"Configuration for tauri-bundler.\\n\\n See more: <https://v2.tauri.app/reference/config/#bundleconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"active\": {\n          \"description\": \"Whether Tauri should bundle your application or just output the executable.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"targets\": {\n          \"description\": \"The bundle targets, currently supports [\\\"deb\\\", \\\"rpm\\\", \\\"appimage\\\", \\\"nsis\\\", \\\"msi\\\", \\\"app\\\", \\\"dmg\\\"] or \\\"all\\\".\",\n          \"default\": \"all\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleTarget\"\n            }\n          ]\n        },\n        \"createUpdaterArtifacts\": {\n          \"description\": \"Produce updaters and their signatures or not\",\n          \"default\": false,\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Updater\"\n            }\n          ]\n        },\n        \"publisher\": {\n          \"description\": \"The application's publisher. Defaults to the second element in the identifier string.\\n\\n Currently maps to the Manufacturer property of the Windows Installer\\n and the Maintainer field of debian packages if the Cargo.toml does not have the authors field.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"homepage\": {\n          \"description\": \"A url to the home page of your application. If unset, will\\n fallback to `homepage` defined in `Cargo.toml`.\\n\\n Supported bundle targets: `deb`, `rpm`, `nsis` and `msi`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"icon\": {\n          \"description\": \"The app's icons\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"resources\": {\n          \"description\": \"App resources to bundle.\\n Each resource is a path to a file or directory.\\n Glob patterns are supported.\\n\\n ## Examples\\n\\n To include a list of files:\\n\\n ```json\\n {\\n   \\\"bundle\\\": {\\n     \\\"resources\\\": [\\n       \\\"./path/to/some-file.txt\\\",\\n       \\\"/absolute/path/to/textfile.txt\\\",\\n       \\\"../relative/path/to/jsonfile.json\\\",\\n       \\\"some-folder/\\\",\\n       \\\"resources/**/*.md\\\"\\n     ]\\n   }\\n }\\n ```\\n\\n The bundled files will be in `$RESOURCES/` with the original directory structure preserved,\\n for example: `./path/to/some-file.txt` -> `$RESOURCE/path/to/some-file.txt`\\n\\n To fine control where the files will get copied to, use a map instead\\n\\n ```json\\n {\\n   \\\"bundle\\\": {\\n     \\\"resources\\\": {\\n       \\\"/absolute/path/to/textfile.txt\\\": \\\"resources/textfile.txt\\\",\\n       \\\"relative/path/to/jsonfile.json\\\": \\\"resources/jsonfile.json\\\",\\n       \\\"resources/\\\": \\\"\\\",\\n       \\\"docs/**/*md\\\": \\\"website-docs/\\\"\\n     }\\n   }\\n }\\n ```\\n\\n Note that when using glob pattern in this case, the original directory structure is not preserved,\\n everything gets copied to the target directory directly\\n\\n See more: <https://v2.tauri.app/develop/resources/>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleResources\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"copyright\": {\n          \"description\": \"A copyright string associated with your application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"license\": {\n          \"description\": \"The package's license identifier to be included in the appropriate bundles.\\n If not set, defaults to the license from the Cargo.toml file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"licenseFile\": {\n          \"description\": \"The path to the license file to be included in the appropriate bundles.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"category\": {\n          \"description\": \"The application kind.\\n\\n Should be one of the following:\\n Business, DeveloperTool, Education, Entertainment, Finance, Game, ActionGame, AdventureGame, ArcadeGame, BoardGame, CardGame, CasinoGame, DiceGame, EducationalGame, FamilyGame, KidsGame, MusicGame, PuzzleGame, RacingGame, RolePlayingGame, SimulationGame, SportsGame, StrategyGame, TriviaGame, WordGame, GraphicsAndDesign, HealthcareAndFitness, Lifestyle, Medical, Music, News, Photography, Productivity, Reference, SocialNetworking, Sports, Travel, Utility, Video, Weather.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fileAssociations\": {\n          \"description\": \"File types to associate with the application.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/definitions/FileAssociation\"\n          }\n        },\n        \"shortDescription\": {\n          \"description\": \"A short description of your application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"longDescription\": {\n          \"description\": \"A longer, multi-line description of the application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"useLocalToolsDir\": {\n          \"description\": \"Whether to use the project's `target` directory, for caching build tools (e.g., Wix and NSIS) when building this application. Defaults to `false`.\\n\\n If true, tools will be cached in `target/.tauri/`.\\n If false, tools will be cached in the current user's platform-specific cache directory.\\n\\n An example where it can be appropriate to set this to `true` is when building this application as a Windows System user (e.g., AWS EC2 workloads),\\n because the Window system's app data directory is restricted.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"externalBin\": {\n          \"description\": \"A list of—either absolute or relative—paths to binaries to embed with your application.\\n\\n Note that Tauri will look for system-specific binaries following the pattern \\\"binary-name{-target-triple}{.system-extension}\\\".\\n\\n E.g. for the external binary \\\"my-binary\\\", Tauri looks for:\\n\\n - \\\"my-binary-x86_64-pc-windows-msvc.exe\\\" for Windows\\n - \\\"my-binary-x86_64-apple-darwin\\\" for macOS\\n - \\\"my-binary-x86_64-unknown-linux-gnu\\\" for Linux\\n\\n so don't forget to provide binaries for all targeted platforms.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"windows\": {\n          \"description\": \"Configuration for the Windows bundles.\",\n          \"default\": {\n            \"allowDowngrades\": true,\n            \"certificateThumbprint\": null,\n            \"digestAlgorithm\": null,\n            \"nsis\": null,\n            \"signCommand\": null,\n            \"timestampUrl\": null,\n            \"tsp\": false,\n            \"webviewInstallMode\": {\n              \"silent\": true,\n              \"type\": \"downloadBootstrapper\"\n            },\n            \"wix\": null\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowsConfig\"\n            }\n          ]\n        },\n        \"linux\": {\n          \"description\": \"Configuration for the Linux bundles.\",\n          \"default\": {\n            \"appimage\": {\n              \"bundleMediaFramework\": false,\n              \"files\": {}\n            },\n            \"deb\": {\n              \"files\": {}\n            },\n            \"rpm\": {\n              \"epoch\": 0,\n              \"files\": {},\n              \"release\": \"1\"\n            }\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/LinuxConfig\"\n            }\n          ]\n        },\n        \"macOS\": {\n          \"description\": \"Configuration for the macOS bundles.\",\n          \"default\": {\n            \"dmg\": {\n              \"appPosition\": {\n                \"x\": 180,\n                \"y\": 170\n              },\n              \"applicationFolderPosition\": {\n                \"x\": 480,\n                \"y\": 170\n              },\n              \"windowSize\": {\n                \"height\": 400,\n                \"width\": 660\n              }\n            },\n            \"files\": {},\n            \"hardenedRuntime\": true,\n            \"minimumSystemVersion\": \"10.13\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/MacConfig\"\n            }\n          ]\n        },\n        \"iOS\": {\n          \"description\": \"iOS configuration.\",\n          \"default\": {\n            \"minimumSystemVersion\": \"14.0\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/IosConfig\"\n            }\n          ]\n        },\n        \"android\": {\n          \"description\": \"Android configuration.\",\n          \"default\": {\n            \"autoIncrementVersionCode\": false,\n            \"minSdkVersion\": 24\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/AndroidConfig\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"BundleTarget\": {\n      \"description\": \"Targets to bundle. Each value is case insensitive.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Bundle all targets.\",\n          \"const\": \"all\"\n        },\n        {\n          \"description\": \"A list of bundle targets.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/BundleType\"\n          }\n        },\n        {\n          \"description\": \"A single bundle target.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleType\"\n            }\n          ]\n        }\n      ]\n    },\n    \"BundleType\": {\n      \"description\": \"A bundle referenced by tauri-bundler.\",\n      \"oneOf\": [\n        {\n          \"description\": \"The debian bundle (.deb).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"deb\"\n          ]\n        },\n        {\n          \"description\": \"The RPM bundle (.rpm).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"rpm\"\n          ]\n        },\n        {\n          \"description\": \"The AppImage bundle (.appimage).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"appimage\"\n          ]\n        },\n        {\n          \"description\": \"The Microsoft Installer bundle (.msi).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"msi\"\n          ]\n        },\n        {\n          \"description\": \"The NSIS bundle (.exe).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"nsis\"\n          ]\n        },\n        {\n          \"description\": \"The macOS application bundle (.app).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"app\"\n          ]\n        },\n        {\n          \"description\": \"The Apple Disk Image bundle (.dmg).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"dmg\"\n          ]\n        }\n      ]\n    },\n    \"Updater\": {\n      \"description\": \"Updater type\",\n      \"anyOf\": [\n        {\n          \"description\": \"Generates legacy zipped v1 compatible updaters\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/V1Compatible\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Produce updaters and their signatures or not\",\n          \"type\": \"boolean\"\n        }\n      ]\n    },\n    \"V1Compatible\": {\n      \"description\": \"Generates legacy zipped v1 compatible updaters\",\n      \"oneOf\": [\n        {\n          \"description\": \"Generates legacy zipped v1 compatible updaters\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"v1Compatible\"\n          ]\n        }\n      ]\n    },\n    \"BundleResources\": {\n      \"description\": \"Definition for bundle resources.\\n Can be either a list of paths to include or a map of source to target paths.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A list of paths to include.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A map of source to target paths.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"FileAssociation\": {\n      \"description\": \"File association\",\n      \"type\": \"object\",\n      \"required\": [\n        \"ext\"\n      ],\n      \"properties\": {\n        \"ext\": {\n          \"description\": \"File extensions to associate with this app. e.g. 'png'\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/AssociationExt\"\n          }\n        },\n        \"contentTypes\": {\n          \"description\": \"Declare support to a file with the given content type. Maps to `LSItemContentTypes` on macOS.\\n\\n This allows supporting any file format declared by another application that conforms to this type.\\n Declaration of new types can be done with [`Self::exported_type`] and linking to certain content types are done via [`ExportedFileAssociation::conforms_to`].\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"name\": {\n          \"description\": \"The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"description\": {\n          \"description\": \"The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"role\": {\n          \"description\": \"The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.\",\n          \"default\": \"Editor\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleTypeRole\"\n            }\n          ]\n        },\n        \"mimeType\": {\n          \"description\": \"The mime-type e.g. 'image/png' or 'text/plain'. Linux-only.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"rank\": {\n          \"description\": \"The ranking of this app among apps that declare themselves as editors or viewers of the given file type.  Maps to `LSHandlerRank` on macOS.\",\n          \"default\": \"Default\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/HandlerRank\"\n            }\n          ]\n        },\n        \"exportedType\": {\n          \"description\": \"The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.\\n\\n You should define this if the associated file is a custom file type defined by your application.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/ExportedFileAssociation\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"AssociationExt\": {\n      \"description\": \"An extension for a [`FileAssociation`].\\n\\n A leading `.` is automatically stripped.\",\n      \"type\": \"string\"\n    },\n    \"BundleTypeRole\": {\n      \"description\": \"macOS-only. Corresponds to CFBundleTypeRole\",\n      \"oneOf\": [\n        {\n          \"description\": \"CFBundleTypeRole.Editor. Files can be read and edited.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Editor\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.Viewer. Files can be read.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Viewer\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.Shell\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Shell\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.QLGenerator\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"QLGenerator\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.None\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"None\"\n          ]\n        }\n      ]\n    },\n    \"HandlerRank\": {\n      \"description\": \"Corresponds to LSHandlerRank\",\n      \"oneOf\": [\n        {\n          \"description\": \"LSHandlerRank.Default. This app is an opener of files of this type; this value is also used if no rank is specified.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Default\"\n          ]\n        },\n        {\n          \"description\": \"LSHandlerRank.Owner. This app is the primary creator of files of this type.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Owner\"\n          ]\n        },\n        {\n          \"description\": \"LSHandlerRank.Alternate. This app is a secondary viewer of files of this type.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Alternate\"\n          ]\n        },\n        {\n          \"description\": \"LSHandlerRank.None. This app is never selected to open files of this type, but it accepts drops of files of this type.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"None\"\n          ]\n        }\n      ]\n    },\n    \"ExportedFileAssociation\": {\n      \"description\": \"The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"identifier\"\n      ],\n      \"properties\": {\n        \"identifier\": {\n          \"description\": \"The unique identifier for the exported type. Maps to `UTTypeIdentifier`.\",\n          \"type\": \"string\"\n        },\n        \"conformsTo\": {\n          \"description\": \"The types that this type conforms to. Maps to `UTTypeConformsTo`.\\n\\n Examples are `public.data`, `public.image`, `public.json` and `public.database`.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WindowsConfig\": {\n      \"description\": \"Windows bundler configuration.\\n\\n See more: <https://v2.tauri.app/reference/config/#windowsconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"digestAlgorithm\": {\n          \"description\": \"Specifies the file digest algorithm to use for creating file signatures.\\n Required for code signing. SHA-256 is recommended.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"certificateThumbprint\": {\n          \"description\": \"Specifies the SHA1 hash of the signing certificate.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"timestampUrl\": {\n          \"description\": \"Server to use during timestamping.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tsp\": {\n          \"description\": \"Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may\\n use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"webviewInstallMode\": {\n          \"description\": \"The installation mode for the Webview2 runtime.\",\n          \"default\": {\n            \"silent\": true,\n            \"type\": \"downloadBootstrapper\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WebviewInstallMode\"\n            }\n          ]\n        },\n        \"allowDowngrades\": {\n          \"description\": \"Validates a second app installation, blocking the user from installing an older version if set to `false`.\\n\\n For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\\n\\n The default value of this flag is `true`.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"wix\": {\n          \"description\": \"Configuration for the MSI generated with WiX.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WixConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"nsis\": {\n          \"description\": \"Configuration for the installer generated with NSIS.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/NsisConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"signCommand\": {\n          \"description\": \"Specify a custom command to sign the binaries.\\n This command needs to have a `%1` in args which is just a placeholder for the binary path,\\n which we will detect and replace before calling the command.\\n\\n By Default we use `signtool.exe` which can be found only on Windows so\\n if you are on another platform and want to cross-compile and sign you will\\n need to use another tool like `osslsigncode`.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/CustomSignCommandConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WebviewInstallMode\": {\n      \"description\": \"Install modes for the Webview2 runtime.\\n Note that for the updater bundle [`Self::DownloadBootstrapper`] is used.\\n\\n For more information see <https://v2.tauri.app/distribute/windows-installer/#webview2-installation-options>.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Do not install the Webview2 as part of the Windows Installer.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"skip\"\n              ]\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Download the bootstrapper and run it.\\n Requires an internet connection.\\n Results in a smaller installer size, but is not recommended on Windows 7.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"downloadBootstrapper\"\n              ]\n            },\n            \"silent\": {\n              \"description\": \"Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.\",\n              \"default\": true,\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Embed the bootstrapper and run it.\\n Requires an internet connection.\\n Increases the installer size by around 1.8MB, but offers better support on Windows 7.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"embedBootstrapper\"\n              ]\n            },\n            \"silent\": {\n              \"description\": \"Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.\",\n              \"default\": true,\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Embed the offline installer and run it.\\n Does not require an internet connection.\\n Increases the installer size by around 127MB.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"offlineInstaller\"\n              ]\n            },\n            \"silent\": {\n              \"description\": \"Instructs the installer to run the installer in silent mode. Defaults to `true`.\",\n              \"default\": true,\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Embed a fixed webview2 version and use it at runtime.\\n Increases the installer size by around 180MB.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"path\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"fixedRuntime\"\n              ]\n            },\n            \"path\": {\n              \"description\": \"The path to the fixed runtime to use.\\n\\n The fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section).\\n The `.cab` file must be extracted to a folder and this folder path must be defined on this field.\",\n              \"type\": \"string\"\n            }\n          },\n          \"additionalProperties\": false\n        }\n      ]\n    },\n    \"WixConfig\": {\n      \"description\": \"Configuration for the MSI bundle using WiX.\\n\\n See more: <https://v2.tauri.app/reference/config/#wixconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"version\": {\n          \"description\": \"MSI installer version in the format `major.minor.patch.build` (build is optional).\\n\\n Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.\\n\\n The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\\n The third and fourth fields have a maximum value of 65,535.\\n\\n See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"upgradeCode\": {\n          \"description\": \"A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,\\n otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\\n\\n By default, tauri generates this code by generating a Uuid v5 using the string `<productName>.exe.app.x64` in the DNS namespace.\\n You can use Tauri's CLI to generate and print this code for you, run `tauri inspect wix-upgrade-code`.\\n\\n It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code\\n whenever you want to change your product name.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"format\": \"uuid\"\n        },\n        \"language\": {\n          \"description\": \"The installer languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.\",\n          \"default\": \"en-US\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WixLanguage\"\n            }\n          ]\n        },\n        \"template\": {\n          \"description\": \"A custom .wxs template to use.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fragmentPaths\": {\n          \"description\": \"A list of paths to .wxs files with WiX fragments to use.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"componentGroupRefs\": {\n          \"description\": \"The ComponentGroup element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"componentRefs\": {\n          \"description\": \"The Component element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"featureGroupRefs\": {\n          \"description\": \"The FeatureGroup element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"featureRefs\": {\n          \"description\": \"The Feature element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"mergeRefs\": {\n          \"description\": \"The Merge element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"enableElevatedUpdateTask\": {\n          \"description\": \"Create an elevated update task within Windows Task Scheduler.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"bannerPath\": {\n          \"description\": \"Path to a bitmap file to use as the installation user interface banner.\\n This bitmap will appear at the top of all but the first page of the installer.\\n\\n The required dimensions are 493px × 58px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dialogImagePath\": {\n          \"description\": \"Path to a bitmap file to use on the installation user interface dialogs.\\n It is used on the welcome and completion dialogs.\\n\\n The required dimensions are 493px × 312px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fipsCompliant\": {\n          \"description\": \"Enables FIPS compliant algorithms.\\n Can also be enabled via the `TAURI_BUNDLER_WIX_FIPS_COMPLIANT` env var.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WixLanguage\": {\n      \"description\": \"The languages to build using WiX.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A single language to build, without configuration.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A list of languages to build, without configuration.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A map of languages and its configuration.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/WixLanguageConfig\"\n          }\n        }\n      ]\n    },\n    \"WixLanguageConfig\": {\n      \"description\": \"Configuration for a target language for the WiX build.\\n\\n See more: <https://v2.tauri.app/reference/config/#wixlanguageconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"localePath\": {\n          \"description\": \"The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NsisConfig\": {\n      \"description\": \"Configuration for the Installer bundle using NSIS.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"template\": {\n          \"description\": \"A custom .nsi template to use.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"headerImage\": {\n          \"description\": \"The path to a bitmap file to display on the header of installers pages.\\n\\n The recommended dimensions are 150px x 57px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"sidebarImage\": {\n          \"description\": \"The path to a bitmap file for the Welcome page and the Finish page.\\n\\n The recommended dimensions are 164px x 314px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installerIcon\": {\n          \"description\": \"The path to an icon file used as the installer icon.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installMode\": {\n          \"description\": \"Whether the installation will be for all users or just the current user.\",\n          \"default\": \"currentUser\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/NSISInstallerMode\"\n            }\n          ]\n        },\n        \"languages\": {\n          \"description\": \"A list of installer languages.\\n By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.\\n To allow the user to select the language, set `display_language_selector` to `true`.\\n\\n See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"customLanguageFiles\": {\n          \"description\": \"A key-value pair where the key is the language and the\\n value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.\\n\\n See <https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-bundler/src/bundle/windows/nsis/languages/English.nsh> for an example `.nsh` file.\\n\\n **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array,\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"displayLanguageSelector\": {\n          \"description\": \"Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.\\n By default the OS language is selected, with a fallback to the first language in the `languages` array.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"compression\": {\n          \"description\": \"Set the compression algorithm used to compress files in the installer.\\n\\n See <https://nsis.sourceforge.io/Reference/SetCompressor>\",\n          \"default\": \"lzma\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/NsisCompression\"\n            }\n          ]\n        },\n        \"startMenuFolder\": {\n          \"description\": \"Set the folder name for the start menu shortcut.\\n\\n Use this option if you have multiple apps and wish to group their shortcuts under one folder\\n or if you generally prefer to set your shortcut inside a folder.\\n\\n Examples:\\n - `AwesomePublisher`, shortcut will be placed in `%AppData%\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\AwesomePublisher\\\\<your-app>.lnk`\\n - If unset, shortcut will be placed in `%AppData%\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\<your-app>.lnk`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installerHooks\": {\n          \"description\": \"A path to a `.nsh` file that contains special NSIS macros to be hooked into the\\n main installer.nsi script.\\n\\n Supported hooks are:\\n\\n - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\\n - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\\n - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\\n - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\\n\\n ### Example\\n\\n ```nsh\\n !macro NSIS_HOOK_PREINSTALL\\n   MessageBox MB_OK \\\"PreInstall\\\"\\n !macroend\\n\\n !macro NSIS_HOOK_POSTINSTALL\\n   MessageBox MB_OK \\\"PostInstall\\\"\\n !macroend\\n\\n !macro NSIS_HOOK_PREUNINSTALL\\n   MessageBox MB_OK \\\"PreUnInstall\\\"\\n !macroend\\n\\n !macro NSIS_HOOK_POSTUNINSTALL\\n   MessageBox MB_OK \\\"PostUninstall\\\"\\n !macroend\\n ```\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimumWebview2Version\": {\n          \"description\": \"Try to ensure that the WebView2 version is equal to or newer than this version,\\n if the user's WebView2 is older than this version,\\n the installer will try to trigger a WebView2 update.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NSISInstallerMode\": {\n      \"description\": \"Install Modes for the NSIS installer.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Default mode for the installer.\\n\\n Install the app by default in a directory that doesn't require Administrator access.\\n\\n Installer metadata will be saved under the `HKCU` registry path.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"currentUser\"\n          ]\n        },\n        {\n          \"description\": \"Install the app by default in the `Program Files` folder directory requires Administrator\\n access for the installation.\\n\\n Installer metadata will be saved under the `HKLM` registry path.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"perMachine\"\n          ]\n        },\n        {\n          \"description\": \"Combines both modes and allows the user to choose at install time\\n whether to install for the current user or per machine. Note that this mode\\n will require Administrator access even if the user wants to install it for the current user only.\\n\\n Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"both\"\n          ]\n        }\n      ]\n    },\n    \"NsisCompression\": {\n      \"description\": \"Compression algorithms used in the NSIS installer.\\n\\n See <https://nsis.sourceforge.io/Reference/SetCompressor>\",\n      \"oneOf\": [\n        {\n          \"description\": \"ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"zlib\"\n          ]\n        },\n        {\n          \"description\": \"BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"bzip2\"\n          ]\n        },\n        {\n          \"description\": \"LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"lzma\"\n          ]\n        },\n        {\n          \"description\": \"Disable compression\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"none\"\n          ]\n        }\n      ]\n    },\n    \"CustomSignCommandConfig\": {\n      \"description\": \"Custom Signing Command configuration.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A string notation of the script to execute.\\n\\n \\\"%1\\\" will be replaced with the path to the binary to be signed.\\n\\n This is a simpler notation for the command.\\n Tauri will split the string with `' '` and use the first element as the command name and the rest as arguments.\\n\\n If you need to use whitespace in the command or arguments, use the object notation [`Self::CommandWithOptions`].\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An object notation of the command.\\n\\n This is more complex notation for the command but\\n this allows you to use whitespace in the command and arguments.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"args\",\n            \"cmd\"\n          ],\n          \"properties\": {\n            \"cmd\": {\n              \"description\": \"The command to run to sign the binary.\",\n              \"type\": \"string\"\n            },\n            \"args\": {\n              \"description\": \"The arguments to pass to the command.\\n\\n \\\"%1\\\" will be replaced with the path to the binary to be signed.\",\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            }\n          },\n          \"additionalProperties\": false\n        }\n      ]\n    },\n    \"LinuxConfig\": {\n      \"description\": \"Configuration for Linux bundles.\\n\\n See more: <https://v2.tauri.app/reference/config/#linuxconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"appimage\": {\n          \"description\": \"Configuration for the AppImage bundle.\",\n          \"default\": {\n            \"bundleMediaFramework\": false,\n            \"files\": {}\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/AppImageConfig\"\n            }\n          ]\n        },\n        \"deb\": {\n          \"description\": \"Configuration for the Debian bundle.\",\n          \"default\": {\n            \"files\": {}\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/DebConfig\"\n            }\n          ]\n        },\n        \"rpm\": {\n          \"description\": \"Configuration for the RPM bundle.\",\n          \"default\": {\n            \"epoch\": 0,\n            \"files\": {},\n            \"release\": \"1\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/RpmConfig\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"AppImageConfig\": {\n      \"description\": \"Configuration for AppImage bundles.\\n\\n See more: <https://v2.tauri.app/reference/config/#appimageconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bundleMediaFramework\": {\n          \"description\": \"Include additional gstreamer dependencies needed for audio and video playback.\\n This increases the bundle size by ~15-35MB depending on your build system.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"files\": {\n          \"description\": \"The files to include in the Appimage Binary.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"DebConfig\": {\n      \"description\": \"Configuration for Debian (.deb) bundles.\\n\\n See more: <https://v2.tauri.app/reference/config/#debconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"depends\": {\n          \"description\": \"The list of deb dependencies your application relies on.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"recommends\": {\n          \"description\": \"The list of deb dependencies your application recommends.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"provides\": {\n          \"description\": \"The list of dependencies the package provides.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"conflicts\": {\n          \"description\": \"The list of package conflicts.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"replaces\": {\n          \"description\": \"The list of package replaces.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"files\": {\n          \"description\": \"The files to include on the package.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"section\": {\n          \"description\": \"Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"priority\": {\n          \"description\": \"Change the priority of the Debian Package. By default, it is set to `optional`.\\n Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"changelog\": {\n          \"description\": \"Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\\n <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"desktopTemplate\": {\n          \"description\": \"Path to a custom desktop file Handlebars template.\\n\\n Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preInstallScript\": {\n          \"description\": \"Path to script that will be executed before the package is unpacked. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postInstallScript\": {\n          \"description\": \"Path to script that will be executed after the package is unpacked. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preRemoveScript\": {\n          \"description\": \"Path to script that will be executed before the package is removed. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postRemoveScript\": {\n          \"description\": \"Path to script that will be executed after the package is removed. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"RpmConfig\": {\n      \"description\": \"Configuration for RPM bundles.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"depends\": {\n          \"description\": \"The list of RPM dependencies your application relies on.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"recommends\": {\n          \"description\": \"The list of RPM dependencies your application recommends.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"provides\": {\n          \"description\": \"The list of RPM dependencies your application provides.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"conflicts\": {\n          \"description\": \"The list of RPM dependencies your application conflicts with. They must not be present\\n in order for the package to be installed.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"obsoletes\": {\n          \"description\": \"The list of RPM dependencies your application supersedes - if this package is installed,\\n packages listed as \\\"obsoletes\\\" will be automatically removed (if they are present).\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"release\": {\n          \"description\": \"The RPM release tag.\",\n          \"default\": \"1\",\n          \"type\": \"string\"\n        },\n        \"epoch\": {\n          \"description\": \"The RPM epoch.\",\n          \"default\": 0,\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"files\": {\n          \"description\": \"The files to include on the package.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"desktopTemplate\": {\n          \"description\": \"Path to a custom desktop file Handlebars template.\\n\\n Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preInstallScript\": {\n          \"description\": \"Path to script that will be executed before the package is unpacked. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postInstallScript\": {\n          \"description\": \"Path to script that will be executed after the package is unpacked. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preRemoveScript\": {\n          \"description\": \"Path to script that will be executed before the package is removed. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postRemoveScript\": {\n          \"description\": \"Path to script that will be executed after the package is removed. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"compression\": {\n          \"description\": \"Compression algorithm and level. Defaults to `Gzip` with level 6.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/RpmCompression\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"RpmCompression\": {\n      \"description\": \"Compression algorithms used when bundling RPM packages.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Gzip compression\",\n          \"type\": \"object\",\n          \"required\": [\n            \"level\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"gzip\"\n              ]\n            },\n            \"level\": {\n              \"description\": \"Gzip compression level\",\n              \"type\": \"integer\",\n              \"format\": \"uint32\",\n              \"minimum\": 0.0\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Zstd compression\",\n          \"type\": \"object\",\n          \"required\": [\n            \"level\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"zstd\"\n              ]\n            },\n            \"level\": {\n              \"description\": \"Zstd compression level\",\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Xz compression\",\n          \"type\": \"object\",\n          \"required\": [\n            \"level\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"xz\"\n              ]\n            },\n            \"level\": {\n              \"description\": \"Xz compression level\",\n              \"type\": \"integer\",\n              \"format\": \"uint32\",\n              \"minimum\": 0.0\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Bzip2 compression\",\n          \"type\": \"object\",\n          \"required\": [\n            \"level\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"bzip2\"\n              ]\n            },\n            \"level\": {\n              \"description\": \"Bzip2 compression level\",\n              \"type\": \"integer\",\n              \"format\": \"uint32\",\n              \"minimum\": 0.0\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Disable compression\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"none\"\n              ]\n            }\n          },\n          \"additionalProperties\": false\n        }\n      ]\n    },\n    \"MacConfig\": {\n      \"description\": \"Configuration for the macOS bundles.\\n\\n See more: <https://v2.tauri.app/reference/config/#macconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"frameworks\": {\n          \"description\": \"A list of strings indicating any macOS X frameworks that need to be bundled with the application.\\n\\n If a name is used, \\\".framework\\\" must be omitted and it will look for standard install locations. You may also use a path to a specific framework.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"files\": {\n          \"description\": \"The files to include in the application relative to the Contents directory.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"bundleVersion\": {\n          \"description\": \"The version of the build that identifies an iteration of the bundle.\\n\\n Translates to the bundle's CFBundleVersion property.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"bundleName\": {\n          \"description\": \"The name of the builder that built the bundle.\\n\\n Translates to the bundle's CFBundleName property.\\n\\n If not set, defaults to the package's product name.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimumSystemVersion\": {\n          \"description\": \"A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\\n\\n Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`\\n and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\\n\\n Ignored in `tauri dev`.\\n\\n An empty string is considered an invalid value so the default value is used.\",\n          \"default\": \"10.13\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"exceptionDomain\": {\n          \"description\": \"Allows your application to communicate with the outside world.\\n It should be a lowercase, without port and protocol domain name.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"signingIdentity\": {\n          \"description\": \"Identity to use for code signing.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"hardenedRuntime\": {\n          \"description\": \"Whether the codesign should enable [hardened runtime](https://developer.apple.com/documentation/security/hardened_runtime) (for executables) or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"providerShortName\": {\n          \"description\": \"Provider short name for notarization.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"entitlements\": {\n          \"description\": \"Path to the entitlements file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"infoPlist\": {\n          \"description\": \"Path to a Info.plist file to merge with the default Info.plist.\\n\\n Note that Tauri also looks for a `Info.plist` file in the same directory as the Tauri configuration file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dmg\": {\n          \"description\": \"DMG-specific settings.\",\n          \"default\": {\n            \"appPosition\": {\n              \"x\": 180,\n              \"y\": 170\n            },\n            \"applicationFolderPosition\": {\n              \"x\": 480,\n              \"y\": 170\n            },\n            \"windowSize\": {\n              \"height\": 400,\n              \"width\": 660\n            }\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/DmgConfig\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"DmgConfig\": {\n      \"description\": \"Configuration for Apple Disk Image (.dmg) bundles.\\n\\n See more: <https://v2.tauri.app/reference/config/#dmgconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"background\": {\n          \"description\": \"Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"windowPosition\": {\n          \"description\": \"Position of volume window on screen.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"windowSize\": {\n          \"description\": \"Size of volume window.\",\n          \"default\": {\n            \"height\": 400,\n            \"width\": 660\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Size\"\n            }\n          ]\n        },\n        \"appPosition\": {\n          \"description\": \"Position of app file on window.\",\n          \"default\": {\n            \"x\": 180,\n            \"y\": 170\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            }\n          ]\n        },\n        \"applicationFolderPosition\": {\n          \"description\": \"Position of application folder on window.\",\n          \"default\": {\n            \"x\": 480,\n            \"y\": 170\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Position\": {\n      \"description\": \"Position coordinates struct.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"x\",\n        \"y\"\n      ],\n      \"properties\": {\n        \"x\": {\n          \"description\": \"X coordinate.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"y\": {\n          \"description\": \"Y coordinate.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Size\": {\n      \"description\": \"Size of the window.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"height\",\n        \"width\"\n      ],\n      \"properties\": {\n        \"width\": {\n          \"description\": \"Width of the window.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"height\": {\n          \"description\": \"Height of the window.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"IosConfig\": {\n      \"description\": \"General configuration for the iOS target.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"template\": {\n          \"description\": \"A custom [XcodeGen] project.yml template to use.\\n\\n [XcodeGen]: <https://github.com/yonaskolb/XcodeGen>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"frameworks\": {\n          \"description\": \"A list of strings indicating any iOS frameworks that need to be bundled with the application.\\n\\n Note that you need to recreate the iOS project for the changes to be applied.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"developmentTeam\": {\n          \"description\": \"The development team. This value is required for iOS development because code signing is enforced.\\n The `APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"bundleVersion\": {\n          \"description\": \"The version of the build that identifies an iteration of the bundle.\\n\\n Translates to the bundle's CFBundleVersion property.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimumSystemVersion\": {\n          \"description\": \"A version string indicating the minimum iOS version that the bundled application supports. Defaults to `13.0`.\\n\\n Maps to the IPHONEOS_DEPLOYMENT_TARGET value.\",\n          \"default\": \"14.0\",\n          \"type\": \"string\"\n        },\n        \"infoPlist\": {\n          \"description\": \"Path to a Info.plist file to merge with the default Info.plist.\\n\\n Note that Tauri also looks for a `Info.plist` and `Info.ios.plist` file in the same directory as the Tauri configuration file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"AndroidConfig\": {\n      \"description\": \"General configuration for the Android target.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"minSdkVersion\": {\n          \"description\": \"The minimum API level required for the application to run.\\n The Android system will prevent the user from installing the application if the system's API level is lower than the value specified.\",\n          \"default\": 24,\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"versionCode\": {\n          \"description\": \"The version code of the application.\\n It is limited to 2,100,000,000 as per Google Play Store requirements.\\n\\n By default we use your configured version and perform the following math:\\n versionCode = version.major * 1000000 + version.minor * 1000 + version.patch\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint32\",\n          \"maximum\": 2100000000.0,\n          \"minimum\": 1.0\n        },\n        \"autoIncrementVersionCode\": {\n          \"description\": \"Whether to automatically increment the `versionCode` on each build.\\n\\n - If `true`, the generator will try to read the last `versionCode` from\\n   `tauri.properties` and increment it by 1 for every build.\\n - If `false` or not set, it falls back to `version_code` or semver-derived logic.\\n\\n Note that to use this feature, you should remove `/tauri.properties` from `src-tauri/gen/android/app/.gitignore` so the current versionCode is committed to the repository.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"PluginConfig\": {\n      \"description\": \"The plugin configs holds a HashMap mapping a plugin name to its configuration object.\\n\\n See more: <https://v2.tauri.app/reference/config/#pluginconfig>\",\n      \"type\": \"object\",\n      \"additionalProperties\": true\n    }\n  }\n}"
  },
  {
    "path": "crates/tauri-cli/metadata-v2.json",
    "content": "{\n  \"cli.js\": {\n    \"version\": \"2.10.1\",\n    \"node\": \">= 10.0.0\"\n  },\n  \"tauri\": \"2.10.3\",\n  \"tauri-build\": \"2.5.6\",\n  \"tauri-plugin\": \"2.5.4\"\n}\n"
  },
  {
    "path": "crates/tauri-cli/metadata.json",
    "content": "{\n  \"cli.js\": {\n    \"version\": \"1.5.11\",\n    \"node\": \">= 10.0.0\"\n  },\n  \"tauri\": \"1.6.1\",\n  \"tauri-build\": \"1.5.1\"\n}\n"
  },
  {
    "path": "crates/tauri-cli/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"title\": \"Config\",\n  \"description\": \"The Tauri configuration object.\\n It is read from a file where you can define your frontend assets,\\n configure the bundler and define a tray icon.\\n\\n The configuration file is generated by the\\n [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in\\n your Tauri application source directory (src-tauri).\\n\\n Once generated, you may modify it at will to customize your Tauri application.\\n\\n ## File Formats\\n\\n By default, the configuration is defined as a JSON file named `tauri.conf.json`.\\n\\n Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.\\n The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.\\n The TOML file name is `Tauri.toml`.\\n\\n ## Platform-Specific Configuration\\n\\n In addition to the default configuration file, Tauri can\\n read a platform-specific configuration from `tauri.linux.conf.json`,\\n `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`\\n (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),\\n which gets merged with the main configuration object.\\n\\n ## Configuration Structure\\n\\n The configuration is composed of the following objects:\\n\\n - [`app`](#appconfig): The Tauri configuration\\n - [`build`](#buildconfig): The build configuration\\n - [`bundle`](#bundleconfig): The bundle configurations\\n - [`plugins`](#pluginconfig): The plugins configuration\\n\\n Example tauri.config.json file:\\n\\n ```json\\n {\\n   \\\"productName\\\": \\\"tauri-app\\\",\\n   \\\"version\\\": \\\"0.1.0\\\",\\n   \\\"build\\\": {\\n     \\\"beforeBuildCommand\\\": \\\"\\\",\\n     \\\"beforeDevCommand\\\": \\\"\\\",\\n     \\\"devUrl\\\": \\\"../dist\\\",\\n     \\\"frontendDist\\\": \\\"../dist\\\"\\n   },\\n   \\\"app\\\": {\\n     \\\"security\\\": {\\n       \\\"csp\\\": null\\n     },\\n     \\\"windows\\\": [\\n       {\\n         \\\"fullscreen\\\": false,\\n         \\\"height\\\": 600,\\n         \\\"resizable\\\": true,\\n         \\\"title\\\": \\\"Tauri App\\\",\\n         \\\"width\\\": 800\\n       }\\n     ]\\n   },\\n   \\\"bundle\\\": {},\\n   \\\"plugins\\\": {}\\n }\\n ```\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"$schema\": {\n      \"description\": \"The JSON schema for the Tauri config.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"productName\": {\n      \"description\": \"App name.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ],\n      \"pattern\": \"^[^/\\\\:*?\\\"<>|]+$\"\n    },\n    \"version\": {\n      \"description\": \"App version. It is a semver version number or a path to a `package.json` file containing the `version` field. If removed the version number from `Cargo.toml` is used.\\n\\n By default version 1.0 is used on Android.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"identifier\": {\n      \"description\": \"The application identifier in reverse domain name notation (e.g. `com.tauri.example`).\\n This string must be unique across applications since it is used in system configurations like\\n the bundle ID and path to the webview data directory.\\n This string must contain only alphanumeric characters (A–Z, a–z, and 0–9), hyphens (-),\\n and periods (.).\",\n      \"default\": \"\",\n      \"type\": \"string\"\n    },\n    \"app\": {\n      \"description\": \"The App configuration.\",\n      \"default\": {\n        \"enableGTKAppId\": false,\n        \"macOSPrivateApi\": false,\n        \"security\": {\n          \"assetProtocol\": {\n            \"enable\": false,\n            \"scope\": []\n          },\n          \"capabilities\": [],\n          \"dangerousDisableAssetCspModification\": false,\n          \"freezePrototype\": false,\n          \"pattern\": {\n            \"use\": \"brownfield\"\n          }\n        },\n        \"windows\": [],\n        \"withGlobalTauri\": false\n      },\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/AppConfig\"\n        }\n      ]\n    },\n    \"build\": {\n      \"description\": \"The build configuration.\",\n      \"default\": {},\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/BuildConfig\"\n        }\n      ]\n    },\n    \"bundle\": {\n      \"description\": \"The bundler configuration.\",\n      \"default\": {\n        \"active\": false,\n        \"android\": {\n          \"minSdkVersion\": 24\n        },\n        \"createUpdaterArtifacts\": false,\n        \"iOS\": {\n          \"minimumSystemVersion\": \"13.0\"\n        },\n        \"icon\": [],\n        \"linux\": {\n          \"appimage\": {\n            \"bundleMediaFramework\": false,\n            \"files\": {}\n          },\n          \"deb\": {\n            \"files\": {}\n          },\n          \"rpm\": {\n            \"epoch\": 0,\n            \"files\": {},\n            \"release\": \"1\"\n          }\n        },\n        \"macOS\": {\n          \"dmg\": {\n            \"appPosition\": {\n              \"x\": 180,\n              \"y\": 170\n            },\n            \"applicationFolderPosition\": {\n              \"x\": 480,\n              \"y\": 170\n            },\n            \"windowSize\": {\n              \"height\": 400,\n              \"width\": 660\n            }\n          },\n          \"files\": {},\n          \"hardenedRuntime\": true,\n          \"minimumSystemVersion\": \"10.13\"\n        },\n        \"targets\": \"all\",\n        \"windows\": {\n          \"allowDowngrades\": true,\n          \"certificateThumbprint\": null,\n          \"digestAlgorithm\": null,\n          \"nsis\": null,\n          \"signCommand\": null,\n          \"timestampUrl\": null,\n          \"tsp\": false,\n          \"webviewInstallMode\": {\n            \"silent\": true,\n            \"type\": \"downloadBootstrapper\"\n          },\n          \"wix\": null\n        }\n      },\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/BundleConfig\"\n        }\n      ]\n    },\n    \"plugins\": {\n      \"description\": \"The plugins config.\",\n      \"default\": {},\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/PluginConfig\"\n        }\n      ]\n    }\n  },\n  \"additionalProperties\": false,\n  \"definitions\": {\n    \"AppConfig\": {\n      \"description\": \"The App configuration object.\\n\\n See more: <https://tauri.app/v1/api/config#appconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"windows\": {\n          \"description\": \"The windows configuration.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/WindowConfig\"\n          }\n        },\n        \"security\": {\n          \"description\": \"Security configuration.\",\n          \"default\": {\n            \"assetProtocol\": {\n              \"enable\": false,\n              \"scope\": []\n            },\n            \"capabilities\": [],\n            \"dangerousDisableAssetCspModification\": false,\n            \"freezePrototype\": false,\n            \"pattern\": {\n              \"use\": \"brownfield\"\n            }\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/SecurityConfig\"\n            }\n          ]\n        },\n        \"trayIcon\": {\n          \"description\": \"Configuration for app tray icon.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/TrayIconConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"macOSPrivateApi\": {\n          \"description\": \"MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"withGlobalTauri\": {\n          \"description\": \"Whether we should inject the Tauri API on `window.__TAURI__` or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"enableGTKAppId\": {\n          \"description\": \"If set to true \\\"identifier\\\" will be set as GTK app ID (on systems that use GTK).\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WindowConfig\": {\n      \"description\": \"The window configuration object.\\n\\n See more: <https://tauri.app/v1/api/config#windowconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"label\": {\n          \"description\": \"The window identifier. It must be alphanumeric.\",\n          \"default\": \"main\",\n          \"type\": \"string\"\n        },\n        \"url\": {\n          \"description\": \"The window webview URL.\",\n          \"default\": \"index.html\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WebviewUrl\"\n            }\n          ]\n        },\n        \"userAgent\": {\n          \"description\": \"The user agent for the webview\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dragDropEnabled\": {\n          \"description\": \"Whether the drag and drop is enabled or not on the webview. By default it is enabled.\\n\\n Disabling it is required to use HTML5 drag and drop on the frontend on Windows.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"center\": {\n          \"description\": \"Whether or not the window starts centered or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"x\": {\n          \"description\": \"The horizontal position of the window's top left corner\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"y\": {\n          \"description\": \"The vertical position of the window's top left corner\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"width\": {\n          \"description\": \"The window width.\",\n          \"default\": 800.0,\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"height\": {\n          \"description\": \"The window height.\",\n          \"default\": 600.0,\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"minWidth\": {\n          \"description\": \"The min window width.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"minHeight\": {\n          \"description\": \"The min window height.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"maxWidth\": {\n          \"description\": \"The max window width.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"maxHeight\": {\n          \"description\": \"The max window height.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"preventOverflow\": {\n          \"description\": \"Whether or not to prevent window overflow\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/PreventOverflowMarginConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"resizable\": {\n          \"description\": \"Whether the window is resizable or not. When resizable is set to false, native window's maximize button is automatically disabled.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"maximizable\": {\n          \"description\": \"Whether the window's native maximize button is enabled or not.\\n If resizable is set to false, this setting is ignored.\\n\\n ## Platform-specific\\n\\n - **macOS:** Disables the \\\"zoom\\\" button in the window titlebar, which is also used to enter fullscreen mode.\\n - **Linux / iOS / Android:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"minimizable\": {\n          \"description\": \"Whether the window's native minimize button is enabled or not.\\n\\n ## Platform-specific\\n\\n - **Linux / iOS / Android:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"closable\": {\n          \"description\": \"Whether the window's native close button is enabled or not.\\n\\n ## Platform-specific\\n\\n - **Linux:** \\\"GTK+ will do its best to convince the window manager not to show a close button.\\n   Depending on the system, this function may not have any effect when called on a window that is already visible\\\"\\n - **iOS / Android:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"title\": {\n          \"description\": \"The window title.\",\n          \"default\": \"Tauri App\",\n          \"type\": \"string\"\n        },\n        \"fullscreen\": {\n          \"description\": \"Whether the window starts as fullscreen or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"focus\": {\n          \"description\": \"Whether the window will be initially focused or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"transparent\": {\n          \"description\": \"Whether the window is transparent or not.\\n\\n Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri > macOSPrivateApi`.\\n WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"maximized\": {\n          \"description\": \"Whether the window is maximized or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"visible\": {\n          \"description\": \"Whether the window is visible or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"decorations\": {\n          \"description\": \"Whether the window should have borders and bars.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"alwaysOnBottom\": {\n          \"description\": \"Whether the window should always be below other windows.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"alwaysOnTop\": {\n          \"description\": \"Whether the window should always be on top of other windows.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"visibleOnAllWorkspaces\": {\n          \"description\": \"Whether the window should be visible on all workspaces or virtual desktops.\\n\\n ## Platform-specific\\n\\n - **Windows / iOS / Android:** Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"contentProtected\": {\n          \"description\": \"Prevents the window contents from being captured by other apps.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"skipTaskbar\": {\n          \"description\": \"If `true`, hides the window icon from the taskbar on Windows and Linux.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"theme\": {\n          \"description\": \"The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Theme\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"titleBarStyle\": {\n          \"description\": \"The style of the macOS title bar.\",\n          \"default\": \"Visible\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/TitleBarStyle\"\n            }\n          ]\n        },\n        \"hiddenTitle\": {\n          \"description\": \"If `true`, sets the window title to be hidden on macOS.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"acceptFirstMouse\": {\n          \"description\": \"Whether clicking an inactive window also clicks through to the webview on macOS.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"tabbingIdentifier\": {\n          \"description\": \"Defines the window [tabbing identifier] for macOS.\\n\\n Windows with matching tabbing identifiers will be grouped together.\\n If the tabbing identifier is not set, automatic tabbing will be disabled.\\n\\n [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"additionalBrowserArgs\": {\n          \"description\": \"Defines additional browser arguments on Windows. By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`\\n so if you use this method, you also need to disable these components by yourself if you want.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"shadow\": {\n          \"description\": \"Whether or not the window has shadow.\\n\\n ## Platform-specific\\n\\n - **Windows:**\\n   - `false` has no effect on decorated window, shadow are always ON.\\n   - `true` will make undecorated window have a 1px white border,\\n and on Windows 11, it will have a rounded corners.\\n - **Linux:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"windowEffects\": {\n          \"description\": \"Window effects.\\n\\n Requires the window to be transparent.\\n\\n ## Platform-specific:\\n\\n - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>\\n - **Linux**: Unsupported\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowEffectsConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"incognito\": {\n          \"description\": \"Whether or not the webview should be launched in incognito  mode.\\n\\n  ## Platform-specific:\\n\\n  - **Android**: Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"parent\": {\n          \"description\": \"Sets the window associated with this label to be the parent of the window to be created.\\n\\n ## Platform-specific\\n\\n - **Windows**: This sets the passed parent as an owner window to the window to be created.\\n   From [MSDN owned windows docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows):\\n     - An owned window is always above its owner in the z-order.\\n     - The system automatically destroys an owned window when its owner is destroyed.\\n     - An owned window is hidden when its owner is minimized.\\n - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\\n - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"proxyUrl\": {\n          \"description\": \"The proxy URL for the WebView for all network requests.\\n\\n Must be either a `http://` or a `socks5://` URL.\\n\\n ## Platform-specific\\n\\n - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"format\": \"uri\"\n        },\n        \"zoomHotkeysEnabled\": {\n          \"description\": \"Whether page zooming by hotkeys is enabled\\n\\n ## Platform-specific:\\n\\n - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.\\n - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,\\n 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission\\n\\n - **Android / iOS**: Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"backgroundThrottling\": {\n          \"description\": \"Change the default background throttling behaviour.\\n\\n By default, browsers use a suspend policy that will throttle timers and even unload\\n the whole tab (view) to free resources after roughly 5 minutes when a view became\\n minimized or hidden. This will pause all tasks until the documents visibility state\\n changes back from hidden to visible by bringing the view back to the foreground.\\n\\n ## Platform-specific\\n\\n - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.\\n - **iOS**: Supported since version 17.0+.\\n - **macOS**: Supported since version 14.0+.\\n\\n see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"disabled\",\n            \"throttle\",\n            \"suspend\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WebviewUrl\": {\n      \"description\": \"An URL to open on a Tauri webview window.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An external URL. Must use either the `http` or `https` schemes.\",\n          \"type\": \"string\",\n          \"format\": \"uri\"\n        },\n        {\n          \"description\": \"The path portion of an app URL.\\n For instance, to load `tauri://localhost/users/john`,\\n you can simply provide `users/john` in this configuration.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A custom protocol url, for example, `doom://index.html`\",\n          \"type\": \"string\",\n          \"format\": \"uri\"\n        }\n      ]\n    },\n    \"PreventOverflowMarginConfig\": {\n      \"description\": \"Prevent overflow with a margin\",\n      \"anyOf\": [\n        {\n          \"description\": \"Enable prevent overflow or not\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Enable prevent overflow with a margin\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/PreventOverflowMargin\"\n            }\n          ]\n        }\n      ]\n    },\n    \"PreventOverflowMargin\": {\n      \"description\": \"Enable prevent overflow with a margin\",\n      \"type\": \"object\",\n      \"required\": [\n        \"height\",\n        \"width\"\n      ],\n      \"properties\": {\n        \"width\": {\n          \"description\": \"Horizontal margin in physical unit\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"height\": {\n          \"description\": \"Vertical margin in physical unit\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Theme\": {\n      \"description\": \"System theme.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Light theme.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Light\"\n          ]\n        },\n        {\n          \"description\": \"Dark theme.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Dark\"\n          ]\n        }\n      ]\n    },\n    \"TitleBarStyle\": {\n      \"description\": \"How the window title bar should be displayed on macOS.\",\n      \"oneOf\": [\n        {\n          \"description\": \"A normal title bar.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Visible\"\n          ]\n        },\n        {\n          \"description\": \"Makes the title bar transparent, so the window background color is shown instead.\\n\\n Useful if you don't need to have actual HTML under the title bar. This lets you avoid the caveats of using `TitleBarStyle::Overlay`. Will be more useful when Tauri lets you set a custom window background color.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Transparent\"\n          ]\n        },\n        {\n          \"description\": \"Shows the title bar as a transparent overlay over the window's content.\\n\\n Keep in mind:\\n - The height of the title bar is different on different OS versions, which can lead to window the controls and title not being where you don't expect.\\n - You need to define a custom drag region to make your window draggable, however due to a limitation you can't drag the window when it's not in focus <https://github.com/tauri-apps/tauri/issues/4316>.\\n - The color of the window title depends on the system theme.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Overlay\"\n          ]\n        }\n      ]\n    },\n    \"WindowEffectsConfig\": {\n      \"description\": \"The window effects configuration object\",\n      \"type\": \"object\",\n      \"required\": [\n        \"effects\"\n      ],\n      \"properties\": {\n        \"effects\": {\n          \"description\": \"List of Window effects to apply to the Window.\\n Conflicting effects will apply the first one and ignore the rest.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/WindowEffect\"\n          }\n        },\n        \"state\": {\n          \"description\": \"Window effect state **macOS Only**\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowEffectState\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"radius\": {\n          \"description\": \"Window effect corner radius **macOS Only**\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"color\": {\n          \"description\": \"Window effect color. Affects [`WindowEffect::Blur`] and [`WindowEffect::Acrylic`] only\\n on Windows 10 v1903+. Doesn't have any effect on Windows 7 or Windows 11.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Color\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WindowEffect\": {\n      \"description\": \"Platform-specific window effects\",\n      \"oneOf\": [\n        {\n          \"description\": \"A default material appropriate for the view's effectiveAppearance. **macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"appearanceBased\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"light\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"dark\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"mediumLight\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"ultraDark\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.10+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"titlebar\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.10+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"selection\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.11+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"menu\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.11+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"popover\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.11+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"sidebar\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"headerView\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"sheet\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"windowBackground\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"hudWindow\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"fullScreenUI\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tooltip\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"contentBackground\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"underWindowBackground\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"underPageBackground\"\n          ]\n        },\n        {\n          \"description\": \"Mica effect that matches the system dark perefence **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"mica\"\n          ]\n        },\n        {\n          \"description\": \"Mica effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"micaDark\"\n          ]\n        },\n        {\n          \"description\": \"Mica effect with light mode **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"micaLight\"\n          ]\n        },\n        {\n          \"description\": \"Tabbed effect that matches the system dark perefence **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tabbed\"\n          ]\n        },\n        {\n          \"description\": \"Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tabbedDark\"\n          ]\n        },\n        {\n          \"description\": \"Tabbed effect with light mode **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tabbedLight\"\n          ]\n        },\n        {\n          \"description\": \"**Windows 7/10/11(22H1) Only**\\n\\n ## Notes\\n\\n This effect has bad performance when resizing/dragging the window on Windows 11 build 22621.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"blur\"\n          ]\n        },\n        {\n          \"description\": \"**Windows 10/11 Only**\\n\\n ## Notes\\n\\n This effect has bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"acrylic\"\n          ]\n        }\n      ]\n    },\n    \"WindowEffectState\": {\n      \"description\": \"Window effect state **macOS only**\\n\\n <https://developer.apple.com/documentation/appkit/nsvisualeffectview/state>\",\n      \"oneOf\": [\n        {\n          \"description\": \"Make window effect state follow the window's active state\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"followsWindowActiveState\"\n          ]\n        },\n        {\n          \"description\": \"Make window effect state always active\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"active\"\n          ]\n        },\n        {\n          \"description\": \"Make window effect state always inactive\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"inactive\"\n          ]\n        }\n      ]\n    },\n    \"Color\": {\n      \"description\": \"a tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255.\",\n      \"type\": \"array\",\n      \"items\": [\n        {\n          \"type\": \"integer\",\n          \"format\": \"uint8\",\n          \"minimum\": 0.0\n        },\n        {\n          \"type\": \"integer\",\n          \"format\": \"uint8\",\n          \"minimum\": 0.0\n        },\n        {\n          \"type\": \"integer\",\n          \"format\": \"uint8\",\n          \"minimum\": 0.0\n        },\n        {\n          \"type\": \"integer\",\n          \"format\": \"uint8\",\n          \"minimum\": 0.0\n        }\n      ],\n      \"maxItems\": 4,\n      \"minItems\": 4\n    },\n    \"SecurityConfig\": {\n      \"description\": \"Security configuration.\\n\\n See more: <https://tauri.app/v1/api/config#securityconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"csp\": {\n          \"description\": \"The Content Security Policy that will be injected on all HTML files on the built application.\\n If [`dev_csp`](#SecurityConfig.devCsp) is not specified, this value is also injected on dev.\\n\\n This is a really important part of the configuration since it helps you ensure your WebView is secured.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Csp\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"devCsp\": {\n          \"description\": \"The Content Security Policy that will be injected on all HTML files on development.\\n\\n This is a really important part of the configuration since it helps you ensure your WebView is secured.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Csp\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"freezePrototype\": {\n          \"description\": \"Freeze the `Object.prototype` when using the custom protocol.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"dangerousDisableAssetCspModification\": {\n          \"description\": \"Disables the Tauri-injected CSP sources.\\n\\n At compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy\\n to only allow loading of your own scripts and styles by injecting nonce and hash sources.\\n This stricts your CSP, which may introduce issues when using along with other flexing sources.\\n\\n This configuration option allows both a boolean and a list of strings as value.\\n A boolean instructs Tauri to disable the injection for all CSP injections,\\n and a list of strings indicates the CSP directives that Tauri cannot inject.\\n\\n **WARNING:** Only disable this if you know what you are doing and have properly configured the CSP.\\n Your application might be vulnerable to XSS attacks without this Tauri protection.\",\n          \"default\": false,\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/DisabledCspModificationKind\"\n            }\n          ]\n        },\n        \"assetProtocol\": {\n          \"description\": \"Custom protocol config.\",\n          \"default\": {\n            \"enable\": false,\n            \"scope\": []\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/AssetProtocolConfig\"\n            }\n          ]\n        },\n        \"pattern\": {\n          \"description\": \"The pattern to use.\",\n          \"default\": {\n            \"use\": \"brownfield\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/PatternKind\"\n            }\n          ]\n        },\n        \"capabilities\": {\n          \"description\": \"List of capabilities that are enabled on the application.\\n\\n If the list is empty, all capabilities are included.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/CapabilityEntry\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Csp\": {\n      \"description\": \"A Content-Security-Policy definition.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\",\n      \"anyOf\": [\n        {\n          \"description\": \"The entire CSP policy in a single text string.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An object mapping a directive with its sources values as a list of strings.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/CspDirectiveSources\"\n          }\n        }\n      ]\n    },\n    \"CspDirectiveSources\": {\n      \"description\": \"A Content-Security-Policy directive source list.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources>.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An inline list of CSP sources. Same as [`Self::List`], but concatenated with a space separator.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A list of CSP sources. The collection will be concatenated with a space separator for the CSP string.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"DisabledCspModificationKind\": {\n      \"description\": \"The possible values for the `dangerous_disable_asset_csp_modification` config option.\",\n      \"anyOf\": [\n        {\n          \"description\": \"If `true`, disables all CSP modification.\\n `false` is the default value and it configures Tauri to control the CSP.\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Disables the given list of CSP directives modifications.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"AssetProtocolConfig\": {\n      \"description\": \"Config for the asset custom protocol.\\n\\n See more: <https://tauri.app/v1/api/config#assetprotocolconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"scope\": {\n          \"description\": \"The access scope for the asset protocol.\",\n          \"default\": [],\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/FsScope\"\n            }\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Enables the asset protocol.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"FsScope\": {\n      \"description\": \"Protocol scope definition.\\n It is a list of glob patterns that restrict the API access from the webview.\\n\\n Each pattern can start with a variable that resolves to a system base directory.\\n The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,\\n `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,\\n `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,\\n `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A list of paths that are allowed by this scope.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A complete scope configuration.\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"allow\": {\n              \"description\": \"A list of paths that are allowed by this scope.\",\n              \"default\": [],\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            },\n            \"deny\": {\n              \"description\": \"A list of paths that are not allowed by this scope.\\n This gets precedence over the [`Self::Scope::allow`] list.\",\n              \"default\": [],\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            },\n            \"requireLiteralLeadingDot\": {\n              \"description\": \"Whether or not paths that contain components that start with a `.`\\n will require that `.` appears literally in the pattern; `*`, `?`, `**`,\\n or `[...]` will not match. This is useful because such files are\\n conventionally considered hidden on Unix systems and it might be\\n desirable to skip them when listing files.\\n\\n Defaults to `true` on Unix systems and `false` on Windows\",\n              \"type\": [\n                \"boolean\",\n                \"null\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"PatternKind\": {\n      \"description\": \"The application pattern.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Brownfield pattern.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"use\"\n          ],\n          \"properties\": {\n            \"use\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"brownfield\"\n              ]\n            }\n          }\n        },\n        {\n          \"description\": \"Isolation pattern. Recommended for security purposes.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"options\",\n            \"use\"\n          ],\n          \"properties\": {\n            \"use\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"isolation\"\n              ]\n            },\n            \"options\": {\n              \"type\": \"object\",\n              \"required\": [\n                \"dir\"\n              ],\n              \"properties\": {\n                \"dir\": {\n                  \"description\": \"The dir containing the index.html file that contains the secure isolation application.\",\n                  \"type\": \"string\"\n                }\n              }\n            }\n          }\n        }\n      ]\n    },\n    \"CapabilityEntry\": {\n      \"description\": \"A capability entry which can be either an inlined capability or a reference to a capability defined on its own file.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An inlined capability.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Capability\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Reference to a capability identifier.\",\n          \"type\": \"string\"\n        }\n      ]\n    },\n    \"Capability\": {\n      \"description\": \"A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\\n\\n It controls application windows fine grained access to the Tauri core, application, or plugin commands.\\n If a window is not matching any capability then it has no access to the IPC layer at all.\\n\\n This can be done to create groups of windows, based on their required system access, which can reduce\\n impact of frontend vulnerabilities in less privileged windows.\\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\\n A Window can have none, one, or multiple associated capabilities.\\n\\n ## Example\\n\\n ```json\\n {\\n   \\\"identifier\\\": \\\"main-user-files-write\\\",\\n   \\\"description\\\": \\\"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\\\",\\n   \\\"windows\\\": [\\n     \\\"main\\\"\\n   ],\\n  \\\"permissions\\\": [\\n   \\\"core:default\\\",\\n   \\\"dialog:open\\\",\\n   {\\n     \\\"identifier\\\": \\\"fs:allow-write-text-file\\\",\\n     \\\"allow\\\": [{ \\\"path\\\": \\\"$HOME/test.txt\\\" }]\\n   },\\n  \\\"platforms\\\": [\\\"macOS\\\",\\\"windows\\\"]\\n }\\n ```\",\n      \"type\": \"object\",\n      \"required\": [\n        \"identifier\",\n        \"permissions\"\n      ],\n      \"properties\": {\n        \"identifier\": {\n          \"description\": \"Identifier of the capability.\\n\\n ## Example\\n\\n `main-user-files-write`\",\n          \"type\": \"string\"\n        },\n        \"description\": {\n          \"description\": \"Description of what the capability is intended to allow on associated windows.\\n\\n It should contain a description of what the grouped permissions should allow.\\n\\n ## Example\\n\\n This capability allows the `main` window access to `filesystem` write related\\n commands and `dialog` commands to enable programatic access to files selected by the user.\",\n          \"default\": \"\",\n          \"type\": \"string\"\n        },\n        \"remote\": {\n          \"description\": \"Configure remote URLs that can use the capability permissions.\\n\\n This setting is optional and defaults to not being set, as our\\n default use case is that the content is served from our local application.\\n\\n :::caution\\n Make sure you understand the security implications of providing remote\\n sources with local system access.\\n :::\\n\\n ## Example\\n\\n ```json\\n {\\n   \\\"urls\\\": [\\\"https://*.mydomain.dev\\\"]\\n }\\n ```\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/CapabilityRemote\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"local\": {\n          \"description\": \"Whether this capability is enabled for local app URLs or not. Defaults to `true`.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"windows\": {\n          \"description\": \"List of windows that are affected by this capability. Can be a glob pattern.\\n\\n On multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\\n\\n ## Example\\n\\n `[\\\"main\\\"]`\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"webviews\": {\n          \"description\": \"List of webviews that are affected by this capability. Can be a glob pattern.\\n\\n This is only required when using on multiwebview contexts, by default\\n all child webviews of a window that matches [`Self::windows`] are linked.\\n\\n ## Example\\n\\n `[\\\"sub-webview-one\\\", \\\"sub-webview-two\\\"]`\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"permissions\": {\n          \"description\": \"List of permissions attached to this capability.\\n\\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\\n For commands directly implemented in the application itself only `${permission-name}`\\n is required.\\n\\n ## Example\\n\\n ```json\\n [\\n  \\\"core:default\\\",\\n  \\\"shell:allow-open\\\",\\n  \\\"dialog:open\\\",\\n  {\\n    \\\"identifier\\\": \\\"fs:allow-write-text-file\\\",\\n    \\\"allow\\\": [{ \\\"path\\\": \\\"$HOME/test.txt\\\" }]\\n  }\\n ```\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/PermissionEntry\"\n          },\n          \"uniqueItems\": true\n        },\n        \"platforms\": {\n          \"description\": \"Limit which target platforms this capability applies to.\\n\\n By default all platforms are targeted.\\n\\n ## Example\\n\\n `[\\\"macOS\\\",\\\"windows\\\"]`\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/definitions/Target\"\n          }\n        }\n      }\n    },\n    \"CapabilityRemote\": {\n      \"description\": \"Configuration for remote URLs that are associated with the capability.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"urls\"\n      ],\n      \"properties\": {\n        \"urls\": {\n          \"description\": \"Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\\n\\n ## Examples\\n\\n - \\\"https://*.mydomain.dev\\\": allows subdomains of mydomain.dev\\n - \\\"https://mydomain.dev/api/*\\\": allows any subpath of mydomain.dev/api\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"PermissionEntry\": {\n      \"description\": \"An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`]\\n or an object that references a permission and extends its scope.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Reference a permission or permission set by identifier.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Identifier\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Reference a permission or permission set by identifier and extends its scope.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"identifier\"\n          ],\n          \"properties\": {\n            \"identifier\": {\n              \"description\": \"Identifier of the permission or permission set.\",\n              \"allOf\": [\n                {\n                  \"$ref\": \"#/definitions/Identifier\"\n                }\n              ]\n            },\n            \"allow\": {\n              \"description\": \"Data that defines what is allowed by the scope.\",\n              \"type\": [\n                \"array\",\n                \"null\"\n              ],\n              \"items\": {\n                \"$ref\": \"#/definitions/Value\"\n              }\n            },\n            \"deny\": {\n              \"description\": \"Data that defines what is denied by the scope. This should be prioritized by validation logic.\",\n              \"type\": [\n                \"array\",\n                \"null\"\n              ],\n              \"items\": {\n                \"$ref\": \"#/definitions/Value\"\n              }\n            }\n          }\n        }\n      ]\n    },\n    \"Identifier\": {\n      \"type\": \"string\"\n    },\n    \"Value\": {\n      \"description\": \"All supported ACL values.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents a null JSON value.\",\n          \"type\": \"null\"\n        },\n        {\n          \"description\": \"Represents a [`bool`].\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Represents a valid ACL [`Number`].\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Number\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Represents a [`String`].\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Represents a list of other [`Value`]s.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        },\n        {\n          \"description\": \"Represents a map of [`String`] keys to [`Value`]s.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        }\n      ]\n    },\n    \"Number\": {\n      \"description\": \"A valid ACL number.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents an [`i64`].\",\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        {\n          \"description\": \"Represents a [`f64`].\",\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      ]\n    },\n    \"Target\": {\n      \"description\": \"Platform target.\",\n      \"oneOf\": [\n        {\n          \"description\": \"MacOS.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"macOS\"\n          ]\n        },\n        {\n          \"description\": \"Windows.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"windows\"\n          ]\n        },\n        {\n          \"description\": \"Linux.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"linux\"\n          ]\n        },\n        {\n          \"description\": \"Android.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"android\"\n          ]\n        },\n        {\n          \"description\": \"iOS.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"iOS\"\n          ]\n        }\n      ]\n    },\n    \"TrayIconConfig\": {\n      \"description\": \"Configuration for application tray icon.\\n\\n See more: <https://tauri.app/v1/api/config#trayiconconfig>\",\n      \"type\": \"object\",\n      \"required\": [\n        \"iconPath\"\n      ],\n      \"properties\": {\n        \"id\": {\n          \"description\": \"Set an id for this tray icon so you can reference it later, defaults to `main`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"iconPath\": {\n          \"description\": \"Path to the default icon to use for the tray icon.\\n\\n Note: this stores the image in raw pixels to the final binary,\\n so keep the icon size (width and height) small\\n or else it's going to bloat your final executable\",\n          \"type\": \"string\"\n        },\n        \"iconAsTemplate\": {\n          \"description\": \"A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"menuOnLeftClick\": {\n          \"description\": \"A Boolean value that determines whether the menu should appear when the tray icon receives a left click on macOS.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"title\": {\n          \"description\": \"Title for MacOS tray\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tooltip\": {\n          \"description\": \"Tray icon tooltip on Windows and macOS\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"BuildConfig\": {\n      \"description\": \"The Build configuration object.\\n\\n See more: <https://tauri.app/v1/api/config#buildconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"runner\": {\n          \"description\": \"The binary used to build and run the application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"devUrl\": {\n          \"description\": \"The URL to load in development.\\n\\n This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.\\n Most modern JavaScript bundlers like [Vite](https://vite.dev/guide/) provides a way to start a dev server by default.\\n\\n If you don't have a dev server or don't want to use one, ignore this option and use [`frontendDist`](BuildConfig::frontend_dist)\\n and point to a web assets directory, and Tauri CLI will run its built-in dev server and provide a simple hot-reload experience.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"format\": \"uri\"\n        },\n        \"frontendDist\": {\n          \"description\": \"The path to the application assets (usually the `dist` folder of your javascript bundler)\\n or a URL that could be either a custom protocol registered in the tauri app (for example: `myprotocol://`)\\n or a remote URL (for example: `https://site.com/app`).\\n\\n When a path relative to the configuration file is provided,\\n it is read recursively and all files are embedded in the application binary.\\n Tauri then looks for an `index.html` and serves it as the default entry point for your application.\\n\\n You can also provide a list of paths to be embedded, which allows granular control over what files are added to the binary.\\n In this case, all files are added to the root and you must reference it that way in your HTML files.\\n\\n When a URL is provided, the application won't have bundled assets\\n and the application will load that URL by default.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/FrontendDist\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"beforeDevCommand\": {\n          \"description\": \"A shell command to run before `tauri dev` kicks in.\\n\\n The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BeforeDevCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"beforeBuildCommand\": {\n          \"description\": \"A shell command to run before `tauri build` kicks in.\\n\\n The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HookCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"beforeBundleCommand\": {\n          \"description\": \"A shell command to run before the bundling phase in `tauri build` kicks in.\\n\\n The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HookCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"features\": {\n          \"description\": \"Features passed to `cargo` commands.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"FrontendDist\": {\n      \"description\": \"Defines the URL or assets to embed in the application.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An external URL that should be used as the default application URL.\",\n          \"type\": \"string\",\n          \"format\": \"uri\"\n        },\n        {\n          \"description\": \"Path to a directory containing the frontend dist assets.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An array of files to embed on the app.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"BeforeDevCommand\": {\n      \"description\": \"Describes the shell command to run before `tauri dev`.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Run the given script with the default options.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Run the given script with custom options.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"script\"\n          ],\n          \"properties\": {\n            \"script\": {\n              \"description\": \"The script to execute.\",\n              \"type\": \"string\"\n            },\n            \"cwd\": {\n              \"description\": \"The current working directory.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            },\n            \"wait\": {\n              \"description\": \"Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`.\",\n              \"default\": false,\n              \"type\": \"boolean\"\n            }\n          }\n        }\n      ]\n    },\n    \"HookCommand\": {\n      \"description\": \"Describes a shell command to be executed when a CLI hook is triggered.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Run the given script with the default options.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Run the given script with custom options.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"script\"\n          ],\n          \"properties\": {\n            \"script\": {\n              \"description\": \"The script to execute.\",\n              \"type\": \"string\"\n            },\n            \"cwd\": {\n              \"description\": \"The current working directory.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"BundleConfig\": {\n      \"description\": \"Configuration for tauri-bundler.\\n\\n See more: <https://tauri.app/v1/api/config#bundleconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"active\": {\n          \"description\": \"Whether Tauri should bundle your application or just output the executable.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"targets\": {\n          \"description\": \"The bundle targets, currently supports [\\\"deb\\\", \\\"rpm\\\", \\\"appimage\\\", \\\"nsis\\\", \\\"msi\\\", \\\"app\\\", \\\"dmg\\\"] or \\\"all\\\".\",\n          \"default\": \"all\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleTarget\"\n            }\n          ]\n        },\n        \"createUpdaterArtifacts\": {\n          \"description\": \"Produce updaters and their signatures or not\",\n          \"default\": false,\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Updater\"\n            }\n          ]\n        },\n        \"publisher\": {\n          \"description\": \"The application's publisher. Defaults to the second element in the identifier string.\\n Currently maps to the Manufacturer property of the Windows Installer.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"homepage\": {\n          \"description\": \"A url to the home page of your application. If unset, will\\n fallback to `homepage` defined in `Cargo.toml`.\\n\\n Supported bundle targets: `deb`, `rpm`, `nsis` and `msi`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"icon\": {\n          \"description\": \"The app's icons\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"resources\": {\n          \"description\": \"App resources to bundle.\\n Each resource is a path to a file or directory.\\n Glob patterns are supported.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleResources\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"copyright\": {\n          \"description\": \"A copyright string associated with your application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"license\": {\n          \"description\": \"The package's license identifier to be included in the appropriate bundles.\\n If not set, defaults to the license from the Cargo.toml file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"licenseFile\": {\n          \"description\": \"The path to the license file to be included in the appropriate bundles.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"category\": {\n          \"description\": \"The application kind.\\n\\n Should be one of the following:\\n Business, DeveloperTool, Education, Entertainment, Finance, Game, ActionGame, AdventureGame, ArcadeGame, BoardGame, CardGame, CasinoGame, DiceGame, EducationalGame, FamilyGame, KidsGame, MusicGame, PuzzleGame, RacingGame, RolePlayingGame, SimulationGame, SportsGame, StrategyGame, TriviaGame, WordGame, GraphicsAndDesign, HealthcareAndFitness, Lifestyle, Medical, Music, News, Photography, Productivity, Reference, SocialNetworking, Sports, Travel, Utility, Video, Weather.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fileAssociations\": {\n          \"description\": \"File associations to application.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/definitions/FileAssociation\"\n          }\n        },\n        \"shortDescription\": {\n          \"description\": \"A short description of your application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"longDescription\": {\n          \"description\": \"A longer, multi-line description of the application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"externalBin\": {\n          \"description\": \"A list of—either absolute or relative—paths to binaries to embed with your application.\\n\\n Note that Tauri will look for system-specific binaries following the pattern \\\"binary-name{-target-triple}{.system-extension}\\\".\\n\\n E.g. for the external binary \\\"my-binary\\\", Tauri looks for:\\n\\n - \\\"my-binary-x86_64-pc-windows-msvc.exe\\\" for Windows\\n - \\\"my-binary-x86_64-apple-darwin\\\" for macOS\\n - \\\"my-binary-x86_64-unknown-linux-gnu\\\" for Linux\\n\\n so don't forget to provide binaries for all targeted platforms.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"windows\": {\n          \"description\": \"Configuration for the Windows bundles.\",\n          \"default\": {\n            \"allowDowngrades\": true,\n            \"certificateThumbprint\": null,\n            \"digestAlgorithm\": null,\n            \"nsis\": null,\n            \"signCommand\": null,\n            \"timestampUrl\": null,\n            \"tsp\": false,\n            \"webviewInstallMode\": {\n              \"silent\": true,\n              \"type\": \"downloadBootstrapper\"\n            },\n            \"wix\": null\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowsConfig\"\n            }\n          ]\n        },\n        \"linux\": {\n          \"description\": \"Configuration for the Linux bundles.\",\n          \"default\": {\n            \"appimage\": {\n              \"bundleMediaFramework\": false,\n              \"files\": {}\n            },\n            \"deb\": {\n              \"files\": {}\n            },\n            \"rpm\": {\n              \"epoch\": 0,\n              \"files\": {},\n              \"release\": \"1\"\n            }\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/LinuxConfig\"\n            }\n          ]\n        },\n        \"macOS\": {\n          \"description\": \"Configuration for the macOS bundles.\",\n          \"default\": {\n            \"dmg\": {\n              \"appPosition\": {\n                \"x\": 180,\n                \"y\": 170\n              },\n              \"applicationFolderPosition\": {\n                \"x\": 480,\n                \"y\": 170\n              },\n              \"windowSize\": {\n                \"height\": 400,\n                \"width\": 660\n              }\n            },\n            \"files\": {},\n            \"hardenedRuntime\": true,\n            \"minimumSystemVersion\": \"10.13\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/MacConfig\"\n            }\n          ]\n        },\n        \"iOS\": {\n          \"description\": \"iOS configuration.\",\n          \"default\": {\n            \"minimumSystemVersion\": \"13.0\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/IosConfig\"\n            }\n          ]\n        },\n        \"android\": {\n          \"description\": \"Android configuration.\",\n          \"default\": {\n            \"minSdkVersion\": 24\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/AndroidConfig\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"BundleTarget\": {\n      \"description\": \"Targets to bundle. Each value is case insensitive.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Bundle all targets.\",\n          \"enum\": [\n            \"all\"\n          ]\n        },\n        {\n          \"description\": \"A list of bundle targets.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/BundleType\"\n          }\n        },\n        {\n          \"description\": \"A single bundle target.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleType\"\n            }\n          ]\n        }\n      ]\n    },\n    \"BundleType\": {\n      \"description\": \"A bundle referenced by tauri-bundler.\",\n      \"oneOf\": [\n        {\n          \"description\": \"The debian bundle (.deb).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"deb\"\n          ]\n        },\n        {\n          \"description\": \"The RPM bundle (.rpm).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"rpm\"\n          ]\n        },\n        {\n          \"description\": \"The AppImage bundle (.appimage).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"appimage\"\n          ]\n        },\n        {\n          \"description\": \"The Microsoft Installer bundle (.msi).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"msi\"\n          ]\n        },\n        {\n          \"description\": \"The NSIS bundle (.exe).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"nsis\"\n          ]\n        },\n        {\n          \"description\": \"The macOS application bundle (.app).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"app\"\n          ]\n        },\n        {\n          \"description\": \"The Apple Disk Image bundle (.dmg).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"dmg\"\n          ]\n        }\n      ]\n    },\n    \"Updater\": {\n      \"description\": \"Updater type\",\n      \"anyOf\": [\n        {\n          \"description\": \"Generates lagacy zipped v1 compatible updaters\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/V1Compatible\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Produce updaters and their signatures or not\",\n          \"type\": \"boolean\"\n        }\n      ]\n    },\n    \"V1Compatible\": {\n      \"description\": \"Generates lagacy zipped v1 compatible updaters\",\n      \"oneOf\": [\n        {\n          \"description\": \"Generates lagacy zipped v1 compatible updaters\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"v1Compatible\"\n          ]\n        }\n      ]\n    },\n    \"BundleResources\": {\n      \"description\": \"Definition for bundle resources.\\n Can be either a list of paths to include or a map of source to target paths.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A list of paths to include.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A map of source to target paths.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"FileAssociation\": {\n      \"description\": \"File association\",\n      \"type\": \"object\",\n      \"required\": [\n        \"ext\"\n      ],\n      \"properties\": {\n        \"ext\": {\n          \"description\": \"File extensions to associate with this app. e.g. 'png'\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/AssociationExt\"\n          }\n        },\n        \"name\": {\n          \"description\": \"The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"description\": {\n          \"description\": \"The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"role\": {\n          \"description\": \"The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.\",\n          \"default\": \"Editor\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleTypeRole\"\n            }\n          ]\n        },\n        \"mimeType\": {\n          \"description\": \"The mime-type e.g. 'image/png' or 'text/plain'. Linux-only.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"rank\": {\n          \"description\": \"The ranking of this app among apps that declare themselves as editors or viewers of the given file type.  Maps to `LSHandlerRank` on macOS.\",\n          \"default\": \"Default\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/HandlerRank\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"AssociationExt\": {\n      \"description\": \"An extension for a [`FileAssociation`].\\n\\n A leading `.` is automatically stripped.\",\n      \"type\": \"string\"\n    },\n    \"BundleTypeRole\": {\n      \"description\": \"macOS-only. Corresponds to CFBundleTypeRole\",\n      \"oneOf\": [\n        {\n          \"description\": \"CFBundleTypeRole.Editor. Files can be read and edited.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Editor\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.Viewer. Files can be read.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Viewer\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.Shell\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Shell\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.QLGenerator\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"QLGenerator\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.None\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"None\"\n          ]\n        }\n      ]\n    },\n    \"HandlerRank\": {\n      \"description\": \"Corresponds to LSHandlerRank\",\n      \"oneOf\": [\n        {\n          \"description\": \"LSHandlerRank.Default. This app is an opener of files of this type; this value is also used if no rank is specified.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Default\"\n          ]\n        },\n        {\n          \"description\": \"LSHandlerRank.Owner. This app is the primary creator of files of this type.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Owner\"\n          ]\n        },\n        {\n          \"description\": \"LSHandlerRank.Alternate. This app is a secondary viewer of files of this type.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Alternate\"\n          ]\n        },\n        {\n          \"description\": \"LSHandlerRank.None. This app is never selected to open files of this type, but it accepts drops of files of this type.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"None\"\n          ]\n        }\n      ]\n    },\n    \"WindowsConfig\": {\n      \"description\": \"Windows bundler configuration.\\n\\n See more: <https://tauri.app/v1/api/config#windowsconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"digestAlgorithm\": {\n          \"description\": \"Specifies the file digest algorithm to use for creating file signatures.\\n Required for code signing. SHA-256 is recommended.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"certificateThumbprint\": {\n          \"description\": \"Specifies the SHA1 hash of the signing certificate.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"timestampUrl\": {\n          \"description\": \"Server to use during timestamping.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tsp\": {\n          \"description\": \"Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may\\n use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"webviewInstallMode\": {\n          \"description\": \"The installation mode for the Webview2 runtime.\",\n          \"default\": {\n            \"silent\": true,\n            \"type\": \"downloadBootstrapper\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WebviewInstallMode\"\n            }\n          ]\n        },\n        \"allowDowngrades\": {\n          \"description\": \"Validates a second app installation, blocking the user from installing an older version if set to `false`.\\n\\n For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\\n\\n The default value of this flag is `true`.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"wix\": {\n          \"description\": \"Configuration for the MSI generated with WiX.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WixConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"nsis\": {\n          \"description\": \"Configuration for the installer generated with NSIS.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/NsisConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"signCommand\": {\n          \"description\": \"Specify a custom command to sign the binaries.\\n This command needs to have a `%1` in args which is just a placeholder for the binary path,\\n which we will detect and replace before calling the command.\\n\\n By Default we use `signtool.exe` which can be found only on Windows so\\n if you are on another platform and want to cross-compile and sign you will\\n need to use another tool like `osslsigncode`.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/CustomSignCommandConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WebviewInstallMode\": {\n      \"description\": \"Install modes for the Webview2 runtime.\\n Note that for the updater bundle [`Self::DownloadBootstrapper`] is used.\\n\\n For more information see <https://tauri.app/v1/guides/building/windows>.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Do not install the Webview2 as part of the Windows Installer.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"skip\"\n              ]\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Download the bootstrapper and run it.\\n Requires an internet connection.\\n Results in a smaller installer size, but is not recommended on Windows 7.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"downloadBootstrapper\"\n              ]\n            },\n            \"silent\": {\n              \"description\": \"Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.\",\n              \"default\": true,\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Embed the bootstrapper and run it.\\n Requires an internet connection.\\n Increases the installer size by around 1.8MB, but offers better support on Windows 7.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"embedBootstrapper\"\n              ]\n            },\n            \"silent\": {\n              \"description\": \"Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.\",\n              \"default\": true,\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Embed the offline installer and run it.\\n Does not require an internet connection.\\n Increases the installer size by around 127MB.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"offlineInstaller\"\n              ]\n            },\n            \"silent\": {\n              \"description\": \"Instructs the installer to run the installer in silent mode. Defaults to `true`.\",\n              \"default\": true,\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Embed a fixed webview2 version and use it at runtime.\\n Increases the installer size by around 180MB.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"path\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"fixedRuntime\"\n              ]\n            },\n            \"path\": {\n              \"description\": \"The path to the fixed runtime to use.\\n\\n The fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section).\\n The `.cab` file must be extracted to a folder and this folder path must be defined on this field.\",\n              \"type\": \"string\"\n            }\n          },\n          \"additionalProperties\": false\n        }\n      ]\n    },\n    \"WixConfig\": {\n      \"description\": \"Configuration for the MSI bundle using WiX.\\n\\n See more: <https://tauri.app/v1/api/config#wixconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"language\": {\n          \"description\": \"The installer languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.\",\n          \"default\": \"en-US\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WixLanguage\"\n            }\n          ]\n        },\n        \"template\": {\n          \"description\": \"A custom .wxs template to use.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fragmentPaths\": {\n          \"description\": \"A list of paths to .wxs files with WiX fragments to use.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"componentGroupRefs\": {\n          \"description\": \"The ComponentGroup element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"componentRefs\": {\n          \"description\": \"The Component element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"featureGroupRefs\": {\n          \"description\": \"The FeatureGroup element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"featureRefs\": {\n          \"description\": \"The Feature element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"mergeRefs\": {\n          \"description\": \"The Merge element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"enableElevatedUpdateTask\": {\n          \"description\": \"Create an elevated update task within Windows Task Scheduler.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"bannerPath\": {\n          \"description\": \"Path to a bitmap file to use as the installation user interface banner.\\n This bitmap will appear at the top of all but the first page of the installer.\\n\\n The required dimensions are 493px × 58px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dialogImagePath\": {\n          \"description\": \"Path to a bitmap file to use on the installation user interface dialogs.\\n It is used on the welcome and completion dialogs.\\n The required dimensions are 493px × 312px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fipsCompliant\": {\n          \"description\": \"Enables FIPS compliant algorithms.\",\n          \"default\": null,\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WixLanguage\": {\n      \"description\": \"The languages to build using WiX.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A single language to build, without configuration.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A list of languages to build, without configuration.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A map of languages and its configuration.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/WixLanguageConfig\"\n          }\n        }\n      ]\n    },\n    \"WixLanguageConfig\": {\n      \"description\": \"Configuration for a target language for the WiX build.\\n\\n See more: <https://tauri.app/v1/api/config#wixlanguageconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"localePath\": {\n          \"description\": \"The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NsisConfig\": {\n      \"description\": \"Configuration for the Installer bundle using NSIS.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"template\": {\n          \"description\": \"A custom .nsi template to use.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"headerImage\": {\n          \"description\": \"The path to a bitmap file to display on the header of installers pages.\\n\\n The recommended dimensions are 150px x 57px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"sidebarImage\": {\n          \"description\": \"The path to a bitmap file for the Welcome page and the Finish page.\\n\\n The recommended dimensions are 164px x 314px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installerIcon\": {\n          \"description\": \"The path to an icon file used as the installer icon.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installMode\": {\n          \"description\": \"Whether the installation will be for all users or just the current user.\",\n          \"default\": \"currentUser\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/NSISInstallerMode\"\n            }\n          ]\n        },\n        \"languages\": {\n          \"description\": \"A list of installer languages.\\n By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.\\n To allow the user to select the language, set `display_language_selector` to `true`.\\n\\n See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"customLanguageFiles\": {\n          \"description\": \"A key-value pair where the key is the language and the\\n value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.\\n\\n See <https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-bundler/src/bundle/windows/templates/nsis-languages/English.nsh> for an example `.nsh` file.\\n\\n **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array,\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"displayLanguageSelector\": {\n          \"description\": \"Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.\\n By default the OS language is selected, with a fallback to the first language in the `languages` array.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"compression\": {\n          \"description\": \"Set the compression algorithm used to compress files in the installer.\\n\\n See <https://nsis.sourceforge.io/Reference/SetCompressor>\",\n          \"default\": \"lzma\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/NsisCompression\"\n            }\n          ]\n        },\n        \"startMenuFolder\": {\n          \"description\": \"Set the folder name for the start menu shortcut.\\n\\n Use this option if you have multiple apps and wish to group their shortcuts under one folder\\n or if you generally prefer to set your shortcut inside a folder.\\n\\n Examples:\\n - `AwesomePublisher`, shortcut will be placed in `%AppData%\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\AwesomePublisher\\\\<your-app>.lnk`\\n - If unset, shortcut will be placed in `%AppData%\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\<your-app>.lnk`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installerHooks\": {\n          \"description\": \"A path to a `.nsh` file that contains special NSIS macros to be hooked into the\\n main installer.nsi script.\\n\\n Supported hooks are:\\n - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\\n - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\\n - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\\n - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\\n\\n\\n ### Example\\n\\n ```nsh\\n !macro NSIS_HOOK_PREINSTALL\\n   MessageBox MB_OK \\\"PreInstall\\\"\\n !macroend\\n\\n !macro NSIS_HOOK_POSTINSTALL\\n   MessageBox MB_OK \\\"PostInstall\\\"\\n !macroend\\n\\n !macro NSIS_HOOK_PREUNINSTALL\\n   MessageBox MB_OK \\\"PreUnInstall\\\"\\n !macroend\\n\\n !macro NSIS_HOOK_POSTUNINSTALL\\n   MessageBox MB_OK \\\"PostUninstall\\\"\\n !macroend\\n\\n ```\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimumWebview2Version\": {\n          \"description\": \"Try to ensure that the WebView2 version is equal to or newer than this version,\\n if the user's WebView2 is older than this version,\\n the installer will try to trigger a WebView2 update.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NSISInstallerMode\": {\n      \"description\": \"Install Modes for the NSIS installer.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Default mode for the installer.\\n\\n Install the app by default in a directory that doesn't require Administrator access.\\n\\n Installer metadata will be saved under the `HKCU` registry path.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"currentUser\"\n          ]\n        },\n        {\n          \"description\": \"Install the app by default in the `Program Files` folder directory requires Administrator\\n access for the installation.\\n\\n Installer metadata will be saved under the `HKLM` registry path.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"perMachine\"\n          ]\n        },\n        {\n          \"description\": \"Combines both modes and allows the user to choose at install time\\n whether to install for the current user or per machine. Note that this mode\\n will require Administrator access even if the user wants to install it for the current user only.\\n\\n Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"both\"\n          ]\n        }\n      ]\n    },\n    \"NsisCompression\": {\n      \"description\": \"Compression algorithms used in the NSIS installer.\\n\\n See <https://nsis.sourceforge.io/Reference/SetCompressor>\",\n      \"oneOf\": [\n        {\n          \"description\": \"ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"zlib\"\n          ]\n        },\n        {\n          \"description\": \"BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"bzip2\"\n          ]\n        },\n        {\n          \"description\": \"LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"lzma\"\n          ]\n        },\n        {\n          \"description\": \"Disable compression\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"none\"\n          ]\n        }\n      ]\n    },\n    \"CustomSignCommandConfig\": {\n      \"description\": \"Custom Signing Command configuration.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A string notation of the script to execute.\\n\\n \\\"%1\\\" will be replaced with the path to the binary to be signed.\\n\\n This is a simpler notation for the command.\\n Tauri will split the string with `' '` and use the first element as the command name and the rest as arguments.\\n\\n If you need to use whitespace in the command or arguments, use the object notation [`Self::ScriptWithOptions`].\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An object notation of the command.\\n\\n This is more complex notation for the command but\\n this allows you to use whitespace in the command and arguments.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"args\",\n            \"cmd\"\n          ],\n          \"properties\": {\n            \"cmd\": {\n              \"description\": \"The command to run to sign the binary.\",\n              \"type\": \"string\"\n            },\n            \"args\": {\n              \"description\": \"The arguments to pass to the command.\\n\\n \\\"%1\\\" will be replaced with the path to the binary to be signed.\",\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            }\n          },\n          \"additionalProperties\": false\n        }\n      ]\n    },\n    \"LinuxConfig\": {\n      \"description\": \"Configuration for Linux bundles.\\n\\n See more: <https://tauri.app/v1/api/config#linuxconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"appimage\": {\n          \"description\": \"Configuration for the AppImage bundle.\",\n          \"default\": {\n            \"bundleMediaFramework\": false,\n            \"files\": {}\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/AppImageConfig\"\n            }\n          ]\n        },\n        \"deb\": {\n          \"description\": \"Configuration for the Debian bundle.\",\n          \"default\": {\n            \"files\": {}\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/DebConfig\"\n            }\n          ]\n        },\n        \"rpm\": {\n          \"description\": \"Configuration for the RPM bundle.\",\n          \"default\": {\n            \"epoch\": 0,\n            \"files\": {},\n            \"release\": \"1\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/RpmConfig\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"AppImageConfig\": {\n      \"description\": \"Configuration for AppImage bundles.\\n\\n See more: <https://tauri.app/v1/api/config#appimageconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bundleMediaFramework\": {\n          \"description\": \"Include additional gstreamer dependencies needed for audio and video playback.\\n This increases the bundle size by ~15-35MB depending on your build system.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"files\": {\n          \"description\": \"The files to include in the Appimage Binary.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"DebConfig\": {\n      \"description\": \"Configuration for Debian (.deb) bundles.\\n\\n See more: <https://tauri.app/v1/api/config#debconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"depends\": {\n          \"description\": \"The list of deb dependencies your application relies on.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"provides\": {\n          \"description\": \"The list of dependencies the package provides.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"conflicts\": {\n          \"description\": \"The list of package conflicts.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"replaces\": {\n          \"description\": \"The list of package replaces.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"files\": {\n          \"description\": \"The files to include on the package.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"section\": {\n          \"description\": \"Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"priority\": {\n          \"description\": \"Change the priority of the Debian Package. By default, it is set to `optional`.\\n Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"changelog\": {\n          \"description\": \"Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\\n <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"desktopTemplate\": {\n          \"description\": \"Path to a custom desktop file Handlebars template.\\n\\n Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preInstallScript\": {\n          \"description\": \"Path to script that will be executed before the package is unpacked. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postInstallScript\": {\n          \"description\": \"Path to script that will be executed after the package is unpacked. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preRemoveScript\": {\n          \"description\": \"Path to script that will be executed before the package is removed. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postRemoveScript\": {\n          \"description\": \"Path to script that will be executed after the package is removed. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"RpmConfig\": {\n      \"description\": \"Configuration for RPM bundles.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"depends\": {\n          \"description\": \"The list of RPM dependencies your application relies on.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"provides\": {\n          \"description\": \"The list of RPM dependencies your application provides.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"conflicts\": {\n          \"description\": \"The list of RPM dependencies your application conflicts with. They must not be present\\n in order for the package to be installed.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"obsoletes\": {\n          \"description\": \"The list of RPM dependencies your application supersedes - if this package is installed,\\n packages listed as “obsoletes” will be automatically removed (if they are present).\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"release\": {\n          \"description\": \"The RPM release tag.\",\n          \"default\": \"1\",\n          \"type\": \"string\"\n        },\n        \"epoch\": {\n          \"description\": \"The RPM epoch.\",\n          \"default\": 0,\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"files\": {\n          \"description\": \"The files to include on the package.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"desktopTemplate\": {\n          \"description\": \"Path to a custom desktop file Handlebars template.\\n\\n Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preInstallScript\": {\n          \"description\": \"Path to script that will be executed before the package is unpacked. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postInstallScript\": {\n          \"description\": \"Path to script that will be executed after the package is unpacked. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preRemoveScript\": {\n          \"description\": \"Path to script that will be executed before the package is removed. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postRemoveScript\": {\n          \"description\": \"Path to script that will be executed after the package is removed. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"MacConfig\": {\n      \"description\": \"Configuration for the macOS bundles.\\n\\n See more: <https://tauri.app/v1/api/config#macconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"frameworks\": {\n          \"description\": \"A list of strings indicating any macOS X frameworks that need to be bundled with the application.\\n\\n If a name is used, \\\".framework\\\" must be omitted and it will look for standard install locations. You may also use a path to a specific framework.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"files\": {\n          \"description\": \"The files to include in the application relative to the Contents directory.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"minimumSystemVersion\": {\n          \"description\": \"A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\\n\\n Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`\\n and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\\n\\n An empty string is considered an invalid value so the default value is used.\",\n          \"default\": \"10.13\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"exceptionDomain\": {\n          \"description\": \"Allows your application to communicate with the outside world.\\n It should be a lowercase, without port and protocol domain name.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"signingIdentity\": {\n          \"description\": \"Identity to use for code signing.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"hardenedRuntime\": {\n          \"description\": \"Whether the codesign should enable [hardened runtime] (for executables) or not.\\n\\n [hardened runtime]: <https://developer.apple.com/documentation/security/hardened_runtime>\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"providerShortName\": {\n          \"description\": \"Provider short name for notarization.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"entitlements\": {\n          \"description\": \"Path to the entitlements file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dmg\": {\n          \"description\": \"DMG-specific settings.\",\n          \"default\": {\n            \"appPosition\": {\n              \"x\": 180,\n              \"y\": 170\n            },\n            \"applicationFolderPosition\": {\n              \"x\": 480,\n              \"y\": 170\n            },\n            \"windowSize\": {\n              \"height\": 400,\n              \"width\": 660\n            }\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/DmgConfig\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"DmgConfig\": {\n      \"description\": \"Configuration for Apple Disk Image (.dmg) bundles.\\n\\n See more: <https://tauri.app/v1/api/config#dmgconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"background\": {\n          \"description\": \"Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"windowPosition\": {\n          \"description\": \"Position of volume window on screen.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"windowSize\": {\n          \"description\": \"Size of volume window.\",\n          \"default\": {\n            \"height\": 400,\n            \"width\": 660\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Size\"\n            }\n          ]\n        },\n        \"appPosition\": {\n          \"description\": \"Position of app file on window.\",\n          \"default\": {\n            \"x\": 180,\n            \"y\": 170\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            }\n          ]\n        },\n        \"applicationFolderPosition\": {\n          \"description\": \"Position of application folder on window.\",\n          \"default\": {\n            \"x\": 480,\n            \"y\": 170\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Position\": {\n      \"description\": \"Position coordinates struct.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"x\",\n        \"y\"\n      ],\n      \"properties\": {\n        \"x\": {\n          \"description\": \"X coordinate.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"y\": {\n          \"description\": \"Y coordinate.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Size\": {\n      \"description\": \"Size of the window.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"height\",\n        \"width\"\n      ],\n      \"properties\": {\n        \"width\": {\n          \"description\": \"Width of the window.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"height\": {\n          \"description\": \"Height of the window.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"IosConfig\": {\n      \"description\": \"General configuration for the iOS target.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"template\": {\n          \"description\": \"A custom [XcodeGen] project.yml template to use.\\n\\n [XcodeGen]: <https://github.com/yonaskolb/XcodeGen>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"frameworks\": {\n          \"description\": \"A list of strings indicating any iOS frameworks that need to be bundled with the application.\\n\\n Note that you need to recreate the iOS project for the changes to be applied.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"developmentTeam\": {\n          \"description\": \"The development team. This value is required for iOS development because code signing is enforced.\\n The `APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimumSystemVersion\": {\n          \"description\": \"A version string indicating the minimum iOS version that the bundled application supports. Defaults to `13.0`.\\n\\n Maps to the IPHONEOS_DEPLOYMENT_TARGET value.\",\n          \"default\": \"13.0\",\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"AndroidConfig\": {\n      \"description\": \"General configuration for the iOS target.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"minSdkVersion\": {\n          \"description\": \"The minimum API level required for the application to run.\\n The Android system will prevent the user from installing the application if the system's API level is lower than the value specified.\",\n          \"default\": 24,\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"versionCode\": {\n          \"description\": \"The version code of the application.\\n It is limited to 2,100,000,000 as per Google Play Store requirements.\\n\\n By default we use your configured version and perform the following math:\\n versionCode = version.major * 1000000 + version.minor * 1000 + version.patch\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint32\",\n          \"maximum\": 2100000000.0,\n          \"minimum\": 1.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"PluginConfig\": {\n      \"description\": \"The plugin configs holds a HashMap mapping a plugin name to its configuration object.\\n\\n See more: <https://tauri.app/v1/api/config#pluginconfig>\",\n      \"type\": \"object\",\n      \"additionalProperties\": true\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/scripts/kill-children.sh",
    "content": "#!/usr/bin/env sh\n\ngetcpid() {\n    cpids=$(pgrep -P $1|xargs)\n    for cpid in $cpids;\n    do\n        echo \"$cpid\"\n        getcpid $cpid\n    done\n}\n\nkill $(getcpid $1)\n"
  },
  {
    "path": "crates/tauri-cli/src/acl/capability/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse clap::{Parser, Subcommand};\n\nuse crate::Result;\n\nmod new;\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Manage or create capabilities for your app\")]\npub struct Cli {\n  #[clap(subcommand)]\n  command: Commands,\n}\n\n#[derive(Subcommand, Debug)]\nenum Commands {\n  #[clap(alias = \"create\")]\n  New(new::Options),\n}\n\npub fn command(cli: Cli) -> Result<()> {\n  match cli.command {\n    Commands::New(options) => new::command(options),\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/acl/capability/new.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{collections::HashSet, path::PathBuf};\n\nuse clap::Parser;\nuse tauri_utils::acl::capability::{Capability, PermissionEntry};\n\nuse crate::{acl::FileFormat, error::ErrorExt, helpers::prompts, Result};\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Create a new permission file\")]\npub struct Options {\n  /// Capability identifier.\n  identifier: Option<String>,\n  /// Capability description\n  #[clap(long)]\n  description: Option<String>,\n  /// Capability windows\n  #[clap(long)]\n  windows: Option<Vec<String>>,\n  /// Capability permissions\n  #[clap(long)]\n  permission: Option<Vec<String>>,\n  /// Output file format.\n  #[clap(long, default_value_t = FileFormat::Json)]\n  format: FileFormat,\n  /// The output file.\n  #[clap(short, long)]\n  out: Option<PathBuf>,\n}\n\npub fn command(options: Options) -> Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n\n  let identifier = match options.identifier {\n    Some(i) => i,\n    None => prompts::input(\"What's the capability identifier?\", None, false, false)?.unwrap(),\n  };\n\n  let description = match options.description {\n    Some(d) => Some(d),\n    None => prompts::input::<String>(\"What's the capability description?\", None, false, true)?\n      .and_then(|d| if d.is_empty() { None } else { Some(d) }),\n  };\n\n  let windows = match options.windows.map(FromIterator::from_iter) {\n    Some(w) => w,\n    None => prompts::input::<String>(\n      \"Which windows should be affected by this? (comma separated)\",\n      Some(\"main\".into()),\n      false,\n      false,\n    )?\n    .and_then(|d| {\n      if d.is_empty() {\n        None\n      } else {\n        Some(d.split(',').map(ToString::to_string).collect())\n      }\n    })\n    .unwrap_or_default(),\n  };\n\n  let permissions: HashSet<String> = match options.permission.map(FromIterator::from_iter) {\n    Some(p) => p,\n    None => prompts::input::<String>(\n      \"What permissions to enable? (comma separated)\",\n      None,\n      false,\n      true,\n    )?\n    .and_then(|p| {\n      if p.is_empty() {\n        None\n      } else {\n        Some(p.split(',').map(ToString::to_string).collect())\n      }\n    })\n    .unwrap_or_default(),\n  };\n\n  let capability = Capability {\n    identifier,\n    description: description.unwrap_or_default(),\n    remote: None,\n    local: true,\n    windows,\n    webviews: Vec::new(),\n    permissions: permissions\n      .into_iter()\n      .map(|p| {\n        PermissionEntry::PermissionRef(\n          p.clone()\n            .try_into()\n            .unwrap_or_else(|_| panic!(\"invalid permission {p}\")),\n        )\n      })\n      .collect(),\n    platforms: None,\n  };\n\n  let path = match options.out {\n    Some(o) => o\n      .canonicalize()\n      .fs_context(\"failed to canonicalize capability file path\", o.clone())?,\n    None => {\n      let capabilities_dir = dirs.tauri.join(\"capabilities\");\n      capabilities_dir.join(format!(\n        \"{}.{}\",\n        capability.identifier,\n        options.format.extension()\n      ))\n    }\n  };\n\n  if path.exists() {\n    let msg = format!(\n      \"Capability already exists at {}\",\n      dunce::simplified(&path).display()\n    );\n    let overwrite = prompts::confirm(&format!(\"{msg}, overwrite?\"), Some(false))?;\n    if overwrite {\n      std::fs::remove_file(&path).fs_context(\"failed to remove capability file\", path.clone())?;\n    } else {\n      crate::error::bail!(msg);\n    }\n  }\n\n  if let Some(parent) = path.parent() {\n    std::fs::create_dir_all(parent).fs_context(\n      \"failed to create capability directory\",\n      parent.to_path_buf(),\n    )?;\n  }\n\n  std::fs::write(&path, options.format.serialize(&capability)?)\n    .fs_context(\"failed to write capability file\", path.clone())?;\n\n  log::info!(action = \"Created\"; \"capability at {}\", dunce::simplified(&path).display());\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/acl/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::error::Context;\nuse serde::Serialize;\nuse std::fmt::Display;\n\npub mod capability;\npub mod permission;\n\n#[derive(Debug, clap::ValueEnum, Clone)]\nenum FileFormat {\n  Json,\n  Toml,\n}\n\nimpl Display for FileFormat {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    match self {\n      Self::Json => write!(f, \"json\"),\n      Self::Toml => write!(f, \"toml\"),\n    }\n  }\n}\n\nimpl FileFormat {\n  pub fn extension(&self) -> &'static str {\n    match self {\n      Self::Json => \"json\",\n      Self::Toml => \"toml\",\n    }\n  }\n\n  pub fn serialize<S: Serialize>(&self, s: &S) -> crate::Result<String> {\n    let contents = match self {\n      Self::Json => serde_json::to_string_pretty(s).context(\"failed to serialize JSON\")?,\n      Self::Toml => toml_edit::ser::to_string_pretty(s).context(\"failed to serialize TOML\")?,\n    };\n    Ok(contents)\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/acl/permission/add.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::Path;\n\nuse clap::Parser;\n\nuse crate::{\n  error::{Context, ErrorExt},\n  helpers::{app_paths::resolve_tauri_dir, prompts},\n  Result,\n};\n\n#[derive(Clone)]\nenum TomlOrJson {\n  Toml(toml_edit::DocumentMut),\n  Json(serde_json::Value),\n}\n\nimpl TomlOrJson {\n  fn identifier(&self) -> &str {\n    match self {\n      TomlOrJson::Toml(t) => t\n        .get(\"identifier\")\n        .and_then(|k| k.as_str())\n        .unwrap_or_default(),\n      TomlOrJson::Json(j) => j\n        .get(\"identifier\")\n        .and_then(|k| k.as_str())\n        .unwrap_or_default(),\n    }\n  }\n\n  fn platforms(&self) -> Option<Vec<&str>> {\n    match self {\n      TomlOrJson::Toml(t) => t.get(\"platforms\").and_then(|k| {\n        k.as_array()\n          .and_then(|array| array.iter().map(|v| v.as_str()).collect())\n      }),\n      TomlOrJson::Json(j) => j.get(\"platforms\").and_then(|k| {\n        if let Some(array) = k.as_array() {\n          let mut items = Vec::new();\n          for item in array {\n            if let Some(s) = item.as_str() {\n              items.push(s);\n            }\n          }\n          Some(items)\n        } else {\n          None\n        }\n      }),\n    }\n  }\n\n  fn insert_permission(&mut self, identifier: String) {\n    match self {\n      TomlOrJson::Toml(t) => {\n        let permissions = t.entry(\"permissions\").or_insert_with(|| {\n          toml_edit::Item::Value(toml_edit::Value::Array(toml_edit::Array::new()))\n        });\n        if let Some(permissions) = permissions.as_array_mut() {\n          permissions.push(identifier)\n        };\n      }\n\n      TomlOrJson::Json(j) => {\n        if let Some(o) = j.as_object_mut() {\n          let permissions = o\n            .entry(\"permissions\")\n            .or_insert_with(|| serde_json::Value::Array(Vec::new()));\n          if let Some(permissions) = permissions.as_array_mut() {\n            permissions.push(serde_json::Value::String(identifier))\n          };\n        }\n      }\n    };\n  }\n\n  fn has_permission(&self, identifier: &str) -> bool {\n    (|| {\n      Some(match self {\n        TomlOrJson::Toml(t) => t\n          .get(\"permissions\")?\n          .as_array()?\n          .iter()\n          .any(|value| value.as_str() == Some(identifier)),\n\n        TomlOrJson::Json(j) => j\n          .as_object()?\n          .get(\"permissions\")?\n          .as_array()?\n          .iter()\n          .any(|value| value.as_str() == Some(identifier)),\n      })\n    })()\n    .unwrap_or_default()\n  }\n\n  fn to_string(&self) -> Result<String> {\n    Ok(match self {\n      TomlOrJson::Toml(t) => t.to_string(),\n      TomlOrJson::Json(j) => {\n        serde_json::to_string_pretty(&j).context(\"failed to serialize JSON\")?\n      }\n    })\n  }\n}\n\nfn capability_from_path<P: AsRef<Path>>(path: P) -> Option<TomlOrJson> {\n  match path.as_ref().extension().and_then(|o| o.to_str()) {\n    Some(\"toml\") => std::fs::read_to_string(&path)\n      .ok()\n      .and_then(|c| c.parse::<toml_edit::DocumentMut>().ok())\n      .map(TomlOrJson::Toml),\n    Some(\"json\") => std::fs::read(&path)\n      .ok()\n      .and_then(|c| serde_json::from_slice::<serde_json::Value>(&c).ok())\n      .map(TomlOrJson::Json),\n    _ => None,\n  }\n}\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Add a permission to capabilities\")]\npub struct Options {\n  /// Permission to add.\n  pub identifier: String,\n  /// Capability to add the permission to.\n  pub capability: Option<String>,\n}\n\npub fn command(options: Options) -> Result<()> {\n  let dir = match resolve_tauri_dir() {\n    Some(t) => t,\n    None => std::env::current_dir().context(\"failed to resolve current directory\")?,\n  };\n\n  let capabilities_dir = dir.join(\"capabilities\");\n  if !capabilities_dir.exists() {\n    crate::error::bail!(\n      \"Couldn't find capabilities directory at {}\",\n      dunce::simplified(&capabilities_dir).display()\n    );\n  }\n\n  let known_plugins = crate::helpers::plugins::known_plugins();\n  let known_plugin = options\n    .identifier\n    .split_once(':')\n    .and_then(|(plugin, _permission)| known_plugins.get(&plugin));\n\n  let capabilities_iter = std::fs::read_dir(&capabilities_dir)\n    .fs_context(\n      \"failed to read capabilities directory\",\n      capabilities_dir.clone(),\n    )?\n    .flatten()\n    .filter(|e| e.file_type().map(|e| e.is_file()).unwrap_or_default())\n    .filter_map(|e| {\n      let path = e.path();\n      capability_from_path(&path).and_then(|capability| match &options.capability {\n        Some(c) => (c == capability.identifier()).then_some((capability, path)),\n        None => Some((capability, path)),\n      })\n    });\n\n  let (desktop_only, mobile_only) = known_plugin\n    .map(|p| (p.desktop_only, p.mobile_only))\n    .unwrap_or_default();\n\n  let expected_capability_config = if desktop_only {\n    Some((\n      vec![\n        tauri_utils::platform::Target::MacOS.to_string(),\n        tauri_utils::platform::Target::Windows.to_string(),\n        tauri_utils::platform::Target::Linux.to_string(),\n      ],\n      \"desktop\",\n    ))\n  } else if mobile_only {\n    Some((\n      vec![\n        tauri_utils::platform::Target::Android.to_string(),\n        tauri_utils::platform::Target::Ios.to_string(),\n      ],\n      \"mobile\",\n    ))\n  } else {\n    None\n  };\n\n  let capabilities = if let Some((expected_platforms, target_name)) = expected_capability_config {\n    let mut capabilities = capabilities_iter\n      .filter(|(capability, _path)| {\n        capability.platforms().is_some_and(|platforms| {\n          // all platforms must be in the expected platforms list\n          platforms\n            .iter()\n            .all(|p| expected_platforms.contains(&p.to_string()))\n        })\n      })\n      .collect::<Vec<_>>();\n\n    if capabilities.is_empty() {\n      let identifier = format!(\"{target_name}-capability\");\n      let capability_path = capabilities_dir.join(target_name).with_extension(\"json\");\n      log::info!(\n        \"Capability matching platforms {expected_platforms:?} not found, creating {}\",\n        capability_path.display()\n      );\n      capabilities.push((\n        TomlOrJson::Json(serde_json::json!({\n          \"identifier\": identifier,\n          \"platforms\": expected_platforms,\n          \"windows\": [\"main\"]\n        })),\n        capability_path,\n      ));\n    }\n\n    capabilities\n  } else {\n    capabilities_iter.collect::<Vec<_>>()\n  };\n\n  let mut capabilities = if capabilities.len() > 1 {\n    let selections = prompts::multiselect(\n      &format!(\n        \"Choose which capabilities to add the permission `{}` to:\",\n        options.identifier\n      ),\n      capabilities\n        .iter()\n        .map(|(c, p)| {\n          let id = c.identifier();\n          if id.is_empty() {\n            dunce::simplified(p).to_str().unwrap_or_default()\n          } else {\n            id\n          }\n        })\n        .collect::<Vec<_>>()\n        .as_slice(),\n      None,\n    )?;\n\n    if selections.is_empty() {\n      crate::error::bail!(\"You did not select any capabilities to update\");\n    }\n\n    selections\n      .into_iter()\n      .map(|idx| capabilities[idx].clone())\n      .collect()\n  } else {\n    capabilities\n  };\n\n  if capabilities.is_empty() {\n    crate::error::bail!(\"Could not find a capability to update\");\n  }\n\n  for (capability, path) in &mut capabilities {\n    if capability.has_permission(&options.identifier) {\n      log::info!(\n        \"Permission `{}` already found in `{}` at {}\",\n        options.identifier,\n        capability.identifier(),\n        dunce::simplified(path).display()\n      );\n    } else {\n      capability.insert_permission(options.identifier.clone());\n      std::fs::write(&*path, capability.to_string()?)\n        .fs_context(\"failed to write capability file\", path.clone())?;\n      log::info!(action = \"Added\"; \"permission `{}` to `{}` at {}\", options.identifier, capability.identifier(), dunce::simplified(path).display());\n    }\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/acl/permission/ls.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse clap::Parser;\n\nuse crate::{\n  error::{Context, ErrorExt},\n  Result,\n};\nuse colored::Colorize;\nuse tauri_utils::acl::{manifest::Manifest, APP_ACL_KEY};\n\nuse std::{collections::BTreeMap, fs::read_to_string};\n\n#[derive(Debug, Parser)]\n#[clap(about = \"List permissions available to your application\")]\npub struct Options {\n  /// Name of the plugin to list permissions.\n  plugin: Option<String>,\n  /// Permission identifier filter.\n  #[clap(short, long)]\n  filter: Option<String>,\n}\n\npub fn command(options: Options) -> Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n\n  let acl_manifests_path = dirs\n    .tauri\n    .join(\"gen\")\n    .join(\"schemas\")\n    .join(\"acl-manifests.json\");\n\n  if acl_manifests_path.exists() {\n    let plugin_manifest_json = read_to_string(&acl_manifests_path)\n      .fs_context(\"failed to read plugin manifest\", acl_manifests_path)?;\n    let acl = serde_json::from_str::<BTreeMap<String, Manifest>>(&plugin_manifest_json)\n      .context(\"failed to parse plugin manifest as JSON\")?;\n\n    for (key, manifest) in acl {\n      if options\n        .plugin\n        .as_ref()\n        .map(|p| p != &key)\n        .unwrap_or_default()\n      {\n        continue;\n      }\n\n      let mut permissions = Vec::new();\n\n      let prefix = if key == APP_ACL_KEY {\n        \"\".to_string()\n      } else {\n        format!(\"{}:\", key.magenta())\n      };\n\n      if let Some(default) = manifest.default_permission {\n        if options\n          .filter\n          .as_ref()\n          .map(|f| \"default\".contains(f))\n          .unwrap_or(true)\n        {\n          permissions.push(format!(\n            \"{prefix}{}\\n{}\\nPermissions: {}\",\n            \"default\".cyan(),\n            default.description,\n            default\n              .permissions\n              .iter()\n              .map(|c| c.cyan().to_string())\n              .collect::<Vec<_>>()\n              .join(\", \")\n          ));\n        }\n      }\n\n      for set in manifest.permission_sets.values() {\n        if options\n          .filter\n          .as_ref()\n          .map(|f| set.identifier.contains(f))\n          .unwrap_or(true)\n        {\n          permissions.push(format!(\n            \"{prefix}{}\\n{}\\nPermissions: {}\",\n            set.identifier.cyan(),\n            set.description,\n            set\n              .permissions\n              .iter()\n              .map(|c| c.cyan().to_string())\n              .collect::<Vec<_>>()\n              .join(\", \")\n          ));\n        }\n      }\n\n      for permission in manifest.permissions.into_values() {\n        if options\n          .filter\n          .as_ref()\n          .map(|f| permission.identifier.contains(f))\n          .unwrap_or(true)\n        {\n          permissions.push(format!(\n            \"{prefix}{}{}{}{}\",\n            permission.identifier.cyan(),\n            permission\n              .description\n              .map(|d| format!(\"\\n{d}\"))\n              .unwrap_or_default(),\n            if permission.commands.allow.is_empty() {\n              \"\".to_string()\n            } else {\n              format!(\n                \"\\n{}: {}\",\n                \"Allow commands\".bold(),\n                permission\n                  .commands\n                  .allow\n                  .iter()\n                  .map(|c| c.green().to_string())\n                  .collect::<Vec<_>>()\n                  .join(\", \")\n              )\n            },\n            if permission.commands.deny.is_empty() {\n              \"\".to_string()\n            } else {\n              format!(\n                \"\\n{}: {}\",\n                \"Deny commands\".bold(),\n                permission\n                  .commands\n                  .deny\n                  .iter()\n                  .map(|c| c.red().to_string())\n                  .collect::<Vec<_>>()\n                  .join(\", \")\n              )\n            },\n          ));\n        }\n      }\n\n      if !permissions.is_empty() {\n        println!(\"{}\\n\", permissions.join(\"\\n\\n\"));\n      }\n    }\n\n    Ok(())\n  } else {\n    crate::error::bail!(\"permission file not found, please build your application once first\")\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/acl/permission/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse clap::{Parser, Subcommand};\n\nuse crate::Result;\n\npub mod add;\nmod ls;\nmod new;\npub mod rm;\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Manage or create permissions for your app or plugin\")]\npub struct Cli {\n  #[clap(subcommand)]\n  command: Commands,\n}\n\n#[derive(Subcommand, Debug)]\nenum Commands {\n  #[clap(alias = \"create\")]\n  New(new::Options),\n  Add(add::Options),\n  #[clap(alias = \"remove\")]\n  Rm(rm::Options),\n  #[clap(alias = \"list\")]\n  Ls(ls::Options),\n}\n\npub fn command(cli: Cli) -> Result<()> {\n  match cli.command {\n    Commands::New(options) => new::command(options),\n    Commands::Add(options) => add::command(options),\n    Commands::Rm(options) => rm::command(options),\n    Commands::Ls(options) => ls::command(options),\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/acl/permission/new.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::PathBuf;\n\nuse clap::Parser;\n\nuse crate::{\n  acl::FileFormat,\n  error::{Context, ErrorExt},\n  helpers::{app_paths::resolve_tauri_dir, prompts},\n  Result,\n};\n\nuse tauri_utils::acl::{manifest::PermissionFile, Commands, Permission};\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Create a new permission file\")]\npub struct Options {\n  /// Permission identifier.\n  identifier: Option<String>,\n  /// Permission description\n  #[clap(long)]\n  description: Option<String>,\n  /// List of commands to allow\n  #[clap(short, long, value_delimiter = ',')]\n  allow: Option<Vec<String>>,\n  /// List of commands to deny\n  #[clap(short, long, value_delimiter = ',')]\n  deny: Option<Vec<String>>,\n  /// Output file format.\n  #[clap(long, default_value_t = FileFormat::Json)]\n  format: FileFormat,\n  /// The output file.\n  #[clap(short, long)]\n  out: Option<PathBuf>,\n}\n\npub fn command(options: Options) -> Result<()> {\n  let identifier = match options.identifier {\n    Some(i) => i,\n    None => prompts::input(\"What's the permission identifier?\", None, false, false)?.unwrap(),\n  };\n\n  let description = match options.description {\n    Some(d) => Some(d),\n    None => prompts::input::<String>(\"What's the permission description?\", None, false, true)?\n      .and_then(|d| if d.is_empty() { None } else { Some(d) }),\n  };\n\n  let allow: Vec<String> = options\n    .allow\n    .map(FromIterator::from_iter)\n    .unwrap_or_default();\n  let deny: Vec<String> = options\n    .deny\n    .map(FromIterator::from_iter)\n    .unwrap_or_default();\n\n  let permission = Permission {\n    version: None,\n    identifier,\n    description,\n    commands: Commands { allow, deny },\n    scope: Default::default(),\n    platforms: Default::default(),\n  };\n\n  let path = match options.out {\n    Some(o) => o\n      .canonicalize()\n      .fs_context(\"failed to canonicalize permission file path\", o.clone())?,\n    None => {\n      let dir = match resolve_tauri_dir() {\n        Some(t) => t,\n        None => std::env::current_dir().context(\"failed to resolve current directory\")?,\n      };\n      let permissions_dir = dir.join(\"permissions\");\n      permissions_dir.join(format!(\n        \"{}.{}\",\n        permission.identifier,\n        options.format.extension()\n      ))\n    }\n  };\n\n  if path.exists() {\n    let msg = format!(\n      \"Permission already exists at {}\",\n      dunce::simplified(&path).display()\n    );\n    let overwrite = prompts::confirm(&format!(\"{msg}, overwrite?\"), Some(false))?;\n    if overwrite {\n      std::fs::remove_file(&path).fs_context(\"failed to remove permission file\", path.clone())?;\n    } else {\n      crate::error::bail!(msg);\n    }\n  }\n\n  if let Some(parent) = path.parent() {\n    std::fs::create_dir_all(parent).fs_context(\n      \"failed to create permission directory\",\n      parent.to_path_buf(),\n    )?;\n  }\n\n  std::fs::write(\n    &path,\n    options\n      .format\n      .serialize(&PermissionFile {\n        default: None,\n        set: Vec::new(),\n        permission: vec![permission],\n      })\n      .context(\"failed to serialize permission\")?,\n  )\n  .fs_context(\"failed to write permission file\", path.clone())?;\n\n  log::info!(action = \"Created\"; \"permission at {}\", dunce::simplified(&path).display());\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/acl/permission/rm.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::Path;\n\nuse clap::Parser;\nuse tauri_utils::acl::{manifest::PermissionFile, PERMISSION_SCHEMA_FILE_NAME};\n\nuse crate::{\n  acl::FileFormat,\n  error::{Context, ErrorExt},\n  helpers::app_paths::resolve_tauri_dir,\n  Result,\n};\n\nfn rm_permission_files(identifier: &str, dir: &Path) -> Result<()> {\n  for entry in std::fs::read_dir(dir)\n    .fs_context(\"failed to read permissions directory\", dir.to_path_buf())?\n    .flatten()\n  {\n    let file_type = entry\n      .file_type()\n      .fs_context(\"failed to get permission file type\", entry.path())?;\n    let path = entry.path();\n    if file_type.is_dir() {\n      rm_permission_files(identifier, &path)?;\n    } else {\n      if path\n        .file_name()\n        .map(|name| name == PERMISSION_SCHEMA_FILE_NAME)\n        .unwrap_or_default()\n      {\n        continue;\n      }\n\n      let (mut permission_file, format): (PermissionFile, FileFormat) =\n        match path.extension().and_then(|o| o.to_str()) {\n          Some(\"toml\") => {\n            let content = std::fs::read_to_string(&path)\n              .fs_context(\"failed to read permission file\", path.clone())?;\n            (\n              toml::from_str(&content).context(\"failed to deserialize permission file\")?,\n              FileFormat::Toml,\n            )\n          }\n          Some(\"json\") => {\n            let content =\n              std::fs::read(&path).fs_context(\"failed to read permission file\", path.clone())?;\n            (\n              serde_json::from_slice(&content)\n                .context(\"failed to parse permission file as JSON\")?,\n              FileFormat::Json,\n            )\n          }\n          _ => {\n            continue;\n          }\n        };\n\n      let mut updated;\n\n      if identifier == \"default\" {\n        updated = permission_file.default.is_some();\n        permission_file.default = None;\n      } else {\n        let set_len = permission_file.set.len();\n        permission_file\n          .set\n          .retain(|s| !identifier_match(identifier, &s.identifier));\n        updated = permission_file.set.len() != set_len;\n\n        let permission_len = permission_file.permission.len();\n        permission_file\n          .permission\n          .retain(|s| !identifier_match(identifier, &s.identifier));\n        updated = updated || permission_file.permission.len() != permission_len;\n      }\n\n      // if the file is empty, let's remove it\n      if permission_file.default.is_none()\n        && permission_file.set.is_empty()\n        && permission_file.permission.is_empty()\n      {\n        std::fs::remove_file(&path).fs_context(\"failed to remove permission file\", path.clone())?;\n        log::info!(action = \"Removed\"; \"file {}\", dunce::simplified(&path).display());\n      } else if updated {\n        std::fs::write(\n          &path,\n          format\n            .serialize(&permission_file)\n            .context(\"failed to serialize permission\")?,\n        )\n        .fs_context(\"failed to write permission file\", path.clone())?;\n        log::info!(action = \"Removed\"; \"permission {identifier} from {}\", dunce::simplified(&path).display());\n      }\n    }\n  }\n\n  Ok(())\n}\n\nfn rm_permission_from_capabilities(identifier: &str, dir: &Path) -> Result<()> {\n  for entry in std::fs::read_dir(dir)\n    .fs_context(\"failed to read capabilities directory\", dir.to_path_buf())?\n    .flatten()\n  {\n    let file_type = entry\n      .file_type()\n      .fs_context(\"failed to get capability file type\", entry.path())?;\n    if file_type.is_file() {\n      let path = entry.path();\n      match path.extension().and_then(|o| o.to_str()) {\n        Some(\"toml\") => {\n          let content = std::fs::read_to_string(&path)\n            .fs_context(\"failed to read capability file\", path.clone())?;\n          if let Ok(mut value) = content.parse::<toml_edit::DocumentMut>() {\n            if let Some(permissions) = value.get_mut(\"permissions\").and_then(|p| p.as_array_mut()) {\n              let prev_len = permissions.len();\n              permissions.retain(|p| match p {\n                toml_edit::Value::String(s) => !identifier_match(identifier, s.value()),\n                toml_edit::Value::InlineTable(o) => {\n                  if let Some(toml_edit::Value::String(permission_name)) = o.get(\"identifier\") {\n                    return !identifier_match(identifier, permission_name.value());\n                  }\n\n                  true\n                }\n                _ => false,\n              });\n              if prev_len != permissions.len() {\n                std::fs::write(&path, value.to_string())\n                  .fs_context(\"failed to write capability file\", path.clone())?;\n                log::info!(action = \"Removed\"; \"permission from capability at {}\", dunce::simplified(&path).display());\n              }\n            }\n          }\n        }\n        Some(\"json\") => {\n          let content =\n            std::fs::read(&path).fs_context(\"failed to read capability file\", path.clone())?;\n          if let Ok(mut value) = serde_json::from_slice::<serde_json::Value>(&content) {\n            if let Some(permissions) = value.get_mut(\"permissions\").and_then(|p| p.as_array_mut()) {\n              let prev_len = permissions.len();\n              permissions.retain(|p| match p {\n                serde_json::Value::String(s) => !identifier_match(identifier, s),\n                serde_json::Value::Object(o) => {\n                  if let Some(serde_json::Value::String(permission_name)) = o.get(\"identifier\") {\n                    return !identifier_match(identifier, permission_name);\n                  }\n\n                  true\n                }\n                _ => false,\n              });\n              if prev_len != permissions.len() {\n                std::fs::write(\n                  &path,\n                  serde_json::to_vec_pretty(&value)\n                    .context(\"failed to serialize capability JSON\")?,\n                )\n                .fs_context(\"failed to write capability file\", path.clone())?;\n                log::info!(action = \"Removed\"; \"permission from capability at {}\", dunce::simplified(&path).display());\n              }\n            }\n          }\n        }\n        _ => {}\n      }\n    }\n  }\n\n  Ok(())\n}\n\nfn identifier_match(identifier: &str, permission: &str) -> bool {\n  match identifier.split_once(':') {\n    Some((plugin_name, \"*\")) => permission.contains(plugin_name),\n    _ => permission == identifier,\n  }\n}\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Remove a permission file, and its reference from any capability\")]\npub struct Options {\n  /// Permission to remove.\n  ///\n  /// To remove all permissions for a given plugin, provide `<plugin-name>:*`\n  pub identifier: String,\n}\n\npub fn command(options: Options) -> Result<()> {\n  let permissions_dir = std::env::current_dir()\n    .context(\"failed to resolve current directory\")?\n    .join(\"permissions\");\n  if permissions_dir.exists() {\n    rm_permission_files(&options.identifier, &permissions_dir)?;\n  }\n\n  if let Some(tauri_dir) = resolve_tauri_dir() {\n    let capabilities_dir = tauri_dir.join(\"capabilities\");\n    if capabilities_dir.exists() {\n      rm_permission_from_capabilities(&options.identifier, &capabilities_dir)?;\n    }\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/add.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse clap::Parser;\nuse colored::Colorize;\nuse regex::Regex;\n\nuse crate::{\n  acl,\n  error::ErrorExt,\n  helpers::{\n    app_paths::{resolve_frontend_dir, Dirs},\n    cargo,\n    npm::PackageManager,\n  },\n  Result,\n};\n\nuse std::process::Command;\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Add a tauri plugin to the project\")]\npub struct Options {\n  /// The plugin to add.\n  pub plugin: String,\n  /// Git tag to use.\n  #[clap(short, long)]\n  pub tag: Option<String>,\n  /// Git rev to use.\n  #[clap(short, long)]\n  pub rev: Option<String>,\n  /// Git branch to use.\n  #[clap(short, long)]\n  pub branch: Option<String>,\n  /// Don't format code with rustfmt\n  #[clap(long)]\n  pub no_fmt: bool,\n}\n\npub fn command(options: Options) -> Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n  run(options, &dirs)\n}\n\npub fn run(options: Options, dirs: &Dirs) -> Result<()> {\n  let (plugin, version) = options\n    .plugin\n    .split_once('@')\n    .map(|(p, v)| (p, Some(v)))\n    .unwrap_or((&options.plugin, None));\n\n  let mut plugins = crate::helpers::plugins::known_plugins();\n  let (metadata, is_known) = plugins\n    .remove(plugin)\n    .map(|metadata| (metadata, true))\n    .unwrap_or_default();\n\n  let plugin_snake_case = plugin.replace('-', \"_\");\n  let crate_name = format!(\"tauri-plugin-{plugin}\");\n  let npm_name = if is_known {\n    format!(\"@tauri-apps/plugin-{plugin}\")\n  } else {\n    format!(\"tauri-plugin-{plugin}-api\")\n  };\n\n  if !is_known && (options.tag.is_some() || options.rev.is_some() || options.branch.is_some()) {\n    crate::error::bail!(\n      \"Git options --tag, --rev and --branch can only be used with official Tauri plugins\"\n    );\n  }\n\n  let frontend_dir = resolve_frontend_dir();\n\n  let target_str = metadata\n    .desktop_only\n    .then_some(r#\"cfg(not(any(target_os = \"android\", target_os = \"ios\")))\"#)\n    .or_else(|| {\n      metadata\n        .mobile_only\n        .then_some(r#\"cfg(any(target_os = \"android\", target_os = \"ios\"))\"#)\n    });\n\n  let cargo_version_req = version.or(metadata.version_req.as_deref());\n\n  cargo::install_one(cargo::CargoInstallOptions {\n    name: &crate_name,\n    version: cargo_version_req,\n    branch: options.branch.as_deref(),\n    rev: options.rev.as_deref(),\n    tag: options.tag.as_deref(),\n    cwd: Some(dirs.tauri),\n    target: target_str,\n  })?;\n\n  if !metadata.rust_only {\n    if let Some(manager) = frontend_dir.map(PackageManager::from_project) {\n      let npm_version_req = version\n        .map(ToString::to_string)\n        .or(metadata.version_req.as_ref().map(|v| match manager {\n          PackageManager::Npm => format!(\">={v}\"),\n          _ => format!(\"~{v}\"),\n        }));\n\n      let npm_spec = match (npm_version_req, options.tag, options.rev, options.branch) {\n        (Some(version_req), _, _, _) => format!(\"{npm_name}@{version_req}\"),\n        (None, Some(tag), None, None) => {\n          format!(\"tauri-apps/tauri-plugin-{plugin}#{tag}\")\n        }\n        (None, None, Some(rev), None) => {\n          format!(\"tauri-apps/tauri-plugin-{plugin}#{rev}\")\n        }\n        (None, None, None, Some(branch)) => {\n          format!(\"tauri-apps/tauri-plugin-{plugin}#{branch}\")\n        }\n        (None, None, None, None) => npm_name,\n        _ => crate::error::bail!(\"Only one of --tag, --rev and --branch can be specified\"),\n      };\n      manager.install(&[npm_spec], dirs.tauri)?;\n    }\n\n    let _ = acl::permission::add::command(acl::permission::add::Options {\n      identifier: format!(\"{plugin}:default\"),\n      capability: None,\n    });\n  }\n\n  // add plugin init code to main.rs or lib.rs\n  let plugin_init_fn = if plugin == \"stronghold\" {\n    \"Builder::new(|pass| todo!()).build()\"\n  } else if plugin == \"localhost\" {\n    \"Builder::new(todo!()).build()\"\n  } else if plugin == \"single-instance\" {\n    \"init(|app, args, cwd| {})\"\n  } else if plugin == \"log\" {\n    \"Builder::new().level(tauri_plugin_log::log::LevelFilter::Info).build()\"\n  } else if metadata.builder {\n    \"Builder::new().build()\"\n  } else {\n    \"init()\"\n  };\n  let plugin_init = format!(\".plugin(tauri_plugin_{plugin_snake_case}::{plugin_init_fn})\");\n\n  let re = Regex::new(r\"(tauri\\s*::\\s*Builder\\s*::\\s*default\\(\\))(\\s*)\").unwrap();\n  for file in [\n    dirs.tauri.join(\"src/main.rs\"),\n    dirs.tauri.join(\"src/lib.rs\"),\n  ] {\n    let contents =\n      std::fs::read_to_string(&file).fs_context(\"failed to read Rust entry point\", file.clone())?;\n\n    if contents.contains(&plugin_init) {\n      log::info!(\n        \"Plugin initialization code already found on {}\",\n        file.display()\n      );\n      return Ok(());\n    }\n\n    if re.is_match(&contents) {\n      let out = re.replace(&contents, format!(\"$1$2{plugin_init}$2\"));\n\n      log::info!(\"Adding plugin to {}\", file.display());\n      std::fs::write(&file, out.as_bytes()).fs_context(\"failed to write plugin init code\", file)?;\n\n      if !options.no_fmt {\n        // reformat code with rustfmt\n        log::info!(\"Running `cargo fmt`...\");\n        let _ = Command::new(\"cargo\")\n          .arg(\"fmt\")\n          .current_dir(dirs.tauri)\n          .status();\n      }\n\n      return Ok(());\n    }\n  }\n\n  let builder_code = if metadata.builder {\n    format!(r#\"+    .plugin(tauri_plugin_{plugin_snake_case}::Builder::new().build())\"#,)\n  } else {\n    format!(r#\"+    .plugin(tauri_plugin_{plugin_snake_case}::init())\"#)\n  };\n\n  let rust_code = format!(\n    r#\" {}\n{}\n     {}\"#,\n    \"tauri::Builder::default()\".dimmed(),\n    builder_code.normal().green(),\n    r#\".invoke_handler(tauri::generate_handler![])\n     .run(tauri::generate_context!())\n     .expect(\"error while running tauri application\");\"#\n      .dimmed(),\n  );\n\n  log::warn!(\n    \"Couldn't find `{}` in `{}` or `{}`, you must enable the plugin in your Rust code manually:\\n\\n{}\",\n    \"tauri::Builder\".cyan(),\n    \"main.rs\".cyan(),\n    \"lib.rs\".cyan(),\n    rust_code\n  );\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  bundle::BundleFormat,\n  error::{Context, ErrorExt},\n  helpers::{\n    self,\n    app_paths::Dirs,\n    config::{get_config, ConfigMetadata, FrontendDist},\n  },\n  info::plugins::check_mismatched_packages,\n  interface::{rust::get_cargo_target_dir, AppInterface},\n  ConfigValue, Result,\n};\nuse clap::{ArgAction, Parser};\nuse std::env::set_current_dir;\nuse tauri_utils::config::RunnerConfig;\nuse tauri_utils::platform::Target;\n\n#[derive(Debug, Clone, Parser)]\n#[clap(\n  about = \"Build your app in release mode and generate bundles and installers\",\n  long_about = \"Build your app in release mode and generate bundles and installers. It makes use of the `build.frontendDist` property from your `tauri.conf.json` file. It also runs your `build.beforeBuildCommand` which usually builds your frontend into `build.frontendDist`. This will also run `build.beforeBundleCommand` before generating the bundles and installers of your app.\"\n)]\npub struct Options {\n  /// Binary to use to build the application, defaults to `cargo`\n  #[clap(short, long)]\n  pub runner: Option<RunnerConfig>,\n  /// Builds with the debug flag\n  #[clap(short, long)]\n  pub debug: bool,\n  /// Target triple to build against.\n  ///\n  /// It must be one of the values outputted by `$rustc --print target-list` or `universal-apple-darwin` for an universal macOS application.\n  ///\n  /// Note that compiling an universal macOS application requires both `aarch64-apple-darwin` and `x86_64-apple-darwin` targets to be installed.\n  #[clap(short, long)]\n  pub target: Option<String>,\n  /// Space or comma separated list of features to activate\n  #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]\n  pub features: Vec<String>,\n  /// Space or comma separated list of bundles to package.\n  #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]\n  pub bundles: Option<Vec<BundleFormat>>,\n  /// Skip the bundling step even if `bundle > active` is `true` in tauri config.\n  #[clap(long)]\n  pub no_bundle: bool,\n  /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file\n  ///\n  /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.\n  ///\n  /// Note that a platform-specific file is looked up and merged with the default file by default\n  /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)\n  /// but you can use this for more specific use cases such as different build flavors.\n  #[clap(short, long)]\n  pub config: Vec<ConfigValue>,\n  /// Command line arguments passed to the runner. Use `--` to explicitly mark the start of the arguments.\n  pub args: Vec<String>,\n  /// Skip prompting for values\n  #[clap(long, env = \"CI\")]\n  pub ci: bool,\n  /// Whether to wait for notarization to finish and `staple` the ticket onto the app.\n  ///\n  /// Gatekeeper will look for stapled tickets to tell whether your app was notarized without\n  /// reaching out to Apple's servers which is helpful in offline environments.\n  ///\n  /// Enabling this option will also result in `tauri build` not waiting for notarization to finish\n  /// which is helpful for the very first time your app is notarized as this can take multiple hours.\n  /// On subsequent runs, it's recommended to disable this setting again.\n  #[clap(long)]\n  pub skip_stapling: bool,\n  /// Do not error out if a version mismatch is detected on a Tauri package.\n  ///\n  /// Only use this when you are sure the mismatch is incorrectly detected as version mismatched Tauri packages can lead to unknown behavior.\n  #[clap(long)]\n  pub ignore_version_mismatches: bool,\n  /// Skip code signing when bundling the app\n  #[clap(long)]\n  pub no_sign: bool,\n}\n\npub fn command(mut options: Options, verbosity: u8) -> Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n\n  if options.no_sign {\n    log::warn!(\"--no-sign flag detected: Signing will be skipped.\");\n  }\n\n  let ci = options.ci;\n\n  let target = options\n    .target\n    .as_deref()\n    .map(Target::from_triple)\n    .unwrap_or_else(Target::current);\n\n  let config = get_config(\n    target,\n    &options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),\n    dirs.tauri,\n  )?;\n\n  let mut interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?;\n\n  setup(&interface, &mut options, &config, &dirs, false)?;\n\n  if let Some(minimum_system_version) = &config.bundle.macos.minimum_system_version {\n    std::env::set_var(\"MACOSX_DEPLOYMENT_TARGET\", minimum_system_version);\n  }\n\n  let app_settings = interface.app_settings();\n  let interface_options = options.clone().into();\n\n  let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?;\n\n  let bin_path = interface.build(interface_options, &dirs)?;\n\n  log::info!(action = \"Built\"; \"application at: {}\", tauri_utils::display_path(bin_path));\n\n  let app_settings = interface.app_settings();\n\n  if !options.no_bundle && (config.bundle.active || options.bundles.is_some()) {\n    crate::bundle::bundle(\n      &options.into(),\n      verbosity,\n      ci,\n      &interface,\n      &*app_settings,\n      &config,\n      &dirs,\n      &out_dir,\n    )?;\n  }\n\n  Ok(())\n}\n\npub fn setup(\n  interface: &AppInterface,\n  options: &mut Options,\n  config: &ConfigMetadata,\n  dirs: &Dirs,\n  mobile: bool,\n) -> Result<()> {\n  // TODO: Maybe optimize this to run in parallel in the future\n  // see https://github.com/tauri-apps/tauri/pull/13993#discussion_r2280697117\n  log::info!(\"Looking up installed tauri packages to check mismatched versions...\");\n  if let Err(error) = check_mismatched_packages(dirs.frontend, dirs.tauri) {\n    if options.ignore_version_mismatches {\n      log::error!(\"{error}\");\n    } else {\n      return Err(error);\n    }\n  }\n\n  set_current_dir(dirs.tauri).context(\"failed to set current directory\")?;\n\n  let bundle_identifier_source = config\n    .find_bundle_identifier_overwriter()\n    .unwrap_or_else(|| \"tauri.conf.json\".into());\n\n  if config.identifier == \"com.tauri.dev\" {\n    crate::error::bail!(\n      \"You must change the bundle identifier in `{bundle_identifier_source} identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.\",\n    );\n  }\n\n  if config\n    .identifier\n    .chars()\n    .any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '.'))\n  {\n    crate::error::bail!(\n      \"The bundle identifier \\\"{}\\\" set in `{bundle_identifier_source:?} identifier`. The bundle identifier string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-), and periods (.).\",\n      config.identifier,\n    );\n  }\n\n  if config.identifier.ends_with(\".app\") {\n    log::warn!(\n      \"The bundle identifier \\\"{}\\\" set in `{bundle_identifier_source:?} identifier` ends with `.app`. This is not recommended because it conflicts with the application bundle extension on macOS.\",\n      config.identifier,\n    );\n  }\n\n  if let Some(before_build) = config.build.before_build_command.clone() {\n    helpers::run_hook(\n      \"beforeBuildCommand\",\n      before_build,\n      interface,\n      options.debug,\n      dirs.frontend,\n    )?;\n  }\n\n  if let Some(FrontendDist::Directory(web_asset_path)) = &config.build.frontend_dist {\n    if !web_asset_path.exists() {\n      let absolute_path = web_asset_path\n        .parent()\n        .and_then(|p| p.canonicalize().ok())\n        .map(|p| p.join(web_asset_path.file_name().unwrap()))\n        .unwrap_or_else(|| std::env::current_dir().unwrap().join(web_asset_path));\n      crate::error::bail!(\n        \"Unable to find your web assets, did you forget to build your web app? Your frontendDist is set to \\\"{}\\\" (which is `{}`).\",\n        web_asset_path.display(), absolute_path.display(),\n      );\n    }\n    if web_asset_path\n      .canonicalize()\n      .fs_context(\"failed to canonicalize path\", web_asset_path.to_path_buf())?\n      .file_name()\n      == Some(std::ffi::OsStr::new(\"src-tauri\"))\n    {\n      crate::error::bail!(\n          \"The configured frontendDist is the `src-tauri` folder. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > frontendDist`.\",\n        );\n    }\n\n    // Issue #13287 - Allow the use of target dir inside frontendDist/distDir\n    // https://github.com/tauri-apps/tauri/issues/13287\n    let target_path = get_cargo_target_dir(&options.args, dirs.tauri)?;\n    let mut out_folders = Vec::new();\n    if let Ok(web_asset_canonical) = dunce::canonicalize(web_asset_path) {\n      if let Ok(relative_path) = target_path.strip_prefix(&web_asset_canonical) {\n        let relative_str = relative_path.to_string_lossy();\n        if !relative_str.is_empty() {\n          out_folders.push(relative_str.to_string());\n        }\n      }\n\n      for folder in &[\"node_modules\", \"src-tauri\"] {\n        let sub_path = web_asset_canonical.join(folder);\n        if sub_path.is_dir() {\n          out_folders.push(folder.to_string());\n        }\n      }\n    }\n\n    if !out_folders.is_empty() {\n      crate::error::bail!(\n        \"The configured frontendDist includes the `{:?}` {}. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > frontendDist`.\",\n        out_folders,\n        if out_folders.len() == 1 { \"folder\" } else { \"folders\" }\n      );\n    }\n  }\n\n  if options.runner.is_none() {\n    options.runner = config.build.runner.clone();\n  }\n\n  options\n    .features\n    .extend_from_slice(config.build.features.as_deref().unwrap_or_default());\n  interface.build_options(&mut options.args, &mut options.features, mobile);\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/bundle.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  path::{Path, PathBuf},\n  str::FromStr,\n  sync::OnceLock,\n};\n\nuse clap::{builder::PossibleValue, ArgAction, Parser, ValueEnum};\nuse tauri_bundler::PackageType;\nuse tauri_utils::platform::Target;\n\nuse crate::{\n  error::{Context, ErrorExt},\n  helpers::{\n    self,\n    app_paths::Dirs,\n    config::{get_config, ConfigMetadata},\n    updater_signature,\n  },\n  interface::{AppInterface, AppSettings},\n  ConfigValue,\n};\n\n#[derive(Debug, Clone)]\npub struct BundleFormat(PackageType);\n\nimpl FromStr for BundleFormat {\n  type Err = crate::Error;\n  fn from_str(s: &str) -> crate::Result<Self> {\n    PackageType::from_short_name(s)\n      .map(Self)\n      .with_context(|| format!(\"unknown bundle format {s}\"))\n  }\n}\n\nimpl ValueEnum for BundleFormat {\n  fn value_variants<'a>() -> &'a [Self] {\n    static VARIANTS: OnceLock<Vec<BundleFormat>> = OnceLock::new();\n    VARIANTS.get_or_init(|| PackageType::all().iter().map(|t| Self(*t)).collect())\n  }\n\n  fn to_possible_value(&self) -> Option<PossibleValue> {\n    let hide = (!cfg!(windows) && self.0 == PackageType::Nsis) || self.0 == PackageType::Updater;\n    Some(PossibleValue::new(self.0.short_name()).hide(hide))\n  }\n}\n\n#[derive(Debug, Parser, Clone)]\n#[clap(\n  about = \"Generate bundles and installers for your app (already built by `tauri build`)\",\n  long_about = \"Generate bundles and installers for your app (already built by `tauri build`). This run `build.beforeBundleCommand` before generating the bundles and installers of your app.\"\n)]\npub struct Options {\n  /// Builds with the debug flag\n  #[clap(short, long)]\n  pub debug: bool,\n  /// Space or comma separated list of bundles to package.\n  #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]\n  pub bundles: Option<Vec<BundleFormat>>,\n  /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file\n  ///\n  /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.\n  ///\n  /// Note that a platform-specific file is looked up and merged with the default file by default\n  /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)\n  /// but you can use this for more specific use cases such as different build flavors.\n  #[clap(short, long)]\n  pub config: Vec<ConfigValue>,\n  /// Space or comma separated list of features, should be the same features passed to `tauri build` if any.\n  #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]\n  pub features: Vec<String>,\n  /// Target triple to build against.\n  ///\n  /// It must be one of the values outputted by `$rustc --print target-list` or `universal-apple-darwin` for an universal macOS application.\n  ///\n  /// Note that compiling an universal macOS application requires both `aarch64-apple-darwin` and `x86_64-apple-darwin` targets to be installed.\n  #[clap(short, long)]\n  pub target: Option<String>,\n  /// Skip prompting for values\n  #[clap(long, env = \"CI\")]\n  pub ci: bool,\n  /// Whether to wait for notarization to finish and `staple` the ticket onto the app.\n  ///\n  /// Gatekeeper will look for stapled tickets to tell whether your app was notarized without\n  /// reaching out to Apple's servers which is helpful in offline environments.\n  ///\n  /// Enabling this option will also result in `tauri build` not waiting for notarization to finish\n  /// which is helpful for the very first time your app is notarized as this can take multiple hours.\n  /// On subsequent runs, it's recommended to disable this setting again.\n  #[clap(long)]\n  pub skip_stapling: bool,\n\n  /// Skip code signing during the build or bundling process.\n  ///\n  /// Useful for local development and CI environments\n  /// where signing certificates or environment variables\n  /// are not available or not needed.\n  #[clap(long)]\n  pub no_sign: bool,\n}\n\nimpl From<crate::build::Options> for Options {\n  fn from(value: crate::build::Options) -> Self {\n    Self {\n      bundles: value.bundles,\n      target: value.target,\n      features: value.features,\n      debug: value.debug,\n      ci: value.ci,\n      config: value.config,\n      skip_stapling: value.skip_stapling,\n      no_sign: value.no_sign,\n    }\n  }\n}\n\npub fn command(options: Options, verbosity: u8) -> crate::Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n\n  let ci = options.ci;\n\n  let target = options\n    .target\n    .as_deref()\n    .map(Target::from_triple)\n    .unwrap_or_else(Target::current);\n\n  let config = get_config(\n    target,\n    &options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),\n    dirs.tauri,\n  )?;\n\n  let interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?;\n\n  std::env::set_current_dir(dirs.tauri).context(\"failed to set current directory\")?;\n\n  if let Some(minimum_system_version) = &config.bundle.macos.minimum_system_version {\n    std::env::set_var(\"MACOSX_DEPLOYMENT_TARGET\", minimum_system_version);\n  }\n\n  let app_settings = interface.app_settings();\n  let interface_options = options.clone().into();\n\n  let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?;\n\n  bundle(\n    &options,\n    verbosity,\n    ci,\n    &interface,\n    &*app_settings,\n    &config,\n    &dirs,\n    &out_dir,\n  )\n}\n\n#[allow(clippy::too_many_arguments)]\npub fn bundle<A: AppSettings>(\n  options: &Options,\n  verbosity: u8,\n  ci: bool,\n  interface: &AppInterface,\n  app_settings: &A,\n  config: &ConfigMetadata,\n  dirs: &Dirs,\n  out_dir: &Path,\n) -> crate::Result<()> {\n  let package_types: Vec<PackageType> = if let Some(bundles) = &options.bundles {\n    bundles.iter().map(|bundle| bundle.0).collect::<Vec<_>>()\n  } else {\n    config\n      .bundle\n      .targets\n      .to_vec()\n      .into_iter()\n      .map(Into::into)\n      .collect()\n  };\n\n  if package_types.is_empty() {\n    return Ok(());\n  }\n\n  // if we have a package to bundle, let's run the `before_bundle_command`.\n  if !package_types.is_empty() {\n    if let Some(before_bundle) = config.build.before_bundle_command.clone() {\n      helpers::run_hook(\n        \"beforeBundleCommand\",\n        before_bundle,\n        interface,\n        options.debug,\n        dirs.frontend,\n      )?;\n    }\n  }\n\n  let mut settings = app_settings\n    .get_bundler_settings(\n      options.clone().into(),\n      config,\n      out_dir,\n      package_types,\n      dirs.tauri,\n    )\n    .with_context(|| \"failed to build bundler settings\")?;\n  settings.set_no_sign(options.no_sign);\n\n  settings.set_log_level(match verbosity {\n    0 => log::Level::Error,\n    1 => log::Level::Info,\n    _ => log::Level::Trace,\n  });\n\n  let bundles = tauri_bundler::bundle_project(&settings).map_err(Box::new)?;\n\n  sign_updaters(settings, bundles, ci)?;\n\n  Ok(())\n}\n\nfn sign_updaters(\n  settings: tauri_bundler::Settings,\n  bundles: Vec<tauri_bundler::Bundle>,\n  ci: bool,\n) -> crate::Result<()> {\n  let Some(update_settings) = settings.updater() else {\n    // Updater not enabled\n    return Ok(());\n  };\n\n  let update_enabled_bundles: Vec<&tauri_bundler::Bundle> = bundles\n    .iter()\n    .filter(|bundle| {\n      matches!(\n        bundle.package_type,\n        PackageType::Updater\n          | PackageType::Nsis\n          | PackageType::WindowsMsi\n          | PackageType::AppImage\n          | PackageType::Deb\n          | PackageType::Rpm\n      )\n    })\n    .collect();\n\n  if update_enabled_bundles.is_empty() {\n    return Ok(());\n  }\n\n  if settings.no_sign() {\n    log::warn!(\"Updater signing is skipped due to --no-sign flag.\");\n    return Ok(());\n  }\n\n  // get the public key\n  let pubkey = &update_settings.pubkey;\n  // check if pubkey points to a file...\n  let maybe_path = Path::new(pubkey);\n  let pubkey = if maybe_path.exists() {\n    std::fs::read_to_string(maybe_path)\n      .fs_context(\"failed to read pubkey from file\", maybe_path.to_path_buf())?\n  } else {\n    pubkey.to_string()\n  };\n\n  // if no password provided we use an empty string\n  let password = std::env::var(\"TAURI_SIGNING_PRIVATE_KEY_PASSWORD\")\n    .ok()\n    .or_else(|| if ci { Some(\"\".into()) } else { None });\n\n  // get the private key\n  let private_key = std::env::var(\"TAURI_SIGNING_PRIVATE_KEY\")\n    .ok()\n    .context(\"A public key has been found, but no private key. Make sure to set `TAURI_SIGNING_PRIVATE_KEY` environment variable.\")?;\n  // check if private_key points to a file...\n  let maybe_path = Path::new(&private_key);\n  let private_key = if maybe_path.exists() {\n    std::fs::read_to_string(maybe_path).fs_context(\n      \"failed to read private key from file\",\n      maybe_path.to_path_buf(),\n    )?\n  } else {\n    private_key\n  };\n  if password.is_none() {\n    log::info!(\"Decrypting updater signing key, expect a prompt for password\")\n  }\n  let secret_key =\n    updater_signature::secret_key(private_key, password).context(\"failed to decode secret key\")?;\n  let public_key = updater_signature::pub_key(pubkey).context(\"failed to decode pubkey\")?;\n\n  let mut signed_paths = Vec::new();\n  for bundle in update_enabled_bundles {\n    // we expect to have only one path in the vec but we iter if we add\n    // another type of updater package who require multiple file signature\n    for path in &bundle.bundle_paths {\n      // sign our path from environment variables\n      let (signature_path, signature) = updater_signature::sign_file(&secret_key, path)?;\n      if signature.keynum() != public_key.keynum() {\n        log::warn!(\"The updater secret key from `TAURI_SIGNING_PRIVATE_KEY` does not match the public key from `plugins > updater > pubkey`. If you are not rotating keys, this means your configuration is wrong and won't be accepted at runtime when performing update.\");\n      }\n      signed_paths.push(signature_path);\n    }\n  }\n\n  print_signed_updater_archive(&signed_paths)?;\n\n  Ok(())\n}\n\nfn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> {\n  use std::fmt::Write;\n  if !output_paths.is_empty() {\n    let finished_bundles = output_paths.len();\n    let pluralised = if finished_bundles == 1 {\n      \"updater signature\"\n    } else {\n      \"updater signatures\"\n    };\n    let mut printable_paths = String::new();\n    for path in output_paths {\n      let _ = writeln!(\n        printable_paths,\n        \"        {}\",\n        tauri_utils::display_path(path)\n      );\n    }\n    log::info!( action = \"Finished\"; \"{finished_bundles} {pluralised} at:\\n{printable_paths}\");\n  }\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/completions.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{error::ErrorExt, Result};\nuse clap::{Command, Parser};\nuse clap_complete::{generate, Shell};\n\nuse std::{fs::write, path::PathBuf};\n\nconst PKG_MANAGERS: &[&str] = &[\"cargo\", \"pnpm\", \"npm\", \"yarn\", \"bun\", \"deno\"];\n\n#[derive(Debug, Clone, Parser)]\n#[clap(about = \"Generate Tauri CLI shell completions for Bash, Zsh, PowerShell or Fish\")]\npub struct Options {\n  /// Shell to generate a completion script for.\n  #[clap(short, long, verbatim_doc_comment)]\n  shell: Shell,\n  /// Output file for the shell completions. By default the completions are printed to stdout.\n  #[clap(short, long)]\n  output: Option<PathBuf>,\n}\n\nfn completions_for(shell: Shell, manager: &'static str, cmd: Command) -> Vec<u8> {\n  let tauri = cmd.name(\"tauri\");\n  let mut command = if manager == \"npm\" || manager == \"bun\" {\n    Command::new(manager)\n      .bin_name(manager)\n      .subcommand(Command::new(\"run\").subcommand(tauri))\n  } else if manager == \"deno\" {\n    Command::new(manager)\n      .bin_name(manager)\n      .subcommand(Command::new(\"task\").subcommand(tauri))\n  } else {\n    Command::new(manager).bin_name(manager).subcommand(tauri)\n  };\n\n  let mut buf = Vec::new();\n  generate(shell, &mut command, manager, &mut buf);\n  buf\n}\n\nfn get_completions(shell: Shell, cmd: Command) -> Result<String> {\n  let completions = if shell == Shell::Bash {\n    let mut completions =\n      String::from_utf8_lossy(&completions_for(shell, \"cargo\", cmd)).into_owned();\n    for &manager in PKG_MANAGERS {\n      completions.push_str(&format!(\n        \"complete -F _cargo -o bashdefault -o default {} tauri\\n\",\n        if manager == \"npm\" {\n          \"npm run\"\n        } else if manager == \"bun\" {\n          \"bun run\"\n        } else if manager == \"deno\" {\n          \"deno task\"\n        } else {\n          manager\n        }\n      ));\n    }\n    completions\n  } else {\n    let mut buffer = String::new();\n\n    for (i, manager) in PKG_MANAGERS.iter().enumerate() {\n      let buf = String::from_utf8_lossy(&completions_for(shell, manager, cmd.clone())).into_owned();\n\n      let completions = match shell {\n        Shell::PowerShell => {\n          if i != 0 {\n            // namespaces have already been imported\n            buf\n              .replace(\"using namespace System.Management.Automation.Language\", \"\")\n              .replace(\"using namespace System.Management.Automation\", \"\")\n          } else {\n            buf\n          }\n        }\n        _ => buf,\n      };\n\n      buffer.push_str(&completions);\n      buffer.push('\\n');\n    }\n\n    buffer\n  };\n\n  Ok(completions)\n}\n\npub fn command(options: Options, cmd: Command) -> Result<()> {\n  log::info!(\"Generating completion file for {}...\", options.shell);\n\n  let completions = get_completions(options.shell, cmd)?;\n  if let Some(output) = options.output {\n    write(&output, completions).fs_context(\"failed to write to completions\", output)?;\n  } else {\n    print!(\"{completions}\");\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/dev/auto-reload.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// taken from https://github.com/thedodd/trunk/blob/5c799dc35f1f1d8f8d3d30c8723cbb761a9b6a08/src/autoreload.js\n\n;(function () {\n  const reload_url = '{{reload_url}}'\n  const url = reload_url ? reload_url : window.location.href\n  const poll_interval = 5000\n  const reload_upon_connect = () => {\n    window.setTimeout(() => {\n      // when we successfully reconnect, we'll force a\n      // reload (since we presumably lost connection to\n      // tauri-cli due to it being killed)\n      const ws = new WebSocket(url)\n      ws.onopen = () => window.location.reload()\n      ws.onclose = reload_upon_connect\n    }, poll_interval)\n  }\n\n  const ws = new WebSocket(url)\n  ws.onmessage = (ev) => {\n    const msg = JSON.parse(ev.data)\n    if (msg.reload) {\n      window.location.reload()\n    }\n  }\n  ws.onclose = reload_upon_connect\n})()\n"
  },
  {
    "path": "crates/tauri-cli/src/dev/builtin_dev_server.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse axum::{\n  extract::{ws, State, WebSocketUpgrade},\n  http::{header, StatusCode, Uri},\n  response::{IntoResponse, Response},\n};\nuse html5ever::{namespace_url, ns, LocalName, QualName};\nuse kuchiki::{traits::TendrilSink, NodeRef};\nuse std::{\n  net::{IpAddr, SocketAddr},\n  path::{Path, PathBuf},\n  thread,\n  time::Duration,\n};\nuse tauri_utils::mime_type::MimeType;\nuse tokio::sync::broadcast::{channel, Sender};\n\nuse crate::error::ErrorExt;\n\nconst RELOAD_SCRIPT: &str = include_str!(\"./auto-reload.js\");\n\n#[derive(Clone)]\nstruct ServerState {\n  dir: PathBuf,\n  address: SocketAddr,\n  tx: Sender<()>,\n}\n\npub fn start<P: AsRef<Path>>(dir: P, ip: IpAddr, port: Option<u16>) -> crate::Result<SocketAddr> {\n  let dir = dir.as_ref();\n  let dir =\n    dunce::canonicalize(dir).fs_context(\"failed to canonicalize path\", dir.to_path_buf())?;\n\n  // bind port and tcp listener\n  let auto_port = port.is_none();\n  let mut port = port.unwrap_or(1430);\n  let (tcp_listener, address) = loop {\n    let address = SocketAddr::new(ip, port);\n    if let Ok(tcp) = std::net::TcpListener::bind(address) {\n      tcp.set_nonblocking(true).unwrap();\n      break (tcp, address);\n    }\n\n    if !auto_port {\n      crate::error::bail!(\"Couldn't bind to {port} on {ip}\");\n    }\n\n    port += 1;\n  };\n\n  let (tx, _) = channel(1);\n\n  // watch dir for changes\n  let tx_c = tx.clone();\n  watch(dir.clone(), move || {\n    let _ = tx_c.send(());\n  });\n\n  let state = ServerState { dir, tx, address };\n\n  // start router thread\n  std::thread::spawn(move || {\n    tokio::runtime::Builder::new_current_thread()\n      .enable_io()\n      .build()\n      .expect(\"failed to start tokio runtime for builtin dev server\")\n      .block_on(async move {\n        let router = axum::Router::new()\n          .fallback(handler)\n          .route(\"/__tauri_cli\", axum::routing::get(ws_handler))\n          .with_state(state);\n\n        axum::serve(tokio::net::TcpListener::from_std(tcp_listener)?, router).await\n      })\n      .expect(\"builtin server errored\");\n  });\n\n  Ok(address)\n}\n\nasync fn handler(uri: Uri, state: State<ServerState>) -> impl IntoResponse {\n  // Frontend files should not contain query parameters. This seems to be how Vite handles it.\n  let uri = uri.path();\n\n  let uri = if uri == \"/\" {\n    uri\n  } else {\n    uri.strip_prefix('/').unwrap_or(uri)\n  };\n\n  let bytes = fs_read_scoped(state.dir.join(uri), &state.dir)\n    .or_else(|_| fs_read_scoped(state.dir.join(format!(\"{}.html\", &uri)), &state.dir))\n    .or_else(|_| fs_read_scoped(state.dir.join(format!(\"{}/index.html\", &uri)), &state.dir))\n    .or_else(|_| std::fs::read(state.dir.join(\"index.html\")));\n\n  match bytes {\n    Ok(mut bytes) => {\n      let mime_type = MimeType::parse_with_fallback(&bytes, uri, MimeType::OctetStream);\n      if mime_type == MimeType::Html.to_string() {\n        bytes = inject_address(bytes, &state.address);\n      }\n      (StatusCode::OK, [(header::CONTENT_TYPE, mime_type)], bytes)\n    }\n    Err(_) => (\n      StatusCode::NOT_FOUND,\n      [(header::CONTENT_TYPE, \"text/plain\".into())],\n      vec![],\n    ),\n  }\n}\n\nasync fn ws_handler(ws: WebSocketUpgrade, state: State<ServerState>) -> Response {\n  ws.on_upgrade(move |mut ws| async move {\n    let mut rx = state.tx.subscribe();\n    while tokio::select! {\n        _ = ws.recv() => return,\n        fs_reload_event = rx.recv() => fs_reload_event.is_ok(),\n    } {\n      let msg = ws::Message::Text(r#\"{\"reload\": true}\"#.into());\n      if ws.send(msg).await.is_err() {\n        break;\n      }\n    }\n  })\n}\n\nfn inject_address(html_bytes: Vec<u8>, address: &SocketAddr) -> Vec<u8> {\n  fn with_html_head<F: FnOnce(&NodeRef)>(document: &mut NodeRef, f: F) {\n    if let Ok(ref node) = document.select_first(\"head\") {\n      f(node.as_node())\n    } else {\n      let node = NodeRef::new_element(\n        QualName::new(None, ns!(html), LocalName::from(\"head\")),\n        None,\n      );\n      f(&node);\n      document.prepend(node)\n    }\n  }\n\n  let mut document = kuchiki::parse_html()\n    .one(String::from_utf8_lossy(&html_bytes).into_owned())\n    .document_node;\n  with_html_head(&mut document, |head| {\n    let script = RELOAD_SCRIPT.replace(\"{{reload_url}}\", &format!(\"ws://{address}/__tauri_cli\"));\n    let script_el = NodeRef::new_element(QualName::new(None, ns!(html), \"script\".into()), None);\n    script_el.append(NodeRef::new_text(script));\n    head.prepend(script_el);\n  });\n\n  tauri_utils::html::serialize_node(&document)\n}\n\nfn fs_read_scoped(path: PathBuf, scope: &Path) -> crate::Result<Vec<u8>> {\n  let path = dunce::canonicalize(&path).fs_context(\"failed to canonicalize path\", path)?;\n  if path.starts_with(scope) {\n    std::fs::read(&path).fs_context(\"failed to read file\", &path)\n  } else {\n    crate::error::bail!(\"forbidden path\")\n  }\n}\n\nfn watch<F: Fn() + Send + 'static>(dir: PathBuf, handler: F) {\n  thread::spawn(move || {\n    let (tx, rx) = std::sync::mpsc::channel();\n\n    let mut watcher = notify_debouncer_full::new_debouncer(Duration::from_secs(1), None, tx)\n      .expect(\"failed to start builtin server fs watcher\");\n\n    watcher\n      .watch(&dir, notify::RecursiveMode::Recursive)\n      .expect(\"builtin server failed to watch dir\");\n\n    loop {\n      if let Ok(Ok(event)) = rx.recv() {\n        if let Some(event) = event.first() {\n          if !event.kind.is_access() {\n            handler();\n          }\n        }\n      }\n    }\n  });\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/dev.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::{Context, ErrorExt},\n  helpers::{\n    app_paths::Dirs,\n    command_env,\n    config::{get_config, reload_config, BeforeDevCommand, ConfigMetadata, FrontendDist},\n  },\n  info::plugins::check_mismatched_packages,\n  interface::{AppInterface, ExitReason},\n  CommandExt, ConfigValue, Error, Result,\n};\n\nuse clap::{ArgAction, Parser};\nuse shared_child::SharedChild;\nuse tauri_utils::{config::RunnerConfig, platform::Target};\n\nuse std::{\n  env::set_current_dir,\n  net::{IpAddr, Ipv4Addr},\n  path::PathBuf,\n  process::{exit, Command, Stdio},\n  sync::{\n    atomic::{AtomicBool, Ordering},\n    OnceLock,\n  },\n};\n\nmod builtin_dev_server;\n\nstatic BEFORE_DEV: OnceLock<SharedChild> = OnceLock::new();\nstatic KILL_BEFORE_DEV_FLAG: AtomicBool = AtomicBool::new(false);\n\n#[cfg(unix)]\nconst KILL_CHILDREN_SCRIPT: &[u8] = include_bytes!(\"../scripts/kill-children.sh\");\n\npub const TAURI_CLI_BUILTIN_WATCHER_IGNORE_FILE: &[u8] =\n  include_bytes!(\"../tauri-dev-watcher.gitignore\");\n\n#[derive(Debug, Clone, Parser)]\n#[clap(\n  about = \"Run your app in development mode\",\n  long_about = \"Run your app in development mode with hot-reloading for the Rust code. It makes use of the `build.devUrl` property from your `tauri.conf.json` file. It also runs your `build.beforeDevCommand` which usually starts your frontend devServer.\",\n  trailing_var_arg(true)\n)]\npub struct Options {\n  /// Binary to use to run the application\n  #[clap(short, long)]\n  pub runner: Option<RunnerConfig>,\n  /// Target triple to build against\n  #[clap(short, long)]\n  pub target: Option<String>,\n  /// List of cargo features to activate\n  #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]\n  pub features: Vec<String>,\n  /// Exit on panic\n  #[clap(short, long)]\n  pub exit_on_panic: bool,\n  /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file\n  ///\n  /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.\n  ///\n  /// Note that a platform-specific file is looked up and merged with the default file by default\n  /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)\n  /// but you can use this for more specific use cases such as different build flavors.\n  #[clap(short, long)]\n  pub config: Vec<ConfigValue>,\n  /// Run the code in release mode\n  #[clap(long = \"release\")]\n  pub release_mode: bool,\n  /// Command line arguments passed to the runner.\n  /// Use `--` to explicitly mark the start of the arguments. Arguments after a second `--` are passed to the application\n  /// e.g. `tauri dev -- [runnerArgs] -- [appArgs]`.\n  pub args: Vec<String>,\n  /// Skip waiting for the frontend dev server to start before building the tauri application.\n  #[clap(long, env = \"TAURI_CLI_NO_DEV_SERVER_WAIT\")]\n  pub no_dev_server_wait: bool,\n  /// Disable the file watcher.\n  #[clap(long)]\n  pub no_watch: bool,\n  /// Additional paths to watch for changes.\n  #[clap(long)]\n  pub additional_watch_folders: Vec<PathBuf>,\n\n  /// Disable the built-in dev server for static files.\n  #[clap(long)]\n  pub no_dev_server: bool,\n  /// Specify port for the built-in dev server for static files. Defaults to 1430.\n  #[clap(long, env = \"TAURI_CLI_PORT\")]\n  pub port: Option<u16>,\n\n  #[clap(skip)]\n  pub host: Option<IpAddr>,\n}\n\npub fn command(options: Options) -> Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n\n  let r = command_internal(options, dirs);\n  if r.is_err() {\n    kill_before_dev_process();\n  }\n  r\n}\n\nfn command_internal(mut options: Options, dirs: Dirs) -> Result<()> {\n  let target = options\n    .target\n    .as_deref()\n    .map(Target::from_triple)\n    .unwrap_or_else(Target::current);\n\n  let mut config = get_config(\n    target,\n    &options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),\n    dirs.tauri,\n  )?;\n\n  let mut interface = AppInterface::new(&config, options.target.clone(), dirs.tauri)?;\n\n  setup(&interface, &mut options, &mut config, &dirs)?;\n\n  let exit_on_panic = options.exit_on_panic;\n  let no_watch = options.no_watch;\n  interface.dev(\n    &mut config,\n    options.into(),\n    move |status, reason| on_app_exit(status, reason, exit_on_panic, no_watch),\n    &dirs,\n  )\n}\n\npub fn setup(\n  interface: &AppInterface,\n  options: &mut Options,\n  config: &mut ConfigMetadata,\n  dirs: &Dirs,\n) -> Result<()> {\n  std::thread::spawn(|| {\n    if let Err(error) = check_mismatched_packages(dirs.frontend, dirs.tauri) {\n      log::error!(\"{error}\");\n    }\n  });\n\n  set_current_dir(dirs.tauri).context(\"failed to set current directory\")?;\n\n  if let Some(before_dev) = config.build.before_dev_command.clone() {\n    let (script, script_cwd, wait) = match before_dev {\n      BeforeDevCommand::Script(s) if s.is_empty() => (None, None, false),\n      BeforeDevCommand::Script(s) => (Some(s), None, false),\n      BeforeDevCommand::ScriptWithOptions { script, cwd, wait } => {\n        (Some(script), cwd.map(Into::into), wait)\n      }\n    };\n    let cwd = script_cwd.unwrap_or_else(|| dirs.frontend.to_owned());\n    if let Some(before_dev) = script {\n      log::info!(action = \"Running\"; \"BeforeDevCommand (`{}`)\", before_dev);\n      let mut env = command_env(true);\n      env.extend(interface.env());\n\n      #[cfg(windows)]\n      let mut command = {\n        let mut command = Command::new(\"cmd\");\n        command\n          .arg(\"/S\")\n          .arg(\"/C\")\n          .arg(&before_dev)\n          .current_dir(cwd)\n          .envs(env);\n        command\n      };\n      #[cfg(not(windows))]\n      let mut command = {\n        let mut command = Command::new(\"sh\");\n        command\n          .arg(\"-c\")\n          .arg(&before_dev)\n          .current_dir(cwd)\n          .envs(env);\n        command\n      };\n\n      if wait {\n        let status = command.piped().map_err(|error| Error::CommandFailed {\n          command: format!(\n            \"`{before_dev}` with `{}`\",\n            if cfg!(windows) { \"cmd /S /C\" } else { \"sh -c\" }\n          ),\n          error,\n        })?;\n        if !status.success() {\n          crate::error::bail!(\n            \"beforeDevCommand `{}` failed with exit code {}\",\n            before_dev,\n            status.code().unwrap_or_default()\n          );\n        }\n      } else {\n        command.stdin(Stdio::piped());\n        command.stdout(os_pipe::dup_stdout().unwrap());\n        command.stderr(os_pipe::dup_stderr().unwrap());\n\n        let child = SharedChild::spawn(&mut command)\n          .unwrap_or_else(|_| panic!(\"failed to run `{before_dev}`\"));\n\n        let child = BEFORE_DEV.get_or_init(move || child);\n        std::thread::spawn(move || {\n          let status = child\n            .wait()\n            .expect(\"failed to wait on \\\"beforeDevCommand\\\"\");\n          if !(status.success() || KILL_BEFORE_DEV_FLAG.load(Ordering::SeqCst)) {\n            log::error!(\"The \\\"beforeDevCommand\\\" terminated with a non-zero status code.\");\n            exit(status.code().unwrap_or(1));\n          }\n        });\n\n        let _ = ctrlc::set_handler(move || {\n          kill_before_dev_process();\n          exit(130);\n        });\n      }\n    }\n  }\n\n  if options.runner.is_none() {\n    options.runner = config.build.runner.clone();\n  }\n\n  let mut cargo_features = config.build.features.clone().unwrap_or_default();\n  cargo_features.extend(options.features.clone());\n\n  let mut dev_url = config.build.dev_url.clone();\n  let frontend_dist = config.build.frontend_dist.clone();\n  if !options.no_dev_server && dev_url.is_none() {\n    if let Some(FrontendDist::Directory(path)) = &frontend_dist {\n      if path.exists() {\n        let path = path\n          .canonicalize()\n          .fs_context(\"failed to canonicalize path\", path.to_path_buf())?;\n\n        let ip = options.host.unwrap_or_else(|| Ipv4Addr::LOCALHOST.into());\n\n        let server_url = builtin_dev_server::start(path, ip, options.port)\n          .context(\"failed to start builtin dev server\")?;\n        let server_url = format!(\"http://{server_url}\");\n        dev_url = Some(server_url.parse().unwrap());\n\n        options.config.push(crate::ConfigValue(serde_json::json!({\n          \"build\": {\n            \"devUrl\": server_url\n          }\n        })));\n\n        reload_config(\n          config,\n          &options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),\n          dirs.tauri,\n        )?;\n      }\n    }\n  }\n\n  if !options.no_dev_server_wait {\n    if let Some(url) = dev_url {\n      let host = url.host().expect(\"No host name in the URL\");\n      let port = url\n        .port_or_known_default()\n        .expect(\"No port number in the URL\");\n      let addrs;\n      let addr;\n      let addrs = match host {\n        url::Host::Domain(domain) => {\n          use std::net::ToSocketAddrs;\n          addrs = (domain, port).to_socket_addrs().unwrap();\n          addrs.as_slice()\n        }\n        url::Host::Ipv4(ip) => {\n          addr = (ip, port).into();\n          std::slice::from_ref(&addr)\n        }\n        url::Host::Ipv6(ip) => {\n          addr = (ip, port).into();\n          std::slice::from_ref(&addr)\n        }\n      };\n      let mut i = 0;\n      let sleep_interval = std::time::Duration::from_secs(2);\n      let timeout_duration = std::time::Duration::from_secs(1);\n      let max_attempts = 90;\n      'waiting: loop {\n        for addr in addrs.iter() {\n          if std::net::TcpStream::connect_timeout(addr, timeout_duration).is_ok() {\n            break 'waiting;\n          }\n        }\n\n        if i % 3 == 1 {\n          log::warn!(\"Waiting for your frontend dev server to start on {url}...\",);\n        }\n        i += 1;\n        if i == max_attempts {\n          log::error!(\"Could not connect to `{url}` after {}s. Please make sure that is the URL to your dev server.\", i * sleep_interval.as_secs());\n          exit(1);\n        }\n        std::thread::sleep(sleep_interval);\n      }\n    }\n  }\n\n  if options.additional_watch_folders.is_empty() {\n    options\n      .additional_watch_folders\n      .extend(config.build.additional_watch_folders.clone());\n  }\n\n  Ok(())\n}\n\npub fn on_app_exit(code: Option<i32>, reason: ExitReason, exit_on_panic: bool, no_watch: bool) {\n  if no_watch\n    || (!matches!(reason, ExitReason::TriggeredKill)\n      && (exit_on_panic || matches!(reason, ExitReason::NormalExit)))\n  {\n    kill_before_dev_process();\n    exit(code.unwrap_or(0));\n  }\n}\n\npub fn kill_before_dev_process() {\n  if let Some(child) = BEFORE_DEV.get() {\n    if KILL_BEFORE_DEV_FLAG.load(Ordering::SeqCst) {\n      return;\n    }\n    KILL_BEFORE_DEV_FLAG.store(true, Ordering::SeqCst);\n    #[cfg(windows)]\n    {\n      let powershell_path = std::env::var(\"SYSTEMROOT\").map_or_else(\n        |_| \"powershell.exe\".to_string(),\n        |p| format!(\"{p}\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe\"),\n      );\n      let _ = Command::new(powershell_path)\n      .arg(\"-NoProfile\")\n      .arg(\"-Command\")\n      .arg(format!(\"function Kill-Tree {{ Param([int]$ppid); Get-CimInstance Win32_Process | Where-Object {{ $_.ParentProcessId -eq $ppid }} | ForEach-Object {{ Kill-Tree $_.ProcessId }}; Stop-Process -Id $ppid -ErrorAction SilentlyContinue }}; Kill-Tree {}\", child.id()))\n      .status();\n    }\n    #[cfg(unix)]\n    {\n      use std::io::Write;\n      let mut kill_children_script_path = std::env::temp_dir();\n      kill_children_script_path.push(\"tauri-stop-dev-processes.sh\");\n\n      if !kill_children_script_path.exists() {\n        if let Ok(mut file) = std::fs::File::create(&kill_children_script_path) {\n          use std::os::unix::fs::PermissionsExt;\n          let _ = file.write_all(KILL_CHILDREN_SCRIPT);\n          let mut permissions = file.metadata().unwrap().permissions();\n          permissions.set_mode(0o770);\n          let _ = file.set_permissions(permissions);\n        }\n      }\n      let _ = Command::new(&kill_children_script_path)\n        .arg(child.id().to_string())\n        .output();\n    }\n    let _ = child.kill();\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/error.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{fmt::Display, path::PathBuf};\n\n#[derive(Debug, thiserror::Error)]\npub enum Error {\n  #[error(\"{0}: {1}\")]\n  Context(String, Box<dyn std::error::Error + Send + Sync + 'static>),\n  #[error(\"{0}\")]\n  GenericError(String),\n  #[error(\"failed to bundle project {0}\")]\n  Bundler(#[from] Box<tauri_bundler::Error>),\n  #[error(\"{context} {path}: {error}\")]\n  Fs {\n    context: &'static str,\n    path: PathBuf,\n    error: std::io::Error,\n  },\n  #[error(\"failed to run command {command}: {error}\")]\n  CommandFailed {\n    command: String,\n    error: std::io::Error,\n  },\n  #[cfg(target_os = \"macos\")]\n  #[error(transparent)]\n  MacosSign(#[from] Box<tauri_macos_sign::Error>),\n}\n\n/// Convenient type alias of Result type.\npub type Result<T> = std::result::Result<T, Error>;\n\npub trait Context<T> {\n  // Required methods\n  fn context<C>(self, context: C) -> Result<T>\n  where\n    C: Display + Send + Sync + 'static;\n  fn with_context<C, F>(self, f: F) -> Result<T>\n  where\n    C: Display + Send + Sync + 'static,\n    F: FnOnce() -> C;\n}\n\nimpl<T, E: std::error::Error + Send + Sync + 'static> Context<T> for std::result::Result<T, E> {\n  fn context<C>(self, context: C) -> Result<T>\n  where\n    C: Display + Send + Sync + 'static,\n  {\n    self.map_err(|e| Error::Context(context.to_string(), Box::new(e)))\n  }\n\n  fn with_context<C, F>(self, f: F) -> Result<T>\n  where\n    C: Display + Send + Sync + 'static,\n    F: FnOnce() -> C,\n  {\n    self.map_err(|e| Error::Context(f().to_string(), Box::new(e)))\n  }\n}\n\nimpl<T> Context<T> for Option<T> {\n  fn context<C>(self, context: C) -> Result<T>\n  where\n    C: Display + Send + Sync + 'static,\n  {\n    self.ok_or_else(|| Error::GenericError(context.to_string()))\n  }\n\n  fn with_context<C, F>(self, f: F) -> Result<T>\n  where\n    C: Display + Send + Sync + 'static,\n    F: FnOnce() -> C,\n  {\n    self.ok_or_else(|| Error::GenericError(f().to_string()))\n  }\n}\n\npub trait ErrorExt<T> {\n  fn fs_context(self, context: &'static str, path: impl Into<PathBuf>) -> Result<T>;\n}\n\nimpl<T> ErrorExt<T> for std::result::Result<T, std::io::Error> {\n  fn fs_context(self, context: &'static str, path: impl Into<PathBuf>) -> Result<T> {\n    self.map_err(|error| Error::Fs {\n      context,\n      path: path.into(),\n      error,\n    })\n  }\n}\n\nmacro_rules! bail {\n   ($msg:literal $(,)?) => {\n      return Err(crate::Error::GenericError($msg.into()))\n   };\n    ($err:expr $(,)?) => {\n       return Err(crate::Error::GenericError($err))\n    };\n   ($fmt:expr, $($arg:tt)*) => {\n     return Err(crate::Error::GenericError(format!($fmt, $($arg)*)))\n   };\n}\n\npub(crate) use bail;\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/app_paths.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  cmp::Ordering,\n  env::current_dir,\n  ffi::OsStr,\n  path::{Path, PathBuf},\n  sync::OnceLock,\n};\n\nuse ignore::WalkBuilder;\n\nuse tauri_utils::{\n  config::parse::{folder_has_configuration_file, is_configuration_file, ConfigFormat},\n  platform::Target,\n};\n\nconst TAURI_GITIGNORE: &[u8] = include_bytes!(\"../../tauri.gitignore\");\n// path to the Tauri app (Rust crate) directory, usually `<project>/src-tauri/`\nconst ENV_TAURI_APP_PATH: &str = \"TAURI_APP_PATH\";\n// path to the frontend app directory, usually `<project>/`\nconst ENV_TAURI_FRONTEND_PATH: &str = \"TAURI_FRONTEND_PATH\";\n\npub struct Dirs {\n  pub tauri: &'static Path,\n  pub frontend: &'static Path,\n}\n\nstatic FRONTEND_DIR: OnceLock<PathBuf> = OnceLock::new();\nstatic TAURI_DIR: OnceLock<PathBuf> = OnceLock::new();\n\npub fn walk_builder(path: &Path) -> WalkBuilder {\n  let mut default_gitignore = std::env::temp_dir();\n  default_gitignore.push(\".gitignore\");\n  if !default_gitignore.exists() {\n    if let Ok(mut file) = std::fs::File::create(default_gitignore.clone()) {\n      use std::io::Write;\n      let _ = file.write_all(TAURI_GITIGNORE);\n    }\n  }\n\n  let mut builder = WalkBuilder::new(path);\n  builder.add_custom_ignore_filename(\".taurignore\");\n  builder.git_global(false);\n  builder.parents(false);\n  let _ = builder.add_ignore(default_gitignore);\n  builder\n}\n\nfn lookup<F: Fn(&PathBuf) -> bool>(dir: &Path, checker: F) -> Option<PathBuf> {\n  let mut builder = walk_builder(dir);\n  builder\n    .require_git(false)\n    .ignore(false)\n    .max_depth(Some(\n      std::env::var(\"TAURI_CLI_CONFIG_DEPTH\")\n        .map(|d| {\n          d.parse()\n            .expect(\"`TAURI_CLI_CONFIG_DEPTH` environment variable must be a positive integer\")\n        })\n        .unwrap_or(3),\n    ))\n    .sort_by_file_path(|a, _| {\n      if a.extension().is_some() {\n        Ordering::Less\n      } else {\n        Ordering::Greater\n      }\n    });\n\n  for entry in builder.build().flatten() {\n    let path = dir.join(entry.path());\n    if checker(&path) {\n      return Some(path);\n    }\n  }\n  None\n}\n\nfn env_tauri_app_path() -> Option<PathBuf> {\n  let p = PathBuf::from(std::env::var_os(ENV_TAURI_APP_PATH)?);\n  dunce::canonicalize(p).ok()\n}\n\nfn env_tauri_frontend_path() -> Option<PathBuf> {\n  let p = PathBuf::from(std::env::var_os(ENV_TAURI_FRONTEND_PATH)?);\n  dunce::canonicalize(p).ok()\n}\n\npub fn resolve_tauri_dir() -> Option<PathBuf> {\n  let src_dir = env_tauri_app_path().or_else(|| current_dir().ok())?;\n\n  for standard_tauri_path in [src_dir.clone(), src_dir.join(\"src-tauri\")] {\n    if standard_tauri_path\n      .join(ConfigFormat::Json.into_file_name())\n      .exists()\n      || standard_tauri_path\n        .join(ConfigFormat::Json5.into_file_name())\n        .exists()\n      || standard_tauri_path\n        .join(ConfigFormat::Toml.into_file_name())\n        .exists()\n    {\n      log::debug!(\n        \"Found Tauri project inside {} on early lookup\",\n        standard_tauri_path.display()\n      );\n      return Some(standard_tauri_path);\n    }\n  }\n\n  log::debug!(\"resolving Tauri directory from {}\", src_dir.display());\n\n  lookup(&src_dir, |path| {\n    folder_has_configuration_file(Target::Linux, path) || is_configuration_file(Target::Linux, path)\n  })\n  .map(|p| {\n    if p.is_dir() {\n      log::debug!(\"Found Tauri project directory {}\", p.display());\n      p\n    } else {\n      log::debug!(\"Found Tauri project configuration file {}\", p.display());\n      p.parent().unwrap().to_path_buf()\n    }\n  })\n}\n\npub fn resolve_dirs() -> Dirs {\n  let tauri = TAURI_DIR.get_or_init(|| resolve_tauri_dir().unwrap_or_else(|| {\n    let env_var_name = env_tauri_app_path().is_some().then(|| format!(\"`{ENV_TAURI_APP_PATH}`\"));\n    panic!(\"Couldn't recognize the {} folder as a Tauri project. It must contain a `{}`, `{}` or `{}` file in any subfolder.\",\n      env_var_name.as_deref().unwrap_or(\"current\"),\n      ConfigFormat::Json.into_file_name(),\n      ConfigFormat::Json5.into_file_name(),\n      ConfigFormat::Toml.into_file_name()\n    )\n  }));\n  let frontend = FRONTEND_DIR.get_or_init(|| {\n    resolve_frontend_dir().unwrap_or_else(|| tauri.parent().unwrap().to_path_buf())\n  });\n  Dirs { tauri, frontend }\n}\n\npub fn resolve_frontend_dir() -> Option<PathBuf> {\n  let frontend_dir =\n    env_tauri_frontend_path().unwrap_or_else(|| current_dir().expect(\"failed to read cwd\"));\n\n  if frontend_dir.join(\"package.json\").exists() {\n    return Some(frontend_dir);\n  }\n\n  log::debug!(\n    \"resolving frontend directory from {}\",\n    frontend_dir.display()\n  );\n\n  lookup(&frontend_dir, |path| {\n    if let Some(file_name) = path.file_name() {\n      file_name == OsStr::new(\"package.json\")\n    } else {\n      false\n    }\n  })\n  .map(|p| p.parent().unwrap().to_path_buf())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/cargo.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::process::Command;\n\nuse crate::Error;\n\n#[derive(Debug, Default, Clone, Copy)]\npub struct CargoInstallOptions<'a> {\n  pub name: &'a str,\n  pub version: Option<&'a str>,\n  pub rev: Option<&'a str>,\n  pub tag: Option<&'a str>,\n  pub branch: Option<&'a str>,\n  pub cwd: Option<&'a std::path::Path>,\n  pub target: Option<&'a str>,\n}\n\npub fn install_one(options: CargoInstallOptions) -> crate::Result<()> {\n  let mut cargo = Command::new(\"cargo\");\n  cargo.arg(\"add\");\n\n  if let Some(version) = options.version {\n    cargo.arg(format!(\"{}@{}\", options.name, version));\n  } else {\n    cargo.arg(options.name);\n\n    if options.tag.is_some() || options.rev.is_some() || options.branch.is_some() {\n      cargo.args([\"--git\", \"https://github.com/tauri-apps/plugins-workspace\"]);\n    }\n\n    match (options.tag, options.rev, options.branch) {\n      (Some(tag), None, None) => {\n        cargo.args([\"--tag\", tag]);\n      }\n      (None, Some(rev), None) => {\n        cargo.args([\"--rev\", rev]);\n      }\n      (None, None, Some(branch)) => {\n        cargo.args([\"--branch\", branch]);\n      }\n      (None, None, None) => {}\n      _ => crate::error::bail!(\"Only one of --tag, --rev and --branch can be specified\"),\n    };\n  }\n\n  if let Some(target) = options.target {\n    cargo.args([\"--target\", target]);\n  }\n\n  if let Some(cwd) = options.cwd {\n    cargo.current_dir(cwd);\n  }\n\n  log::info!(\"Installing Cargo dependency \\\"{}\\\"...\", options.name);\n  let status = cargo.status().map_err(|error| Error::CommandFailed {\n    command: \"cargo add\".to_string(),\n    error,\n  })?;\n  if !status.success() {\n    crate::error::bail!(\"Failed to install Cargo dependency\");\n  }\n\n  Ok(())\n}\n\n#[derive(Debug, Default, Clone, Copy)]\npub struct CargoUninstallOptions<'a> {\n  pub name: &'a str,\n  pub cwd: Option<&'a std::path::Path>,\n  pub target: Option<&'a str>,\n}\n\npub fn uninstall_one(options: CargoUninstallOptions) -> crate::Result<()> {\n  let mut cargo = Command::new(\"cargo\");\n  cargo.arg(\"remove\");\n\n  cargo.arg(options.name);\n\n  if let Some(target) = options.target {\n    cargo.args([\"--target\", target]);\n  }\n\n  if let Some(cwd) = options.cwd {\n    cargo.current_dir(cwd);\n  }\n\n  log::info!(\"Uninstalling Cargo dependency \\\"{}\\\"...\", options.name);\n  let status = cargo.status().map_err(|error| Error::CommandFailed {\n    command: \"cargo remove\".to_string(),\n    error,\n  })?;\n  if !status.success() {\n    crate::error::bail!(\"Failed to remove Cargo dependency\");\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/cargo_manifest.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::Deserialize;\n\nuse std::{\n  collections::HashMap,\n  fs,\n  path::{Path, PathBuf},\n};\n\nuse crate::interface::rust::get_workspace_dir;\n\n#[derive(Clone, Deserialize)]\npub struct CargoLockPackage {\n  pub name: String,\n  pub version: String,\n  pub source: Option<String>,\n}\n\n#[derive(Deserialize)]\npub struct CargoLock {\n  pub package: Vec<CargoLockPackage>,\n}\n\n#[derive(Clone, Deserialize)]\npub struct CargoManifestDependencyPackage {\n  pub version: Option<String>,\n  pub git: Option<String>,\n  pub branch: Option<String>,\n  pub rev: Option<String>,\n  pub path: Option<PathBuf>,\n}\n\n#[derive(Clone, Deserialize)]\n#[serde(untagged)]\npub enum CargoManifestDependency {\n  Version(String),\n  Package(CargoManifestDependencyPackage),\n}\n\n#[derive(Deserialize)]\npub struct CargoManifestPackage {\n  pub version: String,\n}\n\n#[derive(Deserialize)]\npub struct CargoManifest {\n  pub package: CargoManifestPackage,\n  pub dependencies: HashMap<String, CargoManifestDependency>,\n}\n\npub fn cargo_manifest_and_lock(tauri_dir: &Path) -> (Option<CargoManifest>, Option<CargoLock>) {\n  let manifest: Option<CargoManifest> = fs::read_to_string(tauri_dir.join(\"Cargo.toml\"))\n    .ok()\n    .and_then(|manifest_contents| toml::from_str(&manifest_contents).ok());\n\n  let lock: Option<CargoLock> = get_workspace_dir(tauri_dir)\n    .ok()\n    .and_then(|p| fs::read_to_string(p.join(\"Cargo.lock\")).ok())\n    .and_then(|s| toml::from_str(&s).ok());\n\n  (manifest, lock)\n}\n#[derive(Default)]\npub struct CrateVersion {\n  pub version: Option<String>,\n  pub git: Option<String>,\n  pub git_branch: Option<String>,\n  pub git_rev: Option<String>,\n  pub path: Option<PathBuf>,\n  pub lock_version: Option<String>,\n}\n\nimpl CrateVersion {\n  pub fn has_version(&self) -> bool {\n    self.version.is_some() || self.git.is_some() || self.path.is_some()\n  }\n}\n\nimpl std::fmt::Display for CrateVersion {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    if let Some(g) = &self.git {\n      if let Some(version) = &self.version {\n        write!(f, \"{g} ({version})\")?;\n      } else {\n        write!(f, \"git:{g}\")?;\n        if let Some(branch) = &self.git_branch {\n          write!(f, \"&branch={branch}\")?;\n        } else if let Some(rev) = &self.git_rev {\n          write!(f, \"#rev={rev}\")?;\n        }\n      }\n    } else if let Some(p) = &self.path {\n      write!(f, \"path:{}\", p.display())?;\n      if let Some(version) = &self.version {\n        write!(f, \" ({version})\")?;\n      }\n    } else if let Some(version) = &self.version {\n      write!(f, \"{version}\")?;\n    } else {\n      return write!(f, \"No version detected\");\n    }\n\n    if let Some(lock_version) = &self.lock_version {\n      write!(f, \" ({lock_version})\")?;\n    }\n\n    Ok(())\n  }\n}\n\n// Reference: https://github.com/rust-lang/crates.io/blob/98c83c8231cbcd15d6b8f06d80a00ad462f71585/src/views.rs#L274\n#[derive(serde::Deserialize)]\nstruct CrateMetadata {\n  /// The \"default\" version of this crate.\n  ///\n  /// This version will be displayed by default on the crate's page.\n  pub default_version: Option<String>,\n}\n\n// Reference: https://github.com/rust-lang/crates.io/blob/98c83c8231cbcd15d6b8f06d80a00ad462f71585/src/controllers/krate/metadata.rs#L44\n#[derive(serde::Deserialize)]\nstruct CrateIoGetResponse {\n  /// The crate metadata.\n  #[serde(rename = \"crate\")]\n  krate: CrateMetadata,\n}\n\npub fn crate_latest_version(name: &str) -> Option<String> {\n  // Reference: https://github.com/rust-lang/crates.io/blob/98c83c8231cbcd15d6b8f06d80a00ad462f71585/src/controllers/krate/metadata.rs#L88\n  let url = format!(\"https://crates.io/api/v1/crates/{name}?include\");\n  let mut response = super::http::get(&url).ok()?;\n  let metadata: CrateIoGetResponse =\n    serde_json::from_reader(response.body_mut().as_reader()).unwrap();\n  metadata.krate.default_version\n}\n\npub fn crate_version(\n  tauri_dir: &Path,\n  manifest: Option<&CargoManifest>,\n  lock: Option<&CargoLock>,\n  name: &str,\n) -> CrateVersion {\n  let mut version = CrateVersion::default();\n\n  let crate_lock_packages: Vec<CargoLockPackage> = lock\n    .as_ref()\n    .map(|lock| {\n      lock\n        .package\n        .iter()\n        .filter(|p| p.name == name)\n        .cloned()\n        .collect()\n    })\n    .unwrap_or_default();\n\n  if crate_lock_packages.len() == 1 {\n    let crate_lock_package = crate_lock_packages.first().unwrap();\n    if let Some(s) = crate_lock_package\n      .source\n      .as_ref()\n      .filter(|s| s.starts_with(\"git\"))\n    {\n      version.git = Some(s.clone());\n    }\n\n    version.version = Some(crate_lock_package.version.clone());\n  } else {\n    if let Some(dep) = manifest.and_then(|m| m.dependencies.get(name).cloned()) {\n      match dep {\n        CargoManifestDependency::Version(v) => version.version = Some(v),\n        CargoManifestDependency::Package(p) => {\n          if let Some(v) = p.version {\n            version.version = Some(v);\n          } else if let Some(p) = p.path {\n            let manifest_path = tauri_dir.join(&p).join(\"Cargo.toml\");\n            let v = fs::read_to_string(manifest_path)\n              .ok()\n              .and_then(|m| toml::from_str::<CargoManifest>(&m).ok())\n              .map(|m| m.package.version);\n            version.version = v;\n            version.path = Some(p);\n          } else if let Some(g) = p.git {\n            version.git = Some(g);\n            version.git_branch = p.branch;\n            version.git_rev = p.rev;\n          }\n        }\n      }\n    }\n\n    if lock.is_some() && crate_lock_packages.is_empty() {\n      let lock_version = crate_lock_packages\n        .iter()\n        .map(|p| p.version.clone())\n        .collect::<Vec<String>>()\n        .join(\", \");\n\n      if !lock_version.is_empty() {\n        version.lock_version = Some(lock_version);\n      }\n    }\n  }\n\n  version\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/config.rs",
    "content": "// Copyright 2019-2025 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse itertools::Itertools;\nuse json_patch::merge;\nuse serde_json::Value as JsonValue;\n\nuse tauri_utils::acl::REMOVE_UNUSED_COMMANDS_ENV_VAR;\npub use tauri_utils::{config::*, platform::Target};\n\nuse std::{\n  collections::HashMap,\n  env::{current_dir, set_current_dir, set_var},\n  ffi::{OsStr, OsString},\n  path::Path,\n  process::exit,\n  sync::OnceLock,\n};\n\nuse crate::error::Context;\n\npub const MERGE_CONFIG_EXTENSION_NAME: &str = \"--config\";\n\npub struct ConfigMetadata {\n  /// The current target.\n  target: Target,\n\n  original_identifier: Option<String>,\n  /// The actual configuration, merged with any extension.\n  inner: Config,\n  /// The config extensions (platform-specific config files or the config CLI argument).\n  /// Maps the extension name to its value.\n  extensions: HashMap<OsString, JsonValue>,\n}\n\nimpl std::ops::Deref for ConfigMetadata {\n  type Target = Config;\n\n  #[inline(always)]\n  fn deref(&self) -> &Config {\n    &self.inner\n  }\n}\n\nimpl ConfigMetadata {\n  /// The original bundle identifier from the config file.\n  /// This does not take any extensions into account.\n  pub fn original_identifier(&self) -> Option<&str> {\n    self.original_identifier.as_deref()\n  }\n\n  /// Checks which config is overwriting the bundle identifier.\n  pub fn find_bundle_identifier_overwriter(&self) -> Option<OsString> {\n    for (ext, config) in &self.extensions {\n      if let Some(identifier) = config\n        .as_object()\n        .and_then(|bundle_config| bundle_config.get(\"identifier\")?.as_str())\n      {\n        if identifier == self.inner.identifier {\n          return Some(ext.clone());\n        }\n      }\n    }\n    None\n  }\n}\n\npub fn wix_settings(config: WixConfig) -> tauri_bundler::WixSettings {\n  tauri_bundler::WixSettings {\n    version: config.version,\n    upgrade_code: config.upgrade_code,\n    fips_compliant: std::env::var_os(\"TAURI_BUNDLER_WIX_FIPS_COMPLIANT\")\n      .map(|v| v == \"true\")\n      .unwrap_or(config.fips_compliant),\n    language: tauri_bundler::WixLanguage(match config.language {\n      WixLanguage::One(lang) => vec![(lang, Default::default())],\n      WixLanguage::List(languages) => languages\n        .into_iter()\n        .map(|lang| (lang, Default::default()))\n        .collect(),\n      WixLanguage::Localized(languages) => languages\n        .into_iter()\n        .map(|(lang, config)| {\n          (\n            lang,\n            tauri_bundler::WixLanguageConfig {\n              locale_path: config.locale_path.map(Into::into),\n            },\n          )\n        })\n        .collect(),\n    }),\n    template: config.template,\n    fragment_paths: config.fragment_paths,\n    component_group_refs: config.component_group_refs,\n    component_refs: config.component_refs,\n    feature_group_refs: config.feature_group_refs,\n    feature_refs: config.feature_refs,\n    merge_refs: config.merge_refs,\n    enable_elevated_update_task: config.enable_elevated_update_task,\n    banner_path: config.banner_path,\n    dialog_image_path: config.dialog_image_path,\n  }\n}\n\npub fn nsis_settings(config: NsisConfig) -> tauri_bundler::NsisSettings {\n  tauri_bundler::NsisSettings {\n    template: config.template,\n    header_image: config.header_image,\n    sidebar_image: config.sidebar_image,\n    installer_icon: config.installer_icon,\n    install_mode: config.install_mode,\n    languages: config.languages,\n    custom_language_files: config.custom_language_files,\n    display_language_selector: config.display_language_selector,\n    compression: config.compression,\n    start_menu_folder: config.start_menu_folder,\n    installer_hooks: config.installer_hooks,\n    minimum_webview2_version: config.minimum_webview2_version,\n  }\n}\n\npub fn custom_sign_settings(\n  config: CustomSignCommandConfig,\n) -> tauri_bundler::CustomSignCommandSettings {\n  match config {\n    CustomSignCommandConfig::Command(command) => {\n      let mut tokens = command.split(' ');\n      tauri_bundler::CustomSignCommandSettings {\n        cmd: tokens.next().unwrap().to_string(), // split always has at least one element\n        args: tokens.map(String::from).collect(),\n      }\n    }\n    CustomSignCommandConfig::CommandWithOptions { cmd, args } => {\n      tauri_bundler::CustomSignCommandSettings { cmd, args }\n    }\n  }\n}\n\nfn config_schema_validator() -> &'static jsonschema::Validator {\n  // TODO: Switch to `LazyLock` when we bump MSRV to above 1.80\n  static CONFIG_SCHEMA_VALIDATOR: OnceLock<jsonschema::Validator> = OnceLock::new();\n  CONFIG_SCHEMA_VALIDATOR.get_or_init(|| {\n    let schema: JsonValue = serde_json::from_str(include_str!(\"../../config.schema.json\"))\n      .expect(\"Failed to parse config schema bundled in the tauri-cli\");\n    jsonschema::validator_for(&schema).expect(\"Config schema bundled in the tauri-cli is invalid\")\n  })\n}\n\nfn load_config(\n  merge_configs: &[&serde_json::Value],\n  reload: bool,\n  target: Target,\n  tauri_dir: &Path,\n) -> crate::Result<ConfigMetadata> {\n  let (mut config, config_path) =\n    tauri_utils::config::parse::parse_value(target, tauri_dir.join(\"tauri.conf.json\"))\n      .context(\"failed to parse config\")?;\n  let config_file_name = config_path.file_name().unwrap();\n  let mut extensions = HashMap::new();\n\n  let original_identifier = config\n    .as_object()\n    .and_then(|config| config.get(\"identifier\")?.as_str())\n    .map(ToString::to_string);\n\n  if let Some((platform_config, config_path)) =\n    tauri_utils::config::parse::read_platform(target, tauri_dir)\n      .context(\"failed to parse platform config\")?\n  {\n    merge(&mut config, &platform_config);\n    extensions.insert(config_path.file_name().unwrap().into(), platform_config);\n  }\n\n  if !merge_configs.is_empty() {\n    let mut merge_config = serde_json::Value::Object(Default::default());\n    for conf in merge_configs {\n      merge_patches(&mut merge_config, conf);\n    }\n\n    let merge_config_str = serde_json::to_string(&merge_config).unwrap();\n    set_var(\"TAURI_CONFIG\", merge_config_str);\n    merge(&mut config, &merge_config);\n    extensions.insert(MERGE_CONFIG_EXTENSION_NAME.into(), merge_config);\n  }\n\n  if config_path.extension() == Some(OsStr::new(\"json\"))\n    || config_path.extension() == Some(OsStr::new(\"json5\"))\n  {\n    let mut errors = config_schema_validator().iter_errors(&config).peekable();\n    if errors.peek().is_some() {\n      for error in errors {\n        let path = error.instance_path.into_iter().join(\" > \");\n        if path.is_empty() {\n          log::error!(\"`{config_file_name:?}` error: {error}\");\n        } else {\n          log::error!(\"`{config_file_name:?}` error on `{path}`: {error}\");\n        }\n      }\n      if !reload {\n        exit(1);\n      }\n    }\n  }\n\n  // the `Config` deserializer for `package > version` can resolve the version from a path relative to the config path\n  // so we actually need to change the current working directory here\n  let current_dir = current_dir().context(\"failed to resolve current directory\")?;\n  set_current_dir(config_path.parent().unwrap()).context(\"failed to set current directory\")?;\n  let config: Config = serde_json::from_value(config).context(\"failed to parse config\")?;\n  // revert to previous working directory\n  set_current_dir(current_dir).context(\"failed to set current directory\")?;\n\n  for (plugin, conf) in &config.plugins.0 {\n    set_var(\n      format!(\n        \"TAURI_{}_PLUGIN_CONFIG\",\n        plugin.to_uppercase().replace('-', \"_\")\n      ),\n      serde_json::to_string(&conf).context(\"failed to serialize config\")?,\n    );\n  }\n\n  if config.build.remove_unused_commands {\n    std::env::set_var(REMOVE_UNUSED_COMMANDS_ENV_VAR, tauri_dir);\n  }\n\n  Ok(ConfigMetadata {\n    target,\n    original_identifier,\n    inner: config,\n    extensions,\n  })\n}\n\npub fn get_config(\n  target: Target,\n  merge_configs: &[&serde_json::Value],\n  tauri_dir: &Path,\n) -> crate::Result<ConfigMetadata> {\n  load_config(merge_configs, false, target, tauri_dir)\n}\n\npub fn reload_config(\n  config: &mut ConfigMetadata,\n  merge_configs: &[&serde_json::Value],\n  tauri_dir: &Path,\n) -> crate::Result<()> {\n  let target = config.target;\n  *config = load_config(merge_configs, true, target, tauri_dir)?;\n  Ok(())\n}\n\n/// merges the loaded config with the given value\npub fn merge_config_with(\n  config: &mut ConfigMetadata,\n  merge_configs: &[&serde_json::Value],\n) -> crate::Result<()> {\n  if merge_configs.is_empty() {\n    return Ok(());\n  }\n\n  let mut merge_config = serde_json::Value::Object(Default::default());\n  for conf in merge_configs {\n    merge_patches(&mut merge_config, conf);\n  }\n\n  let merge_config_str = serde_json::to_string(&merge_config).unwrap();\n  set_var(\"TAURI_CONFIG\", merge_config_str);\n\n  let mut value =\n    serde_json::to_value(config.inner.clone()).context(\"failed to serialize config\")?;\n  merge(&mut value, &merge_config);\n  config.inner = serde_json::from_value(value).context(\"failed to parse config\")?;\n  Ok(())\n}\n\n/// Same as [`json_patch::merge`] but doesn't delete the key when the patch's value is `null`\nfn merge_patches(doc: &mut serde_json::Value, patch: &serde_json::Value) {\n  use serde_json::{Map, Value};\n\n  if !patch.is_object() {\n    *doc = patch.clone();\n    return;\n  }\n\n  if !doc.is_object() {\n    *doc = Value::Object(Map::new());\n  }\n  let map = doc.as_object_mut().unwrap();\n  for (key, value) in patch.as_object().unwrap() {\n    merge_patches(map.entry(key.as_str()).or_insert(Value::Null), value);\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  #[test]\n  fn merge_patches() {\n    let mut json = serde_json::Value::Object(Default::default());\n\n    super::merge_patches(\n      &mut json,\n      &serde_json::json!({\n        \"app\": {\n          \"withGlobalTauri\": true,\n          \"windows\": []\n        },\n        \"plugins\": {\n          \"test\": \"tauri\"\n        },\n        \"build\": {\n          \"devUrl\": \"http://localhost:8080\"\n        }\n      }),\n    );\n\n    super::merge_patches(\n      &mut json,\n      &serde_json::json!({\n        \"app\": { \"withGlobalTauri\": null }\n      }),\n    );\n\n    super::merge_patches(\n      &mut json,\n      &serde_json::json!({\n        \"app\": { \"windows\": null }\n      }),\n    );\n\n    super::merge_patches(\n      &mut json,\n      &serde_json::json!({\n        \"plugins\": { \"updater\": {\n          \"endpoints\": [\"https://tauri.app\"]\n        } }\n      }),\n    );\n\n    assert_eq!(\n      json,\n      serde_json::json!({\n        \"app\": {\n          \"withGlobalTauri\": null,\n          \"windows\": null\n        },\n        \"plugins\": {\n          \"test\": \"tauri\",\n          \"updater\": {\n            \"endpoints\": [\"https://tauri.app\"]\n          }\n        },\n        \"build\": {\n          \"devUrl\": \"http://localhost:8080\"\n        }\n      })\n    )\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/flock.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// taken from https://github.com/rust-lang/cargo/blob/b0c9586f4cbf426914df47c65de38ea323772c74/src/cargo/util/flock.rs\n#![allow(dead_code)]\n\nuse std::fs::{create_dir_all, File, OpenOptions};\nuse std::io;\nuse std::io::{Read, Seek, SeekFrom, Write};\nuse std::path::{Path, PathBuf};\n\nuse crate::{error::ErrorExt, Error, Result};\nuse sys::*;\n\n#[derive(Debug)]\npub struct FileLock {\n  f: Option<File>,\n  path: PathBuf,\n  state: State,\n}\n\n#[derive(PartialEq, Debug)]\nenum State {\n  Unlocked,\n  Shared,\n  Exclusive,\n}\n\nimpl FileLock {\n  /// Returns the underlying file handle of this lock.\n  pub fn file(&self) -> &File {\n    self.f.as_ref().unwrap()\n  }\n\n  /// Returns the underlying path that this lock points to.\n  ///\n  /// Note that special care must be taken to ensure that the path is not\n  /// referenced outside the lifetime of this lock.\n  pub fn path(&self) -> &Path {\n    assert_ne!(self.state, State::Unlocked);\n    &self.path\n  }\n\n  /// Returns the parent path containing this file\n  pub fn parent(&self) -> &Path {\n    assert_ne!(self.state, State::Unlocked);\n    self.path.parent().unwrap()\n  }\n}\n\nimpl Read for FileLock {\n  fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {\n    self.file().read(buf)\n  }\n}\n\nimpl Seek for FileLock {\n  fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {\n    self.file().seek(to)\n  }\n}\n\nimpl Write for FileLock {\n  fn write(&mut self, buf: &[u8]) -> io::Result<usize> {\n    self.file().write(buf)\n  }\n\n  fn flush(&mut self) -> io::Result<()> {\n    self.file().flush()\n  }\n}\n\nimpl Drop for FileLock {\n  fn drop(&mut self) {\n    if self.state != State::Unlocked {\n      if let Some(f) = self.f.take() {\n        let _ = unlock(&f);\n      }\n    }\n  }\n}\n\n/// Opens exclusive access to a file, returning the locked version of a\n/// file.\n///\n/// This function will create a file at `path` if it doesn't already exist\n/// (including intermediate directories), and then it will acquire an\n/// exclusive lock on `path`. If the process must block waiting for the\n/// lock, the `msg` is logged.\n///\n/// The returned file can be accessed to look at the path and also has\n/// read/write access to the underlying file.\npub fn open_rw<P>(path: P, msg: &str) -> Result<FileLock>\nwhere\n  P: AsRef<Path>,\n{\n  open(\n    path.as_ref(),\n    OpenOptions::new().read(true).write(true).create(true),\n    State::Exclusive,\n    msg,\n  )\n}\n\n/// Opens shared access to a file, returning the locked version of a file.\n///\n/// This function will fail if `path` doesn't already exist, but if it does\n/// then it will acquire a shared lock on `path`. If the process must block\n/// waiting for the lock, the `msg` is logged.\n///\n/// The returned file can be accessed to look at the path and also has read\n/// access to the underlying file. Any writes to the file will return an\n/// error.\npub fn open_ro<P>(path: P, msg: &str) -> Result<FileLock>\nwhere\n  P: AsRef<Path>,\n{\n  open(\n    path.as_ref(),\n    OpenOptions::new().read(true),\n    State::Shared,\n    msg,\n  )\n}\n\nfn open(path: &Path, opts: &OpenOptions, state: State, msg: &str) -> Result<FileLock> {\n  // If we want an exclusive lock then if we fail because of NotFound it's\n  // likely because an intermediate directory didn't exist, so try to\n  // create the directory and then continue.\n  let f = opts.open(path).or_else(|e| {\n    if e.kind() == io::ErrorKind::NotFound && state == State::Exclusive {\n      create_dir_all(path.parent().unwrap()).fs_context(\n        \"failed to create directory\",\n        path.parent().unwrap().to_path_buf(),\n      )?;\n      Ok(\n        opts\n          .open(path)\n          .fs_context(\"failed to open file\", path.to_path_buf())?,\n      )\n    } else {\n      Err(Error::Fs {\n        context: \"failed to open file\",\n        path: path.to_path_buf(),\n        error: e,\n      })\n    }\n  })?;\n  match state {\n    State::Exclusive => {\n      acquire(msg, path, &|| try_lock_exclusive(&f), &|| {\n        lock_exclusive(&f)\n      })?;\n    }\n    State::Shared => {\n      acquire(msg, path, &|| try_lock_shared(&f), &|| lock_shared(&f))?;\n    }\n    State::Unlocked => {}\n  }\n  Ok(FileLock {\n    f: Some(f),\n    path: path.to_path_buf(),\n    state,\n  })\n}\n\n/// Acquires a lock on a file in a \"nice\" manner.\n///\n/// Almost all long-running blocking actions in Cargo have a status message\n/// associated with them as we're not sure how long they'll take. Whenever a\n/// conflicted file lock happens, this is the case (we're not sure when the lock\n/// will be released).\n///\n/// This function will acquire the lock on a `path`, printing out a nice message\n/// to the console if we have to wait for it. It will first attempt to use `try`\n/// to acquire a lock on the crate, and in the case of contention it will emit a\n/// status message based on `msg` to `config`'s shell, and then use `block` to\n/// block waiting to acquire a lock.\n///\n/// Returns an error if the lock could not be acquired or if any error other\n/// than a contention error happens.\nfn acquire(\n  msg: &str,\n  path: &Path,\n  lock_try: &dyn Fn() -> io::Result<()>,\n  lock_block: &dyn Fn() -> io::Result<()>,\n) -> Result<()> {\n  // File locking on Unix is currently implemented via `flock`, which is known\n  // to be broken on NFS. We could in theory just ignore errors that happen on\n  // NFS, but apparently the failure mode [1] for `flock` on NFS is **blocking\n  // forever**, even if the \"non-blocking\" flag is passed!\n  //\n  // As a result, we just skip all file locks entirely on NFS mounts. That\n  // should avoid calling any `flock` functions at all, and it wouldn't work\n  // there anyway.\n  //\n  // [1]: https://github.com/rust-lang/cargo/issues/2615\n  if is_on_nfs_mount(path) {\n    return Ok(());\n  }\n\n  match lock_try() {\n    Ok(()) => return Ok(()),\n\n    // In addition to ignoring NFS which is commonly not working we also\n    // just ignore locking on filesystems that look like they don't\n    // implement file locking.\n    Err(e) if error_unsupported(&e) => return Ok(()),\n\n    Err(e) => {\n      if !error_contended(&e) {\n        return Err(Error::Fs {\n          context: \"failed to lock file\",\n          path: path.to_path_buf(),\n          error: e,\n        });\n      }\n    }\n  }\n  let msg = format!(\"waiting for file lock on {msg}\");\n  log::info!(action = \"Blocking\"; \"{}\", &msg);\n\n  lock_block().fs_context(\"failed to lock file\", path.to_path_buf())?;\n  return Ok(());\n\n  #[cfg(all(target_os = \"linux\", not(target_env = \"musl\")))]\n  fn is_on_nfs_mount(path: &Path) -> bool {\n    use std::ffi::CString;\n    use std::mem;\n    use std::os::unix::prelude::*;\n\n    let path = match CString::new(path.as_os_str().as_bytes()) {\n      Ok(path) => path,\n      Err(_) => return false,\n    };\n\n    unsafe {\n      let mut buf: libc::statfs = mem::zeroed();\n      let r = libc::statfs(path.as_ptr(), &mut buf);\n\n      r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32\n    }\n  }\n\n  #[cfg(any(not(target_os = \"linux\"), target_env = \"musl\"))]\n  fn is_on_nfs_mount(_path: &Path) -> bool {\n    false\n  }\n}\n\n#[cfg(unix)]\nmod sys {\n  use std::fs::File;\n  use std::io::{Error, Result};\n  use std::os::unix::io::AsRawFd;\n\n  pub(super) fn lock_shared(file: &File) -> Result<()> {\n    flock(file, libc::LOCK_SH)\n  }\n\n  pub(super) fn lock_exclusive(file: &File) -> Result<()> {\n    flock(file, libc::LOCK_EX)\n  }\n\n  pub(super) fn try_lock_shared(file: &File) -> Result<()> {\n    flock(file, libc::LOCK_SH | libc::LOCK_NB)\n  }\n\n  pub(super) fn try_lock_exclusive(file: &File) -> Result<()> {\n    flock(file, libc::LOCK_EX | libc::LOCK_NB)\n  }\n\n  pub(super) fn unlock(file: &File) -> Result<()> {\n    flock(file, libc::LOCK_UN)\n  }\n\n  pub(super) fn error_contended(err: &Error) -> bool {\n    err.raw_os_error() == Some(libc::EWOULDBLOCK)\n  }\n\n  pub(super) fn error_unsupported(err: &Error) -> bool {\n    match err.raw_os_error() {\n      // Unfortunately, depending on the target, these may or may not be the same.\n      // For targets in which they are the same, the duplicate pattern causes a warning.\n      #[allow(unreachable_patterns)]\n      Some(libc::ENOTSUP | libc::EOPNOTSUPP) => true,\n      Some(libc::ENOSYS) => true,\n      _ => false,\n    }\n  }\n\n  #[cfg(not(target_os = \"solaris\"))]\n  fn flock(file: &File, flag: libc::c_int) -> Result<()> {\n    let ret = unsafe { libc::flock(file.as_raw_fd(), flag) };\n    if ret < 0 {\n      Err(Error::last_os_error())\n    } else {\n      Ok(())\n    }\n  }\n\n  #[cfg(target_os = \"solaris\")]\n  fn flock(file: &File, flag: libc::c_int) -> Result<()> {\n    // Solaris lacks flock(), so simply succeed with a no-op\n    Ok(())\n  }\n}\n\n#[cfg(windows)]\nmod sys {\n  use std::fs::File;\n  use std::io::{Error, Result};\n  use std::mem;\n  use std::os::windows::io::AsRawHandle;\n\n  use windows_sys::Win32::Foundation::{ERROR_INVALID_FUNCTION, ERROR_LOCK_VIOLATION, HANDLE};\n  use windows_sys::Win32::Storage::FileSystem::{\n    LockFileEx, UnlockFile, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LOCK_FILE_FLAGS,\n  };\n\n  pub(super) fn lock_shared(file: &File) -> Result<()> {\n    lock_file(file, 0)\n  }\n\n  pub(super) fn lock_exclusive(file: &File) -> Result<()> {\n    lock_file(file, LOCKFILE_EXCLUSIVE_LOCK)\n  }\n\n  pub(super) fn try_lock_shared(file: &File) -> Result<()> {\n    lock_file(file, LOCKFILE_FAIL_IMMEDIATELY)\n  }\n\n  pub(super) fn try_lock_exclusive(file: &File) -> Result<()> {\n    lock_file(file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY)\n  }\n\n  pub(super) fn error_contended(err: &Error) -> bool {\n    err.raw_os_error() == Some(ERROR_LOCK_VIOLATION as i32)\n  }\n\n  pub(super) fn error_unsupported(err: &Error) -> bool {\n    err.raw_os_error() == Some(ERROR_INVALID_FUNCTION as i32)\n  }\n\n  pub(super) fn unlock(file: &File) -> Result<()> {\n    let ret = unsafe { UnlockFile(file.as_raw_handle() as HANDLE, 0, 0, !0, !0) };\n    if ret == 0 {\n      Err(Error::last_os_error())\n    } else {\n      Ok(())\n    }\n  }\n\n  fn lock_file(file: &File, flags: LOCK_FILE_FLAGS) -> Result<()> {\n    let ret = unsafe {\n      let mut overlapped = mem::zeroed();\n      LockFileEx(\n        file.as_raw_handle() as HANDLE,\n        flags,\n        0,\n        !0,\n        !0,\n        &mut overlapped,\n      )\n    };\n\n    if ret == 0 {\n      Err(Error::last_os_error())\n    } else {\n      Ok(())\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/framework.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::fmt;\n\n#[derive(Debug, Clone)]\npub enum Framework {\n  SolidJS,\n  SolidStart,\n  Svelte,\n  SvelteKit,\n  Angular,\n  React,\n  Nextjs,\n  Gatsby,\n  Nuxt,\n  Quasar,\n  VueCli,\n  Vue,\n}\n\nimpl Framework {\n  pub fn dev_url(&self) -> String {\n    match self {\n      Self::SolidJS => \"http://localhost:3000\",\n      Self::SolidStart => \"http://localhost:3000\",\n      Self::Svelte => \"http://localhost:8080\",\n      Self::SvelteKit => \"http://localhost:5173\",\n      Self::Angular => \"http://localhost:4200\",\n      Self::React => \"http://localhost:3000\",\n      Self::Nextjs => \"http://localhost:3000\",\n      Self::Gatsby => \"http://localhost:8000\",\n      Self::Nuxt => \"http://localhost:3000\",\n      Self::Quasar => \"http://localhost:8080\",\n      Self::VueCli => \"http://localhost:8080\",\n      Self::Vue => \"http://localhost:8080\",\n    }\n    .into()\n  }\n\n  pub fn frontend_dist(&self) -> String {\n    match self {\n      Self::SolidJS => \"../dist\",\n      Self::SolidStart => \"../dist/public\",\n      Self::Svelte => \"../public\",\n      Self::SvelteKit => \"../build\",\n      Self::Angular => \"../dist\",\n      Self::React => \"../build\",\n      Self::Nextjs => \"../out\",\n      Self::Gatsby => \"../public\",\n      Self::Nuxt => \"../dist\",\n      Self::Quasar => \"../dist/spa\",\n      Self::VueCli => \"../dist\",\n      Self::Vue => \"../dist\",\n    }\n    .into()\n  }\n}\n\nimpl fmt::Display for Framework {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    match self {\n      Self::SolidJS => write!(f, \"SolidJS\"),\n      Self::SolidStart => write!(f, \"SolidStart\"),\n      Self::Svelte => write!(f, \"Svelte\"),\n      Self::SvelteKit => write!(f, \"SvelteKit\"),\n      Self::Angular => write!(f, \"Angular\"),\n      Self::React => write!(f, \"React\"),\n      Self::Nextjs => write!(f, \"React (Next.js)\"),\n      Self::Gatsby => write!(f, \"React (Gatsby)\"),\n      Self::Nuxt => write!(f, \"Vue.js (Nuxt)\"),\n      Self::Quasar => write!(f, \"Vue.js (Quasar)\"),\n      Self::VueCli => write!(f, \"Vue.js (Vue CLI)\"),\n      Self::Vue => write!(f, \"Vue.js\"),\n    }\n  }\n}\n\n#[derive(Debug, Clone)]\npub enum Bundler {\n  Webpack,\n  Rollup,\n  Vite,\n}\n\nimpl fmt::Display for Bundler {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    match self {\n      Self::Webpack => write!(f, \"Webpack\"),\n      Self::Rollup => write!(f, \"Rollup\"),\n      Self::Vite => write!(f, \"Vite\"),\n    }\n  }\n}\n\npub fn infer_from_package_json(package_json: &str) -> (Option<Framework>, Option<Bundler>) {\n  let framework_map = [\n    (\"solid-start\", Framework::SolidStart, Some(Bundler::Vite)),\n    (\"solid-js\", Framework::SolidJS, Some(Bundler::Vite)),\n    (\"svelte\", Framework::Svelte, Some(Bundler::Rollup)),\n    (\"@sveltejs/kit\", Framework::SvelteKit, Some(Bundler::Vite)),\n    (\"@angular\", Framework::Angular, Some(Bundler::Webpack)),\n    (r#\"\"next\"\"#, Framework::Nextjs, Some(Bundler::Webpack)),\n    (\"gatsby\", Framework::Gatsby, Some(Bundler::Webpack)),\n    (\"react\", Framework::React, None),\n    (\"nuxt\", Framework::Nuxt, Some(Bundler::Webpack)),\n    (\"quasar\", Framework::Quasar, Some(Bundler::Webpack)),\n    (\"@vue/cli\", Framework::VueCli, Some(Bundler::Webpack)),\n    (\"vue\", Framework::Vue, Some(Bundler::Vite)),\n  ];\n  let bundler_map = [\n    (\"webpack\", Bundler::Webpack),\n    (\"rollup\", Bundler::Rollup),\n    (\"vite\", Bundler::Vite),\n  ];\n\n  let (framework, framework_bundler) = framework_map\n    .iter()\n    .find(|(keyword, _, _)| package_json.contains(keyword))\n    .map(|(_, framework, bundler)| (Some(framework.clone()), bundler.clone()))\n    .unwrap_or((None, None));\n\n  let bundler = bundler_map\n    .iter()\n    .find(|(keyword, _)| package_json.contains(keyword))\n    .map(|(_, bundler)| bundler.clone())\n    .or(framework_bundler);\n\n  (framework, bundler)\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/fs.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::{Context, ErrorExt},\n  Error,\n};\nuse std::path::{Path, PathBuf};\n\npub fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> crate::Result<()> {\n  let from = from.as_ref();\n  let to = to.as_ref();\n  if !from.exists() {\n    Err(Error::Fs {\n      context: \"failed to copy file\",\n      path: from.to_path_buf(),\n      error: std::io::Error::new(std::io::ErrorKind::NotFound, \"source does not exist\"),\n    })?;\n  }\n  if !from.is_file() {\n    Err(Error::Fs {\n      context: \"failed to copy file\",\n      path: from.to_path_buf(),\n      error: std::io::Error::other(\"not a file\"),\n    })?;\n  }\n  let dest_dir = to.parent().expect(\"No data in parent\");\n  std::fs::create_dir_all(dest_dir)\n    .fs_context(\"failed to create directory\", dest_dir.to_path_buf())?;\n  std::fs::copy(from, to).fs_context(\"failed to copy file\", from.to_path_buf())?;\n  Ok(())\n}\n\n/// Find an entry in a directory matching a glob pattern.\n/// Currently does not traverse subdirectories.\n// currently only used on macOS\n#[allow(dead_code)]\npub fn find_in_directory(path: &Path, glob_pattern: &str) -> crate::Result<PathBuf> {\n  let pattern = glob::Pattern::new(glob_pattern)\n    .with_context(|| format!(\"failed to parse glob pattern {glob_pattern}\"))?;\n  for entry in std::fs::read_dir(path)\n    .with_context(|| format!(\"failed to read directory {}\", path.display()))?\n  {\n    let entry = entry.context(\"failed to read directory entry\")?;\n    if pattern.matches_path(&entry.path()) {\n      return Ok(entry.path());\n    }\n  }\n  crate::error::bail!(\n    \"No file found in {} matching {}\",\n    path.display(),\n    glob_pattern\n  )\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/http.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse ureq::{http::Response, Agent, Body};\n\nconst CLI_USER_AGENT: &str = concat!(env!(\"CARGO_PKG_NAME\"), \"/\", env!(\"CARGO_PKG_VERSION\"),);\n\npub fn get(url: &str) -> Result<Response<Body>, ureq::Error> {\n  #[allow(unused_mut)]\n  let mut config_builder = ureq::Agent::config_builder()\n    .user_agent(CLI_USER_AGENT)\n    .proxy(ureq::Proxy::try_from_env());\n\n  #[cfg(feature = \"platform-certs\")]\n  {\n    config_builder = config_builder.tls_config(\n      ureq::tls::TlsConfig::builder()\n        .root_certs(ureq::tls::RootCerts::PlatformVerifier)\n        .build(),\n    );\n  }\n\n  let agent: Agent = config_builder.build().into();\n  agent.get(url).call()\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/icns.json",
    "content": "{\n  \"16x16\": {\n    \"name\": \"icon_16x16.png\",\n    \"size\": 16,\n    \"ostype\": \"is32\"\n  },\n  \"16x16@2x\": {\n    \"name\": \"icon_16x16@2x.png\",\n    \"size\": 32,\n    \"ostype\": \"ic11\"\n  },\n  \"32x32\": {\n    \"name\": \"icon_32x32.png\",\n    \"size\": 32,\n    \"ostype\": \"il32\"\n  },\n  \"32x32@2x\": {\n    \"name\": \"icon_32x32@2x.png\",\n    \"size\": 64,\n    \"ostype\": \"ic12\"\n  },\n  \"128x128\": {\n    \"name\": \"icon_128x128.png\",\n    \"size\": 128,\n    \"ostype\": \"ic07\"\n  },\n  \"128x128@2x\": {\n    \"name\": \"icon_128x128@2x.png\",\n    \"size\": 256,\n    \"ostype\": \"ic13\"\n  },\n  \"256x256\": {\n    \"name\": \"icon_256x256.png\",\n    \"size\": 256,\n    \"ostype\": \"ic08\"\n  },\n  \"256x256@2x\": {\n    \"name\": \"icon_256x256@2x.png\",\n    \"size\": 512,\n    \"ostype\": \"ic14\"\n  },\n  \"512x512\": {\n    \"name\": \"icon_512x512.png\",\n    \"size\": 512,\n    \"ostype\": \"ic09\"\n  },\n  \"512x512@2x\": {\n    \"name\": \"icon_512x512@2x.png\",\n    \"size\": 1024,\n    \"ostype\": \"ic10\"\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npub mod app_paths;\npub mod cargo;\npub mod cargo_manifest;\npub mod config;\npub mod flock;\npub mod framework;\npub mod fs;\npub mod http;\npub mod npm;\n#[cfg(target_os = \"macos\")]\npub mod pbxproj;\n#[cfg(target_os = \"macos\")]\npub mod plist;\npub mod plugins;\npub mod prompts;\npub mod template;\npub mod updater_signature;\n\nuse std::{\n  collections::HashMap,\n  path::{Path, PathBuf},\n  process::Command,\n};\n\nuse tauri_utils::config::HookCommand;\n\n#[cfg(not(target_os = \"windows\"))]\nuse crate::Error;\nuse crate::{interface::AppInterface, CommandExt};\n\npub fn command_env(debug: bool) -> HashMap<&'static str, String> {\n  let mut map = HashMap::new();\n\n  map.insert(\n    \"TAURI_ENV_PLATFORM_VERSION\",\n    os_info::get().version().to_string(),\n  );\n\n  if debug {\n    map.insert(\"TAURI_ENV_DEBUG\", \"true\".into());\n  }\n\n  map\n}\n\npub fn resolve_tauri_path<P: AsRef<Path>>(path: P, crate_name: &str) -> PathBuf {\n  let path = path.as_ref();\n  if path.is_absolute() {\n    path.join(crate_name)\n  } else {\n    PathBuf::from(\"..\").join(path).join(crate_name)\n  }\n}\n\npub fn cross_command(bin: &str) -> Command {\n  #[cfg(target_os = \"windows\")]\n  let cmd = {\n    let mut cmd = Command::new(\"cmd\");\n    cmd.arg(\"/c\").arg(bin);\n    cmd\n  };\n  #[cfg(not(target_os = \"windows\"))]\n  let cmd = Command::new(bin);\n  cmd\n}\n\npub fn run_hook(\n  name: &str,\n  hook: HookCommand,\n  interface: &AppInterface,\n  debug: bool,\n  frontend_dir: &Path,\n) -> crate::Result<()> {\n  let (script, script_cwd) = match hook {\n    HookCommand::Script(s) if s.is_empty() => (None, None),\n    HookCommand::Script(s) => (Some(s), None),\n    HookCommand::ScriptWithOptions { script, cwd } => (Some(script), cwd.map(Into::into)),\n  };\n  let cwd = script_cwd.unwrap_or_else(|| frontend_dir.to_owned());\n  if let Some(script) = script {\n    log::info!(action = \"Running\"; \"{} `{}`\", name, script);\n\n    let mut env = command_env(debug);\n    env.extend(interface.env());\n\n    log::debug!(\"Setting environment for hook {:?}\", env);\n\n    #[cfg(target_os = \"windows\")]\n    let status = Command::new(\"cmd\")\n      .arg(\"/S\")\n      .arg(\"/C\")\n      .arg(&script)\n      .current_dir(cwd)\n      .envs(env)\n      .piped()\n      .map_err(|error| crate::error::Error::CommandFailed {\n        command: script.clone(),\n        error,\n      })?;\n    #[cfg(not(target_os = \"windows\"))]\n    let status = Command::new(\"sh\")\n      .arg(\"-c\")\n      .arg(&script)\n      .current_dir(cwd)\n      .envs(env)\n      .piped()\n      .map_err(|error| Error::CommandFailed {\n        command: script.clone(),\n        error,\n      })?;\n\n    if !status.success() {\n      crate::error::bail!(\n        \"{} `{}` failed with exit code {}\",\n        name,\n        script,\n        status.code().unwrap_or_default()\n      );\n    }\n  }\n\n  Ok(())\n}\n\n#[cfg(target_os = \"macos\")]\npub fn strip_semver_prerelease_tag(version: &mut semver::Version) -> crate::Result<()> {\n  use crate::error::Context;\n  if !version.pre.is_empty() {\n    if let Some((_prerelease_tag, number)) = version.pre.as_str().to_string().split_once('.') {\n      version.pre = semver::Prerelease::EMPTY;\n      version.build = semver::BuildMetadata::new(&format!(\n        \"{prefix}{number}\",\n        prefix = if version.build.is_empty() {\n          \"\".to_string()\n        } else {\n          format!(\".{}\", version.build.as_str())\n        }\n      ))\n      .with_context(|| {\n        format!(\n          \"failed to parse {version} as semver: bundle version {number:?} prerelease is invalid\"\n        )\n      })?;\n    }\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/npm.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::Deserialize;\n\nuse crate::{\n  error::{Context, Error},\n  helpers::cross_command,\n};\nuse std::{collections::HashMap, fmt::Display, path::Path, process::Command};\n\npub fn manager_version(package_manager: &str) -> Option<String> {\n  cross_command(package_manager)\n    .arg(\"-v\")\n    .output()\n    .map(|o| {\n      if o.status.success() {\n        let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string();\n        Some(v.split('\\n').next().unwrap().to_string())\n      } else {\n        None\n      }\n    })\n    .ok()\n    .unwrap_or_default()\n}\n\nfn detect_yarn_or_berry() -> PackageManager {\n  if manager_version(\"yarn\")\n    .map(|v| v.chars().next().map(|c| c > '1').unwrap_or_default())\n    .unwrap_or(false)\n  {\n    PackageManager::YarnBerry\n  } else {\n    PackageManager::Yarn\n  }\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Copy)]\npub enum PackageManager {\n  Npm,\n  Pnpm,\n  Yarn,\n  YarnBerry,\n  Bun,\n  Deno,\n}\n\nimpl Display for PackageManager {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    write!(\n      f,\n      \"{}\",\n      match self {\n        PackageManager::Npm => \"npm\",\n        PackageManager::Pnpm => \"pnpm\",\n        PackageManager::Yarn => \"yarn\",\n        PackageManager::YarnBerry => \"yarn berry\",\n        PackageManager::Bun => \"bun\",\n        PackageManager::Deno => \"deno\",\n      }\n    )\n  }\n}\n\nimpl PackageManager {\n  /// Detects package manager from the given directory, falls back to [`PackageManager::Npm`].\n  pub fn from_project<P: AsRef<Path>>(path: P) -> Self {\n    Self::all_from_project(path)\n      .first()\n      .copied()\n      .unwrap_or(Self::Npm)\n  }\n\n  /// Detects package manager from the `npm_config_user_agent` environment variable\n  fn from_environment_variable() -> Option<Self> {\n    let npm_config_user_agent = std::env::var(\"npm_config_user_agent\").ok()?;\n    match npm_config_user_agent {\n      user_agent if user_agent.starts_with(\"pnpm/\") => Some(Self::Pnpm),\n      user_agent if user_agent.starts_with(\"deno/\") => Some(Self::Deno),\n      user_agent if user_agent.starts_with(\"bun/\") => Some(Self::Bun),\n      user_agent if user_agent.starts_with(\"yarn/\") => Some(detect_yarn_or_berry()),\n      user_agent if user_agent.starts_with(\"npm/\") => Some(Self::Npm),\n      _ => None,\n    }\n  }\n\n  /// Detects all possible package managers from the given directory.\n  pub fn all_from_project<P: AsRef<Path>>(path: P) -> Vec<Self> {\n    if let Some(from_env) = Self::from_environment_variable() {\n      return vec![from_env];\n    }\n\n    let mut found = Vec::new();\n\n    if let Ok(entries) = std::fs::read_dir(path) {\n      for entry in entries.flatten() {\n        let path = entry.path();\n        let name = path.file_name().unwrap().to_string_lossy();\n        match name.as_ref() {\n          \"package-lock.json\" => found.push(PackageManager::Npm),\n          \"pnpm-lock.yaml\" => found.push(PackageManager::Pnpm),\n          \"yarn.lock\" => found.push(detect_yarn_or_berry()),\n          \"bun.lock\" | \"bun.lockb\" => found.push(PackageManager::Bun),\n          \"deno.lock\" => found.push(PackageManager::Deno),\n          _ => (),\n        }\n      }\n    }\n\n    found\n  }\n\n  fn cross_command(&self) -> Command {\n    match self {\n      PackageManager::Yarn => cross_command(\"yarn\"),\n      PackageManager::YarnBerry => cross_command(\"yarn\"),\n      PackageManager::Npm => cross_command(\"npm\"),\n      PackageManager::Pnpm => cross_command(\"pnpm\"),\n      PackageManager::Bun => cross_command(\"bun\"),\n      PackageManager::Deno => cross_command(\"deno\"),\n    }\n  }\n\n  pub fn install<P: AsRef<Path>>(\n    &self,\n    dependencies: &[String],\n    frontend_dir: P,\n  ) -> crate::Result<()> {\n    let dependencies_str = if dependencies.len() > 1 {\n      \"dependencies\"\n    } else {\n      \"dependency\"\n    };\n    log::info!(\n      \"Installing NPM {dependencies_str} {}...\",\n      dependencies\n        .iter()\n        .map(|d| format!(\"\\\"{d}\\\"\"))\n        .collect::<Vec<_>>()\n        .join(\", \")\n    );\n\n    let mut command = self.cross_command();\n    command.arg(\"add\");\n\n    match self {\n      PackageManager::Deno => command.args(dependencies.iter().map(|d| format!(\"npm:{d}\"))),\n      _ => command.args(dependencies),\n    };\n\n    let status = command\n      .current_dir(frontend_dir)\n      .status()\n      .map_err(|error| Error::CommandFailed {\n        command: format!(\"failed to run {self}\"),\n        error,\n      })?;\n\n    if !status.success() {\n      crate::error::bail!(\"Failed to install NPM {dependencies_str}\");\n    }\n\n    Ok(())\n  }\n\n  pub fn remove<P: AsRef<Path>>(\n    &self,\n    dependencies: &[String],\n    frontend_dir: P,\n  ) -> crate::Result<()> {\n    let dependencies_str = if dependencies.len() > 1 {\n      \"dependencies\"\n    } else {\n      \"dependency\"\n    };\n    log::info!(\n      \"Removing NPM {dependencies_str} {}...\",\n      dependencies\n        .iter()\n        .map(|d| format!(\"\\\"{d}\\\"\"))\n        .collect::<Vec<_>>()\n        .join(\", \")\n    );\n\n    let status = self\n      .cross_command()\n      .arg(if *self == Self::Npm {\n        \"uninstall\"\n      } else {\n        \"remove\"\n      })\n      .args(dependencies)\n      .current_dir(frontend_dir)\n      .status()\n      .map_err(|error| Error::CommandFailed {\n        command: format!(\"failed to run {self}\"),\n        error,\n      })?;\n\n    if !status.success() {\n      crate::error::bail!(\"Failed to remove NPM {dependencies_str}\");\n    }\n\n    Ok(())\n  }\n\n  // TODO: Use `current_package_versions` as much as possible for better speed\n  pub fn current_package_version<P: AsRef<Path>>(\n    &self,\n    name: &str,\n    frontend_dir: P,\n  ) -> crate::Result<Option<String>> {\n    let (output, regex) = match self {\n      PackageManager::Yarn => (\n        cross_command(\"yarn\")\n          .args([\"list\", \"--pattern\"])\n          .arg(name)\n          .args([\"--depth\", \"0\"])\n          .current_dir(frontend_dir)\n          .output()\n          .map_err(|error| Error::CommandFailed {\n            command: \"yarn list --pattern\".to_string(),\n            error,\n          })?,\n        None,\n      ),\n      PackageManager::YarnBerry => (\n        cross_command(\"yarn\")\n          .arg(\"info\")\n          .arg(name)\n          .arg(\"--json\")\n          .current_dir(frontend_dir)\n          .output()\n          .map_err(|error| Error::CommandFailed {\n            command: \"yarn info --json\".to_string(),\n            error,\n          })?,\n        Some(regex::Regex::new(\"\\\"Version\\\":\\\"([\\\\da-zA-Z\\\\-\\\\.]+)\\\"\").unwrap()),\n      ),\n      PackageManager::Pnpm => (\n        cross_command(\"pnpm\")\n          .arg(\"list\")\n          .arg(name)\n          .args([\"--parseable\", \"--depth\", \"0\"])\n          .current_dir(frontend_dir)\n          .output()\n          .map_err(|error| Error::CommandFailed {\n            command: \"pnpm list --parseable --depth 0\".to_string(),\n            error,\n          })?,\n        None,\n      ),\n      // Bun and Deno don't support `list` command\n      PackageManager::Npm | PackageManager::Bun | PackageManager::Deno => (\n        cross_command(\"npm\")\n          .arg(\"list\")\n          .arg(name)\n          .args([\"version\", \"--depth\", \"0\"])\n          .current_dir(frontend_dir)\n          .output()\n          .map_err(|error| Error::CommandFailed {\n            command: \"npm list --version --depth 0\".to_string(),\n            error,\n          })?,\n        None,\n      ),\n    };\n    if output.status.success() {\n      let stdout = String::from_utf8_lossy(&output.stdout);\n      let regex = regex.unwrap_or_else(|| regex::Regex::new(\"@(\\\\d[\\\\da-zA-Z\\\\-\\\\.]+)\").unwrap());\n      Ok(\n        regex\n          .captures_iter(&stdout)\n          .last()\n          .and_then(|cap| cap.get(1).map(|v| v.as_str().to_string())),\n      )\n    } else {\n      Ok(None)\n    }\n  }\n\n  pub fn current_package_versions(\n    &self,\n    packages: &[String],\n    frontend_dir: &Path,\n  ) -> crate::Result<HashMap<String, semver::Version>> {\n    let output = match self {\n      PackageManager::Yarn => return yarn_package_versions(packages, frontend_dir),\n      PackageManager::YarnBerry => return yarn_berry_package_versions(packages, frontend_dir),\n      PackageManager::Pnpm => cross_command(\"pnpm\")\n        .arg(\"list\")\n        .args(packages)\n        .args([\"--json\", \"--depth\", \"0\"])\n        .current_dir(frontend_dir)\n        .output()\n        .map_err(|error| Error::CommandFailed {\n          command: \"pnpm list --json --depth 0\".to_string(),\n          error,\n        })?,\n      // Bun and Deno don't support `list` command\n      PackageManager::Npm | PackageManager::Bun | PackageManager::Deno => cross_command(\"npm\")\n        .arg(\"list\")\n        .args(packages)\n        .args([\"--json\", \"--depth\", \"0\"])\n        .current_dir(frontend_dir)\n        .output()\n        .map_err(|error| Error::CommandFailed {\n          command: \"npm list --json --depth 0\".to_string(),\n          error,\n        })?,\n    };\n\n    let mut versions = HashMap::new();\n    let stdout = String::from_utf8_lossy(&output.stdout);\n    if !output.status.success() {\n      return Ok(versions);\n    }\n\n    #[derive(Deserialize)]\n    #[serde(rename_all = \"camelCase\")]\n    struct ListOutput {\n      #[serde(default)]\n      dependencies: HashMap<String, ListDependency>,\n      #[serde(default)]\n      dev_dependencies: HashMap<String, ListDependency>,\n    }\n\n    #[derive(Deserialize)]\n    struct ListDependency {\n      version: String,\n    }\n\n    let json = if matches!(self, PackageManager::Pnpm) {\n      serde_json::from_str::<Vec<ListOutput>>(&stdout)\n        .ok()\n        .and_then(|out| out.into_iter().next())\n        .context(\"failed to parse pnpm list\")?\n    } else {\n      serde_json::from_str::<ListOutput>(&stdout).context(\"failed to parse npm list\")?\n    };\n    for (package, dependency) in json.dependencies.into_iter().chain(json.dev_dependencies) {\n      let version = dependency.version;\n      if let Ok(version) = semver::Version::parse(&version) {\n        versions.insert(package, version);\n      } else {\n        log::debug!(\"Failed to parse version `{version}` for NPM package `{package}`\");\n      }\n    }\n    Ok(versions)\n  }\n}\n\nfn yarn_package_versions(\n  packages: &[String],\n  frontend_dir: &Path,\n) -> crate::Result<HashMap<String, semver::Version>> {\n  let output = cross_command(\"yarn\")\n    .arg(\"list\")\n    .args(packages)\n    .args([\"--json\", \"--depth\", \"0\"])\n    .current_dir(frontend_dir)\n    .output()\n    .map_err(|error| Error::CommandFailed {\n      command: \"yarn list --json --depth 0\".to_string(),\n      error,\n    })?;\n\n  let mut versions = HashMap::new();\n  let stdout = String::from_utf8_lossy(&output.stdout);\n  if !output.status.success() {\n    return Ok(versions);\n  }\n\n  #[derive(Deserialize)]\n  struct YarnListOutput {\n    data: YarnListOutputData,\n  }\n\n  #[derive(Deserialize)]\n  struct YarnListOutputData {\n    trees: Vec<YarnListOutputDataTree>,\n  }\n\n  #[derive(Deserialize)]\n  struct YarnListOutputDataTree {\n    name: String,\n  }\n\n  for line in stdout.lines() {\n    if let Ok(tree) = serde_json::from_str::<YarnListOutput>(line) {\n      for tree in tree.data.trees {\n        let Some((name, version)) = tree.name.rsplit_once('@') else {\n          continue;\n        };\n        if let Ok(version) = semver::Version::parse(version) {\n          versions.insert(name.to_owned(), version);\n        } else {\n          log::debug!(\"Failed to parse version `{version}` for NPM package `{name}`\");\n        }\n      }\n      return Ok(versions);\n    }\n  }\n\n  Ok(versions)\n}\n\nfn yarn_berry_package_versions(\n  packages: &[String],\n  frontend_dir: &Path,\n) -> crate::Result<HashMap<String, semver::Version>> {\n  let output = cross_command(\"yarn\")\n    .args([\"info\", \"--json\"])\n    .current_dir(frontend_dir)\n    .output()\n    .map_err(|error| Error::CommandFailed {\n      command: \"yarn info --json\".to_string(),\n      error,\n    })?;\n\n  let mut versions = HashMap::new();\n  let stdout = String::from_utf8_lossy(&output.stdout);\n  if !output.status.success() {\n    return Ok(versions);\n  }\n\n  #[derive(Deserialize)]\n  struct YarnBerryInfoOutput {\n    value: String,\n    children: YarnBerryInfoOutputChildren,\n  }\n\n  #[derive(Deserialize)]\n  #[serde(rename_all = \"PascalCase\")]\n  struct YarnBerryInfoOutputChildren {\n    version: String,\n  }\n\n  for line in stdout.lines() {\n    if let Ok(info) = serde_json::from_str::<YarnBerryInfoOutput>(line) {\n      let Some((name, _)) = info.value.rsplit_once('@') else {\n        continue;\n      };\n      if !packages.iter().any(|package| package == name) {\n        continue;\n      }\n      let version = info.children.version;\n      if let Ok(version) = semver::Version::parse(&version) {\n        versions.insert(name.to_owned(), version);\n      } else {\n        log::debug!(\"Failed to parse version `{version}` for NPM package `{name}`\");\n      }\n    }\n  }\n\n  Ok(versions)\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/pbxproj.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  collections::BTreeMap,\n  fmt,\n  path::{Path, PathBuf},\n};\n\nuse crate::error::ErrorExt;\n\npub fn parse<P: AsRef<Path>>(path: P) -> crate::Result<Pbxproj> {\n  let path = path.as_ref();\n  let pbxproj =\n    std::fs::read_to_string(path).fs_context(\"failed to read pbxproj file\", path.to_path_buf())?;\n\n  let mut proj = Pbxproj {\n    path: path.to_owned(),\n    raw_lines: pbxproj.split('\\n').map(ToOwned::to_owned).collect(),\n    xc_build_configuration: BTreeMap::new(),\n    xc_configuration_list: BTreeMap::new(),\n    additions: BTreeMap::new(),\n    has_changes: false,\n  };\n\n  let mut state = State::Idle;\n\n  let mut iter = proj.raw_lines.iter().enumerate();\n\n  while let Some((line_number, line)) = iter.next() {\n    match &state {\n      State::Idle => {\n        if line == \"/* Begin XCBuildConfiguration section */\" {\n          state = State::XCBuildConfiguration;\n        } else if line == \"/* Begin XCConfigurationList section */\" {\n          state = State::XCConfigurationList;\n        }\n      }\n      // XCBuildConfiguration\n      State::XCBuildConfiguration => {\n        if line == \"/* End XCBuildConfiguration section */\" {\n          state = State::Idle;\n        } else if let Some((_identation, token)) = split_at_identation(line) {\n          let id: String = token.chars().take_while(|c| !c.is_whitespace()).collect();\n          proj.xc_build_configuration.insert(\n            id.clone(),\n            XCBuildConfiguration {\n              build_settings: Vec::new(),\n            },\n          );\n          state = State::XCBuildConfigurationObject { id };\n        }\n      }\n      State::XCBuildConfigurationObject { id } => {\n        if line.contains(\"buildSettings\") {\n          state = State::XCBuildConfigurationObjectBuildSettings { id: id.clone() };\n        } else if split_at_identation(line).is_some_and(|(_ident, token)| token == \"};\") {\n          state = State::XCBuildConfiguration;\n        }\n      }\n      State::XCBuildConfigurationObjectBuildSettings { id } => {\n        if let Some((identation, token)) = split_at_identation(line) {\n          if token == \"};\" {\n            state = State::XCBuildConfigurationObject { id: id.clone() };\n          } else {\n            let assignment = token.trim_end_matches(';');\n            if let Some((key, value)) = assignment.split_once(\" = \") {\n              // multiline value\n              let value = if value == \"(\" {\n                let mut value = value.to_string();\n                loop {\n                  let Some((_next_line_number, next_line)) = iter.next() else {\n                    break;\n                  };\n\n                  value.push_str(next_line);\n                  value.push('\\n');\n\n                  if let Some((_, token)) = split_at_identation(next_line) {\n                    if token == \");\" {\n                      break;\n                    }\n                  }\n                }\n                value\n              } else {\n                value.trim().to_string()\n              };\n\n              proj\n                .xc_build_configuration\n                .get_mut(id)\n                .unwrap()\n                .build_settings\n                .push(BuildSettings {\n                  identation: identation.into(),\n                  line_number,\n                  key: key.trim().into(),\n                  value,\n                });\n            }\n          }\n        }\n      }\n      // XCConfigurationList\n      State::XCConfigurationList => {\n        if line == \"/* End XCConfigurationList section */\" {\n          state = State::Idle;\n        } else if let Some((_identation, token)) = split_at_identation(line) {\n          let Some((id, comment)) = token.split_once(' ') else {\n            continue;\n          };\n\n          proj.xc_configuration_list.insert(\n            id.to_string(),\n            XCConfigurationList {\n              comment: comment.trim_end_matches(\" = {\").to_string(),\n              build_configurations: Vec::new(),\n            },\n          );\n          state = State::XCConfigurationListObject { id: id.to_string() };\n        }\n      }\n      State::XCConfigurationListObject { id } => {\n        if line.contains(\"buildConfigurations\") {\n          state = State::XCConfigurationListObjectBuildConfigurations { id: id.clone() };\n        } else if split_at_identation(line).is_some_and(|(_ident, token)| token == \"};\") {\n          state = State::XCConfigurationList;\n        }\n      }\n      State::XCConfigurationListObjectBuildConfigurations { id } => {\n        if let Some((_identation, token)) = split_at_identation(line) {\n          if token == \");\" {\n            state = State::XCConfigurationListObject { id: id.clone() };\n          } else {\n            let Some((build_configuration_id, comments)) = token.split_once(' ') else {\n              continue;\n            };\n            proj\n              .xc_configuration_list\n              .get_mut(id)\n              .unwrap()\n              .build_configurations\n              .push(BuildConfigurationRef {\n                id: build_configuration_id.to_string(),\n                comments: comments.trim_end_matches(',').to_string(),\n              });\n          }\n        }\n      }\n    }\n  }\n\n  Ok(proj)\n}\n\nfn split_at_identation(s: &str) -> Option<(&str, &str)> {\n  s.chars()\n    .position(|c| !c.is_ascii_whitespace())\n    .map(|pos| s.split_at(pos))\n}\n\nenum State {\n  Idle,\n  // XCBuildConfiguration\n  XCBuildConfiguration,\n  XCBuildConfigurationObject { id: String },\n  XCBuildConfigurationObjectBuildSettings { id: String },\n  // XCConfigurationList\n  XCConfigurationList,\n  XCConfigurationListObject { id: String },\n  XCConfigurationListObjectBuildConfigurations { id: String },\n}\n\npub struct Pbxproj {\n  pub path: PathBuf,\n  raw_lines: Vec<String>,\n  pub xc_build_configuration: BTreeMap<String, XCBuildConfiguration>,\n  pub xc_configuration_list: BTreeMap<String, XCConfigurationList>,\n\n  // maps the line number to the line to add\n  additions: BTreeMap<usize, String>,\n\n  has_changes: bool,\n}\n\nimpl fmt::Debug for Pbxproj {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"Pbxproj\")\n      .field(\"xc_build_configuration\", &self.xc_build_configuration)\n      .field(\"xc_configuration_list\", &self.xc_configuration_list)\n      .finish()\n  }\n}\n\nimpl Pbxproj {\n  pub fn has_changes(&self) -> bool {\n    !self.additions.is_empty() || self.has_changes\n  }\n\n  fn serialize(&self) -> String {\n    let mut proj = String::new();\n    let last_line_number = self.raw_lines.len() - 1;\n\n    for (number, line) in self.raw_lines.iter().enumerate() {\n      if let Some(new) = self.additions.get(&number) {\n        proj.push_str(new);\n        proj.push('\\n');\n      }\n\n      proj.push_str(line);\n      if number != last_line_number {\n        proj.push('\\n');\n      }\n    }\n\n    proj\n  }\n\n  pub fn save(&self) -> std::io::Result<()> {\n    std::fs::write(&self.path, self.serialize())\n  }\n\n  pub fn set_build_settings(&mut self, build_configuration_id: &str, key: &str, value: &str) {\n    let Some(build_configuration) = self.xc_build_configuration.get_mut(build_configuration_id)\n    else {\n      return;\n    };\n\n    if let Some(build_setting) = build_configuration\n      .build_settings\n      .iter_mut()\n      .find(|s| s.key == key)\n    {\n      if build_setting.value != value {\n        let Some(line) = self.raw_lines.get_mut(build_setting.line_number) else {\n          return;\n        };\n\n        *line = format!(\"{}{key} = {value};\", build_setting.identation);\n        self.has_changes = true;\n      }\n    } else {\n      let Some(last_build_setting) = build_configuration.build_settings.last().cloned() else {\n        return;\n      };\n      build_configuration.build_settings.push(BuildSettings {\n        identation: last_build_setting.identation.clone(),\n        line_number: last_build_setting.line_number + 1,\n        key: key.to_string(),\n        value: value.to_string(),\n      });\n      self.additions.insert(\n        last_build_setting.line_number + 1,\n        format!(\"{}{key} = {value};\", last_build_setting.identation),\n      );\n    }\n  }\n}\n\n#[derive(Debug)]\npub struct XCBuildConfiguration {\n  build_settings: Vec<BuildSettings>,\n}\n\nimpl XCBuildConfiguration {\n  pub fn get_build_setting(&self, key: &str) -> Option<&BuildSettings> {\n    self.build_settings.iter().find(|s| s.key == key)\n  }\n}\n\n#[derive(Debug, Clone)]\npub struct BuildSettings {\n  identation: String,\n  line_number: usize,\n  pub key: String,\n  pub value: String,\n}\n\n#[derive(Debug, Clone)]\npub struct XCConfigurationList {\n  pub comment: String,\n  pub build_configurations: Vec<BuildConfigurationRef>,\n}\n\n#[derive(Debug, Clone)]\npub struct BuildConfigurationRef {\n  pub id: String,\n  pub comments: String,\n}\n\n#[cfg(test)]\nmod tests {\n  #[test]\n  fn parse() {\n    let manifest_dir = std::path::Path::new(env!(\"CARGO_MANIFEST_DIR\"));\n    let fixtures_path = manifest_dir.join(\"tests\").join(\"fixtures\").join(\"pbxproj\");\n\n    let mut settings = insta::Settings::clone_current();\n    settings.set_snapshot_path(fixtures_path.join(\"snapshots\"));\n    let _guard = settings.bind_to_scope();\n\n    insta::assert_debug_snapshot!(\n      \"project.pbxproj\",\n      super::parse(fixtures_path.join(\"project.pbxproj\")).expect(\"failed to parse pbxproj\")\n    );\n  }\n\n  #[test]\n  fn modify() {\n    let manifest_dir = std::path::Path::new(env!(\"CARGO_MANIFEST_DIR\"));\n    let fixtures_path = manifest_dir.join(\"tests\").join(\"fixtures\").join(\"pbxproj\");\n\n    let mut settings = insta::Settings::clone_current();\n    settings.set_snapshot_path(fixtures_path.join(\"snapshots\"));\n    let _guard = settings.bind_to_scope();\n\n    let mut pbxproj =\n      super::parse(fixtures_path.join(\"project.pbxproj\")).expect(\"failed to parse pbxproj\");\n\n    pbxproj.set_build_settings(\n      \"DB_0E254D0FD84970B57F6410\",\n      \"PRODUCT_NAME\",\n      \"\\\"Tauri Test\\\"\",\n    );\n    pbxproj.set_build_settings(\"DB_0E254D0FD84970B57F6410\", \"UNKNOWN\", \"9283j49238h\");\n\n    insta::assert_snapshot!(\"project-modified.pbxproj\", pbxproj.serialize());\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/plist.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::PathBuf;\n\nuse crate::error::Context;\n\npub enum PlistKind {\n  Path(PathBuf),\n  Plist(plist::Value),\n}\n\nimpl From<PathBuf> for PlistKind {\n  fn from(p: PathBuf) -> Self {\n    Self::Path(p)\n  }\n}\nimpl From<plist::Value> for PlistKind {\n  fn from(p: plist::Value) -> Self {\n    Self::Plist(p)\n  }\n}\n\npub fn merge_plist(src: Vec<PlistKind>) -> crate::Result<plist::Value> {\n  let mut merged_plist = plist::Dictionary::new();\n\n  for plist_kind in src {\n    let src_plist = match plist_kind {\n      PlistKind::Path(p) => plist::Value::from_file(&p)\n        .with_context(|| format!(\"failed to parse plist from {}\", p.display()))?,\n      PlistKind::Plist(v) => v,\n    };\n    if let Some(dict) = src_plist.into_dictionary() {\n      for (key, value) in dict {\n        merged_plist.insert(key, value);\n      }\n    }\n  }\n\n  Ok(plist::Value::Dictionary(merged_plist))\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/plugins.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::collections::HashMap;\n\n#[derive(Default)]\npub struct PluginMetadata {\n  pub desktop_only: bool,\n  pub mobile_only: bool,\n  pub rust_only: bool,\n  pub builder: bool,\n  pub version_req: Option<String>,\n}\n\n// known plugins with particular cases\npub fn known_plugins() -> HashMap<&'static str, PluginMetadata> {\n  let mut plugins: HashMap<&'static str, PluginMetadata> = HashMap::new();\n\n  // desktop-only\n  for p in [\n    \"authenticator\",\n    \"autostart\",\n    \"cli\",\n    \"global-shortcut\",\n    \"positioner\",\n    \"single-instance\",\n    \"updater\",\n    \"window-state\",\n  ] {\n    plugins.entry(p).or_default().desktop_only = true;\n  }\n\n  // mobile-only\n  for p in [\"barcode-scanner\", \"biometric\", \"nfc\", \"haptics\"] {\n    plugins.entry(p).or_default().mobile_only = true;\n  }\n\n  // uses builder pattern\n  for p in [\n    \"autostart\",\n    \"global-shortcut\",\n    \"localhost\",\n    \"log\",\n    \"sql\",\n    \"store\",\n    \"stronghold\",\n    \"updater\",\n    \"window-state\",\n  ] {\n    plugins.entry(p).or_default().builder = true;\n  }\n\n  // rust-only\n  #[allow(clippy::single_element_loop)]\n  for p in [\"localhost\", \"persisted-scope\", \"single-instance\"] {\n    plugins.entry(p).or_default().rust_only = true;\n  }\n\n  // known, but no particular config\n  for p in [\n    \"geolocation\",\n    \"deep-link\",\n    \"dialog\",\n    \"fs\",\n    \"http\",\n    \"notification\",\n    \"os\",\n    \"process\",\n    \"shell\",\n    \"upload\",\n    \"websocket\",\n    \"opener\",\n    \"clipboard-manager\",\n  ] {\n    plugins.entry(p).or_default();\n  }\n\n  let version_req = version_req();\n  for plugin in plugins.values_mut() {\n    plugin.version_req.replace(version_req.clone());\n  }\n\n  plugins\n}\n\nfn version_req() -> String {\n  let pre = env!(\"CARGO_PKG_VERSION_PRE\");\n  if pre.is_empty() {\n    env!(\"CARGO_PKG_VERSION_MAJOR\").to_string()\n  } else {\n    format!(\n      \"{}.0.0-{}\",\n      env!(\"CARGO_PKG_VERSION_MAJOR\"),\n      pre.split('.').next().unwrap()\n    )\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/prompts.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{fmt::Display, str::FromStr};\n\nuse crate::{error::Context, Result};\n\npub fn input<T>(\n  prompt: &str,\n  initial: Option<T>,\n  skip: bool,\n  allow_empty: bool,\n) -> Result<Option<T>>\nwhere\n  T: Clone + FromStr + Display + ToString,\n  T::Err: Display + std::fmt::Debug,\n  T: PartialEq<str>,\n{\n  if skip {\n    Ok(initial)\n  } else {\n    let theme = dialoguer::theme::ColorfulTheme::default();\n    let mut builder = dialoguer::Input::with_theme(&theme)\n      .with_prompt(prompt)\n      .allow_empty(allow_empty);\n\n    if let Some(v) = initial {\n      builder = builder.with_initial_text(v.to_string());\n    }\n\n    builder\n      .interact_text()\n      .map(|t: T| if t.ne(\"\") { Some(t) } else { None })\n      .context(\"failed to prompt input\")\n  }\n}\n\npub fn confirm(prompt: &str, default: Option<bool>) -> Result<bool> {\n  let theme = dialoguer::theme::ColorfulTheme::default();\n  let mut builder = dialoguer::Confirm::with_theme(&theme).with_prompt(prompt);\n  if let Some(default) = default {\n    builder = builder.default(default);\n  }\n  builder.interact().context(\"failed to prompt confirm\")\n}\n\npub fn multiselect<T: ToString>(\n  prompt: &str,\n  items: &[T],\n  defaults: Option<&[bool]>,\n) -> Result<Vec<usize>> {\n  let theme = dialoguer::theme::ColorfulTheme::default();\n  let mut builder = dialoguer::MultiSelect::with_theme(&theme)\n    .with_prompt(prompt)\n    .items(items);\n  if let Some(defaults) = defaults {\n    builder = builder.defaults(defaults);\n  }\n  builder.interact().context(\"failed to prompt multi-select\")\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/template.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  fs::{create_dir_all, File},\n  io::Write,\n  path::{Path, PathBuf},\n};\n\nuse handlebars::{to_json, Handlebars};\nuse include_dir::Dir;\nuse serde::Serialize;\nuse serde_json::value::{Map, Value as JsonValue};\n\nuse crate::error::ErrorExt;\n\n/// Map of template variable names and values.\n#[derive(Clone, Debug)]\n#[repr(transparent)]\npub struct JsonMap(Map<String, JsonValue>);\n\nimpl Default for JsonMap {\n  fn default() -> Self {\n    Self(Map::new())\n  }\n}\n\nimpl JsonMap {\n  pub fn insert(&mut self, name: &str, value: impl Serialize) {\n    self.0.insert(name.to_owned(), to_json(value));\n  }\n\n  pub fn inner(&self) -> &Map<String, JsonValue> {\n    &self.0\n  }\n}\n\npub fn render<P: AsRef<Path>, D: Serialize>(\n  handlebars: &Handlebars<'_>,\n  data: &D,\n  dir: &Dir<'_>,\n  out_dir: P,\n) -> crate::Result<()> {\n  let out_dir = out_dir.as_ref();\n  let mut created_dirs = Vec::new();\n  render_with_generator(handlebars, data, dir, out_dir, &mut |file_path: PathBuf| {\n    let path = out_dir.join(file_path);\n    let parent = path.parent().unwrap().to_path_buf();\n    if !created_dirs.contains(&parent) {\n      create_dir_all(&parent)?;\n      created_dirs.push(parent);\n    }\n    File::create(path).map(Some)\n  })\n}\n\npub fn render_with_generator<\n  P: AsRef<Path>,\n  D: Serialize,\n  F: FnMut(PathBuf) -> std::io::Result<Option<File>>,\n>(\n  handlebars: &Handlebars<'_>,\n  data: &D,\n  dir: &Dir<'_>,\n  out_dir: P,\n  out_file_generator: &mut F,\n) -> crate::Result<()> {\n  let out_dir = out_dir.as_ref();\n  for file in dir.files() {\n    let mut file_path = file.path().to_path_buf();\n    // cargo for some reason ignores the /templates folder packaging when it has a Cargo.toml file inside\n    // so we rename the extension to `.crate-manifest`\n    if let Some(extension) = file_path.extension() {\n      if extension == \"crate-manifest\" {\n        file_path.set_extension(\"toml\");\n      }\n    }\n    if let Some(mut output_file) = out_file_generator(file_path.clone())\n      .fs_context(\"failed to generate output file\", file_path.clone())?\n    {\n      if let Some(utf8) = file.contents_utf8() {\n        handlebars\n          .render_template_to_write(utf8, &data, &mut output_file)\n          .expect(\"Failed to render template\");\n      } else {\n        output_file\n          .write_all(file.contents())\n          .fs_context(\"failed to write template\", file_path.clone())?;\n      }\n    }\n  }\n  for dir in dir.dirs() {\n    render_with_generator(handlebars, data, dir, out_dir, out_file_generator)?;\n  }\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/helpers/updater_signature.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse base64::Engine;\nuse minisign::{\n  sign, KeyPair as KP, PublicKey, PublicKeyBox, SecretKey, SecretKeyBox, SignatureBox,\n};\nuse std::{\n  fs::{self, File, OpenOptions},\n  io::{BufReader, BufWriter, Write},\n  path::{Path, PathBuf},\n  str,\n  time::{SystemTime, UNIX_EPOCH},\n};\n\nuse crate::error::{Context, ErrorExt};\n\n/// A key pair (`PublicKey` and `SecretKey`).\n#[derive(Clone, Debug)]\npub struct KeyPair {\n  pub pk: String,\n  pub sk: String,\n}\n\nfn create_file(path: &Path) -> crate::Result<BufWriter<File>> {\n  if let Some(parent) = path.parent() {\n    fs::create_dir_all(parent).fs_context(\"failed to create directory\", parent.to_path_buf())?;\n  }\n  let file = File::create(path).fs_context(\"failed to create file\", path.to_path_buf())?;\n  Ok(BufWriter::new(file))\n}\n\n/// Generate base64 encoded keypair\npub fn generate_key(password: Option<String>) -> crate::Result<KeyPair> {\n  let KP { pk, sk } = KP::generate_encrypted_keypair(password).unwrap();\n\n  let pk_box_str = pk.to_box().unwrap().to_string();\n  let sk_box_str = sk.to_box(None).unwrap().to_string();\n\n  let encoded_pk = base64::engine::general_purpose::STANDARD.encode(pk_box_str);\n  let encoded_sk = base64::engine::general_purpose::STANDARD.encode(sk_box_str);\n\n  Ok(KeyPair {\n    pk: encoded_pk,\n    sk: encoded_sk,\n  })\n}\n\n/// Transform a base64 String to readable string for the main signer\npub fn decode_key<S: AsRef<[u8]>>(base64_key: S) -> crate::Result<String> {\n  let decoded_str = &base64::engine::general_purpose::STANDARD\n    .decode(base64_key)\n    .context(\"failed to decode base64 key\")?[..];\n  Ok(String::from(\n    str::from_utf8(decoded_str).context(\"failed to convert base64 to utf8\")?,\n  ))\n}\n\n/// Save KeyPair to disk\npub fn save_keypair<P>(\n  force: bool,\n  sk_path: P,\n  key: &str,\n  pubkey: &str,\n) -> crate::Result<(PathBuf, PathBuf)>\nwhere\n  P: AsRef<Path>,\n{\n  let sk_path = sk_path.as_ref();\n\n  let pubkey_path = format!(\"{}.pub\", sk_path.display());\n  let pk_path = Path::new(&pubkey_path);\n\n  if sk_path.exists() {\n    if !force {\n      crate::error::bail!(\n        \"Key generation aborted:\\n{} already exists\\nIf you really want to overwrite the existing key pair, add the --force switch to force this operation.\",\n        sk_path.display()\n      );\n    } else {\n      std::fs::remove_file(sk_path)\n        .fs_context(\"failed to remove secret key file\", sk_path.to_path_buf())?;\n    }\n  }\n\n  if pk_path.exists() {\n    std::fs::remove_file(pk_path)\n      .fs_context(\"failed to remove public key file\", pk_path.to_path_buf())?;\n  }\n\n  let write_file = |mut writer: BufWriter<File>, contents: &str| -> std::io::Result<()> {\n    write!(writer, \"{contents:}\")?;\n    writer.flush()?;\n    Ok(())\n  };\n\n  write_file(create_file(sk_path)?, key)\n    .fs_context(\"failed to write secret key\", sk_path.to_path_buf())?;\n\n  write_file(create_file(pk_path)?, pubkey)\n    .fs_context(\"failed to write public key\", pk_path.to_path_buf())?;\n\n  Ok((\n    fs::canonicalize(sk_path).fs_context(\n      \"failed to canonicalize secret key path\",\n      sk_path.to_path_buf(),\n    )?,\n    fs::canonicalize(pk_path).fs_context(\n      \"failed to canonicalize public key path\",\n      pk_path.to_path_buf(),\n    )?,\n  ))\n}\n\n/// Sign files\npub fn sign_file<P>(secret_key: &SecretKey, bin_path: P) -> crate::Result<(PathBuf, SignatureBox)>\nwhere\n  P: AsRef<Path>,\n{\n  let bin_path = bin_path.as_ref();\n  // We need to append .sig at the end it's where the signature will be stored\n  // TODO: use with_added_extension when we bump MSRV to > 1.91'\n  let signature_path = if let Some(ext) = bin_path.extension() {\n    let mut extension = ext.to_os_string();\n    extension.push(\".sig\");\n    bin_path.with_extension(extension)\n  } else {\n    bin_path.with_extension(\"sig\")\n  };\n\n  let trusted_comment = format!(\n    \"timestamp:{}\\tfile:{}\",\n    unix_timestamp(),\n    bin_path.file_name().unwrap().to_string_lossy()\n  );\n\n  let data_reader = open_data_file(bin_path)?;\n\n  let signature_box = sign(\n    None,\n    secret_key,\n    data_reader,\n    Some(trusted_comment.as_str()),\n    Some(\"signature from tauri secret key\"),\n  )\n  .context(\"failed to sign file\")?;\n\n  let encoded_signature =\n    base64::engine::general_purpose::STANDARD.encode(signature_box.to_string());\n  std::fs::write(&signature_path, encoded_signature.as_bytes())\n    .fs_context(\"failed to write signature file\", signature_path.clone())?;\n  Ok((\n    fs::canonicalize(&signature_path)\n      .fs_context(\"failed to canonicalize signature file\", &signature_path)?,\n    signature_box,\n  ))\n}\n\n/// Gets the updater secret key from the given private key and password.\n///\n/// If `password` is `None`, a password is going to be prompted interactively.\npub fn secret_key<S: AsRef<[u8]>>(\n  private_key: S,\n  password: Option<String>,\n) -> crate::Result<SecretKey> {\n  let decoded_secret = decode_key(private_key).context(\"failed to decode base64 secret key\")?;\n  let sk_box =\n    SecretKeyBox::from_string(&decoded_secret).context(\"failed to load updater private key\")?;\n  let sk = sk_box\n    .into_secret_key(password)\n    .context(\"incorrect updater private key password\")?;\n  Ok(sk)\n}\n\n/// Gets the updater secret key from the given private key and password.\npub fn pub_key<S: AsRef<[u8]>>(public_key: S) -> crate::Result<PublicKey> {\n  let decoded_publick = decode_key(public_key).context(\"failed to decode base64 pubkey\")?;\n  let pk_box =\n    PublicKeyBox::from_string(&decoded_publick).context(\"failed to load updater pubkey\")?;\n  let pk = pk_box\n    .into_public_key()\n    .context(\"failed to convert updater pubkey\")?;\n  Ok(pk)\n}\n\nfn unix_timestamp() -> u64 {\n  let start = SystemTime::now();\n  let since_the_epoch = start\n    .duration_since(UNIX_EPOCH)\n    .expect(\"system clock is incorrect\");\n  since_the_epoch.as_secs()\n}\n\nfn open_data_file<P>(data_path: P) -> crate::Result<BufReader<File>>\nwhere\n  P: AsRef<Path>,\n{\n  let data_path = data_path.as_ref();\n  let file = OpenOptions::new()\n    .read(true)\n    .open(data_path)\n    .fs_context(\"failed to open data file\", data_path.to_path_buf())?;\n  Ok(BufReader::new(file))\n}\n\n#[cfg(test)]\nmod tests {\n  use super::*;\n\n  // This was encrypted with an empty string\n  const PRIVATE_KEY: &str = \"dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5dkpDN09RZm5GeVAzc2RuYlNzWVVJelJRQnNIV2JUcGVXZUplWXZXYXpqUUFBQkFBQUFBQUFBQUFBQUlBQUFBQTZrN2RnWGh5dURxSzZiL1ZQSDdNcktiaHRxczQwMXdQelRHbjRNcGVlY1BLMTBxR2dpa3I3dDE1UTVDRDE4MXR4WlQwa1BQaXdxKy9UU2J2QmVSNXhOQWFDeG1GSVllbUNpTGJQRkhhTnROR3I5RmdUZi90OGtvaGhJS1ZTcjdZU0NyYzhQWlQ5cGM9Cg==\";\n\n  // minisign >=0.7.4,<0.8.0 couldn't handle empty passwords if the private key is encrypted with an empty string.\n  #[test]\n  fn empty_password_is_valid() {\n    let path = std::env::temp_dir().join(\"minisign-password-text.txt\");\n    std::fs::write(&path, b\"TAURI\").expect(\"failed to write test file\");\n\n    let secret_key =\n      secret_key(PRIVATE_KEY, Some(\"\".into())).expect(\"failed to resolve secret key\");\n    sign_file(&secret_key, &path).expect(\"failed to sign file\");\n  }\n\n  // This tests the newly generated keys with empty string password works\n  // minisign >=0.7.4,<=0.8.0 generate keys unencrypted if the password is empty but is marked encrypted hence unusable\n  #[test]\n  fn generate_empty_password_keys_and_use() {\n    let KeyPair { pk, sk } = generate_key(Some(\"\".to_owned())).unwrap();\n    let pk = pub_key(pk).unwrap();\n    let sk = secret_key(sk, Some(\"\".into())).unwrap();\n    let data = b\"TAURI\".as_slice();\n    sign(Some(&pk), &sk, data, None, None).expect(\"failed to sign file\");\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/icon.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::{Context, Error, ErrorExt},\n  Result,\n};\n\nuse std::{\n  borrow::Cow,\n  collections::HashMap,\n  fs::{create_dir_all, File},\n  io::{BufWriter, Write},\n  path::{Path, PathBuf},\n  str::FromStr,\n  sync::Arc,\n};\n\nuse clap::Parser;\nuse icns::{IconFamily, IconType};\nuse image::{\n  codecs::{\n    ico::{IcoEncoder, IcoFrame},\n    png::{CompressionType, FilterType as PngFilterType, PngEncoder},\n  },\n  imageops::FilterType,\n  open, DynamicImage, ExtendedColorType, GenericImageView, ImageBuffer, ImageEncoder, Pixel, Rgba,\n};\nuse rayon::iter::ParallelIterator;\nuse resvg::{tiny_skia, usvg};\nuse serde::Deserialize;\n\n#[derive(Debug, Deserialize)]\nstruct IcnsEntry {\n  size: u32,\n  ostype: String,\n}\n\n#[derive(Debug)]\nstruct PngEntry {\n  name: String,\n  size: u32,\n  out_path: PathBuf,\n}\n\nenum AndroidIconKind {\n  Regular,\n  Rounded,\n}\n\nstruct AndroidEntries {\n  icon: Vec<(PngEntry, AndroidIconKind)>,\n  foreground: Vec<PngEntry>,\n  background: Vec<PngEntry>,\n  monochrome: Vec<PngEntry>,\n}\n\n#[derive(Deserialize)]\nstruct Manifest {\n  default: String,\n  bg_color: Option<String>,\n  android_bg: Option<String>,\n  android_fg: Option<String>,\n  android_monochrome: Option<String>,\n  android_fg_scale: Option<f32>,\n}\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Generate various icons for all major platforms\")]\npub struct Options {\n  /// Path to the source icon (squared PNG or SVG file with transparency) or a manifest file.\n  ///\n  /// The manifest file is a JSON file with the following structure:\n  /// {\n  ///   \"default\": \"app-icon.png\",\n  ///   \"bg_color\": \"#fff\",\n  ///   \"android_bg\": \"app-icon-bg.png\",\n  ///   \"android_fg\": \"app-icon-fg.png\",\n  ///   \"android_fg_scale\": 85,\n  ///   \"android_monochrome\": \"app-icon-monochrome.png\"\n  /// }\n  ///\n  /// All file paths defined in the manifest JSON are relative to the manifest file path.\n  ///\n  /// Only the `default` manifest property is required.\n  ///\n  /// The `bg_color` manifest value overwrites the `--ios-color` option if set.\n  #[clap(default_value = \"./app-icon.png\")]\n  input: PathBuf,\n  /// Output directory.\n  /// Default: 'icons' directory next to the tauri.conf.json file.\n  #[clap(short, long)]\n  output: Option<PathBuf>,\n\n  /// Custom PNG icon sizes to generate. When set, the default icons are not generated.\n  #[clap(short, long, value_delimiter = ',')]\n  png: Option<Vec<u32>>,\n\n  /// The background color of the iOS icon - string as defined in the W3C's CSS Color Module Level 4 <https://www.w3.org/TR/css-color-4/>.\n  #[clap(long, default_value = \"#fff\")]\n  ios_color: String,\n}\n\n#[derive(Clone)]\n#[allow(clippy::large_enum_variant)]\nenum Source {\n  Svg(resvg::usvg::Tree),\n  DynamicImage(DynamicImage),\n}\n\nimpl Source {\n  fn width(&self) -> u32 {\n    match self {\n      Self::Svg(svg) => svg.size().width() as u32,\n      Self::DynamicImage(i) => i.width(),\n    }\n  }\n\n  fn height(&self) -> u32 {\n    match self {\n      Self::Svg(svg) => svg.size().height() as u32,\n      Self::DynamicImage(i) => i.height(),\n    }\n  }\n\n  fn resize_exact(&self, size: u32) -> DynamicImage {\n    match self {\n      Self::Svg(svg) => {\n        let mut pixmap = tiny_skia::Pixmap::new(size, size).unwrap();\n        let scale = size as f32 / svg.size().height();\n        resvg::render(\n          svg,\n          tiny_skia::Transform::from_scale(scale, scale),\n          &mut pixmap.as_mut(),\n        );\n        // Switch to use `Pixmap::take_demultiplied` in the future when it's published\n        // https://github.com/linebender/tiny-skia/blob/624257c0feb394bf6c4d0d688f8ea8030aae320f/src/pixmap.rs#L266\n        let img_buffer = ImageBuffer::from_par_fn(size, size, |x, y| {\n          let pixel = pixmap.pixel(x, y).unwrap().demultiply();\n          Rgba([pixel.red(), pixel.green(), pixel.blue(), pixel.alpha()])\n        });\n        DynamicImage::ImageRgba8(img_buffer)\n      }\n      Self::DynamicImage(image) => {\n        // image.resize_exact(size, size, FilterType::Lanczos3)\n        resize_image(image, size, size)\n      }\n    }\n  }\n}\n\n// `image` does not use premultiplied alpha in resize, so we do it manually here,\n// see https://github.com/image-rs/image/issues/1655\nfn resize_image(image: &DynamicImage, new_width: u32, new_height: u32) -> DynamicImage {\n  // Premultiply alpha\n  let premultiplied_image = ImageBuffer::from_par_fn(image.width(), image.height(), |x, y| {\n    let mut pixel = image.get_pixel(x, y);\n    let alpha = pixel.0[3] as f32 / u8::MAX as f32;\n    pixel.apply_without_alpha(|channel_value| (channel_value as f32 * alpha) as u8);\n    pixel\n  });\n\n  let mut resized = image::imageops::resize(\n    &premultiplied_image,\n    new_width,\n    new_height,\n    FilterType::Lanczos3,\n  );\n\n  // Demultiply alpha\n  resized.par_pixels_mut().for_each(|pixel| {\n    let alpha = pixel.0[3] as f32 / u8::MAX as f32;\n    pixel.apply_without_alpha(|channel_value| (channel_value as f32 / alpha) as u8);\n  });\n\n  DynamicImage::ImageRgba8(resized)\n}\n\nfn read_source(path: PathBuf) -> Result<Source> {\n  if let Some(extension) = path.extension() {\n    if extension == \"svg\" {\n      let rtree = {\n        let mut fontdb = usvg::fontdb::Database::new();\n        fontdb.load_system_fonts();\n\n        let opt = usvg::Options {\n          // Get file's absolute directory.\n          resources_dir: std::fs::canonicalize(&path)\n            .ok()\n            .and_then(|p| p.parent().map(|p| p.to_path_buf())),\n          fontdb: Arc::new(fontdb),\n          ..Default::default()\n        };\n\n        let svg_data = std::fs::read(&path).fs_context(\"Failed to read source icon\", &path)?;\n        usvg::Tree::from_data(&svg_data, &opt).unwrap()\n      };\n\n      Ok(Source::Svg(rtree))\n    } else {\n      Ok(Source::DynamicImage(DynamicImage::ImageRgba8(\n        open(&path)\n          .context(format!(\n            \"failed to read and decode source image {}\",\n            path.display()\n          ))?\n          .into_rgba8(),\n      )))\n    }\n  } else {\n    crate::error::bail!(\"Error loading image\");\n  }\n}\n\nfn parse_bg_color(bg_color_string: &String) -> Result<Rgba<u8>> {\n  let bg_color = css_color::Srgb::from_str(bg_color_string)\n    .map(|color| {\n      Rgba([\n        (color.red * 255.) as u8,\n        (color.green * 255.) as u8,\n        (color.blue * 255.) as u8,\n        (color.alpha * 255.) as u8,\n      ])\n    })\n    .map_err(|_e| {\n      Error::Context(\n        format!(\"failed to parse color {bg_color_string}\"),\n        \"invalid RGBA color\".into(),\n      )\n    })?;\n\n  Ok(bg_color)\n}\n\npub fn command(options: Options) -> Result<()> {\n  let input = options.input;\n  let out_dir = options.output.unwrap_or_else(|| {\n    let dirs = crate::helpers::app_paths::resolve_dirs();\n    dirs.tauri.join(\"icons\")\n  });\n  let png_icon_sizes = options.png.unwrap_or_default();\n\n  create_dir_all(&out_dir).fs_context(\"Can't create output directory\", &out_dir)?;\n\n  let manifest = if input.extension().is_some_and(|ext| ext == \"json\") {\n    parse_manifest(&input).map(Some)?\n  } else {\n    None\n  };\n\n  let bg_color_string = match manifest {\n    Some(ref manifest) => manifest\n      .bg_color\n      .as_ref()\n      .unwrap_or(&options.ios_color)\n      .clone(),\n    None => options.ios_color,\n  };\n  let bg_color = parse_bg_color(&bg_color_string)?;\n\n  let default_icon = match manifest {\n    Some(ref manifest) => input.parent().unwrap().join(manifest.default.clone()),\n    None => input.clone(),\n  };\n\n  let source = read_source(default_icon)?;\n\n  if source.height() != source.width() {\n    crate::error::bail!(\"Source image must be square\");\n  }\n\n  if png_icon_sizes.is_empty() {\n    appx(&source, &out_dir).context(\"Failed to generate appx icons\")?;\n    icns(&source, &out_dir).context(\"Failed to generate .icns file\")?;\n    ico(&source, &out_dir).context(\"Failed to generate .ico file\")?;\n\n    png(&source, &out_dir, bg_color).context(\"Failed to generate png icons\")?;\n    android(&source, &input, manifest, &bg_color_string, &out_dir)\n      .context(\"Failed to generate android icons\")?;\n  } else {\n    for target in png_icon_sizes.into_iter().map(|size| {\n      let name = format!(\"{size}x{size}.png\");\n      let out_path = out_dir.join(&name);\n      PngEntry {\n        name,\n        out_path,\n        size,\n      }\n    }) {\n      log::info!(action = \"PNG\"; \"Creating {}\", target.name);\n      resize_and_save_png(&source, target.size, &target.out_path, None, None)?;\n    }\n  }\n\n  Ok(())\n}\n\nfn parse_manifest(manifest_path: &Path) -> Result<Manifest> {\n  let manifest: Manifest = serde_json::from_str(\n    &std::fs::read_to_string(manifest_path)\n      .fs_context(\"cannot read manifest file\", manifest_path)?,\n  )\n  .context(format!(\n    \"failed to parse manifest file {}\",\n    manifest_path.display()\n  ))?;\n  log::debug!(\"Read manifest file from {}\", manifest_path.display());\n  Ok(manifest)\n}\n\nfn appx(source: &Source, out_dir: &Path) -> Result<()> {\n  log::info!(action = \"Appx\"; \"Creating StoreLogo.png\");\n  resize_and_save_png(source, 50, &out_dir.join(\"StoreLogo.png\"), None, None)?;\n\n  for size in [30, 44, 71, 89, 107, 142, 150, 284, 310] {\n    let file_name = format!(\"Square{size}x{size}Logo.png\");\n    log::info!(action = \"Appx\"; \"Creating {}\", file_name);\n\n    resize_and_save_png(source, size, &out_dir.join(&file_name), None, None)?;\n  }\n\n  Ok(())\n}\n\n// Main target: macOS\nfn icns(source: &Source, out_dir: &Path) -> Result<()> {\n  log::info!(action = \"ICNS\"; \"Creating icon.icns\");\n  let entries: HashMap<String, IcnsEntry> =\n    serde_json::from_slice(include_bytes!(\"helpers/icns.json\")).unwrap();\n\n  let mut family = IconFamily::new();\n\n  for (_name, entry) in entries {\n    let size = entry.size;\n    let mut buf = Vec::new();\n\n    let image = source.resize_exact(size);\n\n    write_png(image.as_bytes(), &mut buf, size).context(\"failed to write output file\")?;\n\n    let image = icns::Image::read_png(&buf[..]).context(\"failed to read output file\")?;\n\n    family\n      .add_icon_with_type(\n        &image,\n        IconType::from_ostype(entry.ostype.parse().unwrap()).unwrap(),\n      )\n      .context(\"failed to add icon to Icns Family\")?;\n  }\n\n  let icns_path = out_dir.join(\"icon.icns\");\n  let mut out_file = BufWriter::new(\n    File::create(&icns_path).fs_context(\"failed to create output file\", &icns_path)?,\n  );\n  family\n    .write(&mut out_file)\n    .fs_context(\"failed to write output file\", &icns_path)?;\n  out_file\n    .flush()\n    .fs_context(\"failed to flush output file\", &icns_path)?;\n\n  Ok(())\n}\n\n// Generate .ico file with layers for the most common sizes.\n// Main target: Windows\nfn ico(source: &Source, out_dir: &Path) -> Result<()> {\n  log::info!(action = \"ICO\"; \"Creating icon.ico\");\n  let mut frames = Vec::new();\n\n  for size in [32, 16, 24, 48, 64, 256] {\n    let image = source.resize_exact(size);\n\n    // Only the 256px layer can be compressed according to the ico specs.\n    if size == 256 {\n      let mut buf = Vec::new();\n\n      write_png(image.as_bytes(), &mut buf, size).context(\"failed to write output file\")?;\n\n      frames.push(\n        IcoFrame::with_encoded(buf, size, size, ExtendedColorType::Rgba8)\n          .context(\"failed to create ico frame\")?,\n      );\n    } else {\n      frames.push(\n        IcoFrame::as_png(image.as_bytes(), size, size, ExtendedColorType::Rgba8)\n          .context(\"failed to create PNG frame\")?,\n      );\n    }\n  }\n\n  let ico_path = out_dir.join(\"icon.ico\");\n  let mut out_file =\n    BufWriter::new(File::create(&ico_path).fs_context(\"failed to create output file\", &ico_path)?);\n  let encoder = IcoEncoder::new(&mut out_file);\n  encoder\n    .encode_images(&frames)\n    .context(\"failed to encode images\")?;\n  out_file\n    .flush()\n    .fs_context(\"failed to flush output file\", &ico_path)?;\n\n  Ok(())\n}\n\nfn android(\n  source: &Source,\n  input: &Path,\n  manifest: Option<Manifest>,\n  bg_color: &String,\n  out_dir: &Path,\n) -> Result<()> {\n  fn android_entries(out_dir: &Path) -> Result<AndroidEntries> {\n    struct AndroidEntry {\n      name: &'static str,\n      size: u32,\n      foreground_size: u32,\n    }\n\n    let targets = vec![\n      AndroidEntry {\n        name: \"hdpi\",\n        size: 49,\n        foreground_size: 162,\n      },\n      AndroidEntry {\n        name: \"mdpi\",\n        size: 48,\n        foreground_size: 108,\n      },\n      AndroidEntry {\n        name: \"xhdpi\",\n        size: 96,\n        foreground_size: 216,\n      },\n      AndroidEntry {\n        name: \"xxhdpi\",\n        size: 144,\n        foreground_size: 324,\n      },\n      AndroidEntry {\n        name: \"xxxhdpi\",\n        size: 192,\n        foreground_size: 432,\n      },\n    ];\n    let mut icon_entries = Vec::new();\n    let mut fg_entries = Vec::new();\n    let mut bg_entries = Vec::new();\n    let mut monochrome_entries = Vec::new();\n\n    for target in targets {\n      let folder_name = format!(\"mipmap-{}\", target.name);\n      let out_folder = out_dir.join(&folder_name);\n\n      create_dir_all(&out_folder).fs_context(\n        \"failed to create Android mipmap output directory\",\n        &out_folder,\n      )?;\n\n      fg_entries.push(PngEntry {\n        name: format!(\"{}/{}\", folder_name, \"ic_launcher_foreground.png\"),\n        out_path: out_folder.join(\"ic_launcher_foreground.png\"),\n        size: target.foreground_size,\n      });\n      icon_entries.push((\n        PngEntry {\n          name: format!(\"{}/{}\", folder_name, \"ic_launcher_round.png\"),\n          out_path: out_folder.join(\"ic_launcher_round.png\"),\n          size: target.size,\n        },\n        AndroidIconKind::Rounded,\n      ));\n      icon_entries.push((\n        PngEntry {\n          name: format!(\"{}/{}\", folder_name, \"ic_launcher.png\"),\n          out_path: out_folder.join(\"ic_launcher.png\"),\n          size: target.size,\n        },\n        AndroidIconKind::Regular,\n      ));\n\n      bg_entries.push(PngEntry {\n        name: format!(\"{}/{}\", folder_name, \"ic_launcher_background.png\"),\n        out_path: out_folder.join(\"ic_launcher_background.png\"),\n        size: target.foreground_size,\n      });\n\n      monochrome_entries.push(PngEntry {\n        name: format!(\"{}/{}\", folder_name, \"ic_launcher_monochrome.png\"),\n        out_path: out_folder.join(\"ic_launcher_monochrome.png\"),\n        size: target.foreground_size,\n      });\n    }\n\n    Ok(AndroidEntries {\n      icon: icon_entries,\n      foreground: fg_entries,\n      background: bg_entries,\n      monochrome: monochrome_entries,\n    })\n  }\n  fn create_color_file(out_dir: &Path, color: &String) -> Result<()> {\n    let values_folder = out_dir.join(\"values\");\n    create_dir_all(&values_folder).fs_context(\n      \"Can't create Android values output directory\",\n      &values_folder,\n    )?;\n    let launcher_background_xml_path = values_folder.join(\"ic_launcher_background.xml\");\n    let mut color_file = File::create(&launcher_background_xml_path).fs_context(\n      \"failed to create Android color file\",\n      &launcher_background_xml_path,\n    )?;\n    color_file\n      .write_all(\n        format!(\n          r#\"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <color name=\"ic_launcher_background\">{color}</color>\n</resources>\"#,\n        )\n        .as_bytes(),\n      )\n      .fs_context(\n        \"failed to write Android color file\",\n        &launcher_background_xml_path,\n      )?;\n    Ok(())\n  }\n\n  let android_out = out_dir\n    .parent()\n    .unwrap()\n    .join(\"gen/android/app/src/main/res/\");\n  let out = if android_out.exists() {\n    android_out\n  } else {\n    let out = out_dir.join(\"android\");\n    create_dir_all(&out).fs_context(\"Can't create Android output directory\", &out)?;\n    out\n  };\n  let entries = android_entries(&out)?;\n\n  let fg_source = match manifest {\n    Some(ref manifest) => {\n      Some(read_source(input.parent().unwrap().join(\n        manifest.android_fg.as_ref().unwrap_or(&manifest.default),\n      ))?)\n    }\n    None => None,\n  };\n\n  for entry in entries.foreground {\n    log::info!(action = \"Android\"; \"Creating {}\", entry.name);\n    resize_and_save_png(\n      fg_source.as_ref().unwrap_or(source),\n      entry.size,\n      &entry.out_path,\n      None,\n      None,\n    )?;\n  }\n\n  let mut bg_source = None;\n  let mut has_monochrome_image = false;\n  if let Some(ref manifest) = manifest {\n    if let Some(ref background_path) = manifest.android_bg {\n      let bg = read_source(input.parent().unwrap().join(background_path))?;\n      for entry in entries.background {\n        log::info!(action = \"Android\"; \"Creating {}\", entry.name);\n        resize_and_save_png(&bg, entry.size, &entry.out_path, None, None)?;\n      }\n      bg_source.replace(bg);\n    }\n    if let Some(ref monochrome_path) = manifest.android_monochrome {\n      has_monochrome_image = true;\n      let mc = read_source(input.parent().unwrap().join(monochrome_path))?;\n      for entry in entries.monochrome {\n        log::info!(action = \"Android\"; \"Creating {}\", entry.name);\n        resize_and_save_png(&mc, entry.size, &entry.out_path, None, None)?;\n      }\n    }\n  }\n\n  for (entry, kind) in entries.icon {\n    log::info!(action = \"Android\"; \"Creating {}\", entry.name);\n\n    let (margin, radius) = match kind {\n      AndroidIconKind::Regular => {\n        let radius = ((entry.size as f32) * 0.0833).round() as u32;\n        (radius, radius)\n      }\n      AndroidIconKind::Rounded => {\n        let margin = ((entry.size as f32) * 0.04).round() as u32;\n        let radius = ((entry.size as f32) * 0.5).round() as u32;\n        (margin, radius)\n      }\n    };\n\n    let image = if let (Some(bg_source), Some(fg_source)) = (bg_source.as_ref(), fg_source.as_ref())\n    {\n      resize_png(\n        fg_source,\n        entry.size,\n        Some(Background::Image(bg_source)),\n        manifest\n          .as_ref()\n          .and_then(|manifest| manifest.android_fg_scale),\n      )?\n    } else {\n      resize_png(source, entry.size, None, None)?\n    };\n\n    let image = apply_round_mask(&image, entry.size, margin, radius);\n\n    let mut out_file = BufWriter::new(\n      File::create(&entry.out_path).fs_context(\"failed to create output file\", &entry.out_path)?,\n    );\n    write_png(image.as_bytes(), &mut out_file, entry.size)\n      .context(\"failed to write output file\")?;\n    out_file\n      .flush()\n      .fs_context(\"failed to flush output file\", &entry.out_path)?;\n  }\n\n  let mut launcher_content = r#\"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\"#\n    .to_owned();\n\n  if bg_source.is_some() {\n    launcher_content\n      .push_str(\"\\n  <background android:drawable=\\\"@mipmap/ic_launcher_background\\\"/>\");\n  } else {\n    create_color_file(&out, bg_color)?;\n    launcher_content\n      .push_str(\"\\n  <background android:drawable=\\\"@color/ic_launcher_background\\\"/>\");\n  }\n  if has_monochrome_image {\n    launcher_content\n      .push_str(\"\\n  <monochrome android:drawable=\\\"@mipmap/ic_launcher_monochrome\\\"/>\");\n  }\n  launcher_content.push_str(\"\\n</adaptive-icon>\");\n\n  let any_dpi_folder = out.join(\"mipmap-anydpi-v26\");\n  create_dir_all(&any_dpi_folder).fs_context(\n    \"Can't create Android mipmap-anydpi-v26 output directory\",\n    &any_dpi_folder,\n  )?;\n\n  let launcher_xml_path = any_dpi_folder.join(\"ic_launcher.xml\");\n  let mut launcher_file = File::create(&launcher_xml_path)\n    .fs_context(\"failed to create Android launcher file\", &launcher_xml_path)?;\n  launcher_file\n    .write_all(launcher_content.as_bytes())\n    .fs_context(\"failed to write Android launcher file\", &launcher_xml_path)?;\n\n  Ok(())\n}\n\n// Generate .png files in 32x32, 64x64, 128x128, 256x256, 512x512 (icon.png)\n// Main target: Linux\nfn png(source: &Source, out_dir: &Path, ios_color: Rgba<u8>) -> Result<()> {\n  fn desktop_entries(out_dir: &Path) -> Vec<PngEntry> {\n    let mut entries = Vec::new();\n\n    for size in [32, 64, 128, 256, 512] {\n      let file_name = match size {\n        256 => \"128x128@2x.png\".to_string(),\n        512 => \"icon.png\".to_string(),\n        _ => format!(\"{size}x{size}.png\"),\n      };\n\n      entries.push(PngEntry {\n        out_path: out_dir.join(&file_name),\n        name: file_name,\n        size,\n      });\n    }\n\n    entries\n  }\n\n  fn ios_entries(out_dir: &Path) -> Result<Vec<PngEntry>> {\n    struct IosEntry {\n      size: f32,\n      multipliers: Vec<u8>,\n      has_extra: bool,\n    }\n\n    let mut entries = Vec::new();\n\n    let targets = vec![\n      IosEntry {\n        size: 20.,\n        multipliers: vec![1, 2, 3],\n        has_extra: true,\n      },\n      IosEntry {\n        size: 29.,\n        multipliers: vec![1, 2, 3],\n        has_extra: true,\n      },\n      IosEntry {\n        size: 40.,\n        multipliers: vec![1, 2, 3],\n        has_extra: true,\n      },\n      IosEntry {\n        size: 60.,\n        multipliers: vec![2, 3],\n        has_extra: false,\n      },\n      IosEntry {\n        size: 76.,\n        multipliers: vec![1, 2],\n        has_extra: false,\n      },\n      IosEntry {\n        size: 83.5,\n        multipliers: vec![2],\n        has_extra: false,\n      },\n      IosEntry {\n        size: 512.,\n        multipliers: vec![2],\n        has_extra: false,\n      },\n    ];\n\n    for target in targets {\n      let size_str = if target.size == 512. {\n        \"512\".to_string()\n      } else {\n        format!(\"{size}x{size}\", size = target.size)\n      };\n      if target.has_extra {\n        let name = format!(\"AppIcon-{size_str}@2x-1.png\");\n        entries.push(PngEntry {\n          out_path: out_dir.join(&name),\n          name,\n          size: (target.size * 2.) as u32,\n        });\n      }\n      for multiplier in target.multipliers {\n        let name = format!(\"AppIcon-{size_str}@{multiplier}x.png\");\n        entries.push(PngEntry {\n          out_path: out_dir.join(&name),\n          name,\n          size: (target.size * multiplier as f32) as u32,\n        });\n      }\n    }\n\n    Ok(entries)\n  }\n\n  let entries = desktop_entries(out_dir);\n\n  let ios_out = out_dir\n    .parent()\n    .unwrap()\n    .join(\"gen/apple/Assets.xcassets/AppIcon.appiconset\");\n  let out = if ios_out.exists() {\n    ios_out\n  } else {\n    let out = out_dir.join(\"ios\");\n    create_dir_all(&out).fs_context(\"failed to create iOS output directory\", &out)?;\n    out\n  };\n\n  for entry in entries {\n    log::info!(action = \"PNG\"; \"Creating {}\", entry.name);\n    resize_and_save_png(source, entry.size, &entry.out_path, None, None)?;\n  }\n\n  for entry in ios_entries(&out)? {\n    log::info!(action = \"iOS\"; \"Creating {}\", entry.name);\n    resize_and_save_png(\n      source,\n      entry.size,\n      &entry.out_path,\n      Some(Background::Color(ios_color)),\n      None,\n    )?;\n  }\n\n  Ok(())\n}\n\nenum Background<'a> {\n  Color(Rgba<u8>),\n  Image(&'a Source),\n}\n\n// Resize image.\nfn resize_png(\n  source: &Source,\n  size: u32,\n  bg: Option<Background>,\n  scale_percent: Option<f32>,\n) -> Result<DynamicImage> {\n  let mut image = source.resize_exact(size);\n\n  match bg {\n    Some(Background::Color(bg_color)) => {\n      let mut bg_img = ImageBuffer::from_fn(size, size, |_, _| bg_color);\n\n      let fg = scale_percent\n        .map(|scale| resize_asset(&image, size, scale))\n        .unwrap_or(image);\n\n      image::imageops::overlay(&mut bg_img, &fg, 0, 0);\n      image = bg_img.into();\n    }\n    Some(Background::Image(bg_source)) => {\n      let mut bg = bg_source.resize_exact(size);\n\n      let fg = scale_percent\n        .map(|scale| resize_asset(&image, size, scale))\n        .unwrap_or(image);\n\n      image::imageops::overlay(&mut bg, &fg, 0, 0);\n      image = bg;\n    }\n    None => {}\n  }\n\n  Ok(image)\n}\n\n// Resize image and save it to disk.\nfn resize_and_save_png(\n  source: &Source,\n  size: u32,\n  file_path: &Path,\n  bg: Option<Background>,\n  scale_percent: Option<f32>,\n) -> Result<()> {\n  let image = resize_png(source, size, bg, scale_percent)?;\n  let mut out_file =\n    BufWriter::new(File::create(file_path).fs_context(\"failed to create output file\", file_path)?);\n  write_png(image.as_bytes(), &mut out_file, size).context(\"failed to write output file\")?;\n  out_file\n    .flush()\n    .fs_context(\"failed to save output file\", file_path)\n}\n\n// Encode image data as png with compression.\nfn write_png<W: Write>(image_data: &[u8], w: W, size: u32) -> image::ImageResult<()> {\n  let encoder = PngEncoder::new_with_quality(w, CompressionType::Best, PngFilterType::Adaptive);\n  encoder.write_image(image_data, size, size, ExtendedColorType::Rgba8)?;\n  Ok(())\n}\n\n// finds the bounding box of non-transparent pixels in an RGBA image.\nfn content_bounds(img: &DynamicImage) -> Option<(u32, u32, u32, u32)> {\n  let rgba = img.to_rgba8();\n  let (width, height) = img.dimensions();\n\n  let mut min_x = width;\n  let mut min_y = height;\n  let mut max_x = 0;\n  let mut max_y = 0;\n  let mut found = false;\n\n  for y in 0..height {\n    for x in 0..width {\n      let a = rgba.get_pixel(x, y)[3];\n      if a > 0 {\n        found = true;\n        if x < min_x {\n          min_x = x;\n        }\n        if y < min_y {\n          min_y = y;\n        }\n        if x > max_x {\n          max_x = x;\n        }\n        if y > max_y {\n          max_y = y;\n        }\n      }\n    }\n  }\n\n  if found {\n    Some((min_x, min_y, max_x - min_x + 1, max_y - min_y + 1))\n  } else {\n    None\n  }\n}\n\nfn resize_asset(img: &DynamicImage, target_size: u32, scale_percent: f32) -> DynamicImage {\n  let cropped = if let Some((x, y, cw, ch)) = content_bounds(img) {\n    // TODO: Use `&` here instead when we raise MSRV to above 1.79\n    Cow::Owned(img.crop_imm(x, y, cw, ch))\n  } else {\n    Cow::Borrowed(img)\n  };\n\n  let (cw, ch) = cropped.dimensions();\n  let max_dim = cw.max(ch) as f32;\n  let scale = (target_size as f32 * (scale_percent / 100.0)) / max_dim;\n\n  let new_w = (cw as f32 * scale).round() as u32;\n  let new_h = (ch as f32 * scale).round() as u32;\n\n  let resized = resize_image(&cropped, new_w, new_h);\n\n  // Place on transparent square canvas\n  let mut canvas = ImageBuffer::from_pixel(target_size, target_size, Rgba([0, 0, 0, 0]));\n  let offset_x = if new_w > target_size {\n    // Image wider than canvas → start at negative offset\n    -((new_w - target_size) as i32 / 2)\n  } else {\n    (target_size - new_w) as i32 / 2\n  };\n\n  let offset_y = if new_h > target_size {\n    -((new_h - target_size) as i32 / 2)\n  } else {\n    (target_size - new_h) as i32 / 2\n  };\n\n  image::imageops::overlay(&mut canvas, &resized, offset_x.into(), offset_y.into());\n\n  DynamicImage::ImageRgba8(canvas)\n}\n\nfn apply_round_mask(\n  img: &DynamicImage,\n  target_size: u32,\n  margin: u32,\n  radius: u32,\n) -> DynamicImage {\n  // Clamp radius to half of inner size\n  let inner_size = target_size.saturating_sub(2 * margin);\n  let radius = radius.min(inner_size / 2);\n\n  // Resize inner image to fit inside margins\n  let resized = img.resize_exact(inner_size, inner_size, image::imageops::Lanczos3);\n\n  // Prepare output canvas\n  let mut out = ImageBuffer::from_pixel(target_size, target_size, Rgba([0, 0, 0, 0]));\n\n  // Draw the resized image at (margin, margin)\n  image::imageops::overlay(&mut out, &resized, margin as i64, margin as i64);\n\n  // Apply rounded corners\n  for y in 0..target_size {\n    for x in 0..target_size {\n      let inside = if x >= margin + radius\n        && x < target_size - margin - radius\n        && y >= margin + radius\n        && y < target_size - margin - radius\n      {\n        true // inside central rectangle\n      } else {\n        // Determine corner centers\n        let (cx, cy) = if x < margin + radius && y < margin + radius {\n          (margin + radius, margin + radius) // top-left\n        } else if x >= target_size - margin - radius && y < margin + radius {\n          (target_size - margin - radius, margin + radius) // top-right\n        } else if x < margin + radius && y >= target_size - margin - radius {\n          (margin + radius, target_size - margin - radius) // bottom-left\n        } else if x >= target_size - margin - radius && y >= target_size - margin - radius {\n          (target_size - margin - radius, target_size - margin - radius) // bottom-right\n        } else {\n          continue; // edges that are not corners are inside\n        };\n        let dx = x as i32 - cx as i32;\n        let dy = y as i32 - cy as i32;\n        dx * dx + dy * dy <= (radius as i32 * radius as i32)\n      };\n\n      if !inside {\n        out.put_pixel(x, y, Rgba([0, 0, 0, 0]));\n      }\n    }\n  }\n\n  DynamicImage::ImageRgba8(out)\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/info/app.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::SectionItem;\nuse crate::helpers::config::ConfigMetadata;\nuse crate::helpers::framework;\nuse std::{fs::read_to_string, path::PathBuf};\n\npub fn items(config: &ConfigMetadata, frontend_dir: Option<&PathBuf>) -> Vec<SectionItem> {\n  let mut items = Vec::new();\n  let bundle_or_build = if config.bundle.active {\n    \"bundle\"\n  } else {\n    \"build\"\n  };\n  items.push(SectionItem::new().description(format!(\"build-type: {bundle_or_build}\")));\n\n  let csp = config\n    .app\n    .security\n    .csp\n    .clone()\n    .map(|c| c.to_string())\n    .unwrap_or_else(|| \"unset\".to_string());\n  items.push(SectionItem::new().description(format!(\"CSP: {csp}\")));\n\n  if let Some(frontend_dist) = &config.build.frontend_dist {\n    items.push(SectionItem::new().description(format!(\"frontendDist: {frontend_dist}\")));\n  }\n\n  if let Some(dev_url) = &config.build.dev_url {\n    items.push(SectionItem::new().description(format!(\"devUrl: {dev_url}\")));\n  }\n\n  if let Some(frontend_dir) = frontend_dir {\n    if let Ok(package_json) = read_to_string(frontend_dir.join(\"package.json\")) {\n      let (framework, bundler) = framework::infer_from_package_json(&package_json);\n\n      if let Some(framework) = framework {\n        items.push(SectionItem::new().description(format!(\"framework: {framework}\")));\n      }\n\n      if let Some(bundler) = bundler {\n        items.push(SectionItem::new().description(format!(\"bundler: {bundler}\")));\n      }\n    }\n  }\n\n  items\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/info/env_nodejs.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{ActionResult, SectionItem, VersionMetadata};\nuse colored::Colorize;\n\nuse crate::helpers::{cross_command, npm::manager_version};\n\npub fn items(metadata: &VersionMetadata) -> Vec<SectionItem> {\n  let node_target_ver = metadata.js_cli.node.replace(\">= \", \"\");\n\n  vec![\n    SectionItem::new().action(move || {\n      cross_command(\"node\")\n        .arg(\"-v\")\n        .output()\n        .map(|o| {\n          if o.status.success() {\n            let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string();\n            let v = v\n              .split('\\n')\n              .next()\n              .unwrap()\n              .strip_prefix('v')\n              .unwrap_or_default()\n              .trim();\n            ActionResult::Description(format!(\"node: {}{}\", v, {\n              let version = semver::Version::parse(v);\n              let target_version = semver::Version::parse(node_target_ver.as_str());\n              match (version, target_version) {\n                (Ok(version), Ok(target_version)) if version < target_version => {\n                  format!(\n                    \" ({}, latest: {})\",\n                    \"outdated\".red(),\n                    target_version.to_string().green()\n                  )\n                }\n                _ => \"\".into(),\n              }\n            }))\n          } else {\n            ActionResult::None\n          }\n        })\n        .ok()\n        .unwrap_or_default()\n    }),\n    SectionItem::new().action(|| manager_version(\"pnpm\").map(|v| format!(\"pnpm: {v}\")).into()),\n    SectionItem::new().action(|| manager_version(\"yarn\").map(|v| format!(\"yarn: {v}\")).into()),\n    SectionItem::new().action(|| manager_version(\"npm\").map(|v| format!(\"npm: {v}\")).into()),\n    SectionItem::new().action(|| manager_version(\"bun\").map(|v| format!(\"bun: {v}\")).into()),\n    SectionItem::new().action(|| manager_version(\"deno\").map(|v| format!(\"deno: {v}\")).into()),\n  ]\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/info/env_rust.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::SectionItem;\nuse super::Status;\nuse colored::Colorize;\nuse std::process::Command;\n\nfn component_version(component: &str) -> Option<(String, Status)> {\n  Command::new(component)\n    .arg(\"-V\")\n    .output()\n    .map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string())\n    .map(|v| {\n      format!(\n        \"{component}: {}\",\n        v.split('\\n')\n          .next()\n          .unwrap()\n          .strip_prefix(&format!(\"{component} \"))\n          .unwrap_or_default()\n      )\n    })\n    .map(|desc| (desc, Status::Success))\n    .ok()\n}\n\npub fn items() -> Vec<SectionItem> {\n  vec![\n    SectionItem::new().action(|| {\n       component_version(\"rustc\")\n          .unwrap_or_else(|| {\n            (\n              format!(\n                \"rustc: {}\\nMaybe you don't have rust installed! Visit {}\",\n                \"not installed!\".red(),\n                \"https://rustup.rs/\".cyan()\n              ),\n              Status::Error,\n            )\n          }).into()\n    }),\n    SectionItem::new().action(|| {\n        component_version(\"cargo\")\n          .unwrap_or_else(|| {\n            (\n              format!(\n                \"Cargo: {}\\nMaybe you don't have rust installed! Visit {}\",\n                \"not installed!\".red(),\n                \"https://rustup.rs/\".cyan()\n              ),\n              Status::Error,\n            )\n          }).into()\n    }),\n    SectionItem::new().action(|| {\n        component_version(\"rustup\")\n            .unwrap_or_else(|| {\n              (\n                format!(\n                  \"rustup: {}\\nIf you have rust installed some other way, we recommend uninstalling it\\nthen use rustup instead. Visit {}\",\n                  \"not installed!\".red(),\n                  \"https://rustup.rs/\".cyan()\n                ),\n                Status::Warning,\n              )\n            }).into()\n    }),\n    SectionItem::new().action(|| {\n          Command::new(\"rustup\")\n            .args([\"show\", \"active-toolchain\"])\n            .output()\n            .map(|o| String::from_utf8_lossy(o.stdout.as_slice()).to_string())\n            .map(|v| {\n              format!(\n                \"Rust toolchain: {}\",\n                v.split('\\n')\n                  .next()\n                  .unwrap()\n              )\n            })\n            .map(|desc| (desc, Status::Success))\n            .ok()\n            .unwrap_or_else(|| {\n              (\n                format!(\n                  \"Rust toolchain: couldn't be detected!\\nMaybe you don't have rustup installed? if so, Visit {}\", \"https://rustup.rs/\".cyan()\n                ),\n                Status::Warning,\n              )\n            }).into()\n    }),\n  ]\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/info/env_system.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{SectionItem, Status};\n#[cfg(windows)]\nuse crate::error::Context;\nuse colored::Colorize;\n#[cfg(windows)]\nuse serde::Deserialize;\nuse std::process::Command;\n\n#[cfg(windows)]\n#[derive(Deserialize, Debug)]\n#[serde(rename_all = \"camelCase\")]\nstruct VsInstanceInfo {\n  display_name: String,\n}\n\n#[cfg(windows)]\nconst VSWHERE: &[u8] = include_bytes!(\"../../scripts/vswhere.exe\");\n\n#[cfg(windows)]\nfn build_tools_version() -> crate::Result<Vec<String>> {\n  let mut vswhere = std::env::temp_dir();\n  vswhere.push(\"vswhere.exe\");\n\n  if !vswhere.exists() {\n    if let Ok(mut file) = std::fs::File::create(&vswhere) {\n      use std::io::Write;\n      let _ = file.write_all(VSWHERE);\n    }\n  }\n\n  // Check if there are Visual Studio installations that have the \"MSVC - C++ Buildtools\" and \"Windows SDK\" components.\n  // Both the Windows 10 and Windows 11 SDKs work so we need to query it twice.\n  let output_sdk10 = Command::new(&vswhere)\n    .args([\n      \"-prerelease\",\n      \"-products\",\n      \"*\",\n      \"-requires\",\n      \"Microsoft.VisualStudio.Component.VC.Tools.x86.x64\",\n      \"-requires\",\n      \"Microsoft.VisualStudio.Component.Windows10SDK.*\",\n      \"-format\",\n      \"json\",\n      \"-utf8\",\n    ])\n    .output()\n    .map_err(|error| crate::error::Error::CommandFailed {\n      command: \"vswhere -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -requires Microsoft.VisualStudio.Component.Windows10SDK.* -format json -utf8\".to_string(),\n      error,\n    })?;\n\n  let output_sdk11 = Command::new(vswhere)\n    .args([\n      \"-prerelease\",\n      \"-products\",\n      \"*\",\n      \"-requires\",\n      \"Microsoft.VisualStudio.Component.VC.Tools.x86.x64\",\n      \"-requires\",\n      \"Microsoft.VisualStudio.Component.Windows11SDK.*\",\n      \"-format\",\n      \"json\",\n      \"-utf8\",\n    ])\n    .output()\n    .map_err(|error| crate::error::Error::CommandFailed {\n      command: \"vswhere -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -requires Microsoft.VisualStudio.Component.Windows11SDK.* -format json -utf8\".to_string(),\n      error,\n    })?;\n\n  let mut instances: Vec<VsInstanceInfo> = Vec::new();\n\n  if output_sdk10.status.success() {\n    let stdout = String::from_utf8_lossy(&output_sdk10.stdout);\n    let found: Vec<VsInstanceInfo> =\n      serde_json::from_str(&stdout).context(\"failed to parse vswhere output\")?;\n    instances.extend(found);\n  }\n\n  if output_sdk11.status.success() {\n    let stdout = String::from_utf8_lossy(&output_sdk11.stdout);\n    let found: Vec<VsInstanceInfo> =\n      serde_json::from_str(&stdout).context(\"failed to parse vswhere output\")?;\n    instances.extend(found);\n  }\n\n  let mut instances: Vec<String> = instances\n    .iter()\n    .map(|i| i.display_name.clone())\n    .collect::<Vec<String>>();\n\n  instances.sort_unstable();\n  instances.dedup();\n\n  Ok(instances)\n}\n\n#[cfg(windows)]\nfn webview2_version() -> crate::Result<Option<String>> {\n  let powershell_path = std::env::var(\"SYSTEMROOT\").map_or_else(\n    |_| \"powershell.exe\".to_string(),\n    |p| format!(\"{p}\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe\"),\n  );\n  // check 64bit machine-wide installation\n  let output = Command::new(&powershell_path)\n      .args([\"-NoProfile\", \"-Command\"])\n      .arg(\"Get-ItemProperty -Path 'HKLM:\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}\")\n      .output()\n      .map_err(|error| crate::error::Error::CommandFailed {\n        command: \"Get-ItemProperty -Path 'HKLM:\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}\".to_string(),\n        error,\n      })?;\n  if output.status.success() {\n    return Ok(Some(\n      String::from_utf8_lossy(&output.stdout).replace('\\n', \"\"),\n    ));\n  }\n  // check 32bit machine-wide installation\n  let output = Command::new(&powershell_path)\n        .args([\"-NoProfile\", \"-Command\"])\n        .arg(\"Get-ItemProperty -Path 'HKLM:\\\\SOFTWARE\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}\")\n        .output()\n        .map_err(|error| crate::error::Error::CommandFailed {\n          command: \"Get-ItemProperty -Path 'HKLM:\\\\SOFTWARE\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}\".to_string(),\n          error,\n        })?;\n  if output.status.success() {\n    return Ok(Some(\n      String::from_utf8_lossy(&output.stdout).replace('\\n', \"\"),\n    ));\n  }\n  // check user-wide installation\n  let output = Command::new(&powershell_path)\n      .args([\"-NoProfile\", \"-Command\"])\n      .arg(\"Get-ItemProperty -Path 'HKCU:\\\\SOFTWARE\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}\")\n      .output()\n      .map_err(|error| crate::error::Error::CommandFailed {\n        command: \"Get-ItemProperty -Path 'HKCU:\\\\SOFTWARE\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}\".to_string(),\n        error,\n      })?;\n  if output.status.success() {\n    return Ok(Some(\n      String::from_utf8_lossy(&output.stdout).replace('\\n', \"\"),\n    ));\n  }\n\n  Ok(None)\n}\n\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"openbsd\",\n  target_os = \"netbsd\"\n))]\nfn pkg_conf_version(package: &str) -> Option<String> {\n  Command::new(\"pkg-config\")\n    .args([package, \"--print-provides\"])\n    .output()\n    .map(|o| {\n      String::from_utf8_lossy(&o.stdout)\n        .split('=')\n        .nth(1)\n        .map(|s| s.trim().to_string())\n    })\n    .unwrap_or(None)\n}\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"openbsd\",\n  target_os = \"netbsd\"\n))]\nfn webkit2gtk_ver() -> Option<String> {\n  pkg_conf_version(\"webkit2gtk-4.1\")\n}\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"openbsd\",\n  target_os = \"netbsd\"\n))]\nfn rsvg2_ver() -> Option<String> {\n  pkg_conf_version(\"librsvg-2.0\")\n}\n\n#[cfg(target_os = \"macos\")]\nfn is_xcode_command_line_tools_installed() -> bool {\n  Command::new(\"xcode-select\")\n    .arg(\"-p\")\n    .output()\n    .map(|o| o.status.success())\n    .unwrap_or(false)\n}\n\n#[cfg(target_os = \"macos\")]\npub fn xcode_version() -> Option<String> {\n  Command::new(\"xcodebuild\")\n    .arg(\"-version\")\n    .output()\n    .ok()\n    .map(|o| String::from_utf8_lossy(&o.stdout).into_owned())\n    .and_then(|s| {\n      s.split('\\n')\n        .filter_map(|line| line.strip_prefix(\"Xcode \"))\n        .next()\n        .map(ToString::to_string)\n    })\n}\n\nfn de_and_session() -> String {\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"openbsd\",\n    target_os = \"netbsd\"\n  ))]\n  return {\n    let de = std::env::var(\"XDG_SESSION_DESKTOP\");\n    let session = std::env::var(\"XDG_SESSION_TYPE\");\n    format!(\n      \" ({} on {})\",\n      de.as_deref().unwrap_or(\"Unknown DE\"),\n      session.as_deref().unwrap_or(\"Unknown Session\")\n    )\n  };\n\n  #[cfg(not(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"openbsd\",\n    target_os = \"netbsd\"\n  )))]\n  String::new()\n}\n\npub fn items() -> Vec<SectionItem> {\n  vec![\n    SectionItem::new().action(|| {\n      let os_info = os_info::get();\n      format!(\n        \"OS: {} {} {} ({:?}){}\",\n        os_info.os_type(),\n        os_info.version(),\n        os_info.architecture().unwrap_or(\"Unknown Architecture\"),\n        os_info.bitness(),\n        de_and_session(),\n      ).into()\n    }),\n    #[cfg(windows)]\n    SectionItem::new().action(|| {\n      let error = format!(\n          \"Webview2: {}\\nVisit {}\",\n          \"not installed!\".red(),\n          \"https://developer.microsoft.com/en-us/microsoft-edge/webview2/\".cyan()\n        );\n      webview2_version()\n        .map(|v| {\n          v.map(|v| (format!(\"WebView2: {v}\"), Status::Success))\n            .unwrap_or_else(|| (error.clone(), Status::Error))\n        })\n        .unwrap_or_else(|_| (error, Status::Error)).into()\n    }),\n    #[cfg(windows)]\n    SectionItem::new().action(|| {\n      let build_tools = build_tools_version().unwrap_or_default();\n      if build_tools.is_empty() {\n        (\n            format!(\n              \"Couldn't detect any Visual Studio or VS Build Tools instance with MSVC and SDK components. Download from {}\",\n              \"https://aka.ms/vs/17/release/vs_BuildTools.exe\".cyan()\n            ),\n            Status::Error,\n          ).into()\n      } else {\n        (\n          format!(\n            \"MSVC: {}{}\",\n            if build_tools.len() > 1 {\n              format!(\"\\n  {} \", \"-\".cyan())\n            } else {\n              \"\".into()\n            },\n            build_tools.join(format!(\"\\n  {} \", \"-\".cyan()).as_str()),\n          ),\n          Status::Success,\n        ).into()\n      }\n    }),\n    #[cfg(any(\n      target_os = \"linux\",\n      target_os = \"dragonfly\",\n      target_os = \"freebsd\",\n      target_os = \"openbsd\",\n      target_os = \"netbsd\"\n    ))]\n    SectionItem::new().action(|| {\n          webkit2gtk_ver()\n            .map(|v| (format!(\"webkit2gtk-4.1: {v}\"), Status::Success))\n            .unwrap_or_else(|| {\n              (\n                format!(\n                  \"webkit2gtk-4.1: {}\\nVisit {} to learn more about tauri prerequisites\",\n                  \"not installed\".red(),\n                  \"https://v2.tauri.app/start/prerequisites/\".cyan()\n                ),\n                Status::Error,\n              )\n            }).into()\n      },\n    ),\n    #[cfg(any(\n      target_os = \"linux\",\n      target_os = \"dragonfly\",\n      target_os = \"freebsd\",\n      target_os = \"openbsd\",\n      target_os = \"netbsd\"\n    ))]\n    SectionItem::new().action(|| {\n          rsvg2_ver()\n            .map(|v| (format!(\"rsvg2: {v}\"), Status::Success))\n            .unwrap_or_else(|| {\n              (\n                format!(\n                  \"rsvg2: {}\\nVisit {} to learn more about tauri prerequisites\",\n                  \"not installed\".red(),\n                  \"https://v2.tauri.app/start/prerequisites/\".cyan()\n                ),\n                Status::Error,\n              )\n            }).into()\n      },\n    ),\n    #[cfg(target_os = \"macos\")]\n    SectionItem::new().action(|| {\n        if is_xcode_command_line_tools_installed() {\n          (\n            \"Xcode Command Line Tools: installed\".into(),\n            Status::Success,\n          )\n        } else {\n          (\n            format!(\n              \"Xcode Command Line Tools: {}\\n Run `{}`\",\n              \"not installed!\".red(),\n              \"xcode-select --install\".cyan()\n            ),\n            Status::Error,\n          )\n        }.into()\n      },\n    ),\n    #[cfg(target_os = \"macos\")]\n    SectionItem::new().action(|| {\n      xcode_version().map(|v| (format!(\"Xcode: {v}\"), Status::Success)).unwrap_or_else(|| {\n          (format!(\"Xcode: {}\", \"not installed!\".red()), Status::Error)\n      }).into()\n    }),\n  ]\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/info/ios.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::SectionItem;\n\nuse colored::Colorize;\n\npub fn items() -> Vec<SectionItem> {\n  vec![SectionItem::new().action(|| {\n    let teams = cargo_mobile2::apple::teams::find_development_teams().unwrap_or_default();\n\n    if teams.is_empty() {\n      \"Developer Teams: None\".red().to_string().into()\n    } else {\n      format!(\n        \"Developer Teams: {}\",\n        teams\n          .iter()\n          .map(|t| format!(\"{} (ID: {})\", t.name, t.id))\n          .collect::<Vec<String>>()\n          .join(\", \")\n      )\n      .into()\n    }\n  })]\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/info/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::Context,\n  helpers::app_paths::{resolve_frontend_dir, resolve_tauri_dir},\n  Result,\n};\nuse clap::Parser;\nuse colored::{ColoredString, Colorize};\nuse dialoguer::{theme::ColorfulTheme, Confirm};\nuse serde::Deserialize;\nuse std::fmt::{self, Display, Formatter};\nuse tauri_utils::platform::Target;\n\nmod app;\nmod env_nodejs;\nmod env_rust;\npub mod env_system;\n#[cfg(target_os = \"macos\")]\nmod ios;\nmod packages_nodejs;\nmod packages_rust;\npub mod plugins;\n\n#[derive(Deserialize)]\nstruct JsCliVersionMetadata {\n  version: String,\n  node: String,\n}\n\n#[derive(Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct VersionMetadata {\n  #[serde(rename = \"cli.js\")]\n  js_cli: JsCliVersionMetadata,\n}\n\nfn version_metadata() -> Result<VersionMetadata> {\n  serde_json::from_str::<VersionMetadata>(include_str!(\"../../metadata-v2.json\"))\n    .context(\"failed to parse version metadata\")\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]\npub enum Status {\n  Neutral = 0,\n  #[default]\n  Success,\n  Warning,\n  Error,\n}\n\nimpl Status {\n  fn color<S: AsRef<str>>(&self, s: S) -> ColoredString {\n    let s = s.as_ref();\n    match self {\n      Status::Neutral => s.normal(),\n      Status::Success => s.green(),\n      Status::Warning => s.yellow(),\n      Status::Error => s.red(),\n    }\n  }\n}\n\nimpl Display for Status {\n  fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n    write!(\n      f,\n      \"{}\",\n      match self {\n        Status::Neutral => \"-\".cyan(),\n        Status::Success => \"✔\".green(),\n        Status::Warning => \"⚠\".yellow(),\n        Status::Error => \"✘\".red(),\n      }\n    )\n  }\n}\n\n#[derive(Default)]\npub enum ActionResult {\n  Full {\n    description: String,\n    status: Status,\n  },\n  Description(String),\n  #[default]\n  None,\n}\n\nimpl From<String> for ActionResult {\n  fn from(value: String) -> Self {\n    ActionResult::Description(value)\n  }\n}\n\nimpl From<(String, Status)> for ActionResult {\n  fn from(value: (String, Status)) -> Self {\n    ActionResult::Full {\n      description: value.0,\n      status: value.1,\n    }\n  }\n}\n\nimpl From<Option<String>> for ActionResult {\n  fn from(value: Option<String>) -> Self {\n    value.map(ActionResult::Description).unwrap_or_default()\n  }\n}\n\nimpl From<Option<(String, Status)>> for ActionResult {\n  fn from(value: Option<(String, Status)>) -> Self {\n    value\n      .map(|v| ActionResult::Full {\n        description: v.0,\n        status: v.1,\n      })\n      .unwrap_or_default()\n  }\n}\n\npub struct SectionItem {\n  /// If description is none, the item is skipped\n  description: Option<String>,\n  status: Status,\n  action: Option<Box<dyn FnMut() -> ActionResult>>,\n  action_if_err: Option<Box<dyn FnMut() -> ActionResult>>,\n}\n\nimpl Display for SectionItem {\n  fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n    let desc = self\n      .description\n      .as_ref()\n      .map(|s| s.replace('\\n', \"\\n      \"))\n      .unwrap_or_default();\n    let (first, second) = desc.split_once(':').unwrap();\n    write!(f, \"{} {}:{}\", self.status, first.bold(), second)\n  }\n}\n\nimpl SectionItem {\n  fn new() -> Self {\n    Self {\n      action: None,\n      action_if_err: None,\n      description: None,\n      status: Status::Neutral,\n    }\n  }\n\n  fn action<F: FnMut() -> ActionResult + 'static>(mut self, action: F) -> Self {\n    self.action = Some(Box::new(action));\n    self\n  }\n\n  // fn action_if_err<F: FnMut() -> ActionResult + 'static>(mut self, action: F) -> Self {\n  //   self.action_if_err = Some(Box::new(action));\n  //   self\n  // }\n\n  fn description<S: AsRef<str>>(mut self, description: S) -> Self {\n    self.description = Some(description.as_ref().to_string());\n    self\n  }\n\n  fn run_action(&mut self) {\n    let mut res = ActionResult::None;\n    if let Some(action) = &mut self.action {\n      res = action();\n    }\n    self.apply_action_result(res);\n  }\n\n  fn run_action_if_err(&mut self) {\n    let mut res = ActionResult::None;\n    if let Some(action) = &mut self.action_if_err {\n      res = action();\n    }\n    self.apply_action_result(res);\n  }\n\n  fn apply_action_result(&mut self, result: ActionResult) {\n    match result {\n      ActionResult::Full {\n        description,\n        status,\n      } => {\n        self.description = Some(description);\n        self.status = status;\n      }\n      ActionResult::Description(description) => {\n        self.description = Some(description);\n      }\n      ActionResult::None => {}\n    }\n  }\n\n  fn run(&mut self, interactive: bool) -> Status {\n    self.run_action();\n\n    if self.status == Status::Error && interactive && self.action_if_err.is_some() {\n      if let Some(description) = &self.description {\n        let confirmed = Confirm::with_theme(&ColorfulTheme::default())\n          .with_prompt(format!(\n            \"{}\\n  Run the automatic fix?\",\n            description.replace('\\n', \"\\n  \")\n          ))\n          .interact()\n          .unwrap_or(false);\n        if confirmed {\n          self.run_action_if_err()\n        }\n      }\n    }\n\n    self.status\n  }\n}\n\nstruct Section<'a> {\n  label: &'a str,\n  interactive: bool,\n  items: Vec<SectionItem>,\n}\n\nimpl Section<'_> {\n  fn display(&mut self) {\n    let mut status = Status::Neutral;\n\n    for item in &mut self.items {\n      let s = item.run(self.interactive);\n      if s > status {\n        status = s;\n      }\n    }\n\n    let status_str = format!(\"[{status}]\");\n    let status = status.color(status_str);\n\n    println!();\n    println!(\"{} {}\", status, self.label.bold().yellow());\n    for item in &self.items {\n      if item.description.is_some() {\n        println!(\"    {item}\");\n      }\n    }\n  }\n}\n\n#[derive(Debug, Parser)]\n#[clap(\n  about = \"Show a concise list of information about the environment, Rust, Node.js and their versions as well as a few relevant project configurations\"\n)]\npub struct Options {\n  /// Interactive mode to apply automatic fixes.\n  #[clap(long)]\n  pub interactive: bool,\n}\n\npub fn command(options: Options) -> Result<()> {\n  let Options { interactive } = options;\n\n  let frontend_dir = resolve_frontend_dir();\n  let tauri_dir = resolve_tauri_dir();\n\n  let package_manager = frontend_dir\n    .as_ref()\n    .map(packages_nodejs::package_manager)\n    .unwrap_or(crate::helpers::npm::PackageManager::Npm);\n\n  let metadata = version_metadata()?;\n\n  let mut environment = Section {\n    label: \"Environment\",\n    interactive,\n    items: Vec::new(),\n  };\n  environment.items.extend(env_system::items());\n  environment.items.extend(env_rust::items());\n  let items = env_nodejs::items(&metadata);\n  environment.items.extend(items);\n\n  let mut packages = Section {\n    label: \"Packages\",\n    interactive,\n    items: Vec::new(),\n  };\n  packages.items.extend(packages_rust::items(\n    frontend_dir.as_ref(),\n    tauri_dir.as_deref(),\n  ));\n  packages.items.extend(packages_nodejs::items(\n    frontend_dir.as_ref(),\n    package_manager,\n    &metadata,\n  ));\n\n  let mut plugins = Section {\n    label: \"Plugins\",\n    interactive,\n    items: plugins::items(frontend_dir.as_ref(), tauri_dir.as_deref(), package_manager),\n  };\n\n  let mut app = Section {\n    label: \"App\",\n    interactive,\n    items: Vec::new(),\n  };\n  if let Some(tauri_dir) = &tauri_dir {\n    if let Ok(config) = crate::helpers::config::get_config(Target::current(), &[], tauri_dir) {\n      app.items.extend(app::items(&config, frontend_dir.as_ref()));\n    };\n  }\n\n  environment.display();\n\n  packages.display();\n\n  plugins.display();\n\n  if let (Some(frontend_dir), Some(tauri_dir)) = (&frontend_dir, &tauri_dir) {\n    if let Err(error) = plugins::check_mismatched_packages(frontend_dir, tauri_dir) {\n      println!(\"\\n{}: {error}\", \"Error\".bright_red().bold());\n    }\n  }\n\n  app.display();\n\n  // iOS\n  #[cfg(target_os = \"macos\")]\n  {\n    if let Some(p) = &tauri_dir {\n      if p.join(\"gen/apple\").exists() {\n        let mut ios = Section {\n          label: \"iOS\",\n          interactive,\n          items: Vec::new(),\n        };\n        ios.items.extend(ios::items());\n        ios.display();\n      }\n    }\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/info/packages_nodejs.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::SectionItem;\nuse super::VersionMetadata;\nuse colored::Colorize;\nuse serde::Deserialize;\nuse std::path::PathBuf;\n\nuse crate::error::Context;\nuse crate::{\n  error::Error,\n  helpers::{cross_command, npm::PackageManager},\n};\n\n#[derive(Deserialize)]\nstruct YarnVersionInfo {\n  data: Vec<String>,\n}\n\npub fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result<Option<String>> {\n  match pm {\n    PackageManager::Yarn => {\n      let mut cmd = cross_command(\"yarn\");\n\n      let output = cmd\n        .arg(\"info\")\n        .arg(name)\n        .args([\"version\", \"--json\"])\n        .output()\n        .map_err(|error| Error::CommandFailed {\n          command: \"yarn info --json\".to_string(),\n          error,\n        })?;\n      if output.status.success() {\n        let stdout = String::from_utf8_lossy(&output.stdout);\n        let info: YarnVersionInfo =\n          serde_json::from_str(&stdout).context(\"failed to parse yarn info\")?;\n        Ok(Some(info.data.last().unwrap().to_string()))\n      } else {\n        Ok(None)\n      }\n    }\n    PackageManager::YarnBerry => {\n      let mut cmd = cross_command(\"yarn\");\n\n      let output = cmd\n        .arg(\"npm\")\n        .arg(\"info\")\n        .arg(name)\n        .args([\"--fields\", \"version\", \"--json\"])\n        .output()\n        .map_err(|error| Error::CommandFailed {\n          command: \"yarn npm info --fields version --json\".to_string(),\n          error,\n        })?;\n      if output.status.success() {\n        let info: crate::PackageJson = serde_json::from_reader(std::io::Cursor::new(output.stdout))\n          .context(\"failed to parse yarn npm info\")?;\n        Ok(info.version)\n      } else {\n        Ok(None)\n      }\n    }\n    // Bun and Deno don't support show command\n    PackageManager::Npm | PackageManager::Deno | PackageManager::Bun => {\n      let mut cmd = cross_command(\"npm\");\n\n      let output = cmd\n        .arg(\"show\")\n        .arg(name)\n        .arg(\"version\")\n        .output()\n        .map_err(|error| Error::CommandFailed {\n          command: \"npm show --version\".to_string(),\n          error,\n        })?;\n      if output.status.success() {\n        let stdout = String::from_utf8_lossy(&output.stdout);\n        Ok(Some(stdout.replace('\\n', \"\")))\n      } else {\n        Ok(None)\n      }\n    }\n    PackageManager::Pnpm => {\n      let mut cmd = cross_command(\"pnpm\");\n\n      let output = cmd\n        .arg(\"info\")\n        .arg(name)\n        .arg(\"version\")\n        .output()\n        .map_err(|error| Error::CommandFailed {\n          command: \"pnpm info --version\".to_string(),\n          error,\n        })?;\n      if output.status.success() {\n        let stdout = String::from_utf8_lossy(&output.stdout);\n        Ok(Some(stdout.replace('\\n', \"\")))\n      } else {\n        Ok(None)\n      }\n    }\n  }\n}\n\npub fn package_manager(frontend_dir: &PathBuf) -> PackageManager {\n  let found = PackageManager::all_from_project(frontend_dir);\n\n  if found.is_empty() {\n    println!(\n      \"{}: no lock files found, defaulting to npm\",\n      \"WARNING\".yellow()\n    );\n    return PackageManager::Npm;\n  }\n\n  let pkg_manager = found[0];\n\n  if found.len() > 1 {\n    println!(\n          \"{}: Only one package manager should be used, but found {}.\\n         Please remove unused package manager lock files, will use {} for now!\",\n          \"WARNING\".yellow(),\n          found.iter().map(ToString::to_string).collect::<Vec<_>>().join(\" and \"),\n          pkg_manager\n        );\n  }\n\n  pkg_manager\n}\n\npub fn items(\n  frontend_dir: Option<&PathBuf>,\n  package_manager: PackageManager,\n  metadata: &VersionMetadata,\n) -> Vec<SectionItem> {\n  let mut items = Vec::new();\n  if let Some(frontend_dir) = frontend_dir {\n    for (package, version) in [\n      (\"@tauri-apps/api\", None),\n      (\"@tauri-apps/cli\", Some(metadata.js_cli.version.clone())),\n    ] {\n      let frontend_dir = frontend_dir.clone();\n      let item = nodejs_section_item(package.into(), version, frontend_dir, package_manager);\n      items.push(item);\n    }\n  }\n\n  items\n}\n\npub fn nodejs_section_item(\n  package: String,\n  version: Option<String>,\n  frontend_dir: PathBuf,\n  package_manager: PackageManager,\n) -> SectionItem {\n  SectionItem::new().action(move || {\n    let version = version.clone().unwrap_or_else(|| {\n      package_manager\n        .current_package_version(&package, &frontend_dir)\n        .unwrap_or_default()\n        .unwrap_or_default()\n    });\n\n    let latest_ver = super::packages_nodejs::npm_latest_version(&package_manager, &package)\n      .unwrap_or_default()\n      .unwrap_or_default();\n\n    if version.is_empty() {\n      format!(\"{} {}: not installed!\", package, \" ⱼₛ\".black().on_yellow())\n    } else {\n      format!(\n        \"{} {}: {}{}\",\n        package,\n        \" ⱼₛ\".black().on_yellow(),\n        version,\n        if !(version.is_empty() || latest_ver.is_empty()) {\n          let version = semver::Version::parse(version.as_str()).unwrap();\n          let target_version = semver::Version::parse(latest_ver.as_str()).unwrap();\n\n          if version < target_version {\n            format!(\" ({}, latest: {})\", \"outdated\".yellow(), latest_ver.green())\n          } else {\n            \"\".into()\n          }\n        } else {\n          \"\".into()\n        }\n      )\n    }\n    .into()\n  })\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/info/packages_rust.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{ActionResult, SectionItem};\nuse crate::helpers::cargo_manifest::{\n  cargo_manifest_and_lock, crate_latest_version, crate_version, CrateVersion,\n};\nuse colored::Colorize;\nuse std::path::{Path, PathBuf};\n\npub fn items(frontend_dir: Option<&PathBuf>, tauri_dir: Option<&Path>) -> Vec<SectionItem> {\n  let mut items = Vec::new();\n\n  if tauri_dir.is_some() || frontend_dir.is_some() {\n    if let Some(tauri_dir) = tauri_dir {\n      let (manifest, lock) = cargo_manifest_and_lock(tauri_dir);\n      for dep in [\"tauri\", \"tauri-build\", \"wry\", \"tao\"] {\n        let crate_version = crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), dep);\n        let item = rust_section_item(dep, crate_version);\n        items.push(item);\n      }\n    }\n  }\n\n  let tauri_cli_rust_item = SectionItem::new().action(|| {\n    std::process::Command::new(\"cargo\")\n      .arg(\"tauri\")\n      .arg(\"-V\")\n      .output()\n      .ok()\n      .map(|o| {\n        if o.status.success() {\n          let out = String::from_utf8_lossy(o.stdout.as_slice());\n          let (package, version) = out.split_once(' ').unwrap_or_default();\n          let version = version.strip_suffix('\\n').unwrap_or(version);\n          let latest_version = crate_latest_version(package).unwrap_or_default();\n          format!(\n            \"{package} 🦀: {version}{}\",\n            if !(version.is_empty() || latest_version.is_empty()) {\n              let current_version = semver::Version::parse(version).unwrap();\n              let target_version = semver::Version::parse(latest_version.as_str()).unwrap();\n\n              if current_version < target_version {\n                format!(\n                  \" ({}, latest: {})\",\n                  \"outdated\".yellow(),\n                  latest_version.green()\n                )\n              } else {\n                \"\".into()\n              }\n            } else {\n              \"\".into()\n            }\n          )\n          .into()\n        } else {\n          ActionResult::None\n        }\n      })\n      .unwrap_or_default()\n  });\n  items.push(tauri_cli_rust_item);\n\n  items\n}\n\npub fn rust_section_item(dep: &str, crate_version: CrateVersion) -> SectionItem {\n  let version = crate_version\n    .version\n    .as_ref()\n    .and_then(|v| semver::Version::parse(v).ok());\n\n  let version_suffix = match (version, crate_latest_version(dep)) {\n    (Some(version), Some(target_version)) => {\n      let target_version = semver::Version::parse(&target_version).unwrap();\n      if version < target_version {\n        Some(format!(\n          \" ({}, latest: {})\",\n          \"outdated\".yellow(),\n          target_version.to_string().green()\n        ))\n      } else {\n        None\n      }\n    }\n    _ => None,\n  };\n\n  SectionItem::new().description(format!(\n    \"{} {}: {}{}\",\n    dep,\n    \"🦀\",\n    crate_version,\n    version_suffix\n      .map(|s| format!(\",{s}\"))\n      .unwrap_or_else(|| \"\".into())\n  ))\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/info/plugins.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  collections::HashMap,\n  iter,\n  path::{Path, PathBuf},\n};\n\nuse crate::{\n  helpers::{\n    self,\n    cargo_manifest::{cargo_manifest_and_lock, crate_version},\n    npm::PackageManager,\n  },\n  Error,\n};\n\nuse super::{packages_nodejs, packages_rust, SectionItem};\n\n#[derive(Debug)]\npub struct InstalledPackage {\n  pub crate_name: String,\n  pub npm_name: String,\n  pub crate_version: semver::Version,\n  pub npm_version: semver::Version,\n}\n\n#[derive(Debug)]\npub struct InstalledPackages(Vec<InstalledPackage>);\n\nimpl InstalledPackages {\n  pub fn mismatched(&self) -> Vec<&InstalledPackage> {\n    self\n      .0\n      .iter()\n      .filter(|p| {\n        p.crate_version.major != p.npm_version.major || p.crate_version.minor != p.npm_version.minor\n      })\n      .collect()\n  }\n}\n\npub fn installed_tauri_packages(\n  frontend_dir: &Path,\n  tauri_dir: &Path,\n  package_manager: PackageManager,\n) -> InstalledPackages {\n  let know_plugins = helpers::plugins::known_plugins();\n  let crate_names: Vec<String> = iter::once(\"tauri\".to_owned())\n    .chain(\n      know_plugins\n        .keys()\n        .map(|plugin_name| format!(\"tauri-plugin-{plugin_name}\")),\n    )\n    .collect();\n  let npm_names: Vec<String> = iter::once(\"@tauri-apps/api\".to_owned())\n    .chain(\n      know_plugins\n        .keys()\n        .map(|plugin_name| format!(\"@tauri-apps/plugin-{plugin_name}\")),\n    )\n    .collect();\n\n  let (manifest, lock) = cargo_manifest_and_lock(tauri_dir);\n\n  let mut rust_plugins: HashMap<String, semver::Version> = crate_names\n    .iter()\n    .filter_map(|crate_name| {\n      let crate_version =\n        crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), crate_name).version?;\n      let crate_version = semver::Version::parse(&crate_version)\n        .inspect_err(|_| {\n          // On first run there's no lockfile yet so we get the version requirement from Cargo.toml.\n          // In our templates that's `2` which is not a valid semver version but a version requirement.\n          // log::error confused users so we use log::debug to still be able to see this error if needed.\n          log::debug!(\"Failed to parse version `{crate_version}` for crate `{crate_name}`\");\n        })\n        .ok()?;\n      Some((crate_name.clone(), crate_version))\n    })\n    .collect();\n\n  let mut npm_plugins = package_manager\n    .current_package_versions(&npm_names, frontend_dir)\n    .unwrap_or_default();\n\n  let installed_plugins = crate_names\n    .iter()\n    .zip(npm_names.iter())\n    .filter_map(|(crate_name, npm_name)| {\n      let (crate_name, crate_version) = rust_plugins.remove_entry(crate_name)?;\n      let (npm_name, npm_version) = npm_plugins.remove_entry(npm_name)?;\n      Some(InstalledPackage {\n        npm_name,\n        npm_version,\n        crate_name,\n        crate_version,\n      })\n    })\n    .collect();\n\n  InstalledPackages(installed_plugins)\n}\n\npub fn items(\n  frontend_dir: Option<&PathBuf>,\n  tauri_dir: Option<&Path>,\n  package_manager: PackageManager,\n) -> Vec<SectionItem> {\n  let mut items = Vec::new();\n\n  if let Some(tauri_dir) = tauri_dir {\n    let (manifest, lock) = cargo_manifest_and_lock(tauri_dir);\n\n    for p in helpers::plugins::known_plugins().keys() {\n      let dep = format!(\"tauri-plugin-{p}\");\n      let crate_version = crate_version(tauri_dir, manifest.as_ref(), lock.as_ref(), &dep);\n      if !crate_version.has_version() {\n        continue;\n      }\n      let item = packages_rust::rust_section_item(&dep, crate_version);\n      items.push(item);\n\n      let Some(frontend_dir) = frontend_dir else {\n        continue;\n      };\n\n      let package = format!(\"@tauri-apps/plugin-{p}\");\n\n      let item =\n        packages_nodejs::nodejs_section_item(package, None, frontend_dir.clone(), package_manager);\n      items.push(item);\n    }\n  }\n\n  items\n}\n\npub fn check_mismatched_packages(frontend_dir: &Path, tauri_path: &Path) -> crate::Result<()> {\n  let installed_packages = installed_tauri_packages(\n    frontend_dir,\n    tauri_path,\n    PackageManager::from_project(frontend_dir),\n  );\n  let mismatched_packages = installed_packages.mismatched();\n  if mismatched_packages.is_empty() {\n    return Ok(());\n  }\n  let mismatched_text = mismatched_packages\n    .iter()\n    .map(\n      |InstalledPackage {\n         crate_name,\n         crate_version,\n         npm_name,\n         npm_version,\n       }| format!(\"{crate_name} (v{crate_version}) : {npm_name} (v{npm_version})\"),\n    )\n    .collect::<Vec<_>>()\n    .join(\"\\n\");\n  Err(Error::GenericError(format!(\"Found version mismatched Tauri packages. Make sure the NPM package and Rust crate versions are on the same major/minor releases:\\n{mismatched_text}\")))\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/init.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  helpers::{\n    framework::{infer_from_package_json as infer_framework, Framework},\n    npm::PackageManager,\n    prompts, resolve_tauri_path, template,\n  },\n  VersionMetadata,\n};\nuse std::{\n  collections::BTreeMap,\n  env::current_dir,\n  fs::{read_to_string, remove_dir_all},\n  path::PathBuf,\n};\n\nuse crate::{\n  error::{Context, ErrorExt},\n  Result,\n};\nuse clap::Parser;\nuse handlebars::{to_json, Handlebars};\nuse include_dir::{include_dir, Dir};\n\nconst TEMPLATE_DIR: Dir<'_> = include_dir!(\"$CARGO_MANIFEST_DIR/templates/app\");\nconst TAURI_CONF_TEMPLATE: &str = include_str!(\"../templates/tauri.conf.json\");\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Initialize a Tauri project in an existing directory\")]\npub struct Options {\n  /// Skip prompting for values\n  #[clap(long, env = \"CI\")]\n  ci: bool,\n  /// Force init to overwrite the src-tauri folder\n  #[clap(short, long)]\n  force: bool,\n  /// Enables logging\n  #[clap(short, long)]\n  log: bool,\n  /// Set target directory for init\n  #[clap(short, long)]\n  #[clap(default_value_t = current_dir().expect(\"failed to read cwd\").display().to_string())]\n  directory: String,\n  /// Path of the Tauri project to use (relative to the cwd)\n  #[clap(short, long)]\n  tauri_path: Option<PathBuf>,\n  /// Name of your Tauri application\n  #[clap(short = 'A', long)]\n  app_name: Option<String>,\n  /// Window title of your Tauri application\n  #[clap(short = 'W', long)]\n  window_title: Option<String>,\n  /// Web assets location, relative to <project-dir>/src-tauri\n  #[clap(short = 'D', long)]\n  frontend_dist: Option<String>,\n  /// Url of your dev server\n  #[clap(short = 'P', long)]\n  dev_url: Option<String>,\n  /// A shell command to run before `tauri dev` kicks in.\n  #[clap(long)]\n  before_dev_command: Option<String>,\n  /// A shell command to run before `tauri build` kicks in.\n  #[clap(long)]\n  before_build_command: Option<String>,\n}\n\n#[derive(Default)]\nstruct InitDefaults {\n  app_name: Option<String>,\n  framework: Option<Framework>,\n}\n\nimpl Options {\n  fn load(mut self) -> Result<Self> {\n    let package_json_path = PathBuf::from(&self.directory).join(\"package.json\");\n\n    let init_defaults = if package_json_path.exists() {\n      let package_json_text =\n        read_to_string(&package_json_path).fs_context(\"failed to read\", &package_json_path)?;\n      let package_json: crate::PackageJson =\n        serde_json::from_str(&package_json_text).context(\"failed to parse JSON\")?;\n      let (framework, _) = infer_framework(&package_json_text);\n      InitDefaults {\n        app_name: package_json.product_name.or(package_json.name),\n        framework,\n      }\n    } else {\n      Default::default()\n    };\n\n    self.app_name = self.app_name.map(|s| Ok(Some(s))).unwrap_or_else(|| {\n      prompts::input(\n        \"What is your app name?\",\n        Some(\n          init_defaults\n            .app_name\n            .clone()\n            .unwrap_or_else(|| \"Tauri App\".to_string()),\n        ),\n        self.ci,\n        true,\n      )\n    })?;\n\n    self.window_title = self.window_title.map(|s| Ok(Some(s))).unwrap_or_else(|| {\n      prompts::input(\n        \"What should the window title be?\",\n        Some(\n          init_defaults\n            .app_name\n            .clone()\n            .unwrap_or_else(|| \"Tauri\".to_string()),\n        ),\n        self.ci,\n        true,\n      )\n    })?;\n\n    self.frontend_dist = self.frontend_dist.map(|s| Ok(Some(s))).unwrap_or_else(|| prompts::input(\n      r#\"Where are your web assets (HTML/CSS/JS) located, relative to the \"<current dir>/src-tauri/tauri.conf.json\" file that will be created?\"#,\n      init_defaults.framework.as_ref().map(|f| f.frontend_dist()),\n      self.ci,\n      false,\n    ))?;\n\n    self.dev_url = self.dev_url.map(|s| Ok(Some(s))).unwrap_or_else(|| {\n      prompts::input(\n        \"What is the url of your dev server?\",\n        init_defaults.framework.map(|f| f.dev_url()),\n        self.ci,\n        true,\n      )\n    })?;\n\n    let detected_package_manager = PackageManager::from_project(&self.directory);\n\n    self.before_dev_command = self\n      .before_dev_command\n      .map(|s| Ok(Some(s)))\n      .unwrap_or_else(|| {\n        prompts::input(\n          \"What is your frontend dev command?\",\n          Some(default_dev_command(detected_package_manager).into()),\n          self.ci,\n          true,\n        )\n      })?;\n\n    self.before_build_command = self\n      .before_build_command\n      .map(|s| Ok(Some(s)))\n      .unwrap_or_else(|| {\n        prompts::input(\n          \"What is your frontend build command?\",\n          Some(default_build_command(detected_package_manager).into()),\n          self.ci,\n          true,\n        )\n      })?;\n\n    Ok(self)\n  }\n}\n\nfn default_dev_command(pm: PackageManager) -> &'static str {\n  match pm {\n    PackageManager::Yarn => \"yarn dev\",\n    PackageManager::YarnBerry => \"yarn dev\",\n    PackageManager::Npm => \"npm run dev\",\n    PackageManager::Pnpm => \"pnpm dev\",\n    PackageManager::Bun => \"bun dev\",\n    PackageManager::Deno => \"deno task dev\",\n  }\n}\n\nfn default_build_command(pm: PackageManager) -> &'static str {\n  match pm {\n    PackageManager::Yarn => \"yarn build\",\n    PackageManager::YarnBerry => \"yarn build\",\n    PackageManager::Npm => \"npm run build\",\n    PackageManager::Pnpm => \"pnpm build\",\n    PackageManager::Bun => \"bun build\",\n    PackageManager::Deno => \"deno task build\",\n  }\n}\n\npub fn command(mut options: Options) -> Result<()> {\n  options = options.load()?;\n\n  let template_target_path = PathBuf::from(&options.directory).join(\"src-tauri\");\n  let metadata = serde_json::from_str::<VersionMetadata>(include_str!(\"../metadata-v2.json\"))\n    .context(\"failed to parse version metadata\")?;\n\n  if template_target_path.exists() && !options.force {\n    log::warn!(\n      \"Tauri dir ({:?}) not empty. Run `init --force` to overwrite.\",\n      template_target_path\n    );\n  } else {\n    let (tauri_dep, tauri_build_dep, tauri_utils_dep, tauri_plugin_dep) =\n      if let Some(tauri_path) = &options.tauri_path {\n        (\n          format!(\n            r#\"{{  path = {:?} }}\"#,\n            resolve_tauri_path(tauri_path, \"crates/tauri\")\n          ),\n          format!(\n            \"{{  path = {:?} }}\",\n            resolve_tauri_path(tauri_path, \"crates/tauri-build\")\n          ),\n          format!(\n            \"{{  path = {:?} }}\",\n            resolve_tauri_path(tauri_path, \"crates/tauri-utils\")\n          ),\n          format!(\n            \"{{  path = {:?} }}\",\n            resolve_tauri_path(tauri_path, \"crates/tauri-plugin\")\n          ),\n        )\n      } else {\n        (\n          format!(r#\"{{ version = \"{}\" }}\"#, metadata.tauri),\n          format!(r#\"{{ version = \"{}\" }}\"#, metadata.tauri_build),\n          r#\"{{ version = \"2\" }}\"#.to_string(),\n          r#\"{{ version = \"2\" }}\"#.to_string(),\n        )\n      };\n\n    let _ = remove_dir_all(&template_target_path);\n    let mut handlebars = Handlebars::new();\n    handlebars.register_escape_fn(handlebars::no_escape);\n\n    let mut data = BTreeMap::new();\n    data.insert(\"tauri_dep\", to_json(tauri_dep));\n    if options.tauri_path.is_some() {\n      data.insert(\"patch_tauri_dep\", to_json(true));\n    }\n    data.insert(\"tauri_build_dep\", to_json(tauri_build_dep));\n    data.insert(\"tauri_utils_dep\", to_json(tauri_utils_dep));\n    data.insert(\"tauri_plugin_dep\", to_json(tauri_plugin_dep));\n    data.insert(\n      \"frontend_dist\",\n      to_json(options.frontend_dist.as_deref().unwrap_or(\"../dist\")),\n    );\n    data.insert(\"dev_url\", to_json(options.dev_url));\n    data.insert(\n      \"app_name\",\n      to_json(options.app_name.as_deref().unwrap_or(\"Tauri App\")),\n    );\n    data.insert(\n      \"window_title\",\n      to_json(options.window_title.as_deref().unwrap_or(\"Tauri\")),\n    );\n    data.insert(\"before_dev_command\", to_json(options.before_dev_command));\n    data.insert(\n      \"before_build_command\",\n      to_json(options.before_build_command),\n    );\n\n    let mut config = serde_json::from_str(\n      &handlebars\n        .render_template(TAURI_CONF_TEMPLATE, &data)\n        .expect(\"Failed to render tauri.conf.json template\"),\n    )\n    .unwrap();\n    if option_env!(\"TARGET\") == Some(\"node\") {\n      let mut dir = current_dir().expect(\"failed to read cwd\");\n      let mut count = 0;\n      let mut cli_node_module_path = None;\n      let cli_path = \"node_modules/@tauri-apps/cli\";\n\n      // only go up three folders max\n      while count <= 2 {\n        let test_path = dir.join(cli_path);\n        if test_path.exists() {\n          let mut node_module_path = PathBuf::from(\"..\");\n          for _ in 0..count {\n            node_module_path.push(\"..\");\n          }\n          node_module_path.push(cli_path);\n          node_module_path.push(\"config.schema.json\");\n          cli_node_module_path.replace(node_module_path);\n          break;\n        }\n        count += 1;\n        match dir.parent() {\n          Some(parent) => {\n            dir = parent.to_path_buf();\n          }\n          None => break,\n        }\n      }\n\n      if let Some(cli_node_module_path) = cli_node_module_path {\n        let mut map = serde_json::Map::default();\n        map.insert(\n          \"$schema\".into(),\n          serde_json::Value::String(\n            cli_node_module_path\n              .display()\n              .to_string()\n              .replace('\\\\', \"/\"),\n          ),\n        );\n        let merge_config = serde_json::Value::Object(map);\n        json_patch::merge(&mut config, &merge_config);\n      }\n    }\n\n    data.insert(\n      \"tauri_config\",\n      to_json(serde_json::to_string_pretty(&config).unwrap()),\n    );\n\n    template::render(&handlebars, &data, &TEMPLATE_DIR, &options.directory)\n      .with_context(|| \"failed to render Tauri template\")?;\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/inspect.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\nuse std::path::Path;\n\nuse crate::Result;\nuse clap::{Parser, Subcommand};\n\nuse crate::interface::{AppInterface, AppSettings};\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Inspect values used by Tauri\")]\npub struct Cli {\n  #[clap(subcommand)]\n  command: Commands,\n}\n\n#[derive(Subcommand, Debug)]\nenum Commands {\n  /// Print the default Upgrade Code used by MSI installer derived from productName.\n  WixUpgradeCode,\n}\n\npub fn command(cli: Cli) -> Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n  match cli.command {\n    Commands::WixUpgradeCode => wix_upgrade_code(dirs.tauri),\n  }\n}\n\n// NOTE: if this is ever changed, make sure to also update Wix upgrade code generation in tauri-bundler\nfn wix_upgrade_code(tauri_dir: &Path) -> Result<()> {\n  let target = tauri_utils::platform::Target::Windows;\n  let config = crate::helpers::config::get_config(target, &[], tauri_dir)?;\n\n  let interface = AppInterface::new(&config, None, tauri_dir)?;\n\n  let product_name = interface.app_settings().get_package_settings().product_name;\n\n  let upgrade_code = uuid::Uuid::new_v5(\n    &uuid::Uuid::NAMESPACE_DNS,\n    format!(\"{product_name}.exe.app.x64\").as_bytes(),\n  );\n\n  log::info!(\"Default WiX Upgrade Code, derived from {product_name}: {upgrade_code}\");\n  if let Some(code) = config\n    .bundle\n    .windows\n    .wix\n    .as_ref()\n    .and_then(|wix| wix.upgrade_code)\n  {\n    log::info!(\"Application Upgrade Code override: {code}\");\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/interface/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npub mod rust;\n\nuse std::{\n  path::{Path, PathBuf},\n  process::ExitStatus,\n};\n\nuse crate::{error::Context, helpers::config::Config};\nuse tauri_bundler::bundle::{PackageType, Settings, SettingsBuilder};\n\npub use rust::{MobileOptions, Options, Rust as AppInterface, WatcherOptions};\n\npub trait DevProcess {\n  fn kill(&self) -> std::io::Result<()>;\n  #[allow(unused)]\n  fn wait(&self) -> std::io::Result<ExitStatus>;\n  #[allow(unused)]\n  fn manually_killed_process(&self) -> bool;\n}\n\npub trait AppSettings {\n  fn get_package_settings(&self) -> tauri_bundler::PackageSettings;\n  fn get_bundle_settings(\n    &self,\n    options: &Options,\n    config: &Config,\n    features: &[String],\n    tauri_dir: &Path,\n  ) -> crate::Result<tauri_bundler::BundleSettings>;\n  fn app_binary_path(&self, options: &Options, tauri_dir: &Path) -> crate::Result<PathBuf>;\n  fn get_binaries(\n    &self,\n    options: &Options,\n    tauri_dir: &Path,\n  ) -> crate::Result<Vec<tauri_bundler::BundleBinary>>;\n  fn app_name(&self) -> Option<String>;\n  fn lib_name(&self) -> Option<String>;\n\n  fn get_bundler_settings(\n    &self,\n    options: Options,\n    config: &Config,\n    out_dir: &Path,\n    package_types: Vec<PackageType>,\n    tauri_dir: &Path,\n  ) -> crate::Result<Settings> {\n    let no_default_features = options.args.contains(&\"--no-default-features\".into());\n    let mut enabled_features = options.features.clone();\n    if !no_default_features {\n      enabled_features.push(\"default\".into());\n    }\n\n    let target: String = if let Some(target) = options.target.clone() {\n      target\n    } else {\n      tauri_utils::platform::target_triple().context(\"failed to get target triple\")?\n    };\n\n    let mut bins = self.get_binaries(&options, tauri_dir)?;\n    if let Some(main_binary_name) = &config.main_binary_name {\n      let main = bins.iter_mut().find(|b| b.main()).context(\"no main bin?\")?;\n      main.set_name(main_binary_name.to_owned());\n    }\n\n    let mut settings_builder = SettingsBuilder::new()\n      .package_settings(self.get_package_settings())\n      .bundle_settings(self.get_bundle_settings(&options, config, &enabled_features, tauri_dir)?)\n      .binaries(bins)\n      .project_out_directory(out_dir)\n      .target(target)\n      .package_types(package_types);\n\n    if config.bundle.use_local_tools_dir {\n      settings_builder = settings_builder.local_tools_directory(\n        rust::get_cargo_metadata(tauri_dir)\n          .context(\"failed to get cargo metadata\")?\n          .target_directory,\n      )\n    }\n\n    settings_builder\n      .build()\n      .map_err(Box::new)\n      .map_err(Into::into)\n  }\n}\n\n#[derive(Debug)]\npub enum ExitReason {\n  /// Killed manually.\n  TriggeredKill,\n  /// App compilation failed.\n  CompilationFailed,\n  /// Regular exit.\n  NormalExit,\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/interface/rust/cargo_config.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::Deserialize;\nuse std::{\n  fs,\n  path::{Path, PathBuf},\n};\n\nuse tauri_utils::display_path;\n\nuse crate::{\n  error::{Context, ErrorExt},\n  Result,\n};\n\nstruct PathAncestors<'a> {\n  current: Option<&'a Path>,\n}\n\nimpl<'a> PathAncestors<'a> {\n  fn new(path: &'a Path) -> PathAncestors<'a> {\n    PathAncestors {\n      current: Some(path),\n    }\n  }\n}\n\nimpl<'a> Iterator for PathAncestors<'a> {\n  type Item = &'a Path;\n\n  fn next(&mut self) -> Option<&'a Path> {\n    if let Some(path) = self.current {\n      self.current = path.parent();\n\n      Some(path)\n    } else {\n      None\n    }\n  }\n}\n\n#[derive(Default, Deserialize)]\npub struct BuildConfig {\n  target: Option<String>,\n}\n\n#[derive(Deserialize)]\npub struct ConfigSchema {\n  build: Option<BuildConfig>,\n}\n\n#[derive(Default)]\npub struct Config {\n  build: BuildConfig,\n}\n\nimpl Config {\n  pub fn load(path: &Path) -> Result<Self> {\n    let mut config = Self::default();\n\n    let get_config = |path: PathBuf| -> Result<ConfigSchema> {\n      let contents =\n        fs::read_to_string(&path).fs_context(\"failed to read configuration file\", path.clone())?;\n      toml::from_str(&contents).context(format!(\n        \"could not parse TOML configuration in `{}`\",\n        display_path(&path)\n      ))\n    };\n\n    for current in PathAncestors::new(path) {\n      if let Some(path) = get_file_path(&current.join(\".cargo\"), \"config\", true)? {\n        let toml = get_config(path)?;\n        if let Some(target) = toml.build.and_then(|b| b.target) {\n          config.build.target = Some(target);\n          break;\n        }\n      }\n    }\n\n    if config.build.target.is_none() {\n      if let Ok(cargo_home) = std::env::var(\"CARGO_HOME\") {\n        if let Some(path) = get_file_path(&PathBuf::from(cargo_home), \"config\", true)? {\n          let toml = get_config(path)?;\n          if let Some(target) = toml.build.and_then(|b| b.target) {\n            config.build.target = Some(target);\n          }\n        }\n      }\n    }\n\n    Ok(config)\n  }\n\n  pub fn build(&self) -> &BuildConfig {\n    &self.build\n  }\n}\n\nimpl BuildConfig {\n  pub fn target(&self) -> Option<&str> {\n    self.target.as_deref()\n  }\n}\n\n/// The purpose of this function is to aid in the transition to using\n/// .toml extensions on Cargo's config files, which were historically not used.\n/// Both 'config.toml' and 'credentials.toml' should be valid with or without extension.\n/// When both exist, we want to prefer the one without an extension for\n/// backwards compatibility, but warn the user appropriately.\nfn get_file_path(\n  dir: &Path,\n  filename_without_extension: &str,\n  warn: bool,\n) -> Result<Option<PathBuf>> {\n  let possible = dir.join(filename_without_extension);\n  let possible_with_extension = dir.join(format!(\"{filename_without_extension}.toml\"));\n\n  if possible.exists() {\n    if warn && possible_with_extension.exists() {\n      // We don't want to print a warning if the version\n      // without the extension is just a symlink to the version\n      // WITH an extension, which people may want to do to\n      // support multiple Cargo versions at once and not\n      // get a warning.\n      let skip_warning = if let Ok(target_path) = fs::read_link(&possible) {\n        target_path == possible_with_extension\n      } else {\n        false\n      };\n\n      if !skip_warning {\n        log::warn!(\n          \"Both `{}` and `{}` exist. Using `{}`\",\n          display_path(&possible),\n          display_path(&possible_with_extension),\n          display_path(&possible)\n        );\n      }\n    }\n\n    Ok(Some(possible))\n  } else if possible_with_extension.exists() {\n    Ok(Some(possible_with_extension))\n  } else {\n    Ok(None)\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/interface/rust/desktop.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{AppSettings, DevProcess, ExitReason, Options, RustAppSettings, RustupTarget};\nuse crate::{\n  error::{Context, ErrorExt},\n  CommandExt, Error,\n};\n\nuse shared_child::SharedChild;\nuse std::{\n  fs,\n  io::{BufReader, ErrorKind, Write},\n  path::{Path, PathBuf},\n  process::{Command, ExitStatus, Stdio},\n  sync::{\n    atomic::{AtomicBool, Ordering},\n    Arc, Mutex,\n  },\n};\nuse tauri_utils::platform::Target as TargetPlatform;\n\npub struct DevChild {\n  manually_killed_app: Arc<AtomicBool>,\n  dev_child: Arc<SharedChild>,\n}\n\nimpl DevProcess for DevChild {\n  fn kill(&self) -> std::io::Result<()> {\n    self.dev_child.kill()?;\n    self.manually_killed_app.store(true, Ordering::SeqCst);\n    Ok(())\n  }\n\n  fn wait(&self) -> std::io::Result<ExitStatus> {\n    self.dev_child.wait()\n  }\n\n  fn manually_killed_process(&self) -> bool {\n    self.manually_killed_app.load(Ordering::SeqCst)\n  }\n}\n\npub fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(\n  options: Options,\n  run_args: &[String],\n  available_targets: &mut Option<Vec<RustupTarget>>,\n  config_features: Vec<String>,\n  on_exit: F,\n) -> crate::Result<DevChild> {\n  let mut dev_cmd = cargo_command(true, options, available_targets, config_features)?;\n  let runner = dev_cmd.get_program().to_string_lossy().into_owned();\n\n  dev_cmd\n    .env(\n      \"CARGO_TERM_PROGRESS_WIDTH\",\n      terminal::stderr_width()\n        .map(|width| {\n          if cfg!(windows) {\n            std::cmp::min(60, width)\n          } else {\n            width\n          }\n        })\n        .unwrap_or(if cfg!(windows) { 60 } else { 80 })\n        .to_string(),\n    )\n    .env(\"CARGO_TERM_PROGRESS_WHEN\", \"always\");\n  dev_cmd.arg(\"--color\");\n  dev_cmd.arg(\"always\");\n\n  dev_cmd.stdout(os_pipe::dup_stdout().unwrap());\n  dev_cmd.stderr(Stdio::piped());\n\n  dev_cmd.arg(\"--\");\n  dev_cmd.args(run_args);\n\n  let manually_killed_app = Arc::new(AtomicBool::default());\n  let manually_killed_app_ = manually_killed_app.clone();\n\n  log::info!(action = \"Running\"; \"DevCommand (`{} {}`)\", &dev_cmd.get_program().to_string_lossy(), dev_cmd.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!(\"{acc} {arg}\")));\n\n  let dev_child = match SharedChild::spawn(&mut dev_cmd) {\n    Ok(c) => Ok(c),\n    Err(e) if e.kind() == ErrorKind::NotFound => crate::error::bail!(\n      \"`{runner}` command not found.{}\",\n      if runner == \"cargo\" {\n        \" Please follow the Tauri setup guide: https://v2.tauri.app/start/prerequisites/\"\n      } else {\n        \"\"\n      }\n    ),\n    Err(e) => Err(Error::CommandFailed {\n      command: runner,\n      error: e,\n    }),\n  }?;\n  let dev_child = Arc::new(dev_child);\n  let dev_child_stderr = dev_child.take_stderr().unwrap();\n  let mut stderr = BufReader::new(dev_child_stderr);\n  let stderr_lines = Arc::new(Mutex::new(Vec::new()));\n  let stderr_lines_ = stderr_lines.clone();\n  std::thread::spawn(move || {\n    let mut buf = Vec::new();\n    let mut lines = stderr_lines_.lock().unwrap();\n    let mut io_stderr = std::io::stderr();\n    loop {\n      buf.clear();\n      if let Ok(0) = tauri_utils::io::read_line(&mut stderr, &mut buf) {\n        break;\n      }\n      let _ = io_stderr.write_all(&buf);\n      lines.push(String::from_utf8_lossy(&buf).into_owned());\n    }\n  });\n  let dev_child_ = dev_child.clone();\n  std::thread::spawn(move || {\n    let status = dev_child_.wait().expect(\"failed to build app\");\n\n    if status.success() {\n      on_exit(status.code(), ExitReason::NormalExit);\n    } else {\n      let is_cargo_compile_error = stderr_lines\n        .lock()\n        .unwrap()\n        .last()\n        .map(|l| l.contains(\"could not compile\"))\n        .unwrap_or_default();\n      stderr_lines.lock().unwrap().clear();\n\n      on_exit(\n        status.code(),\n        if status.code() == Some(101) && is_cargo_compile_error {\n          ExitReason::CompilationFailed\n        } else if manually_killed_app_.load(Ordering::SeqCst) {\n          ExitReason::TriggeredKill\n        } else {\n          ExitReason::NormalExit\n        },\n      );\n    }\n  });\n\n  Ok(DevChild {\n    manually_killed_app,\n    dev_child,\n  })\n}\n\npub fn build(\n  options: Options,\n  app_settings: &RustAppSettings,\n  available_targets: &mut Option<Vec<RustupTarget>>,\n  config_features: Vec<String>,\n  main_binary_name: Option<&str>,\n  tauri_dir: &Path,\n) -> crate::Result<PathBuf> {\n  let out_dir = app_settings.out_dir(&options, tauri_dir)?;\n  let bin_path = app_settings.app_binary_path(&options, tauri_dir)?;\n\n  if !std::env::var_os(\"STATIC_VCRUNTIME\").is_some_and(|v| v == \"false\") {\n    std::env::set_var(\"STATIC_VCRUNTIME\", \"true\");\n  }\n\n  if options.target == Some(\"universal-apple-darwin\".into()) {\n    std::fs::create_dir_all(&out_dir)\n      .fs_context(\"failed to create project out directory\", out_dir.clone())?;\n\n    let bin_name = bin_path.file_stem().unwrap();\n\n    let mut lipo_cmd = Command::new(\"lipo\");\n    lipo_cmd\n      .arg(\"-create\")\n      .arg(\"-output\")\n      .arg(out_dir.join(bin_name));\n    for triple in [\"aarch64-apple-darwin\", \"x86_64-apple-darwin\"] {\n      let mut options = options.clone();\n      options.target.replace(triple.into());\n\n      let triple_out_dir = app_settings\n        .out_dir(&options, tauri_dir)\n        .with_context(|| format!(\"failed to get {triple} out dir\"))?;\n\n      build_production_app(options, available_targets, config_features.clone())\n        .with_context(|| format!(\"failed to build {triple} binary\"))?;\n\n      lipo_cmd.arg(triple_out_dir.join(bin_name));\n    }\n\n    let lipo_status = lipo_cmd.output_ok()?.status;\n    if !lipo_status.success() {\n      crate::error::bail!(\n        \"Result of `lipo` command was unsuccessful: {lipo_status}. (Is `lipo` installed?)\"\n      );\n    }\n  } else {\n    build_production_app(options, available_targets, config_features)\n      .with_context(|| \"failed to build app\")?;\n  }\n\n  rename_app(bin_path, main_binary_name, &app_settings.target_platform)\n}\n\nfn build_production_app(\n  options: Options,\n  available_targets: &mut Option<Vec<RustupTarget>>,\n  config_features: Vec<String>,\n) -> crate::Result<()> {\n  let mut build_cmd = cargo_command(false, options, available_targets, config_features)?;\n  let runner = build_cmd.get_program().to_string_lossy().into_owned();\n  match build_cmd.piped() {\n    Ok(status) if status.success() => Ok(()),\n    Ok(_) => crate::error::bail!(\"failed to build app\"),\n    Err(e) if e.kind() == ErrorKind::NotFound => crate::error::bail!(\n      \"`{}` command not found.{}\",\n      runner,\n      if runner == \"cargo\" {\n        \" Please follow the Tauri setup guide: https://v2.tauri.app/start/prerequisites/\"\n      } else {\n        \"\"\n      }\n    ),\n    Err(e) => Err(Error::CommandFailed {\n      command: runner,\n      error: e,\n    }),\n  }\n}\n\nfn cargo_command(\n  dev: bool,\n  options: Options,\n  available_targets: &mut Option<Vec<RustupTarget>>,\n  config_features: Vec<String>,\n) -> crate::Result<Command> {\n  let runner_config = options.runner.unwrap_or_else(|| \"cargo\".into());\n\n  let mut build_cmd = Command::new(runner_config.cmd());\n  build_cmd.arg(if dev { \"run\" } else { \"build\" });\n\n  // Set working directory if specified\n  if let Some(cwd) = runner_config.cwd() {\n    build_cmd.current_dir(cwd);\n  }\n\n  // Add runner-specific arguments first\n  if let Some(runner_args) = runner_config.args() {\n    build_cmd.args(runner_args);\n  }\n\n  if let Some(target) = &options.target {\n    if available_targets.is_none() {\n      *available_targets = fetch_available_targets();\n    }\n    validate_target(available_targets, target)?;\n  }\n\n  build_cmd.args(&options.args);\n\n  let mut features = config_features;\n  features.extend(options.features);\n  if !features.is_empty() {\n    build_cmd.arg(\"--features\");\n    build_cmd.arg(features.join(\",\"));\n  }\n\n  if !options.debug && !options.args.contains(&\"--profile\".to_string()) {\n    build_cmd.arg(\"--release\");\n  }\n\n  if let Some(target) = options.target {\n    build_cmd.arg(\"--target\");\n    build_cmd.arg(target);\n  }\n\n  Ok(build_cmd)\n}\n\nfn fetch_available_targets() -> Option<Vec<RustupTarget>> {\n  if let Ok(output) = Command::new(\"rustup\").args([\"target\", \"list\"]).output() {\n    let stdout = String::from_utf8_lossy(&output.stdout).into_owned();\n    Some(\n      stdout\n        .split('\\n')\n        .map(|t| {\n          let mut s = t.split(' ');\n          let name = s.next().unwrap().to_string();\n          let installed = s.next().map(|v| v == \"(installed)\").unwrap_or_default();\n          RustupTarget { name, installed }\n        })\n        .filter(|t| !t.name.is_empty())\n        .collect(),\n    )\n  } else {\n    None\n  }\n}\n\nfn validate_target(\n  available_targets: &Option<Vec<RustupTarget>>,\n  target: &str,\n) -> crate::Result<()> {\n  if let Some(available_targets) = available_targets {\n    if let Some(target) = available_targets.iter().find(|t| t.name == target) {\n      if !target.installed {\n        crate::error::bail!(\n            \"Target {target} is not installed (installed targets: {installed}). Please run `rustup target add {target}`.\",\n            target = target.name,\n            installed = available_targets.iter().filter(|t| t.installed).map(|t| t.name.as_str()).collect::<Vec<&str>>().join(\", \")\n          );\n      }\n    }\n    if !available_targets.iter().any(|t| t.name == target) {\n      crate::error::bail!(\"Target {target} does not exist. Please run `rustup target list` to see the available targets.\", target = target);\n    }\n  }\n  Ok(())\n}\n\nfn rename_app(\n  bin_path: PathBuf,\n  main_binary_name: Option<&str>,\n  target_platform: &TargetPlatform,\n) -> crate::Result<PathBuf> {\n  if let Some(main_binary_name) = main_binary_name {\n    let extension = if matches!(target_platform, TargetPlatform::Windows) {\n      \".exe\"\n    } else {\n      \"\"\n    };\n    let new_path = bin_path.with_file_name(format!(\"{main_binary_name}{extension}\"));\n    fs::rename(&bin_path, &new_path).fs_context(\"failed to rename app binary\", bin_path)?;\n    Ok(new_path)\n  } else {\n    Ok(bin_path)\n  }\n}\n\n// taken from https://github.com/rust-lang/cargo/blob/78b10d4e611ab0721fc3aeaf0edd5dd8f4fdc372/src/cargo/core/shell.rs#L514\n#[cfg(unix)]\nmod terminal {\n  use std::mem;\n\n  pub fn stderr_width() -> Option<usize> {\n    unsafe {\n      let mut winsize: libc::winsize = mem::zeroed();\n      // The .into() here is needed for FreeBSD which defines TIOCGWINSZ\n      // as c_uint but ioctl wants c_ulong.\n      #[allow(clippy::useless_conversion)]\n      if libc::ioctl(libc::STDERR_FILENO, libc::TIOCGWINSZ.into(), &mut winsize) < 0 {\n        return None;\n      }\n      if winsize.ws_col > 0 {\n        Some(winsize.ws_col as usize)\n      } else {\n        None\n      }\n    }\n  }\n}\n\n// taken from https://github.com/rust-lang/cargo/blob/78b10d4e611ab0721fc3aeaf0edd5dd8f4fdc372/src/cargo/core/shell.rs#L543\n#[cfg(windows)]\nmod terminal {\n  use std::{cmp, mem, ptr};\n\n  use windows_sys::{\n    core::PCSTR,\n    Win32::{\n      Foundation::{CloseHandle, GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE},\n      Storage::FileSystem::{CreateFileA, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING},\n      System::Console::{\n        GetConsoleScreenBufferInfo, GetStdHandle, CONSOLE_SCREEN_BUFFER_INFO, STD_ERROR_HANDLE,\n      },\n    },\n  };\n\n  pub fn stderr_width() -> Option<usize> {\n    unsafe {\n      let stdout = GetStdHandle(STD_ERROR_HANDLE);\n      let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();\n      if GetConsoleScreenBufferInfo(stdout, &mut csbi) != 0 {\n        return Some((csbi.srWindow.Right - csbi.srWindow.Left) as usize);\n      }\n\n      // On mintty/msys/cygwin based terminals, the above fails with\n      // INVALID_HANDLE_VALUE. Use an alternate method which works\n      // in that case as well.\n      let h = CreateFileA(\n        c\"CONOUT$\".as_ptr() as PCSTR,\n        GENERIC_READ | GENERIC_WRITE,\n        FILE_SHARE_READ | FILE_SHARE_WRITE,\n        ptr::null_mut(),\n        OPEN_EXISTING,\n        0,\n        std::ptr::null_mut(),\n      );\n      if h == INVALID_HANDLE_VALUE {\n        return None;\n      }\n\n      let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();\n      let rc = GetConsoleScreenBufferInfo(h, &mut csbi);\n      CloseHandle(h);\n      if rc != 0 {\n        let width = (csbi.srWindow.Right - csbi.srWindow.Left) as usize;\n        // Unfortunately cygwin/mintty does not set the size of the\n        // backing console to match the actual window size. This\n        // always reports a size of 80 or 120 (not sure what\n        // determines that). Use a conservative max of 60 which should\n        // work in most circumstances. ConEmu does some magic to\n        // resize the console correctly, but there's no reasonable way\n        // to detect which kind of terminal we are running in, or if\n        // GetConsoleScreenBufferInfo returns accurate information.\n        return Some(cmp::min(60, width));\n      }\n\n      None\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/interface/rust/installation.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::{Error, ErrorExt},\n  Result,\n};\n\nuse std::{fs::read_dir, path::PathBuf, process::Command};\n\npub fn installed_targets() -> Result<Vec<String>> {\n  let output = Command::new(\"rustc\")\n    .args([\"--print\", \"sysroot\"])\n    .output()\n    .map_err(|error| Error::CommandFailed {\n      command: \"rustc --print sysroot\".to_string(),\n      error,\n    })?;\n  let sysroot_path = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim().to_string());\n\n  let mut targets = Vec::new();\n  for entry in read_dir(sysroot_path.join(\"lib\").join(\"rustlib\"))\n    .fs_context(\n      \"failed to read Rust sysroot\",\n      sysroot_path.join(\"lib\").join(\"rustlib\"),\n    )?\n    .flatten()\n  {\n    if entry.file_type().map(|t| t.is_dir()).unwrap_or_default() {\n      let name = entry.file_name();\n      if name != \"etc\" && name != \"src\" {\n        targets.push(name.to_string_lossy().into_owned());\n      }\n    }\n  }\n\n  Ok(targets)\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/interface/rust/manifest.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::{Context, ErrorExt},\n  helpers::config::{Config, PatternKind},\n};\n\nuse itertools::Itertools;\nuse toml_edit::{Array, DocumentMut, InlineTable, Item, TableLike, Value};\n\nuse std::{\n  collections::{HashMap, HashSet},\n  path::Path,\n};\n\n#[derive(Default)]\npub struct Manifest {\n  pub inner: DocumentMut,\n  pub tauri_features: HashSet<String>,\n}\n\nimpl Manifest {\n  pub fn features(&self) -> HashMap<String, Vec<String>> {\n    let mut f = HashMap::new();\n\n    if let Some(features) = self\n      .inner\n      .as_table()\n      .get(\"features\")\n      .and_then(|f| f.as_table())\n    {\n      for (feature, enabled_features) in features.into_iter() {\n        if let Item::Value(Value::Array(enabled_features)) = enabled_features {\n          let mut enabled = Vec::new();\n          for value in enabled_features {\n            if let Value::String(s) = value {\n              enabled.push(s.value().clone());\n            }\n          }\n          f.insert(feature.to_string(), enabled);\n        }\n      }\n    }\n\n    f\n  }\n\n  pub fn all_enabled_features(&self, enabled_features: &[String]) -> Vec<String> {\n    let mut all_enabled_features: Vec<String> = self\n      .tauri_features\n      .iter()\n      .map(|f| format!(\"tauri/{f}\"))\n      .collect();\n\n    let manifest_features = self.features();\n    for f in enabled_features {\n      all_enabled_features.extend(get_enabled_features(&manifest_features, f));\n    }\n\n    all_enabled_features\n  }\n}\n\nfn get_enabled_features(list: &HashMap<String, Vec<String>>, feature: &str) -> Vec<String> {\n  let mut f = Vec::new();\n\n  if let Some(enabled_features) = list.get(feature) {\n    for enabled in enabled_features {\n      if list.contains_key(enabled) {\n        f.extend(get_enabled_features(list, enabled));\n      } else {\n        f.push(enabled.clone());\n      }\n    }\n  }\n\n  f\n}\n\npub fn read_manifest(manifest_path: &Path) -> crate::Result<(DocumentMut, String)> {\n  let manifest_str = std::fs::read_to_string(manifest_path)\n    .fs_context(\"failed to read Cargo.toml\", manifest_path.to_path_buf())?;\n\n  let manifest: DocumentMut = manifest_str\n    .parse::<DocumentMut>()\n    .context(\"failed to parse Cargo.toml\")?;\n\n  Ok((manifest, manifest_str))\n}\n\npub fn serialize_manifest(manifest: &DocumentMut) -> String {\n  manifest\n    .to_string()\n    // apply some formatting fixes\n    .replace(r#\"\" ,features =[\"#, r#\"\", features = [\"#)\n    .replace(r#\"\" , features\"#, r#\"\", features\"#)\n    .replace(\"]}\", \"] }\")\n    .replace(\"={\", \"= {\")\n    .replace(\"=[\", \"= [\")\n    .replace(r#\"\",\"\"#, r#\"\", \"\"#)\n}\n\npub fn toml_array(features: &HashSet<String>) -> Array {\n  let mut f = Array::default();\n  let mut features: Vec<String> = features.iter().map(|f| f.to_string()).collect();\n  features.sort();\n  for feature in features {\n    f.push(feature.as_str());\n  }\n  f\n}\n\nfn find_dependency<'a>(\n  manifest: &'a mut DocumentMut,\n  name: &'a str,\n  kind: DependencyKind,\n) -> Vec<&'a mut Item> {\n  let table = match kind {\n    DependencyKind::Build => \"build-dependencies\",\n    DependencyKind::Normal => \"dependencies\",\n  };\n\n  let m = manifest.as_table_mut();\n  for (k, v) in m.iter_mut() {\n    if let Some(t) = v.as_table_mut() {\n      if k == table {\n        if let Some(item) = t.get_mut(name) {\n          return vec![item];\n        }\n      } else if k == \"target\" {\n        let mut matching_deps = Vec::new();\n        for (_, target_value) in t.iter_mut() {\n          if let Some(target_table) = target_value.as_table_mut() {\n            if let Some(deps) = target_table.get_mut(table) {\n              if let Some(item) = deps.as_table_mut().and_then(|t| t.get_mut(name)) {\n                matching_deps.push(item);\n              }\n            }\n          }\n        }\n        return matching_deps;\n      }\n    }\n  }\n\n  Vec::new()\n}\n\nfn write_features<F: Fn(&str) -> bool>(\n  dependency_name: &str,\n  item: &mut Item,\n  is_managed_feature: F,\n  features: &mut HashSet<String>,\n) -> crate::Result<bool> {\n  if let Some(dep) = item.as_table_mut() {\n    inject_features_table(dep, is_managed_feature, features);\n    Ok(true)\n  } else if let Some(dep) = item.as_value_mut() {\n    match dep {\n      Value::InlineTable(table) => {\n        inject_features_table(table, is_managed_feature, features);\n      }\n      Value::String(version) => {\n        let mut def = InlineTable::default();\n        def.get_or_insert(\"version\", version.to_string().replace(['\\\"', ' '], \"\"));\n        def.get_or_insert(\"features\", Value::Array(toml_array(features)));\n        *dep = Value::InlineTable(def);\n      }\n      _ => {\n        crate::error::bail!(\n          \"Unsupported {} dependency format on Cargo.toml\",\n          dependency_name\n        );\n      }\n    }\n    Ok(true)\n  } else {\n    Ok(false)\n  }\n}\n\n#[derive(Debug, Clone, Copy)]\nenum DependencyKind {\n  Build,\n  Normal,\n}\n\n#[derive(Debug)]\nstruct DependencyAllowlist {\n  name: String,\n  kind: DependencyKind,\n  all_cli_managed_features: Vec<&'static str>,\n  features: HashSet<String>,\n}\n\nfn inject_features_table<D: TableLike, F: Fn(&str) -> bool>(\n  dep: &mut D,\n  is_managed_feature: F,\n  features: &mut HashSet<String>,\n) {\n  let manifest_features = dep.entry(\"features\").or_insert(Item::None);\n  if let Item::Value(Value::Array(f)) = &manifest_features {\n    for feat in f.iter() {\n      if let Value::String(feature) = feat {\n        if !is_managed_feature(feature.value().as_str()) {\n          features.insert(feature.value().to_string());\n        }\n      }\n    }\n  }\n  if let Some(features_array) = manifest_features.as_array_mut() {\n    // add features that aren't in the manifest\n    for feature in features.iter() {\n      if !features_array.iter().any(|f| f.as_str() == Some(feature)) {\n        features_array.insert(0, feature.as_str());\n      }\n    }\n\n    // remove features that shouldn't be in the manifest anymore\n    let mut i = features_array.len();\n    while i != 0 {\n      let index = i - 1;\n      if let Some(f) = features_array.get(index).and_then(|f| f.as_str()) {\n        if !features.contains(f) {\n          features_array.remove(index);\n        }\n      }\n      i -= 1;\n    }\n  } else {\n    *manifest_features = Item::Value(Value::Array(toml_array(features)));\n  }\n}\n\nfn inject_features(\n  manifest: &mut DocumentMut,\n  dependencies: &mut Vec<DependencyAllowlist>,\n) -> crate::Result<bool> {\n  let mut persist = false;\n  for dependency in dependencies {\n    let name = dependency.name.clone();\n    let items = find_dependency(manifest, &dependency.name, dependency.kind);\n\n    for item in items {\n      // do not rewrite if dependency uses workspace inheritance\n      if item\n        .get(\"workspace\")\n        .and_then(|v| v.as_bool())\n        .unwrap_or_default()\n      {\n        log::info!(\"`{name}` dependency has workspace inheritance enabled. The features array won't be automatically rewritten. Expected features: [{}]\", dependency.features.iter().join(\", \"));\n      } else {\n        let all_cli_managed_features = dependency.all_cli_managed_features.clone();\n        let is_managed_feature: Box<dyn Fn(&str) -> bool> =\n          Box::new(move |feature| all_cli_managed_features.contains(&feature));\n\n        let should_write =\n          write_features(&name, item, is_managed_feature, &mut dependency.features)?;\n\n        if !persist {\n          persist = should_write;\n        }\n      }\n    }\n  }\n\n  Ok(persist)\n}\n\npub fn rewrite_manifest(config: &Config, tauri_dir: &Path) -> crate::Result<(Manifest, bool)> {\n  let manifest_path = tauri_dir.join(\"Cargo.toml\");\n  let (mut manifest, original_manifest_str) = read_manifest(&manifest_path)?;\n\n  let mut dependencies = Vec::new();\n\n  // tauri-build\n  let mut tauri_build_features = HashSet::new();\n  if let PatternKind::Isolation { .. } = config.app.security.pattern {\n    tauri_build_features.insert(\"isolation\".to_string());\n  }\n  dependencies.push(DependencyAllowlist {\n    name: \"tauri-build\".into(),\n    kind: DependencyKind::Build,\n    all_cli_managed_features: vec![\"isolation\"],\n    features: tauri_build_features,\n  });\n\n  // tauri\n  let tauri_features = HashSet::from_iter(config.app.features().into_iter().map(|f| f.to_string()));\n  dependencies.push(DependencyAllowlist {\n    name: \"tauri\".into(),\n    kind: DependencyKind::Normal,\n    all_cli_managed_features: crate::helpers::config::AppConfig::all_features()\n      .into_iter()\n      .filter(|f| f != &\"tray-icon\")\n      .collect(),\n    features: tauri_features,\n  });\n\n  let persist = inject_features(&mut manifest, &mut dependencies)?;\n\n  let tauri_features = dependencies\n    .into_iter()\n    .find(|d| d.name == \"tauri\")\n    .unwrap()\n    .features;\n\n  let new_manifest_str = serialize_manifest(&manifest);\n\n  if persist && original_manifest_str != new_manifest_str {\n    std::fs::write(&manifest_path, new_manifest_str)\n      .fs_context(\"failed to rewrite Cargo manifest\", &manifest_path)?;\n    Ok((\n      Manifest {\n        inner: manifest,\n        tauri_features,\n      },\n      true,\n    ))\n  } else {\n    Ok((\n      Manifest {\n        inner: manifest,\n        tauri_features,\n      },\n      false,\n    ))\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use super::{DependencyAllowlist, DependencyKind};\n  use std::collections::{HashMap, HashSet};\n\n  fn inject_features(toml: &str, mut dependencies: Vec<DependencyAllowlist>) {\n    let mut manifest = toml\n      .parse::<toml_edit::DocumentMut>()\n      .expect(\"invalid toml\");\n\n    let mut expected = HashMap::new();\n    for dep in &dependencies {\n      let mut features = dep.features.clone();\n      for item in super::find_dependency(&mut manifest, &dep.name, dep.kind) {\n        let item_table = if let Some(table) = item.as_table() {\n          Some(table.clone())\n        } else if let Some(toml_edit::Value::InlineTable(table)) = item.as_value() {\n          Some(table.clone().into_table())\n        } else {\n          None\n        };\n        if let Some(f) = item_table.and_then(|t| t.get(\"features\")?.as_array().cloned()) {\n          for feature in f.iter() {\n            let feature = feature.as_str().expect(\"feature is not a string\");\n            if !dep.all_cli_managed_features.contains(&feature) {\n              features.insert(feature.into());\n            }\n          }\n        }\n      }\n      expected.insert(dep.name.clone(), features);\n    }\n\n    super::inject_features(&mut manifest, &mut dependencies).expect(\"failed to migrate manifest\");\n\n    for dep in dependencies {\n      let expected_features = expected.get(&dep.name).unwrap();\n      for item in super::find_dependency(&mut manifest, &dep.name, dep.kind) {\n        let item_table = if let Some(table) = item.as_table() {\n          table.clone()\n        } else if let Some(toml_edit::Value::InlineTable(table)) = item.as_value() {\n          table.clone().into_table()\n        } else {\n          panic!(\"unexpected TOML item kind for {}\", dep.name);\n        };\n\n        let features_array = item_table\n          .get(\"features\")\n          .expect(\"missing features\")\n          .as_array()\n          .expect(\"features must be an array\")\n          .clone();\n\n        let mut features = Vec::new();\n        for feature in features_array.iter() {\n          let feature = feature.as_str().expect(\"feature must be a string\");\n          features.push(feature);\n        }\n        for expected in expected_features {\n          assert!(\n            features.contains(&expected.as_str()),\n            \"feature {expected} should have been injected\"\n          );\n        }\n      }\n    }\n  }\n\n  fn tauri_dependency(features: HashSet<String>) -> DependencyAllowlist {\n    DependencyAllowlist {\n      name: \"tauri\".into(),\n      kind: DependencyKind::Normal,\n      all_cli_managed_features: vec![\"isolation\"],\n      features,\n    }\n  }\n\n  fn tauri_build_dependency(features: HashSet<String>) -> DependencyAllowlist {\n    DependencyAllowlist {\n      name: \"tauri-build\".into(),\n      kind: DependencyKind::Build,\n      all_cli_managed_features: crate::helpers::config::AppConfig::all_features(),\n      features,\n    }\n  }\n\n  #[test]\n  fn inject_features_table() {\n    inject_features(\n      r#\"\n    [dependencies]\n    tauri = { version = \"1\", features = [\"dummy\"] }\n\n    [build-dependencies]\n    tauri-build = { version = \"1\" }\n\"#,\n      vec![\n        tauri_dependency(HashSet::from_iter(\n          crate::helpers::config::AppConfig::all_features()\n            .iter()\n            .map(|f| f.to_string()),\n        )),\n        tauri_build_dependency(HashSet::from_iter(vec![\"isolation\".into()])),\n      ],\n    );\n  }\n\n  #[test]\n  fn inject_features_target() {\n    inject_features(\n      r#\"\n    [target.\"cfg(windows)\".dependencies]\n    tauri = { version = \"1\", features = [\"dummy\"] }\n\n    [target.\"cfg(target_os = \\\"macos\\\")\".build-dependencies]\n    tauri-build = { version = \"1\" }\n\n    [target.\"cfg(target_os = \\\"linux\\\")\".dependencies]\n    tauri = { version = \"1\", features = [\"isolation\"] }\n\n    [target.\"cfg(windows)\".build-dependencies]\n    tauri-build = { version = \"1\" }\n\"#,\n      vec![\n        tauri_dependency(Default::default()),\n        tauri_build_dependency(HashSet::from_iter(vec![\"isolation\".into()])),\n      ],\n    );\n  }\n\n  #[test]\n  fn inject_features_inline_table() {\n    inject_features(\n      r#\"\n    [dependencies.tauri]\n    version = \"1\"\n    features = [\"test\"]\n\n    [build-dependencies.tauri-build]\n    version = \"1\"\n    features = [\"config-toml\", \"codegen\", \"isolation\"]\n\"#,\n      vec![\n        tauri_dependency(HashSet::from_iter(vec![\n          \"isolation\".into(),\n          \"native-tls-vendored\".into(),\n        ])),\n        tauri_build_dependency(HashSet::from_iter(vec![\"isolation\".into()])),\n      ],\n    );\n  }\n\n  #[test]\n  fn inject_features_string() {\n    inject_features(\n      r#\"\n    [dependencies]\n    tauri = \"1\"\n\n    [build-dependencies]\n    tauri-build = \"1\"\n\"#,\n      vec![\n        tauri_dependency(HashSet::from_iter(vec![\n          \"isolation\".into(),\n          \"native-tls-vendored\".into(),\n        ])),\n        tauri_build_dependency(HashSet::from_iter(vec![\"isolation\".into()])),\n      ],\n    );\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/interface/rust.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  collections::HashMap,\n  ffi::OsStr,\n  fs::FileType,\n  io::{BufRead, Write},\n  iter::once,\n  path::{Path, PathBuf},\n  process::Command,\n  str::FromStr,\n  sync::{mpsc::sync_channel, Arc, Mutex},\n  time::Duration,\n};\n\nuse dunce::canonicalize;\nuse ignore::gitignore::{Gitignore, GitignoreBuilder};\nuse notify::RecursiveMode;\nuse notify_debouncer_full::new_debouncer;\nuse serde::{Deserialize, Deserializer};\nuse tauri_bundler::{\n  AppCategory, AppImageSettings, BundleBinary, BundleSettings, DebianSettings, DmgSettings,\n  IosSettings, MacOsSettings, PackageSettings, Position, RpmSettings, Size, UpdaterSettings,\n  WindowsSettings,\n};\nuse tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol, RunnerConfig, Updater};\n\nuse super::{AppSettings, DevProcess, ExitReason};\nuse crate::{\n  error::{bail, Context, Error, ErrorExt},\n  helpers::{\n    app_paths::Dirs,\n    config::{nsis_settings, reload_config, wix_settings, BundleResources, Config, ConfigMetadata},\n  },\n  ConfigValue,\n};\nuse tauri_utils::{display_path, platform::Target as TargetPlatform};\n\nmod cargo_config;\nmod desktop;\npub mod installation;\npub mod manifest;\nuse crate::helpers::config::custom_sign_settings;\nuse cargo_config::Config as CargoConfig;\nuse manifest::{rewrite_manifest, Manifest};\n\n#[derive(Debug, Default, Clone)]\npub struct Options {\n  pub runner: Option<RunnerConfig>,\n  pub debug: bool,\n  pub target: Option<String>,\n  pub features: Vec<String>,\n  pub args: Vec<String>,\n  pub config: Vec<ConfigValue>,\n  pub no_watch: bool,\n  pub skip_stapling: bool,\n  pub additional_watch_folders: Vec<PathBuf>,\n}\n\nimpl From<crate::build::Options> for Options {\n  fn from(options: crate::build::Options) -> Self {\n    Self {\n      runner: options.runner,\n      debug: options.debug,\n      target: options.target,\n      features: options.features,\n      args: options.args,\n      config: options.config,\n      no_watch: true,\n      skip_stapling: options.skip_stapling,\n      additional_watch_folders: Vec::new(),\n    }\n  }\n}\n\nimpl From<crate::bundle::Options> for Options {\n  fn from(options: crate::bundle::Options) -> Self {\n    Self {\n      debug: options.debug,\n      config: options.config,\n      target: options.target,\n      features: options.features,\n      no_watch: true,\n      skip_stapling: options.skip_stapling,\n      ..Default::default()\n    }\n  }\n}\n\nimpl From<crate::dev::Options> for Options {\n  fn from(options: crate::dev::Options) -> Self {\n    Self {\n      runner: options.runner,\n      debug: !options.release_mode,\n      target: options.target,\n      features: options.features,\n      args: options.args,\n      config: options.config,\n      no_watch: options.no_watch,\n      skip_stapling: false,\n      additional_watch_folders: options.additional_watch_folders,\n    }\n  }\n}\n\n#[derive(Debug, Clone)]\npub struct MobileOptions {\n  pub debug: bool,\n  pub features: Vec<String>,\n  pub args: Vec<String>,\n  pub config: Vec<ConfigValue>,\n  pub no_watch: bool,\n  pub additional_watch_folders: Vec<PathBuf>,\n}\n\n#[derive(Debug, Clone)]\npub struct WatcherOptions {\n  pub config: Vec<ConfigValue>,\n  pub additional_watch_folders: Vec<PathBuf>,\n}\n\n#[derive(Debug)]\npub struct RustupTarget {\n  name: String,\n  installed: bool,\n}\n\npub struct Rust {\n  app_settings: Arc<RustAppSettings>,\n  config_features: Vec<String>,\n  available_targets: Option<Vec<RustupTarget>>,\n  main_binary_name: Option<String>,\n}\n\nimpl Rust {\n  pub fn new(config: &Config, target: Option<String>, tauri_dir: &Path) -> crate::Result<Self> {\n    let manifest = {\n      let (tx, rx) = sync_channel(1);\n      let mut watcher = new_debouncer(Duration::from_secs(1), None, move |r| {\n        if let Ok(_events) = r {\n          let _ = tx.send(());\n        }\n      })\n      .unwrap();\n      let manifest_path = tauri_dir.join(\"Cargo.toml\");\n      watcher\n        .watch(&manifest_path, RecursiveMode::NonRecursive)\n        .with_context(|| format!(\"failed to watch {}\", manifest_path.display()))?;\n      let (manifest, modified) = rewrite_manifest(config, tauri_dir)?;\n      if modified {\n        // Wait for the modified event so we don't trigger a re-build later on\n        let _ = rx.recv_timeout(Duration::from_secs(2));\n      }\n      manifest\n    };\n\n    let target_ios = target\n      .as_ref()\n      .is_some_and(|target| target.ends_with(\"ios\") || target.ends_with(\"ios-sim\"));\n    if target_ios {\n      std::env::set_var(\n        \"IPHONEOS_DEPLOYMENT_TARGET\",\n        &config.bundle.ios.minimum_system_version,\n      );\n    }\n\n    let app_settings = RustAppSettings::new(config, manifest, target, tauri_dir)?;\n\n    Ok(Self {\n      app_settings: Arc::new(app_settings),\n      config_features: config.build.features.clone().unwrap_or_default(),\n      main_binary_name: config.main_binary_name.clone(),\n      available_targets: None,\n    })\n  }\n\n  pub fn app_settings(&self) -> Arc<RustAppSettings> {\n    self.app_settings.clone()\n  }\n\n  pub fn build(&mut self, options: Options, dirs: &Dirs) -> crate::Result<PathBuf> {\n    desktop::build(\n      options,\n      &self.app_settings,\n      &mut self.available_targets,\n      self.config_features.clone(),\n      self.main_binary_name.as_deref(),\n      dirs.tauri,\n    )\n  }\n\n  pub fn dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(\n    &mut self,\n    config: &mut ConfigMetadata,\n    mut options: Options,\n    on_exit: F,\n    dirs: &Dirs,\n  ) -> crate::Result<()> {\n    let on_exit = Arc::new(on_exit);\n\n    let mut run_args = Vec::new();\n    dev_options(\n      false,\n      &mut options.args,\n      &mut run_args,\n      &mut options.features,\n      &self.app_settings,\n    );\n\n    if options.no_watch {\n      let (tx, rx) = sync_channel(1);\n      self.run_dev(options, &run_args, move |status, reason| {\n        on_exit(status, reason);\n        tx.send(()).unwrap();\n      })?;\n\n      rx.recv().unwrap();\n      Ok(())\n    } else {\n      let merge_configs = options.config.iter().map(|c| &c.0).collect::<Vec<_>>();\n      self.run_dev_watcher(\n        config,\n        &options.additional_watch_folders,\n        &merge_configs,\n        |rust: &mut Rust, _config| {\n          let on_exit = on_exit.clone();\n          rust\n            .run_dev(options.clone(), &run_args, move |status, reason| {\n              on_exit(status, reason)\n            })\n            .map(|child| Box::new(child) as Box<dyn DevProcess + Send>)\n        },\n        dirs,\n      )\n    }\n  }\n\n  pub fn mobile_dev<\n    R: Fn(MobileOptions, &ConfigMetadata) -> crate::Result<Box<dyn DevProcess + Send>>,\n  >(\n    &mut self,\n    config: &mut ConfigMetadata,\n    mut options: MobileOptions,\n    runner: R,\n    dirs: &Dirs,\n  ) -> crate::Result<()> {\n    let mut run_args = Vec::new();\n    dev_options(\n      true,\n      &mut options.args,\n      &mut run_args,\n      &mut options.features,\n      &self.app_settings,\n    );\n\n    if options.no_watch {\n      runner(options, config)?;\n      Ok(())\n    } else {\n      self.watch(\n        config,\n        WatcherOptions {\n          config: options.config.clone(),\n          additional_watch_folders: options.additional_watch_folders.clone(),\n        },\n        move |config| runner(options.clone(), config),\n        dirs,\n      )\n    }\n  }\n\n  pub fn watch<R: Fn(&ConfigMetadata) -> crate::Result<Box<dyn DevProcess + Send>>>(\n    &mut self,\n    config: &mut ConfigMetadata,\n    options: WatcherOptions,\n    runner: R,\n    dirs: &Dirs,\n  ) -> crate::Result<()> {\n    let merge_configs = options.config.iter().map(|c| &c.0).collect::<Vec<_>>();\n    self.run_dev_watcher(\n      config,\n      &options.additional_watch_folders,\n      &merge_configs,\n      |_rust: &mut Rust, config| runner(config),\n      dirs,\n    )\n  }\n\n  pub fn env(&self) -> HashMap<&str, String> {\n    let mut env = HashMap::new();\n    env.insert(\n      \"TAURI_ENV_TARGET_TRIPLE\",\n      self.app_settings.target_triple.clone(),\n    );\n\n    let target_triple = &self.app_settings.target_triple;\n    let target_components: Vec<&str> = target_triple.split('-').collect();\n    let (arch, host, _host_env) = match target_components.as_slice() {\n      // 3 components like aarch64-apple-darwin\n      [arch, _, host] => (*arch, *host, None),\n      // 4 components like x86_64-pc-windows-msvc and aarch64-apple-ios-sim\n      [arch, _, host, host_env] => (*arch, *host, Some(*host_env)),\n      _ => {\n        log::warn!(\"Invalid target triple: {}\", target_triple);\n        return env;\n      }\n    };\n\n    env.insert(\"TAURI_ENV_ARCH\", arch.into());\n    env.insert(\"TAURI_ENV_PLATFORM\", host.into());\n    env.insert(\n      \"TAURI_ENV_FAMILY\",\n      match host {\n        \"windows\" => \"windows\".into(),\n        _ => \"unix\".into(),\n      },\n    );\n\n    env\n  }\n}\n\nstruct IgnoreMatcher(Vec<Gitignore>);\n\nimpl IgnoreMatcher {\n  fn is_ignore(&self, path: &Path, is_dir: bool) -> bool {\n    for gitignore in &self.0 {\n      if path.starts_with(gitignore.path())\n        && gitignore\n          .matched_path_or_any_parents(path, is_dir)\n          .is_ignore()\n      {\n        return true;\n      }\n    }\n    false\n  }\n}\n\nfn build_ignore_matcher(dir: &Path) -> IgnoreMatcher {\n  let mut matchers = Vec::new();\n\n  // ignore crate doesn't expose an API to build `ignore::gitignore::GitIgnore`\n  // with custom ignore file names so we have to walk the directory and collect\n  // our custom ignore files and add it using `ignore::gitignore::GitIgnoreBuilder::add`\n  for entry in ignore::WalkBuilder::new(dir)\n    .require_git(false)\n    .ignore(false)\n    .overrides(\n      ignore::overrides::OverrideBuilder::new(dir)\n        .add(\".taurignore\")\n        .unwrap()\n        .build()\n        .unwrap(),\n    )\n    .build()\n    .flatten()\n  {\n    let path = entry.path();\n    if path.file_name() == Some(OsStr::new(\".taurignore\")) {\n      let mut ignore_builder = GitignoreBuilder::new(path.parent().unwrap());\n\n      ignore_builder.add(path);\n\n      if let Some(ignore_file) = std::env::var_os(\"TAURI_CLI_WATCHER_IGNORE_FILENAME\") {\n        ignore_builder.add(dir.join(ignore_file));\n      }\n\n      for line in crate::dev::TAURI_CLI_BUILTIN_WATCHER_IGNORE_FILE\n        .lines()\n        .map_while(Result::ok)\n      {\n        let _ = ignore_builder.add_line(None, &line);\n      }\n\n      matchers.push(ignore_builder.build().unwrap());\n    }\n  }\n\n  IgnoreMatcher(matchers)\n}\n\nfn lookup<F: FnMut(FileType, PathBuf)>(dir: &Path, mut f: F) {\n  let mut default_gitignore = std::env::temp_dir();\n  default_gitignore.push(\".tauri\");\n  let _ = std::fs::create_dir_all(&default_gitignore);\n  default_gitignore.push(\".gitignore\");\n  if !default_gitignore.exists() {\n    if let Ok(mut file) = std::fs::File::create(default_gitignore.clone()) {\n      let _ = file.write_all(crate::dev::TAURI_CLI_BUILTIN_WATCHER_IGNORE_FILE);\n    }\n  }\n\n  let mut builder = ignore::WalkBuilder::new(dir);\n  builder.add_custom_ignore_filename(\".taurignore\");\n  let _ = builder.add_ignore(default_gitignore);\n  if let Some(ignore_file) = std::env::var_os(\"TAURI_CLI_WATCHER_IGNORE_FILENAME\") {\n    builder.add_ignore(ignore_file);\n  }\n  builder.require_git(false).ignore(false).max_depth(Some(1));\n\n  for entry in builder.build().flatten() {\n    f(entry.file_type().unwrap(), dir.join(entry.path()));\n  }\n}\n\nfn dev_options(\n  mobile: bool,\n  args: &mut Vec<String>,\n  run_args: &mut Vec<String>,\n  features: &mut Vec<String>,\n  app_settings: &RustAppSettings,\n) {\n  let mut dev_args = Vec::new();\n  let mut reached_run_args = false;\n  for arg in args.clone() {\n    if reached_run_args {\n      run_args.push(arg);\n    } else if arg == \"--\" {\n      reached_run_args = true;\n    } else {\n      dev_args.push(arg);\n    }\n  }\n  *args = dev_args;\n\n  if mobile && !args.contains(&\"--lib\".into()) {\n    args.push(\"--lib\".into());\n  }\n\n  if !args.contains(&\"--no-default-features\".into()) {\n    let manifest_features = app_settings.manifest.lock().unwrap().features();\n    let enable_features: Vec<String> = manifest_features\n      .get(\"default\")\n      .cloned()\n      .unwrap_or_default()\n      .into_iter()\n      .filter(|feature| {\n        if let Some(manifest_feature) = manifest_features.get(feature) {\n          !manifest_feature.contains(&\"tauri/custom-protocol\".into())\n        } else {\n          feature != \"tauri/custom-protocol\"\n        }\n      })\n      .collect();\n    args.push(\"--no-default-features\".into());\n    features.extend(enable_features);\n  }\n}\n\nfn get_watch_folders(\n  additional_watch_folders: &[PathBuf],\n  tauri_dir: &Path,\n) -> crate::Result<Vec<PathBuf>> {\n  // We always want to watch the main tauri folder.\n  let mut watch_folders = vec![tauri_dir.to_path_buf()];\n\n  watch_folders.extend(get_in_workspace_dependency_paths(tauri_dir)?);\n\n  // Add the additional watch folders, resolving the path from the tauri path if it is relative\n  watch_folders.extend(additional_watch_folders.iter().filter_map(|dir| {\n    let path = if dir.is_absolute() {\n      dir.to_owned()\n    } else {\n      tauri_dir.join(dir)\n    };\n\n    let canonicalized = canonicalize(&path).ok();\n    if canonicalized.is_none() {\n      log::warn!(\n        \"Additional watch folder '{}' not found, ignoring\",\n        path.display()\n      );\n    }\n    canonicalized\n  }));\n\n  Ok(watch_folders)\n}\n\nimpl Rust {\n  pub fn build_options(&self, args: &mut Vec<String>, features: &mut Vec<String>, mobile: bool) {\n    features.push(\"tauri/custom-protocol\".into());\n    if mobile {\n      if !args.contains(&\"--lib\".into()) {\n        args.push(\"--lib\".into());\n      }\n    } else {\n      args.push(\"--bins\".into());\n    }\n  }\n\n  fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(\n    &mut self,\n    options: Options,\n    run_args: &[String],\n    on_exit: F,\n  ) -> crate::Result<desktop::DevChild> {\n    desktop::run_dev(\n      options,\n      run_args,\n      &mut self.available_targets,\n      self.config_features.clone(),\n      on_exit,\n    )\n  }\n\n  fn run_dev_watcher<\n    F: Fn(&mut Rust, &ConfigMetadata) -> crate::Result<Box<dyn DevProcess + Send>>,\n  >(\n    &mut self,\n    config: &mut ConfigMetadata,\n    additional_watch_folders: &[PathBuf],\n    merge_configs: &[&serde_json::Value],\n    run: F,\n    dirs: &Dirs,\n  ) -> crate::Result<()> {\n    let mut child = run(self, config)?;\n    let (tx, rx) = sync_channel(1);\n\n    let watch_folders = get_watch_folders(additional_watch_folders, dirs.tauri)?;\n\n    let common_ancestor = common_path::common_path_all(\n      watch_folders\n        .iter()\n        .map(Path::new)\n        .chain(once(self.app_settings.workspace_dir.as_path())),\n    )\n    .expect(\"watch_folders should not be empty\");\n    let ignore_matcher = build_ignore_matcher(&common_ancestor);\n\n    let mut watcher = new_debouncer(Duration::from_secs(1), None, move |r| {\n      if let Ok(events) = r {\n        tx.send(events).unwrap()\n      }\n    })\n    .unwrap();\n    for path in watch_folders {\n      if !ignore_matcher.is_ignore(&path, true) {\n        log::info!(\"Watching {} for changes...\", display_path(&path));\n        lookup(&path, |file_type, p| {\n          if p != path {\n            log::debug!(\"Watching {} for changes...\", display_path(&p));\n            let _ = watcher.watch(\n              &p,\n              if file_type.is_dir() {\n                RecursiveMode::Recursive\n              } else {\n                RecursiveMode::NonRecursive\n              },\n            );\n          }\n        });\n      }\n    }\n\n    while let Ok(events) = rx.recv() {\n      let paths: Vec<PathBuf> = events\n        .into_iter()\n        .filter(|event| !event.kind.is_access())\n        .flat_map(|event| event.event.paths)\n        .filter(|path| !ignore_matcher.is_ignore(path, path.is_dir()))\n        .collect();\n\n      let config_file_changed = paths\n        .iter()\n        .any(|path| is_configuration_file(self.app_settings.target_platform, path));\n      if config_file_changed && reload_config(config, merge_configs, dirs.tauri).is_ok() {\n        let (manifest, modified) = rewrite_manifest(config, dirs.tauri)?;\n        if modified {\n          *self.app_settings.manifest.lock().unwrap() = manifest;\n          // no need to run the watcher logic, the manifest was modified\n          // and it will trigger the watcher again\n          continue;\n        }\n      }\n\n      let Some(first_changed_path) = paths.first() else {\n        continue;\n      };\n\n      log::info!(\n        \"File {} changed. Rebuilding application...\",\n        display_path(\n          first_changed_path\n            .strip_prefix(dirs.frontend)\n            .unwrap_or(first_changed_path)\n        )\n      );\n\n      child.kill().context(\"failed to kill app process\")?;\n      // wait for the process to exit\n      // note that on mobile, kill() already waits for the process to exit (duct implementation)\n      let _ = child.wait();\n      child = run(self, config)?;\n    }\n    bail!(\"File watcher exited unexpectedly\")\n  }\n}\n\n// Taken from https://github.com/rust-lang/cargo/blob/70898e522116f6c23971e2a554b2dc85fd4c84cd/src/cargo/util/toml/mod.rs#L1008-L1065\n/// Enum that allows for the parsing of `field.workspace = true` in a Cargo.toml\n///\n/// It allows for things to be inherited from a workspace or defined as needed\n#[derive(Clone, Debug)]\npub enum MaybeWorkspace<T> {\n  Workspace(TomlWorkspaceField),\n  Defined(T),\n}\n\nimpl<'de, T: Deserialize<'de>> serde::de::Deserialize<'de> for MaybeWorkspace<T> {\n  fn deserialize<D>(deserializer: D) -> Result<MaybeWorkspace<T>, D::Error>\n  where\n    D: serde::de::Deserializer<'de>,\n  {\n    let value = serde_value::Value::deserialize(deserializer)?;\n    if let Ok(workspace) = TomlWorkspaceField::deserialize(\n      serde_value::ValueDeserializer::<D::Error>::new(value.clone()),\n    ) {\n      return Ok(MaybeWorkspace::Workspace(workspace));\n    }\n    T::deserialize(serde_value::ValueDeserializer::<D::Error>::new(value))\n      .map(MaybeWorkspace::Defined)\n  }\n}\n\nimpl<T> MaybeWorkspace<T> {\n  fn resolve(\n    self,\n    label: &str,\n    get_ws_field: impl FnOnce() -> crate::Result<T>,\n  ) -> crate::Result<T> {\n    match self {\n      MaybeWorkspace::Defined(value) => Ok(value),\n      MaybeWorkspace::Workspace(TomlWorkspaceField { workspace: true }) => get_ws_field()\n        .with_context(|| {\n          format!(\n            \"error inheriting `{label}` from workspace root manifest's `workspace.package.{label}`\"\n          )\n        }),\n      MaybeWorkspace::Workspace(TomlWorkspaceField { workspace: false }) => Err(\n        crate::Error::GenericError(\"`workspace=false` is unsupported for `package.{label}`\".into()),\n      ),\n    }\n  }\n  fn _as_defined(&self) -> Option<&T> {\n    match self {\n      MaybeWorkspace::Workspace(_) => None,\n      MaybeWorkspace::Defined(defined) => Some(defined),\n    }\n  }\n}\n\n#[derive(Deserialize, Clone, Debug)]\npub struct TomlWorkspaceField {\n  workspace: bool,\n}\n\n/// The `workspace` section of the app configuration (read from Cargo.toml).\n#[derive(Clone, Debug, Deserialize)]\nstruct WorkspaceSettings {\n  /// the workspace members.\n  // members: Option<Vec<String>>,\n  package: Option<WorkspacePackageSettings>,\n}\n\n#[derive(Clone, Debug, Deserialize)]\nstruct WorkspacePackageSettings {\n  authors: Option<Vec<String>>,\n  description: Option<String>,\n  homepage: Option<String>,\n  version: Option<String>,\n  license: Option<String>,\n}\n\n#[derive(Clone, Debug, Deserialize)]\n#[serde(rename_all = \"kebab-case\")]\nstruct BinarySettings {\n  name: String,\n  /// This is from nightly: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#different-binary-name\n  filename: Option<String>,\n  path: Option<String>,\n  required_features: Option<Vec<String>>,\n}\n\nimpl BinarySettings {\n  /// The file name without the binary extension (e.g. `.exe`)\n  pub fn file_name(&self) -> &str {\n    self.filename.as_ref().unwrap_or(&self.name)\n  }\n}\n\n/// The package settings.\n#[derive(Debug, Clone, Deserialize)]\n#[serde(rename_all = \"kebab-case\")]\npub struct CargoPackageSettings {\n  /// the package's name.\n  pub name: String,\n  /// the package's version.\n  pub version: Option<MaybeWorkspace<String>>,\n  /// the package's description.\n  pub description: Option<MaybeWorkspace<String>>,\n  /// the package's homepage.\n  pub homepage: Option<MaybeWorkspace<String>>,\n  /// the package's authors.\n  pub authors: Option<MaybeWorkspace<Vec<String>>>,\n  /// the package's license.\n  pub license: Option<MaybeWorkspace<String>>,\n  /// the default binary to run.\n  pub default_run: Option<String>,\n}\n\n/// The Cargo settings (Cargo.toml root descriptor).\n#[derive(Clone, Debug, Deserialize)]\nstruct CargoSettings {\n  /// the package settings.\n  ///\n  /// it's optional because ancestor workspace Cargo.toml files may not have package info.\n  package: Option<CargoPackageSettings>,\n  /// the workspace settings.\n  ///\n  /// it's present if the read Cargo.toml belongs to a workspace root.\n  workspace: Option<WorkspaceSettings>,\n  /// the binary targets configuration.\n  bin: Option<Vec<BinarySettings>>,\n}\n\nimpl CargoSettings {\n  /// Try to load a set of CargoSettings from a \"Cargo.toml\" file in the specified directory.\n  fn load(dir: &Path) -> crate::Result<Self> {\n    let toml_path = dir.join(\"Cargo.toml\");\n    let toml_str = std::fs::read_to_string(&toml_path)\n      .fs_context(\"Failed to read Cargo manifest\", toml_path.clone())?;\n    toml::from_str(&toml_str).context(format!(\n      \"failed to parse Cargo manifest at {}\",\n      toml_path.display()\n    ))\n  }\n}\n\npub struct RustAppSettings {\n  manifest: Mutex<Manifest>,\n  cargo_settings: CargoSettings,\n  cargo_package_settings: CargoPackageSettings,\n  cargo_ws_package_settings: Option<WorkspacePackageSettings>,\n  package_settings: PackageSettings,\n  cargo_config: CargoConfig,\n  target_triple: String,\n  target_platform: TargetPlatform,\n  workspace_dir: PathBuf,\n}\n\n#[derive(Deserialize)]\n#[serde(untagged)]\nenum DesktopDeepLinks {\n  One(DeepLinkProtocol),\n  List(Vec<DeepLinkProtocol>),\n}\n\n#[derive(Deserialize)]\npub struct UpdaterConfig {\n  /// Signature public key.\n  pub pubkey: String,\n  /// The Windows configuration for the updater.\n  #[serde(default)]\n  pub windows: UpdaterWindowsConfig,\n}\n\n/// Install modes for the Windows update.\n#[derive(Default, Debug, PartialEq, Eq, Clone)]\npub enum WindowsUpdateInstallMode {\n  /// Specifies there's a basic UI during the installation process, including a final dialog box at the end.\n  BasicUi,\n  /// The quiet mode means there's no user interaction required.\n  /// Requires admin privileges if the installer does.\n  Quiet,\n  /// Specifies unattended mode, which means the installation only shows a progress bar.\n  #[default]\n  Passive,\n  // to add more modes, we need to check if the updater relaunch makes sense\n  // i.e. for a full UI mode, the user can also mark the installer to start the app\n}\n\nimpl<'de> Deserialize<'de> for WindowsUpdateInstallMode {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let s = String::deserialize(deserializer)?;\n    match s.to_lowercase().as_str() {\n      \"basicui\" => Ok(Self::BasicUi),\n      \"quiet\" => Ok(Self::Quiet),\n      \"passive\" => Ok(Self::Passive),\n      _ => Err(serde::de::Error::custom(format!(\n        \"unknown update install mode '{s}'\"\n      ))),\n    }\n  }\n}\n\nimpl WindowsUpdateInstallMode {\n  /// Returns the associated `msiexec.exe` arguments.\n  pub fn msiexec_args(&self) -> &'static [&'static str] {\n    match self {\n      Self::BasicUi => &[\"/qb+\"],\n      Self::Quiet => &[\"/quiet\"],\n      Self::Passive => &[\"/passive\"],\n    }\n  }\n}\n\n#[derive(Default, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct UpdaterWindowsConfig {\n  #[serde(default, alias = \"install-mode\")]\n  pub install_mode: WindowsUpdateInstallMode,\n}\n\nimpl AppSettings for RustAppSettings {\n  fn get_package_settings(&self) -> PackageSettings {\n    self.package_settings.clone()\n  }\n\n  fn get_bundle_settings(\n    &self,\n    options: &Options,\n    config: &Config,\n    features: &[String],\n    tauri_dir: &Path,\n  ) -> crate::Result<BundleSettings> {\n    let arch64bits = self.target_triple.starts_with(\"x86_64\")\n      || self.target_triple.starts_with(\"aarch64\")\n      || self.target_triple.starts_with(\"riscv64\");\n\n    let updater_enabled = config.bundle.create_updater_artifacts != Updater::Bool(false);\n    let v1_compatible = matches!(config.bundle.create_updater_artifacts, Updater::String(_));\n    let updater_settings = if updater_enabled {\n      let updater: UpdaterConfig = serde_json::from_value(\n        config\n          .plugins\n          .0\n          .get(\"updater\")\n          .context(\"failed to get updater configuration: plugins > updater doesn't exist\")?\n          .clone(),\n      )\n      .context(\"failed to parse updater plugin configuration\")?;\n      Some(UpdaterSettings {\n        v1_compatible,\n        pubkey: updater.pubkey,\n        msiexec_args: updater.windows.install_mode.msiexec_args(),\n      })\n    } else {\n      None\n    };\n\n    let mut settings = tauri_config_to_bundle_settings(\n      self,\n      features,\n      config,\n      tauri_dir,\n      config.bundle.clone(),\n      updater_settings,\n      arch64bits,\n    )?;\n\n    settings.macos.skip_stapling = options.skip_stapling;\n\n    if let Some(plugin_config) = config\n      .plugins\n      .0\n      .get(\"deep-link\")\n      .and_then(|c| c.get(\"desktop\").cloned())\n    {\n      let protocols: DesktopDeepLinks =\n        serde_json::from_value(plugin_config).context(\"failed to parse desktop deep links from Tauri configuration > plugins > deep-link > desktop\")?;\n      settings.deep_link_protocols = Some(match protocols {\n        DesktopDeepLinks::One(p) => vec![p],\n        DesktopDeepLinks::List(p) => p,\n      });\n    }\n\n    if let Some(open) = config.plugins.0.get(\"shell\").and_then(|v| v.get(\"open\")) {\n      if open.as_bool().is_some_and(|x| x) || open.is_string() {\n        settings.appimage.bundle_xdg_open = true;\n      }\n    }\n\n    if let Some(deps) = self\n      .manifest\n      .lock()\n      .unwrap()\n      .inner\n      .as_table()\n      .get(\"dependencies\")\n      .and_then(|f| f.as_table())\n    {\n      if deps.contains_key(\"tauri-plugin-opener\") {\n        settings.appimage.bundle_xdg_open = true;\n      };\n    }\n\n    Ok(settings)\n  }\n\n  fn app_binary_path(&self, options: &Options, tauri_dir: &Path) -> crate::Result<PathBuf> {\n    let binaries = self.get_binaries(options, tauri_dir)?;\n    let bin_name = binaries\n      .iter()\n      .find(|x| x.main())\n      .context(\"failed to find main binary, make sure you have a `package > default-run` in the Cargo.toml file\")?\n      .name();\n\n    let out_dir = self\n      .out_dir(options, tauri_dir)\n      .context(\"failed to get project out directory\")?;\n\n    let mut path = out_dir.join(bin_name);\n    if matches!(self.target_platform, TargetPlatform::Windows) {\n      // Append the `.exe` extension without overriding the existing extensions\n      let extension = if let Some(extension) = path.extension() {\n        let mut extension = extension.to_os_string();\n        extension.push(\".exe\");\n        extension\n      } else {\n        \"exe\".into()\n      };\n      path.set_extension(extension);\n    };\n    Ok(path)\n  }\n\n  fn get_binaries(&self, options: &Options, tauri_dir: &Path) -> crate::Result<Vec<BundleBinary>> {\n    let mut binaries = Vec::new();\n\n    if let Some(bins) = &self.cargo_settings.bin {\n      let default_run = self\n        .package_settings\n        .default_run\n        .clone()\n        .unwrap_or_default();\n      for bin in bins {\n        if let Some(req_features) = &bin.required_features {\n          // Check if all required features are enabled.\n          if !req_features\n            .iter()\n            .all(|feat| options.features.contains(feat))\n          {\n            continue;\n          }\n        }\n        let file_name = bin.file_name();\n        let is_main = file_name == self.cargo_package_settings.name || file_name == default_run;\n        binaries.push(BundleBinary::with_path(\n          file_name.to_owned(),\n          is_main,\n          bin.path.clone(),\n        ))\n      }\n    }\n\n    let mut binaries_paths = std::fs::read_dir(tauri_dir.join(\"src/bin\"))\n      .map(|dir| {\n        dir\n          .into_iter()\n          .flatten()\n          .map(|entry| {\n            (\n              entry\n                .path()\n                .file_stem()\n                .unwrap_or_default()\n                .to_string_lossy()\n                .into_owned(),\n              entry.path(),\n            )\n          })\n          .collect::<Vec<_>>()\n      })\n      .unwrap_or_default();\n\n    if !binaries_paths\n      .iter()\n      .any(|(_name, path)| path == Path::new(\"src/main.rs\"))\n      && tauri_dir.join(\"src/main.rs\").exists()\n    {\n      binaries_paths.push((\n        self.cargo_package_settings.name.clone(),\n        tauri_dir.join(\"src/main.rs\"),\n      ));\n    }\n\n    for (name, path) in binaries_paths {\n      // see https://github.com/tauri-apps/tauri/pull/10977#discussion_r1759742414\n      let bin_exists = binaries\n        .iter()\n        .any(|bin| bin.name() == name || path.ends_with(bin.src_path().unwrap_or(&\"\".to_string())));\n      if !bin_exists {\n        binaries.push(BundleBinary::new(name, false))\n      }\n    }\n\n    if let Some(default_run) = self.package_settings.default_run.as_ref() {\n      if let Some(binary) = binaries.iter_mut().find(|bin| bin.name() == default_run) {\n        binary.set_main(true);\n      } else {\n        binaries.push(BundleBinary::new(default_run.clone(), true));\n      }\n    }\n\n    match binaries.len() {\n      0 => binaries.push(BundleBinary::new(\n        self.cargo_package_settings.name.clone(),\n        true,\n      )),\n      1 => binaries.get_mut(0).unwrap().set_main(true),\n      _ => {}\n    }\n\n    Ok(binaries)\n  }\n\n  fn app_name(&self) -> Option<String> {\n    self\n      .manifest\n      .lock()\n      .unwrap()\n      .inner\n      .as_table()\n      .get(\"package\")?\n      .as_table()?\n      .get(\"name\")?\n      .as_str()\n      .map(|n| n.to_string())\n  }\n\n  fn lib_name(&self) -> Option<String> {\n    self\n      .manifest\n      .lock()\n      .unwrap()\n      .inner\n      .as_table()\n      .get(\"lib\")?\n      .as_table()?\n      .get(\"name\")?\n      .as_str()\n      .map(|n| n.to_string())\n  }\n}\n\nimpl RustAppSettings {\n  pub fn new(\n    config: &Config,\n    manifest: Manifest,\n    target: Option<String>,\n    tauri_dir: &Path,\n  ) -> crate::Result<Self> {\n    let cargo_settings = CargoSettings::load(tauri_dir).context(\"failed to load Cargo settings\")?;\n    let cargo_package_settings = match &cargo_settings.package {\n      Some(package_info) => package_info.clone(),\n      None => {\n        return Err(crate::Error::GenericError(\n          \"No package info in the config file\".to_owned(),\n        ))\n      }\n    };\n\n    let workspace_dir = get_workspace_dir(tauri_dir)?;\n    let ws_package_settings = CargoSettings::load(&workspace_dir)\n      .context(\"failed to load Cargo settings from workspace root\")?\n      .workspace\n      .and_then(|v| v.package);\n\n    let version = config.version.clone().unwrap_or_else(|| {\n      cargo_package_settings\n        .version\n        .clone()\n        .expect(\"Cargo manifest must have the `package.version` field\")\n        .resolve(\"version\", || {\n          ws_package_settings\n            .as_ref()\n            .and_then(|p| p.version.clone())\n            .context(\"Couldn't inherit value for `version` from workspace\")\n        })\n        .expect(\"Cargo project does not have a version\")\n    });\n\n    let package_settings = PackageSettings {\n      product_name: config\n        .product_name\n        .clone()\n        .unwrap_or_else(|| cargo_package_settings.name.clone()),\n      version,\n      description: cargo_package_settings\n        .description\n        .clone()\n        .map(|description| {\n          description\n            .resolve(\"description\", || {\n              ws_package_settings\n                .as_ref()\n                .and_then(|v| v.description.clone())\n                .context(\"Couldn't inherit value for `description` from workspace\")\n            })\n            .unwrap()\n        })\n        .unwrap_or_default(),\n      homepage: cargo_package_settings.homepage.clone().map(|homepage| {\n        homepage\n          .resolve(\"homepage\", || {\n            ws_package_settings\n              .as_ref()\n              .and_then(|v| v.homepage.clone())\n              .context(\"Couldn't inherit value for `homepage` from workspace\")\n          })\n          .unwrap()\n      }),\n      authors: cargo_package_settings.authors.clone().map(|authors| {\n        authors\n          .resolve(\"authors\", || {\n            ws_package_settings\n              .as_ref()\n              .and_then(|v| v.authors.clone())\n              .context(\"Couldn't inherit value for `authors` from workspace\")\n          })\n          .unwrap()\n      }),\n      default_run: cargo_package_settings.default_run.clone(),\n    };\n\n    let cargo_config = CargoConfig::load(tauri_dir)?;\n\n    let target_triple = target.unwrap_or_else(|| {\n      cargo_config\n        .build()\n        .target()\n        .map(|t| t.to_string())\n        .unwrap_or_else(|| {\n          let output = Command::new(\"rustc\")\n            .args([\"-vV\"])\n            .output()\n            .expect(\"\\\"rustc\\\" could not be found, did you install Rust?\");\n          let stdout = String::from_utf8_lossy(&output.stdout);\n          stdout\n            .split('\\n')\n            .find(|l| l.starts_with(\"host:\"))\n            .unwrap()\n            .replace(\"host:\", \"\")\n            .trim()\n            .to_string()\n        })\n    });\n    let target_platform = TargetPlatform::from_triple(&target_triple);\n\n    Ok(Self {\n      manifest: Mutex::new(manifest),\n      cargo_settings,\n      cargo_package_settings,\n      cargo_ws_package_settings: ws_package_settings,\n      package_settings,\n      cargo_config,\n      target_triple,\n      target_platform,\n      workspace_dir,\n    })\n  }\n\n  fn target<'a>(&'a self, options: &'a Options) -> Option<&'a str> {\n    options\n      .target\n      .as_deref()\n      .or_else(|| self.cargo_config.build().target())\n  }\n\n  pub fn out_dir(&self, options: &Options, tauri_dir: &Path) -> crate::Result<PathBuf> {\n    get_target_dir(self.target(options), options, tauri_dir)\n  }\n}\n\n#[derive(Deserialize)]\npub(crate) struct CargoMetadata {\n  pub(crate) target_directory: PathBuf,\n  pub(crate) workspace_root: PathBuf,\n  workspace_members: Vec<String>,\n  packages: Vec<Package>,\n}\n\n#[derive(Deserialize)]\nstruct Package {\n  name: String,\n  id: String,\n  manifest_path: PathBuf,\n  dependencies: Vec<Dependency>,\n}\n\n#[derive(Deserialize)]\nstruct Dependency {\n  name: String,\n  /// Local package\n  path: Option<PathBuf>,\n}\n\npub(crate) fn get_cargo_metadata(tauri_dir: &Path) -> crate::Result<CargoMetadata> {\n  let output = Command::new(\"cargo\")\n    .args([\"metadata\", \"--no-deps\", \"--format-version\", \"1\"])\n    .current_dir(tauri_dir)\n    .output()\n    .map_err(|error| Error::CommandFailed {\n      command: \"cargo metadata --no-deps --format-version 1\".to_string(),\n      error,\n    })?;\n\n  if !output.status.success() {\n    return Err(Error::CommandFailed {\n      command: \"cargo metadata\".to_string(),\n      error: std::io::Error::other(String::from_utf8_lossy(&output.stderr)),\n    });\n  }\n\n  serde_json::from_slice(&output.stdout).context(\"failed to parse cargo metadata\")\n}\n\n/// Get the tauri project crate's dependencies that are inside the workspace\nfn get_in_workspace_dependency_paths(tauri_dir: &Path) -> crate::Result<Vec<PathBuf>> {\n  let metadata = get_cargo_metadata(tauri_dir)?;\n  let tauri_project_manifest_path = tauri_dir.join(\"Cargo.toml\");\n  let tauri_project_package = metadata\n    .packages\n    .iter()\n    .find(|package| package.manifest_path == tauri_project_manifest_path)\n    .context(\"tauri project package doesn't exist in cargo metadata output `packages`\")?;\n\n  let workspace_packages = metadata\n    .workspace_members\n    .iter()\n    .map(|member_package_id| {\n      metadata\n        .packages\n        .iter()\n        .find(|package| package.id == *member_package_id)\n        .context(\"workspace member doesn't exist in cargo metadata output `packages`\")\n    })\n    .collect::<crate::Result<Vec<_>>>()?;\n\n  let mut found_dependency_paths = Vec::new();\n  find_dependencies(\n    tauri_project_package,\n    &workspace_packages,\n    &mut found_dependency_paths,\n  );\n  Ok(found_dependency_paths)\n}\n\nfn find_dependencies(\n  package: &Package,\n  workspace_packages: &Vec<&Package>,\n  found_dependency_paths: &mut Vec<PathBuf>,\n) {\n  for dependency in &package.dependencies {\n    if let Some(path) = &dependency.path {\n      if let Some(package) = workspace_packages.iter().find(|workspace_package| {\n        workspace_package.name == dependency.name\n          && path.join(\"Cargo.toml\") == workspace_package.manifest_path\n          && !found_dependency_paths.contains(path)\n      }) {\n        found_dependency_paths.push(path.to_owned());\n        find_dependencies(package, workspace_packages, found_dependency_paths);\n      }\n    }\n  }\n}\n\n/// Get the cargo target directory based on the provided arguments.\n/// If \"--target-dir\" is specified in args, use it as the target directory (relative to current directory).\n/// Otherwise, use the target directory from cargo metadata.\npub(crate) fn get_cargo_target_dir(args: &[String], tauri_dir: &Path) -> crate::Result<PathBuf> {\n  let path = if let Some(target) = get_cargo_option(args, \"--target-dir\") {\n    std::env::current_dir()\n      .context(\"failed to get current directory\")?\n      .join(target)\n  } else {\n    get_cargo_metadata(tauri_dir)\n      .context(\"failed to run 'cargo metadata' command to get target directory\")?\n      .target_directory\n  };\n\n  Ok(path)\n}\n\n/// This function determines the 'target' directory and suffixes it with the profile\n/// to determine where the compiled binary will be located.\nfn get_target_dir(\n  triple: Option<&str>,\n  options: &Options,\n  tauri_dir: &Path,\n) -> crate::Result<PathBuf> {\n  let mut path = get_cargo_target_dir(&options.args, tauri_dir)?;\n\n  if let Some(triple) = triple {\n    path.push(triple);\n  }\n\n  path.push(get_profile_dir(options));\n\n  Ok(path)\n}\n\n#[inline]\nfn get_cargo_option<'a>(args: &'a [String], option: &'a str) -> Option<&'a str> {\n  args\n    .iter()\n    .position(|a| a.starts_with(option))\n    .and_then(|i| {\n      args[i]\n        .split_once('=')\n        .map(|(_, p)| Some(p))\n        .unwrap_or_else(|| args.get(i + 1).map(|s| s.as_str()))\n    })\n}\n\n/// Executes `cargo metadata` to get the workspace directory.\npub fn get_workspace_dir(tauri_dir: &Path) -> crate::Result<PathBuf> {\n  Ok(\n    get_cargo_metadata(tauri_dir)\n      .context(\"failed to run 'cargo metadata' command to get workspace directory\")?\n      .workspace_root,\n  )\n}\n\npub fn get_profile(options: &Options) -> &str {\n  get_cargo_option(&options.args, \"--profile\").unwrap_or(if options.debug {\n    \"dev\"\n  } else {\n    \"release\"\n  })\n}\n\npub fn get_profile_dir(options: &Options) -> &str {\n  match get_profile(options) {\n    \"dev\" => \"debug\",\n    profile => profile,\n  }\n}\n\n#[allow(unused_variables, deprecated)]\nfn tauri_config_to_bundle_settings(\n  settings: &RustAppSettings,\n  features: &[String],\n  tauri_config: &Config,\n  tauri_dir: &Path,\n  config: crate::helpers::config::BundleConfig,\n  updater_config: Option<UpdaterSettings>,\n  arch64bits: bool,\n) -> crate::Result<BundleSettings> {\n  let enabled_features = settings\n    .manifest\n    .lock()\n    .unwrap()\n    .all_enabled_features(features);\n\n  #[allow(unused_mut)]\n  let mut resources = config\n    .resources\n    .unwrap_or(BundleResources::List(Vec::new()));\n  #[allow(unused_mut)]\n  let mut depends_deb = config.linux.deb.depends.unwrap_or_default();\n\n  #[allow(unused_mut)]\n  let mut depends_rpm = config.linux.rpm.depends.unwrap_or_default();\n\n  #[allow(unused_mut)]\n  let mut appimage_files = config.linux.appimage.files;\n\n  // set env vars used by the bundler and inject dependencies\n  #[cfg(target_os = \"linux\")]\n  {\n    let mut libs: Vec<String> = Vec::new();\n\n    if enabled_features.contains(&\"tray-icon\".into())\n      || enabled_features.contains(&\"tauri/tray-icon\".into())\n    {\n      let (tray_kind, path) = std::env::var_os(\"TAURI_LINUX_AYATANA_APPINDICATOR\")\n        .map(|ayatana| {\n          if ayatana == \"true\" || ayatana == \"1\" {\n            (\n              pkgconfig_utils::TrayKind::Ayatana,\n              format!(\n                \"{}/libayatana-appindicator3.so.1\",\n                pkgconfig_utils::get_library_path(\"ayatana-appindicator3-0.1\")\n                  .expect(\"failed to get ayatana-appindicator library path using pkg-config.\")\n              ),\n            )\n          } else {\n            (\n              pkgconfig_utils::TrayKind::Libappindicator,\n              format!(\n                \"{}/libappindicator3.so.1\",\n                pkgconfig_utils::get_library_path(\"appindicator3-0.1\")\n                  .expect(\"failed to get libappindicator-gtk library path using pkg-config.\")\n              ),\n            )\n          }\n        })\n        .unwrap_or_else(pkgconfig_utils::get_appindicator_library_path);\n      match tray_kind {\n        pkgconfig_utils::TrayKind::Ayatana => {\n          depends_deb.push(\"libayatana-appindicator3-1\".into());\n          libs.push(\"libayatana-appindicator3.so.1\".into());\n        }\n        pkgconfig_utils::TrayKind::Libappindicator => {\n          depends_deb.push(\"libappindicator3-1\".into());\n          libs.push(\"libappindicator3.so.1\".into());\n        }\n      }\n\n      // conditionally setting it in case the user provided its own version for some reason\n      let path = PathBuf::from(path);\n      if !appimage_files.contains_key(&path) {\n        // manually construct target path, just in case the source path is something unexpected\n        appimage_files.insert(Path::new(\"/usr/lib/\").join(path.file_name().unwrap()), path);\n      }\n    }\n\n    depends_deb.push(\"libwebkit2gtk-4.1-0\".to_string());\n    depends_deb.push(\"libgtk-3-0\".to_string());\n\n    libs.push(\"libwebkit2gtk-4.1.so.0\".into());\n    libs.push(\"libgtk-3.so.0\".into());\n\n    for lib in libs {\n      let mut requires = lib;\n      if arch64bits {\n        requires.push_str(\"()(64bit)\");\n      }\n      depends_rpm.push(requires);\n    }\n  }\n\n  #[cfg(windows)]\n  {\n    if let crate::helpers::config::WebviewInstallMode::FixedRuntime { path } =\n      &config.windows.webview_install_mode\n    {\n      resources.push(path.display().to_string());\n    }\n  }\n\n  let signing_identity = match std::env::var_os(\"APPLE_SIGNING_IDENTITY\") {\n    Some(signing_identity) => Some(\n      signing_identity\n        .to_str()\n        .expect(\"failed to convert APPLE_SIGNING_IDENTITY to string\")\n        .to_string(),\n    ),\n    None => config.macos.signing_identity,\n  };\n\n  let provider_short_name = match std::env::var_os(\"APPLE_PROVIDER_SHORT_NAME\") {\n    Some(provider_short_name) => Some(\n      provider_short_name\n        .to_str()\n        .expect(\"failed to convert APPLE_PROVIDER_SHORT_NAME to string\")\n        .to_string(),\n    ),\n    None => config.macos.provider_short_name,\n  };\n\n  let (resources, resources_map) = match resources {\n    BundleResources::List(paths) => (Some(paths), None),\n    BundleResources::Map(map) => (None, Some(map)),\n  };\n\n  #[cfg(target_os = \"macos\")]\n  let entitlements = if let Some(plugin_config) = tauri_config\n    .plugins\n    .0\n    .get(\"deep-link\")\n    .and_then(|c| c.get(\"desktop\").cloned())\n  {\n    let protocols: DesktopDeepLinks =\n      serde_json::from_value(plugin_config).context(\"failed to parse deep link plugin config\")?;\n    let domains = match protocols {\n      DesktopDeepLinks::One(protocol) => protocol.domains,\n      DesktopDeepLinks::List(protocols) => protocols.into_iter().flat_map(|p| p.domains).collect(),\n    };\n\n    if domains.is_empty() {\n      config\n        .macos\n        .entitlements\n        .map(PathBuf::from)\n        .map(tauri_bundler::bundle::Entitlements::Path)\n    } else {\n      let mut app_links_entitlements = plist::Dictionary::new();\n      if !domains.is_empty() {\n        app_links_entitlements.insert(\n          \"com.apple.developer.associated-domains\".to_string(),\n          domains\n            .into_iter()\n            .map(|domain| format!(\"applinks:{domain}\").into())\n            .collect::<Vec<_>>()\n            .into(),\n        );\n      }\n      let entitlements = if let Some(user_provided_entitlements) = config.macos.entitlements {\n        crate::helpers::plist::merge_plist(vec![\n          PathBuf::from(user_provided_entitlements).into(),\n          plist::Value::Dictionary(app_links_entitlements).into(),\n        ])?\n      } else {\n        app_links_entitlements.into()\n      };\n\n      Some(tauri_bundler::bundle::Entitlements::Plist(entitlements))\n    }\n  } else {\n    config\n      .macos\n      .entitlements\n      .map(PathBuf::from)\n      .map(tauri_bundler::bundle::Entitlements::Path)\n  };\n  #[cfg(not(target_os = \"macos\"))]\n  let entitlements = None;\n\n  Ok(BundleSettings {\n    identifier: Some(tauri_config.identifier.clone()),\n    publisher: config.publisher,\n    homepage: config.homepage,\n    icon: Some(config.icon),\n    resources,\n    resources_map,\n    copyright: config.copyright,\n    category: match config.category {\n      Some(category) => Some(AppCategory::from_str(&category).map_err(|e| match e {\n        Some(e) => Error::GenericError(format!(\"invalid category, did you mean `{e}`?\")),\n        None => Error::GenericError(\"invalid category\".to_string()),\n      })?),\n      None => None,\n    },\n    file_associations: config.file_associations,\n    short_description: config.short_description,\n    long_description: config.long_description,\n    external_bin: config.external_bin,\n    deb: DebianSettings {\n      depends: if depends_deb.is_empty() {\n        None\n      } else {\n        Some(depends_deb)\n      },\n      recommends: config.linux.deb.recommends,\n      provides: config.linux.deb.provides,\n      conflicts: config.linux.deb.conflicts,\n      replaces: config.linux.deb.replaces,\n      files: config.linux.deb.files,\n      desktop_template: config.linux.deb.desktop_template,\n      section: config.linux.deb.section,\n      priority: config.linux.deb.priority,\n      changelog: config.linux.deb.changelog,\n      pre_install_script: config.linux.deb.pre_install_script,\n      post_install_script: config.linux.deb.post_install_script,\n      pre_remove_script: config.linux.deb.pre_remove_script,\n      post_remove_script: config.linux.deb.post_remove_script,\n    },\n    appimage: AppImageSettings {\n      files: appimage_files,\n      bundle_media_framework: config.linux.appimage.bundle_media_framework,\n      bundle_xdg_open: false,\n    },\n    rpm: RpmSettings {\n      depends: if depends_rpm.is_empty() {\n        None\n      } else {\n        Some(depends_rpm)\n      },\n      recommends: config.linux.rpm.recommends,\n      provides: config.linux.rpm.provides,\n      conflicts: config.linux.rpm.conflicts,\n      obsoletes: config.linux.rpm.obsoletes,\n      release: config.linux.rpm.release,\n      epoch: config.linux.rpm.epoch,\n      files: config.linux.rpm.files,\n      desktop_template: config.linux.rpm.desktop_template,\n      pre_install_script: config.linux.rpm.pre_install_script,\n      post_install_script: config.linux.rpm.post_install_script,\n      pre_remove_script: config.linux.rpm.pre_remove_script,\n      post_remove_script: config.linux.rpm.post_remove_script,\n      compression: config.linux.rpm.compression,\n    },\n    dmg: DmgSettings {\n      background: config.macos.dmg.background,\n      window_position: config\n        .macos\n        .dmg\n        .window_position\n        .map(|window_position| Position {\n          x: window_position.x,\n          y: window_position.y,\n        }),\n      window_size: Size {\n        width: config.macos.dmg.window_size.width,\n        height: config.macos.dmg.window_size.height,\n      },\n      app_position: Position {\n        x: config.macos.dmg.app_position.x,\n        y: config.macos.dmg.app_position.y,\n      },\n      application_folder_position: Position {\n        x: config.macos.dmg.application_folder_position.x,\n        y: config.macos.dmg.application_folder_position.y,\n      },\n    },\n    ios: IosSettings {\n      bundle_version: config.ios.bundle_version,\n    },\n    macos: MacOsSettings {\n      frameworks: config.macos.frameworks,\n      files: config.macos.files,\n      bundle_version: config.macos.bundle_version,\n      bundle_name: config.macos.bundle_name,\n      minimum_system_version: config.macos.minimum_system_version,\n      exception_domain: config.macos.exception_domain,\n      signing_identity,\n      skip_stapling: false,\n      hardened_runtime: config.macos.hardened_runtime,\n      provider_short_name,\n      entitlements,\n      #[cfg(not(target_os = \"macos\"))]\n      info_plist: None,\n      #[cfg(target_os = \"macos\")]\n      info_plist: {\n        let mut src_plists = vec![];\n\n        let path = tauri_dir.join(\"Info.plist\");\n        if path.exists() {\n          src_plists.push(path.into());\n        }\n        if let Some(info_plist) = &config.macos.info_plist {\n          src_plists.push(info_plist.clone().into());\n        }\n\n        Some(tauri_bundler::bundle::PlistKind::Plist(\n          crate::helpers::plist::merge_plist(src_plists)?,\n        ))\n      },\n    },\n    windows: WindowsSettings {\n      timestamp_url: config.windows.timestamp_url,\n      tsp: config.windows.tsp,\n      digest_algorithm: config.windows.digest_algorithm,\n      certificate_thumbprint: config.windows.certificate_thumbprint,\n      wix: config.windows.wix.map(wix_settings),\n      nsis: config.windows.nsis.map(nsis_settings),\n      icon_path: PathBuf::new(),\n      webview_install_mode: config.windows.webview_install_mode,\n      allow_downgrades: config.windows.allow_downgrades,\n      sign_command: config.windows.sign_command.map(custom_sign_settings),\n    },\n    license: config.license.or_else(|| {\n      settings\n        .cargo_package_settings\n        .license\n        .clone()\n        .map(|license| {\n          license\n            .resolve(\"license\", || {\n              settings\n                .cargo_ws_package_settings\n                .as_ref()\n                .and_then(|v| v.license.clone())\n                .context(\"Couldn't inherit value for `license` from workspace\")\n            })\n            .unwrap()\n        })\n    }),\n    license_file: config.license_file.map(|l| tauri_dir.join(l)),\n    updater: updater_config,\n    ..Default::default()\n  })\n}\n\n#[cfg(target_os = \"linux\")]\nmod pkgconfig_utils {\n  use std::process::Command;\n\n  pub enum TrayKind {\n    Ayatana,\n    Libappindicator,\n  }\n\n  pub fn get_appindicator_library_path() -> (TrayKind, String) {\n    match get_library_path(\"ayatana-appindicator3-0.1\") {\n      Some(p) => (\n        TrayKind::Ayatana,\n        format!(\"{p}/libayatana-appindicator3.so.1\"),\n      ),\n      None => match get_library_path(\"appindicator3-0.1\") {\n        Some(p) => (\n          TrayKind::Libappindicator,\n          format!(\"{p}/libappindicator3.so.1\"),\n        ),\n        None => panic!(\"Can't detect any appindicator library\"),\n      },\n    }\n  }\n\n  /// Gets the folder in which a library is located using `pkg-config`.\n  pub fn get_library_path(name: &str) -> Option<String> {\n    let mut cmd = Command::new(\"pkg-config\");\n    cmd.env(\"PKG_CONFIG_ALLOW_SYSTEM_LIBS\", \"1\");\n    cmd.arg(\"--libs-only-L\");\n    cmd.arg(name);\n    if let Ok(output) = cmd.output() {\n      if !output.stdout.is_empty() {\n        // output would be \"-L/path/to/library\\n\"\n        let word = output.stdout[2..].to_vec();\n        Some(String::from_utf8_lossy(&word).trim().to_string())\n      } else {\n        None\n      }\n    } else {\n      None\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use super::*;\n\n  #[test]\n  fn parse_cargo_option() {\n    let args = [\n      \"build\".into(),\n      \"--\".into(),\n      \"--profile\".into(),\n      \"holla\".into(),\n      \"--features\".into(),\n      \"a\".into(),\n      \"b\".into(),\n      \"--target-dir\".into(),\n      \"path/to/dir\".into(),\n    ];\n\n    assert_eq!(get_cargo_option(&args, \"--profile\"), Some(\"holla\"));\n    assert_eq!(get_cargo_option(&args, \"--target-dir\"), Some(\"path/to/dir\"));\n    assert_eq!(get_cargo_option(&args, \"--non-existent\"), None);\n  }\n\n  #[test]\n  fn parse_profile_from_opts() {\n    let options = Options {\n      args: vec![\n        \"build\".into(),\n        \"--\".into(),\n        \"--profile\".into(),\n        \"testing\".into(),\n        \"--features\".into(),\n        \"feat1\".into(),\n      ],\n      ..Default::default()\n    };\n    assert_eq!(get_profile(&options), \"testing\");\n\n    let options = Options {\n      args: vec![\n        \"build\".into(),\n        \"--\".into(),\n        \"--profile=customprofile\".into(),\n        \"testing\".into(),\n        \"--features\".into(),\n        \"feat1\".into(),\n      ],\n      ..Default::default()\n    };\n    assert_eq!(get_profile(&options), \"customprofile\");\n\n    let options = Options {\n      debug: true,\n      args: vec![\n        \"build\".into(),\n        \"--\".into(),\n        \"testing\".into(),\n        \"--features\".into(),\n        \"feat1\".into(),\n      ],\n      ..Default::default()\n    };\n    assert_eq!(get_profile(&options), \"dev\");\n\n    let options = Options {\n      debug: false,\n      args: vec![\n        \"build\".into(),\n        \"--\".into(),\n        \"testing\".into(),\n        \"--features\".into(),\n        \"feat1\".into(),\n      ],\n      ..Default::default()\n    };\n    assert_eq!(get_profile(&options), \"release\");\n\n    let options = Options {\n      args: vec![\"build\".into(), \"--\".into(), \"--profile\".into()],\n      ..Default::default()\n    };\n    assert_eq!(get_profile(&options), \"release\");\n  }\n\n  #[test]\n  fn parse_target_dir_from_opts() {\n    let dirs = crate::helpers::app_paths::resolve_dirs();\n    let current_dir = std::env::current_dir().unwrap();\n\n    let options = Options {\n      args: vec![\n        \"build\".into(),\n        \"--\".into(),\n        \"--target-dir\".into(),\n        \"path/to/some/dir\".into(),\n        \"--features\".into(),\n        \"feat1\".into(),\n      ],\n      debug: false,\n      ..Default::default()\n    };\n\n    assert_eq!(\n      get_target_dir(None, &options, dirs.tauri).unwrap(),\n      current_dir.join(\"path/to/some/dir/release\")\n    );\n    assert_eq!(\n      get_target_dir(Some(\"x86_64-pc-windows-msvc\"), &options, dirs.tauri).unwrap(),\n      current_dir\n        .join(\"path/to/some/dir\")\n        .join(\"x86_64-pc-windows-msvc\")\n        .join(\"release\")\n    );\n\n    let options = Options {\n      args: vec![\n        \"build\".into(),\n        \"--\".into(),\n        \"--features\".into(),\n        \"feat1\".into(),\n      ],\n      debug: false,\n      ..Default::default()\n    };\n\n    #[cfg(windows)]\n    assert!(\n      get_target_dir(Some(\"x86_64-pc-windows-msvc\"), &options, dirs.tauri)\n        .unwrap()\n        .ends_with(\"x86_64-pc-windows-msvc\\\\release\")\n    );\n    #[cfg(not(windows))]\n    assert!(\n      get_target_dir(Some(\"x86_64-pc-windows-msvc\"), &options, dirs.tauri)\n        .unwrap()\n        .ends_with(\"x86_64-pc-windows-msvc/release\")\n    );\n\n    #[cfg(windows)]\n    {\n      std::env::set_var(\"CARGO_TARGET_DIR\", \"D:\\\\path\\\\to\\\\env\\\\dir\");\n      assert_eq!(\n        get_target_dir(None, &options, dirs.tauri).unwrap(),\n        PathBuf::from(\"D:\\\\path\\\\to\\\\env\\\\dir\\\\release\")\n      );\n      assert_eq!(\n        get_target_dir(Some(\"x86_64-pc-windows-msvc\"), &options, dirs.tauri).unwrap(),\n        PathBuf::from(\"D:\\\\path\\\\to\\\\env\\\\dir\\\\x86_64-pc-windows-msvc\\\\release\")\n      );\n    }\n\n    #[cfg(not(windows))]\n    {\n      std::env::set_var(\"CARGO_TARGET_DIR\", \"/path/to/env/dir\");\n      assert_eq!(\n        get_target_dir(None, &options, dirs.tauri).unwrap(),\n        PathBuf::from(\"/path/to/env/dir/release\")\n      );\n      assert_eq!(\n        get_target_dir(Some(\"x86_64-pc-windows-msvc\"), &options, dirs.tauri).unwrap(),\n        PathBuf::from(\"/path/to/env/dir/x86_64-pc-windows-msvc/release\")\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! This Rust executable provides the full interface to all of the required activities for which the CLI is required. It will run on macOS, Windows, and Linux.\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n#![cfg(any(target_os = \"macos\", target_os = \"linux\", windows))]\n\nmod acl;\nmod add;\nmod build;\nmod bundle;\nmod completions;\nmod dev;\nmod error;\nmod helpers;\nmod icon;\nmod info;\nmod init;\nmod inspect;\nmod interface;\nmod migrate;\nmod mobile;\nmod plugin;\nmod remove;\nmod signer;\n\nuse clap::{ArgAction, CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum};\nuse env_logger::fmt::style::{AnsiColor, Style};\nuse env_logger::Builder;\npub use error::{Error, ErrorExt, Result};\nuse log::Level;\nuse serde::{Deserialize, Serialize};\nuse std::io::{BufReader, Write};\nuse std::process::{exit, Command, ExitStatus, Output, Stdio};\nuse std::{\n  ffi::OsString,\n  fmt::Display,\n  fs::read_to_string,\n  io::BufRead,\n  path::PathBuf,\n  str::FromStr,\n  sync::{Arc, Mutex},\n};\n\nuse crate::error::Context;\n\n/// Tauri configuration argument option.\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct ConfigValue(pub(crate) serde_json::Value);\n\nimpl FromStr for ConfigValue {\n  type Err = Error;\n\n  fn from_str(config: &str) -> std::result::Result<Self, Self::Err> {\n    if config.starts_with('{') {\n      Ok(Self(serde_json::from_str(config).with_context(|| {\n        format!(\"failed to parse config `{config}` as JSON\")\n      })?))\n    } else {\n      let path = PathBuf::from(config);\n      let raw =\n        read_to_string(&path).fs_context(\"failed to read configuration file\", path.clone())?;\n\n      match path.extension().and_then(|ext| ext.to_str()) {\n        Some(\"toml\") => Ok(Self(::toml::from_str(&raw).with_context(|| {\n          format!(\"failed to parse config at {} as TOML\", path.display())\n        })?)),\n        Some(\"json5\") => Ok(Self(::json5::from_str(&raw).with_context(|| {\n          format!(\"failed to parse config at {} as JSON5\", path.display())\n        })?)),\n        // treat all other extensions as json\n        _ => Ok(Self(\n          // from tauri-utils/src/config/parse.rs:\n          // we also want to support **valid** json5 in the .json extension\n          // if the json5 is not valid the serde_json error for regular json will be returned.\n          match ::json5::from_str(&raw) {\n            Ok(json5) => json5,\n            Err(_) => serde_json::from_str(&raw)\n              .with_context(|| format!(\"failed to parse config at {} as JSON\", path.display()))?,\n          },\n        )),\n      }\n    }\n  }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]\npub enum RunMode {\n  Desktop,\n  #[cfg(target_os = \"macos\")]\n  Ios,\n  Android,\n}\n\nimpl Display for RunMode {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    write!(\n      f,\n      \"{}\",\n      match self {\n        Self::Desktop => \"desktop\",\n        #[cfg(target_os = \"macos\")]\n        Self::Ios => \"iOS\",\n        Self::Android => \"android\",\n      }\n    )\n  }\n}\n\n#[derive(Deserialize)]\npub struct VersionMetadata {\n  tauri: String,\n  #[serde(rename = \"tauri-build\")]\n  tauri_build: String,\n  #[serde(rename = \"tauri-plugin\")]\n  tauri_plugin: String,\n}\n\n#[derive(Deserialize)]\npub struct PackageJson {\n  name: Option<String>,\n  version: Option<String>,\n  product_name: Option<String>,\n}\n\n#[derive(Parser)]\n#[clap(\n  author,\n  version,\n  about,\n  bin_name(\"cargo-tauri\"),\n  subcommand_required(true),\n  arg_required_else_help(true),\n  propagate_version(true),\n  no_binary_name(true)\n)]\npub(crate) struct Cli {\n  /// Enables verbose logging\n  #[clap(short, long, global = true, action = ArgAction::Count)]\n  verbose: u8,\n  #[clap(subcommand)]\n  command: Commands,\n}\n\n#[derive(Subcommand)]\nenum Commands {\n  Init(init::Options),\n  Dev(dev::Options),\n  Build(build::Options),\n  Bundle(bundle::Options),\n  Android(mobile::android::Cli),\n  #[cfg(target_os = \"macos\")]\n  Ios(mobile::ios::Cli),\n  /// Migrate from v1 to v2\n  Migrate,\n  Info(info::Options),\n  Add(add::Options),\n  Remove(remove::Options),\n  Plugin(plugin::Cli),\n  Icon(icon::Options),\n  Signer(signer::Cli),\n  Completions(completions::Options),\n  Permission(acl::permission::Cli),\n  Capability(acl::capability::Cli),\n  Inspect(inspect::Cli),\n}\n\nfn format_error<I: CommandFactory>(err: clap::Error) -> clap::Error {\n  let mut app = I::command();\n  err.format(&mut app)\n}\n\nfn get_verbosity(cli_verbose: u8) -> u8 {\n  std::env::var(\"TAURI_CLI_VERBOSITY\")\n    .ok()\n    .and_then(|v| v.parse().ok())\n    .unwrap_or(cli_verbose)\n}\n\n/// Run the Tauri CLI with the passed arguments, exiting if an error occurs.\n///\n/// The passed arguments should have the binary argument(s) stripped out before being passed.\n///\n/// e.g.\n/// 1. `tauri-cli 1 2 3` -> `1 2 3`\n/// 2. `cargo tauri 1 2 3` -> `1 2 3`\n/// 3. `node tauri.js 1 2 3` -> `1 2 3`\n///\n/// The passed `bin_name` parameter should be how you want the help messages to display the command.\n/// This defaults to `cargo-tauri`, but should be set to how the program was called, such as\n/// `cargo tauri`.\npub fn run<I, A>(args: I, bin_name: Option<String>)\nwhere\n  I: IntoIterator<Item = A>,\n  A: Into<OsString> + Clone,\n{\n  if let Err(e) = try_run(args, bin_name) {\n    log::error!(\"{e}\");\n    exit(1);\n  }\n}\n\n/// Run the Tauri CLI with the passed arguments.\n///\n/// It is similar to [`run`], but instead of exiting on an error, it returns a result.\npub fn try_run<I, A>(args: I, bin_name: Option<String>) -> Result<()>\nwhere\n  I: IntoIterator<Item = A>,\n  A: Into<OsString> + Clone,\n{\n  let cli = match bin_name {\n    Some(bin_name) => Cli::command().bin_name(bin_name),\n    None => Cli::command(),\n  };\n  let cli_ = cli.clone();\n  let matches = cli.get_matches_from(args);\n\n  let res = Cli::from_arg_matches(&matches).map_err(format_error::<Cli>);\n  let cli = match res {\n    Ok(s) => s,\n    Err(e) => e.exit(),\n  };\n  // set the verbosity level so subsequent CLI calls (xcode-script, android-studio-script) refer to it\n  let verbosity_number = get_verbosity(cli.verbose);\n  std::env::set_var(\"TAURI_CLI_VERBOSITY\", verbosity_number.to_string());\n\n  let mut builder = Builder::from_default_env();\n  if let Err(err) = builder\n    .format_indent(Some(12))\n    .filter(None, verbosity_level(verbosity_number).to_level_filter())\n    // golbin spams an insane amount of really technical logs on the debug level so we're reducing one level\n    .filter(\n      Some(\"goblin\"),\n      verbosity_level(verbosity_number.saturating_sub(1)).to_level_filter(),\n    )\n    // handlebars is not that spammy but its debug logs are typically far from being helpful\n    .filter(\n      Some(\"handlebars\"),\n      verbosity_level(verbosity_number.saturating_sub(1)).to_level_filter(),\n    )\n    .format(|f, record| {\n      let mut is_command_output = false;\n      if let Some(action) = record.key_values().get(\"action\".into()) {\n        let action = action.to_cow_str().unwrap();\n        is_command_output = action == \"stdout\" || action == \"stderr\";\n        if !is_command_output {\n          let style = Style::new().fg_color(Some(AnsiColor::Green.into())).bold();\n          write!(f, \"{style}{action:>12}{style:#} \")?;\n        }\n      } else {\n        let style = f.default_level_style(record.level()).bold();\n        write!(\n          f,\n          \"{style}{:>12}{style:#} \",\n          prettyprint_level(record.level())\n        )?;\n      }\n\n      if !is_command_output && log::log_enabled!(Level::Debug) {\n        let style = Style::new().fg_color(Some(AnsiColor::Black.into()));\n        write!(f, \"[{style}{}{style:#}] \", record.target())?;\n      }\n\n      writeln!(f, \"{}\", record.args())\n    })\n    .try_init()\n  {\n    eprintln!(\"Failed to attach logger: {err}\");\n  }\n\n  match cli.command {\n    Commands::Build(options) => build::command(options, cli.verbose)?,\n    Commands::Bundle(options) => bundle::command(options, cli.verbose)?,\n    Commands::Dev(options) => dev::command(options)?,\n    Commands::Add(options) => add::command(options)?,\n    Commands::Remove(options) => remove::command(options)?,\n    Commands::Icon(options) => icon::command(options)?,\n    Commands::Info(options) => info::command(options)?,\n    Commands::Init(options) => init::command(options)?,\n    Commands::Plugin(cli) => plugin::command(cli)?,\n    Commands::Signer(cli) => signer::command(cli)?,\n    Commands::Completions(options) => completions::command(options, cli_)?,\n    Commands::Permission(options) => acl::permission::command(options)?,\n    Commands::Capability(options) => acl::capability::command(options)?,\n    Commands::Android(c) => mobile::android::command(c, cli.verbose)?,\n    #[cfg(target_os = \"macos\")]\n    Commands::Ios(c) => mobile::ios::command(c, cli.verbose)?,\n    Commands::Migrate => migrate::command()?,\n    Commands::Inspect(cli) => inspect::command(cli)?,\n  }\n\n  Ok(())\n}\n\n/// This maps the occurrence of `--verbose` flags to the correct log level\nfn verbosity_level(num: u8) -> Level {\n  match num {\n    0 => Level::Info,\n    1 => Level::Debug,\n    _ => Level::Trace,\n  }\n}\n\n/// The default string representation for `Level` is all uppercaps which doesn't mix well with the other printed actions.\nfn prettyprint_level(lvl: Level) -> &'static str {\n  match lvl {\n    Level::Error => \"Error\",\n    Level::Warn => \"Warn\",\n    Level::Info => \"Info\",\n    Level::Debug => \"Debug\",\n    Level::Trace => \"Trace\",\n  }\n}\n\npub trait CommandExt {\n  // The `pipe` function sets the stdout and stderr to properly\n  // show the command output in the Node.js wrapper.\n  fn piped(&mut self) -> std::io::Result<ExitStatus>;\n  fn output_ok(&mut self) -> crate::Result<Output>;\n}\n\nimpl CommandExt for Command {\n  fn piped(&mut self) -> std::io::Result<ExitStatus> {\n    self.stdin(os_pipe::dup_stdin()?);\n    self.stdout(os_pipe::dup_stdout()?);\n    self.stderr(os_pipe::dup_stderr()?);\n\n    let program = self.get_program().to_string_lossy().into_owned();\n    let args = self\n      .get_args()\n      .map(|a| a.to_string_lossy())\n      .collect::<Vec<_>>()\n      .join(\" \");\n\n    log::debug!(action = \"Running\"; \"Command `{program} {args}`\");\n    self.status()\n  }\n\n  fn output_ok(&mut self) -> crate::Result<Output> {\n    let program = self.get_program().to_string_lossy().into_owned();\n    let args = self\n      .get_args()\n      .map(|a| a.to_string_lossy())\n      .collect::<Vec<_>>()\n      .join(\" \");\n    let cmdline = format!(\"{program} {args}\");\n    log::debug!(action = \"Running\"; \"Command `{cmdline}`\");\n\n    self.stdout(Stdio::piped());\n    self.stderr(Stdio::piped());\n\n    let mut child = self\n      .spawn()\n      .with_context(|| format!(\"failed to run command `{cmdline}`\"))?;\n\n    let mut stdout = child.stdout.take().map(BufReader::new).unwrap();\n    let stdout_lines = Arc::new(Mutex::new(Vec::new()));\n    let stdout_lines_ = stdout_lines.clone();\n    std::thread::spawn(move || {\n      let mut line = String::new();\n      if let Ok(mut lines) = stdout_lines_.lock() {\n        loop {\n          line.clear();\n          match stdout.read_line(&mut line) {\n            Ok(0) => break,\n            Ok(_) => {\n              log::debug!(action = \"stdout\"; \"{}\", line.trim_end());\n              lines.extend(line.as_bytes());\n            }\n            Err(_) => (),\n          }\n        }\n      }\n    });\n\n    let mut stderr = child.stderr.take().map(BufReader::new).unwrap();\n    let stderr_lines = Arc::new(Mutex::new(Vec::new()));\n    let stderr_lines_ = stderr_lines.clone();\n    std::thread::spawn(move || {\n      let mut line = String::new();\n      if let Ok(mut lines) = stderr_lines_.lock() {\n        loop {\n          line.clear();\n          match stderr.read_line(&mut line) {\n            Ok(0) => break,\n            Ok(_) => {\n              log::debug!(action = \"stderr\"; \"{}\", line.trim_end());\n              lines.extend(line.as_bytes());\n            }\n            Err(_) => (),\n          }\n        }\n      }\n    });\n\n    let status = child\n      .wait()\n      .with_context(|| format!(\"failed to run command `{cmdline}`\"))?;\n\n    let output = Output {\n      status,\n      stdout: std::mem::take(&mut *stdout_lines.lock().unwrap()),\n      stderr: std::mem::take(&mut *stderr_lines.lock().unwrap()),\n    };\n\n    if output.status.success() {\n      Ok(output)\n    } else {\n      crate::error::bail!(\n        \"failed to run command `{cmdline}`: command exited with status code {}\",\n        output.status.code().unwrap_or(-1)\n      );\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use clap::CommandFactory;\n\n  use crate::Cli;\n\n  #[test]\n  fn verify_cli() {\n    Cli::command().debug_assert();\n  }\n\n  #[test]\n  fn help_output_includes_build() {\n    let help = Cli::command().render_help().to_string();\n    assert!(help.contains(\"Build\"));\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#[cfg(not(any(target_os = \"macos\", target_os = \"linux\", windows)))]\nfn main() {\n  println!(\"The Tauri CLI is not supported on this platform\");\n  std::process::exit(1);\n}\n\n#[cfg(any(target_os = \"macos\", target_os = \"linux\", windows))]\nfn main() {\n  use std::env::args_os;\n  use std::ffi::OsStr;\n  use std::path::Path;\n  use std::process::exit;\n\n  let mut args = args_os().peekable();\n  let bin_name = match args\n    .next()\n    .as_deref()\n    .map(Path::new)\n    .and_then(Path::file_stem)\n    .and_then(OsStr::to_str)\n  {\n    Some(\"cargo-tauri\") => {\n      if args.peek().and_then(|s| s.to_str()) == Some(\"tauri\") {\n        // remove the extra cargo subcommand\n        args.next();\n        Some(\"cargo tauri\".into())\n      } else {\n        Some(\"cargo-tauri\".into())\n      }\n    }\n    Some(stem) => Some(stem.to_string()),\n    None => {\n      eprintln!(\"cargo-tauri wrapper unable to read first argument\");\n      exit(1);\n    }\n  };\n\n  tauri_cli::run(args, bin_name)\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/migrate/migrations/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npub mod v1;\npub mod v2_beta;\n"
  },
  {
    "path": "crates/tauri-cli/src/migrate/migrations/v1/config.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{error::Context, ErrorExt, Result};\n\nuse serde_json::{Map, Value};\nuse tauri_utils::acl::{\n  capability::{Capability, PermissionEntry},\n  Scopes, Value as AclValue,\n};\n\nuse std::{\n  collections::{BTreeMap, HashSet},\n  fs,\n  path::Path,\n};\n\npub fn migrate(tauri_dir: &Path) -> Result<MigratedConfig> {\n  if let Ok((mut config, config_path)) =\n    tauri_utils::config_v1::parse::parse_value(tauri_dir.join(\"tauri.conf.json\"))\n  {\n    let migrated = migrate_config(&mut config)?;\n    if config_path.extension().is_some_and(|ext| ext == \"toml\") {\n      fs::write(\n        &config_path,\n        toml::to_string_pretty(&config).context(\"failed to serialize config\")?,\n      )\n      .fs_context(\"failed to write config\", config_path.clone())?;\n    } else {\n      fs::write(\n        &config_path,\n        serde_json::to_string_pretty(&config).context(\"failed to serialize config\")?,\n      )\n      .fs_context(\"failed to write config\", config_path.clone())?;\n    }\n\n    let mut permissions: Vec<PermissionEntry> = vec![\"core:default\"]\n      .into_iter()\n      .map(|p| PermissionEntry::PermissionRef(p.to_string().try_into().unwrap()))\n      .collect();\n    permissions.extend(migrated.permissions.clone());\n\n    let capabilities_path = config_path.parent().unwrap().join(\"capabilities\");\n    fs::create_dir_all(&capabilities_path).fs_context(\n      \"failed to create capabilities directory\",\n      capabilities_path.clone(),\n    )?;\n    fs::write(\n      capabilities_path.join(\"migrated.json\"),\n      serde_json::to_string_pretty(&Capability {\n        identifier: \"migrated\".to_string(),\n        description: \"permissions that were migrated from v1\".into(),\n        local: true,\n        remote: None,\n        windows: vec![\"main\".into()],\n        webviews: vec![],\n        permissions,\n        platforms: None,\n      })\n      .context(\"failed to serialize capabilities\")?,\n    )\n    .fs_context(\n      \"failed to write capabilities\",\n      capabilities_path.join(\"migrated.json\"),\n    )?;\n\n    return Ok(migrated);\n  }\n\n  Ok(Default::default())\n}\n\n#[derive(Default)]\npub struct MigratedConfig {\n  pub permissions: Vec<PermissionEntry>,\n  pub plugins: HashSet<String>,\n}\n\nfn migrate_config(config: &mut Value) -> Result<MigratedConfig> {\n  let mut migrated = MigratedConfig {\n    permissions: Vec::new(),\n    plugins: HashSet::new(),\n  };\n\n  if let Some(config) = config.as_object_mut() {\n    process_package_metadata(config);\n    process_build(config);\n\n    let mut plugins = config\n      .entry(\"plugins\")\n      .or_insert_with(|| Value::Object(Default::default()))\n      .as_object_mut()\n      .unwrap()\n      .clone();\n\n    if let Some(tauri_config) = config.get_mut(\"tauri\").and_then(|c| c.as_object_mut()) {\n      // allowlist\n      if let Some(allowlist) = tauri_config.remove(\"allowlist\") {\n        let allowlist = process_allowlist(tauri_config, allowlist)?;\n        let permissions = allowlist_to_permissions(allowlist);\n        migrated.plugins = plugins_from_permissions(&permissions);\n        migrated.permissions = permissions;\n      }\n\n      // dangerousUseHttpScheme/useHttpsScheme\n      let dangerous_use_http = tauri_config\n        .get(\"security\")\n        .and_then(|w| w.as_object())\n        .and_then(|w| {\n          w.get(\"dangerousUseHttpScheme\")\n            .or_else(|| w.get(\"dangerous-use-http-scheme\"))\n        })\n        .and_then(|v| v.as_bool())\n        .unwrap_or_default();\n\n      if let Some(windows) = tauri_config\n        .get_mut(\"windows\")\n        .and_then(|w| w.as_array_mut())\n      {\n        for window in windows {\n          if let Some(window) = window.as_object_mut() {\n            window.insert(\"useHttpsScheme\".to_string(), (!dangerous_use_http).into());\n          }\n        }\n      }\n\n      // security\n      if let Some(security) = tauri_config\n        .get_mut(\"security\")\n        .and_then(|c| c.as_object_mut())\n      {\n        process_security(security)?;\n      }\n\n      // tauri > pattern\n      if let Some(pattern) = tauri_config.remove(\"pattern\") {\n        tauri_config\n          .entry(\"security\")\n          .or_insert_with(|| Value::Object(Default::default()))\n          .as_object_mut()\n          .map(|s| s.insert(\"pattern\".into(), pattern));\n      }\n\n      // system tray\n      if let Some((tray, key)) = tauri_config\n        .remove(\"systemTray\")\n        .map(|v| (v, \"trayIcon\"))\n        .or_else(|| tauri_config.remove(\"system-tray\").map(|v| (v, \"tray-icon\")))\n      {\n        tauri_config.insert(key.into(), tray);\n      }\n\n      // cli\n      if let Some(cli) = tauri_config.remove(\"cli\") {\n        process_cli(&mut plugins, cli)?;\n      }\n\n      // updater\n      process_updater(tauri_config, &mut plugins, &mut migrated)?;\n    }\n\n    process_bundle(config, &migrated);\n\n    // if we have migrated the updater config, let's ensure createUpdaterArtifacts is set\n    if plugins.contains_key(\"updater\") {\n      let bundle_config = config\n        .entry(\"bundle\")\n        .or_insert_with(|| Value::Object(Default::default()))\n        .as_object_mut()\n        .unwrap();\n      if !bundle_config.contains_key(\"createUpdaterArtifacts\") {\n        bundle_config.insert(\"createUpdaterArtifacts\".to_owned(), \"v1Compatible\".into());\n      }\n    }\n\n    config.insert(\"plugins\".into(), plugins.into());\n\n    if let Some(tauri_config) = config.remove(\"tauri\") {\n      config.insert(\"app\".into(), tauri_config);\n    }\n  }\n\n  Ok(migrated)\n}\n\nfn process_package_metadata(config: &mut Map<String, Value>) {\n  if let Some(mut package_config) = config.remove(\"package\") {\n    if let Some(package_config) = package_config.as_object_mut() {\n      if let Some((product_name, key)) = package_config\n        .remove(\"productName\")\n        .map(|v| (v, \"productName\"))\n        .or_else(|| {\n          package_config\n            .remove(\"product-name\")\n            .map(|v| (v, \"product-name\"))\n        })\n      {\n        config.insert(key.into(), product_name.clone());\n        // keep main binary name unchanged\n        config.insert(\"mainBinaryName\".into(), product_name);\n      }\n\n      if let Some(version) = package_config.remove(\"version\") {\n        config.insert(\"version\".into(), version);\n      }\n    }\n  }\n\n  if let Some(bundle_config) = config\n    .get_mut(\"tauri\")\n    .and_then(|t| t.get_mut(\"bundle\"))\n    .and_then(|b| b.as_object_mut())\n  {\n    if let Some(identifier) = bundle_config.remove(\"identifier\") {\n      config.insert(\"identifier\".into(), identifier);\n    }\n  }\n}\n\nfn process_build(config: &mut Map<String, Value>) {\n  if let Some(build_config) = config.get_mut(\"build\").and_then(|b| b.as_object_mut()) {\n    if let Some((dist_dir, key)) = build_config\n      .remove(\"distDir\")\n      .map(|v| (v, \"frontendDist\"))\n      .or_else(|| {\n        build_config\n          .remove(\"dist-dir\")\n          .map(|v| (v, \"frontend-dist\"))\n      })\n    {\n      build_config.insert(key.into(), dist_dir);\n    }\n    if let Some((dev_path, key)) = build_config\n      .remove(\"devPath\")\n      .map(|v| (v, \"devUrl\"))\n      .or_else(|| build_config.remove(\"dev-path\").map(|v| (v, \"dev-url\")))\n    {\n      let is_url = url::Url::parse(dev_path.as_str().unwrap_or_default()).is_ok();\n      if is_url {\n        build_config.insert(key.into(), dev_path);\n      }\n    }\n    if let Some((with_global_tauri, key)) = build_config\n      .remove(\"withGlobalTauri\")\n      .map(|v| (v, \"withGlobalTauri\"))\n      .or_else(|| {\n        build_config\n          .remove(\"with-global-tauri\")\n          .map(|v| (v, \"with-global-tauri\"))\n      })\n    {\n      config\n        .get_mut(\"tauri\")\n        .and_then(|t| t.as_object_mut())\n        .map(|t| t.insert(key.into(), with_global_tauri));\n    }\n  }\n}\n\nfn process_bundle(config: &mut Map<String, Value>, migrated: &MigratedConfig) {\n  let mut license_file = None;\n\n  if let Some(mut bundle_config) = config\n    .get_mut(\"tauri\")\n    .and_then(|b| b.as_object_mut())\n    .and_then(|t| t.remove(\"bundle\"))\n  {\n    if let Some(bundle_config) = bundle_config.as_object_mut() {\n      if let Some(deb) = bundle_config.remove(\"deb\") {\n        bundle_config\n          .entry(\"linux\")\n          .or_insert_with(|| Value::Object(Default::default()))\n          .as_object_mut()\n          .map(|l| l.insert(\"deb\".into(), deb));\n      }\n\n      if let Some(appimage) = bundle_config.remove(\"appimage\") {\n        bundle_config\n          .entry(\"linux\")\n          .or_insert_with(|| Value::Object(Default::default()))\n          .as_object_mut()\n          .map(|l| l.insert(\"appimage\".into(), appimage));\n      }\n\n      if let Some(rpm) = bundle_config.remove(\"rpm\") {\n        bundle_config\n          .entry(\"linux\")\n          .or_insert_with(|| Value::Object(Default::default()))\n          .as_object_mut()\n          .map(|l| l.insert(\"rpm\".into(), rpm));\n      }\n\n      if let Some(dmg) = bundle_config.remove(\"dmg\") {\n        bundle_config\n          .entry(\"macOS\")\n          .or_insert_with(|| Value::Object(Default::default()))\n          .as_object_mut()\n          .map(|l| l.insert(\"dmg\".into(), dmg));\n      }\n\n      // license file\n      if let Some(macos) = bundle_config\n        .get_mut(\"macOS\")\n        .and_then(|v| v.as_object_mut())\n      {\n        if let Some(license) = macos.remove(\"license\") {\n          license_file = Some(license);\n        }\n      }\n\n      // Windows\n      if let Some(windows) = bundle_config.get_mut(\"windows\") {\n        if let Some(windows) = windows.as_object_mut() {\n          if let Some(wix) = windows.get_mut(\"wix\").and_then(|v| v.as_object_mut()) {\n            if let Some(license_path) = wix.remove(\"license\") {\n              license_file = Some(license_path);\n            }\n          }\n          if let Some(nsis) = windows.get_mut(\"nsis\").and_then(|v| v.as_object_mut()) {\n            if let Some(license_path) = nsis.remove(\"license\") {\n              license_file = Some(license_path);\n            }\n          }\n\n          if let Some((fixed_runtime_path, key)) = windows\n            .remove(\"webviewFixedRuntimePath\")\n            .map(|v| (v, \"webviewInstallMode\"))\n            .or_else(|| {\n              windows\n                .remove(\"webview-fixed-runtime-path\")\n                .map(|v| (v, \"webview-install-mode\"))\n            })\n          {\n            if !fixed_runtime_path.is_null() {\n              windows.insert(\n                key.into(),\n                serde_json::json!({\n                  \"type\": \"fixedRuntime\",\n                  \"path\": fixed_runtime_path\n                }),\n              );\n            }\n          }\n        }\n      }\n      if let Some(license_file) = license_file {\n        bundle_config.insert(\"licenseFile\".into(), license_file);\n      }\n\n      // Migrate updater from targets to update field\n      if let Some(targets) = bundle_config.get_mut(\"targets\") {\n        let should_migrate = if let Some(targets) = targets.as_array_mut() {\n          // targets: [\"updater\", ...]\n          if let Some(index) = targets\n            .iter()\n            .position(|target| *target == serde_json::Value::String(\"updater\".to_owned()))\n          {\n            targets.remove(index);\n            true\n          } else {\n            false\n          }\n        } else if let Some(target) = targets.as_str() {\n          // targets: \"updater\"\n          if target == \"updater\" {\n            bundle_config.remove(\"targets\");\n            true\n          } else {\n            // note that target == \"all\" is the default from the v1 tauri CLI\n            // so we shouldn't bindly force updater bundles to be created\n            // instead we only migrate if the updater has been migrated\n            target == \"all\" && migrated.plugins.contains(\"updater\")\n          }\n        } else {\n          false\n        };\n        if should_migrate {\n          bundle_config.insert(\"createUpdaterArtifacts\".to_owned(), \"v1Compatible\".into());\n        }\n      }\n    }\n\n    config.insert(\"bundle\".into(), bundle_config);\n  }\n}\n\nfn process_security(security: &mut Map<String, Value>) -> Result<()> {\n  // migrate CSP: add `ipc:` to `connect-src`\n  if let Some(csp_value) = security.remove(\"csp\") {\n    let csp = if csp_value.is_null() {\n      csp_value\n    } else {\n      let mut csp: tauri_utils::config_v1::Csp =\n        serde_json::from_value(csp_value).context(\"failed to deserialize CSP\")?;\n      match &mut csp {\n        tauri_utils::config_v1::Csp::Policy(csp) => {\n          if csp.contains(\"connect-src\") {\n            *csp = csp.replace(\"connect-src\", \"connect-src ipc: http://ipc.localhost\");\n          } else {\n            *csp = format!(\"{csp}; connect-src ipc: http://ipc.localhost\");\n          }\n        }\n        tauri_utils::config_v1::Csp::DirectiveMap(csp) => {\n          if let Some(connect_src) = csp.get_mut(\"connect-src\") {\n            if !connect_src.contains(\"ipc: http://ipc.localhost\") {\n              connect_src.push(\"ipc: http://ipc.localhost\");\n            }\n          } else {\n            csp.insert(\n              \"connect-src\".into(),\n              tauri_utils::config_v1::CspDirectiveSources::List(vec![\n                \"ipc: http://ipc.localhost\".to_string()\n              ]),\n            );\n          }\n        }\n      }\n      serde_json::to_value(csp).context(\"failed to serialize CSP\")?\n    };\n\n    security.insert(\"csp\".into(), csp);\n  }\n\n  // dangerous_remote_domain_ipc_access no longer exists\n  if let Some(dangerous_remote_domain_ipc_access) = security\n    .remove(\"dangerousRemoteDomainIpcAccess\")\n    .or_else(|| security.remove(\"dangerous-remote-domain-ipc-access\"))\n  {\n    println!(\"dangerous remote domain IPC access config ({dangerous_remote_domain_ipc_access:?}) no longer exists, see documentation for capabilities and remote access: https://v2.tauri.app/security/capabilities/#remote-api-access\")\n  }\n  security\n    .remove(\"dangerousUseHttpScheme\")\n    .or_else(|| security.remove(\"dangerous-use-http-scheme\"));\n\n  Ok(())\n}\n\nfn process_allowlist(\n  tauri_config: &mut Map<String, Value>,\n  allowlist: Value,\n) -> Result<tauri_utils::config_v1::AllowlistConfig> {\n  let allowlist: tauri_utils::config_v1::AllowlistConfig =\n    serde_json::from_value(allowlist).context(\"failed to deserialize allowlist\")?;\n\n  if allowlist.protocol.asset_scope != Default::default() {\n    let security = tauri_config\n      .entry(\"security\")\n      .or_insert_with(|| Value::Object(Default::default()))\n      .as_object_mut()\n      .unwrap();\n\n    let mut asset_protocol = Map::new();\n    asset_protocol.insert(\n      \"scope\".into(),\n      serde_json::to_value(allowlist.protocol.asset_scope.clone())\n        .context(\"failed to serialize asset scope\")?,\n    );\n    if allowlist.protocol.asset {\n      asset_protocol.insert(\"enable\".into(), true.into());\n    }\n    security.insert(\"assetProtocol\".into(), asset_protocol.into());\n  }\n\n  Ok(allowlist)\n}\n\nfn allowlist_to_permissions(\n  allowlist: tauri_utils::config_v1::AllowlistConfig,\n) -> Vec<PermissionEntry> {\n  macro_rules! permissions {\n    ($allowlist: ident, $permissions_list: ident, $object: ident, $field: ident => $associated_permission: expr) => {{\n      if $allowlist.all || $allowlist.$object.all || $allowlist.$object.$field {\n        $permissions_list.push(PermissionEntry::PermissionRef(\n          $associated_permission.to_string().try_into().unwrap(),\n        ));\n        true\n      } else {\n        false\n      }\n    }};\n  }\n\n  let mut permissions = Vec::new();\n\n  // fs\n  permissions!(allowlist, permissions, fs, read_file => \"fs:allow-read-file\");\n  permissions!(allowlist, permissions, fs, write_file => \"fs:allow-write-file\");\n  permissions!(allowlist, permissions, fs, read_dir => \"fs:allow-read-dir\");\n  permissions!(allowlist, permissions, fs, copy_file => \"fs:allow-copy-file\");\n  permissions!(allowlist, permissions, fs, create_dir => \"fs:allow-mkdir\");\n  permissions!(allowlist, permissions, fs, remove_dir => \"fs:allow-remove\");\n  permissions!(allowlist, permissions, fs, remove_file => \"fs:allow-remove\");\n  permissions!(allowlist, permissions, fs, rename_file => \"fs:allow-rename\");\n  permissions!(allowlist, permissions, fs, exists => \"fs:allow-exists\");\n  let (fs_allowed, fs_denied) = match allowlist.fs.scope {\n    tauri_utils::config_v1::FsAllowlistScope::AllowedPaths(paths) => (paths, Vec::new()),\n    tauri_utils::config_v1::FsAllowlistScope::Scope { allow, deny, .. } => (allow, deny),\n  };\n  if !(fs_allowed.is_empty() && fs_denied.is_empty()) {\n    let fs_allowed = fs_allowed\n      .into_iter()\n      .map(|p| AclValue::String(p.to_string_lossy().into()))\n      .collect::<Vec<_>>();\n    let fs_denied = fs_denied\n      .into_iter()\n      .map(|p| AclValue::String(p.to_string_lossy().into()))\n      .collect::<Vec<_>>();\n    permissions.push(PermissionEntry::ExtendedPermission {\n      identifier: \"fs:scope\".to_string().try_into().unwrap(),\n      scope: Scopes {\n        allow: if fs_allowed.is_empty() {\n          None\n        } else {\n          Some(fs_allowed)\n        },\n        deny: if fs_denied.is_empty() {\n          None\n        } else {\n          Some(fs_denied)\n        },\n      },\n    });\n  }\n\n  // window\n  permissions!(allowlist, permissions, window, create => \"core:window:allow-create\");\n  permissions!(allowlist, permissions, window, center => \"core:window:allow-center\");\n  permissions!(allowlist, permissions, window, request_user_attention => \"core:window:allow-request-user-attention\");\n  permissions!(allowlist, permissions, window, set_resizable => \"core:window:allow-set-resizable\");\n  permissions!(allowlist, permissions, window, set_maximizable => \"core:window:allow-set-maximizable\");\n  permissions!(allowlist, permissions, window, set_minimizable => \"core:window:allow-set-minimizable\");\n  permissions!(allowlist, permissions, window, set_closable => \"core:window:allow-set-closable\");\n  permissions!(allowlist, permissions, window, set_title => \"core:window:allow-set-title\");\n  permissions!(allowlist, permissions, window, maximize => \"core:window:allow-maximize\");\n  permissions!(allowlist, permissions, window, unmaximize => \"core:window:allow-unmaximize\");\n  permissions!(allowlist, permissions, window, minimize => \"core:window:allow-minimize\");\n  permissions!(allowlist, permissions, window, unminimize => \"core:window:allow-unminimize\");\n  permissions!(allowlist, permissions, window, show => \"core:window:allow-show\");\n  permissions!(allowlist, permissions, window, hide => \"core:window:allow-hide\");\n  permissions!(allowlist, permissions, window, close => \"core:window:allow-close\");\n  permissions!(allowlist, permissions, window, set_decorations => \"core:window:allow-set-decorations\");\n  permissions!(allowlist, permissions, window, set_always_on_top => \"core:window:allow-set-always-on-top\");\n  permissions!(allowlist, permissions, window, set_content_protected => \"core:window:allow-set-content-protected\");\n  permissions!(allowlist, permissions, window, set_size => \"core:window:allow-set-size\");\n  permissions!(allowlist, permissions, window, set_min_size => \"core:window:allow-set-min-size\");\n  permissions!(allowlist, permissions, window, set_max_size => \"core:window:allow-set-max-size\");\n  permissions!(allowlist, permissions, window, set_position => \"core:window:allow-set-position\");\n  permissions!(allowlist, permissions, window, set_fullscreen => \"core:window:allow-set-fullscreen\");\n  permissions!(allowlist, permissions, window, set_focus => \"core:window:allow-set-focus\");\n  permissions!(allowlist, permissions, window, set_icon => \"core:window:allow-set-icon\");\n  permissions!(allowlist, permissions, window, set_skip_taskbar => \"core:window:allow-set-skip-taskbar\");\n  permissions!(allowlist, permissions, window, set_cursor_grab => \"core:window:allow-set-cursor-grab\");\n  permissions!(allowlist, permissions, window, set_cursor_visible => \"core:window:allow-set-cursor-visible\");\n  permissions!(allowlist, permissions, window, set_cursor_icon => \"core:window:allow-set-cursor-icon\");\n  permissions!(allowlist, permissions, window, set_cursor_position => \"core:window:allow-set-cursor-position\");\n  permissions!(allowlist, permissions, window, set_ignore_cursor_events => \"core:window:allow-set-ignore-cursor-events\");\n  permissions!(allowlist, permissions, window, start_dragging => \"core:window:allow-start-dragging\");\n  permissions!(allowlist, permissions, window, print => \"core:webview:allow-print\");\n\n  // shell\n  if allowlist.shell.scope.0.is_empty() {\n    let added = permissions!(allowlist, permissions, shell, execute => \"shell:allow-execute\");\n    // prevent duplicated permission\n    if !added {\n      permissions!(allowlist, permissions, shell, sidecar => \"shell:allow-execute\");\n    }\n  } else {\n    let allowed = allowlist\n      .shell\n      .scope\n      .0\n      .into_iter()\n      .map(|p| serde_json::to_value(p).unwrap().into())\n      .collect::<Vec<_>>();\n\n    permissions.push(PermissionEntry::ExtendedPermission {\n      identifier: \"shell:allow-execute\".to_string().try_into().unwrap(),\n      scope: Scopes {\n        allow: Some(allowed),\n        deny: None,\n      },\n    });\n  }\n\n  if allowlist.all\n    || allowlist.shell.all\n    || !matches!(\n      allowlist.shell.open,\n      tauri_utils::config_v1::ShellAllowlistOpen::Flag(false)\n    )\n  {\n    permissions.push(PermissionEntry::PermissionRef(\n      \"shell:allow-open\".to_string().try_into().unwrap(),\n    ));\n  }\n  // dialog\n  permissions!(allowlist, permissions, dialog, open => \"dialog:allow-open\");\n  permissions!(allowlist, permissions, dialog, save => \"dialog:allow-save\");\n  permissions!(allowlist, permissions, dialog, message => \"dialog:allow-message\");\n  permissions!(allowlist, permissions, dialog, ask => \"dialog:allow-ask\");\n  permissions!(allowlist, permissions, dialog, confirm => \"dialog:allow-confirm\");\n\n  // http\n  if allowlist.http.scope.0.is_empty() {\n    permissions!(allowlist, permissions, http, request => \"http:default\");\n  } else {\n    let allowed = allowlist\n      .http\n      .scope\n      .0\n      .into_iter()\n      .map(|p| {\n        let mut map = BTreeMap::new();\n        map.insert(\"url\".to_string(), AclValue::String(p.to_string()));\n        AclValue::Map(map)\n      })\n      .collect::<Vec<_>>();\n\n    permissions.push(PermissionEntry::ExtendedPermission {\n      identifier: \"http:default\".to_string().try_into().unwrap(),\n      scope: Scopes {\n        allow: Some(allowed),\n        deny: None,\n      },\n    });\n  }\n\n  // notification\n  permissions!(allowlist, permissions, notification, all => \"notification:default\");\n  // global-shortcut\n  permissions!(allowlist, permissions, global_shortcut, all => \"global-shortcut:allow-is-registered\");\n  permissions!(allowlist, permissions, global_shortcut, all => \"global-shortcut:allow-register\");\n  permissions!(allowlist, permissions, global_shortcut, all => \"global-shortcut:allow-register-all\");\n  permissions!(allowlist, permissions, global_shortcut, all => \"global-shortcut:allow-unregister\");\n  permissions!(allowlist, permissions, global_shortcut, all => \"global-shortcut:allow-unregister-all\");\n  // os\n  permissions!(allowlist, permissions, os, all => \"os:allow-platform\");\n  permissions!(allowlist, permissions, os, all => \"os:allow-version\");\n  permissions!(allowlist, permissions, os, all => \"os:allow-os-type\");\n  permissions!(allowlist, permissions, os, all => \"os:allow-family\");\n  permissions!(allowlist, permissions, os, all => \"os:allow-arch\");\n  permissions!(allowlist, permissions, os, all => \"os:allow-exe-extension\");\n  permissions!(allowlist, permissions, os, all => \"os:allow-locale\");\n  permissions!(allowlist, permissions, os, all => \"os:allow-hostname\");\n  // process\n  permissions!(allowlist, permissions, process, relaunch => \"process:allow-restart\");\n  permissions!(allowlist, permissions, process, exit => \"process:allow-exit\");\n  // clipboard\n  permissions!(allowlist, permissions, clipboard, read_text => \"clipboard-manager:allow-read-text\");\n  permissions!(allowlist, permissions, clipboard, write_text => \"clipboard-manager:allow-write-text\");\n  // app\n  permissions!(allowlist, permissions, app, show => \"core:app:allow-app-show\");\n  permissions!(allowlist, permissions, app, hide => \"core:app:allow-app-hide\");\n\n  permissions\n}\n\nfn process_cli(plugins: &mut Map<String, Value>, cli: Value) -> Result<()> {\n  if let Some(cli) = cli.as_object() {\n    plugins.insert(\n      \"cli\".into(),\n      serde_json::to_value(cli).context(\"failed to serialize CLI\")?,\n    );\n  }\n  Ok(())\n}\n\nfn process_updater(\n  tauri_config: &mut Map<String, Value>,\n  plugins: &mut Map<String, Value>,\n  migrated: &mut MigratedConfig,\n) -> Result<()> {\n  if let Some(mut updater) = tauri_config.remove(\"updater\") {\n    if let Some(updater) = updater.as_object_mut() {\n      updater.remove(\"dialog\");\n\n      // we only migrate the updater config if it's active\n      // since we now assume it's always active if the config object is set\n      // we also migrate if pubkey is set so we do not lose that information on the migration\n      // in this case, the user need to deal with the updater being inactive on their own\n      if updater\n        .remove(\"active\")\n        .and_then(|a| a.as_bool())\n        .unwrap_or_default()\n        || updater.get(\"pubkey\").is_some()\n      {\n        plugins.insert(\n          \"updater\".into(),\n          serde_json::to_value(updater).context(\"failed to serialize updater\")?,\n        );\n        migrated.plugins.insert(\"updater\".to_string());\n      }\n    }\n  }\n\n  Ok(())\n}\n\nconst KNOWN_PLUGINS: &[&str] = &[\n  \"fs\",\n  \"shell\",\n  \"dialog\",\n  \"http\",\n  \"notification\",\n  \"global-shortcut\",\n  \"os\",\n  \"process\",\n  \"clipboard-manager\",\n];\n\nfn plugins_from_permissions(permissions: &Vec<PermissionEntry>) -> HashSet<String> {\n  let mut plugins = HashSet::new();\n\n  for permission in permissions {\n    let permission = permission.identifier().get();\n    for plugin in KNOWN_PLUGINS {\n      if permission.starts_with(plugin) {\n        plugins.insert(plugin.to_string());\n        break;\n      }\n    }\n  }\n\n  plugins\n}\n\n#[cfg(test)]\nmod test {\n  fn migrate(original: &serde_json::Value) -> serde_json::Value {\n    let mut migrated = original.clone();\n    super::migrate_config(&mut migrated).expect(\"failed to migrate config\");\n\n    if original.get(\"$schema\").is_some() {\n      if let Some(map) = migrated.as_object_mut() {\n        map.insert(\n          \"$schema\".to_string(),\n          serde_json::Value::String(\"https://schema.tauri.app/config/2\".to_string()),\n        );\n      }\n    }\n\n    if original\n      .get(\"tauri\")\n      .and_then(|v| v.get(\"bundle\"))\n      .and_then(|v| v.get(\"identifier\"))\n      .is_none()\n    {\n      if let Some(map) = migrated.as_object_mut() {\n        map.insert(\n          \"identifier\".to_string(),\n          serde_json::Value::String(\"com.tauri.test-injected\".to_string()),\n        );\n      }\n    }\n\n    if let Err(e) = serde_json::from_value::<tauri_utils::config::Config>(migrated.clone()) {\n      panic!(\"migrated config is not valid: {e}\");\n    }\n\n    migrated\n  }\n\n  #[test]\n  fn migrate_full() {\n    let original = serde_json::json!({\n      \"$schema\": \"../node_modules/@tauri-apps/cli/schema.json\",\n      \"build\": {\n        \"distDir\": \"../dist\",\n        \"devPath\": \"http://localhost:1240\",\n        \"withGlobalTauri\": true\n      },\n      \"package\": {\n        \"productName\": \"Tauri app\",\n        \"version\": \"0.0.0\"\n      },\n      \"tauri\": {\n        \"bundle\": {\n          \"identifier\": \"com.tauri.test\",\n          \"deb\": {\n            \"depends\": [\"dep1\"]\n          },\n          \"appimage\": {\n            \"bundleMediaFramework\": true\n          },\n          \"macOS\": {\n            \"license\": \"license-file.txt\"\n          },\n          \"windows\": {\n            \"wix\": {\n              \"license\": \"license-file.txt\"\n            },\n            \"nsis\": {\n              \"license\": \"license-file.txt\"\n            },\n          },\n        },\n        \"cli\": {\n          \"description\": \"Tauri TEST\"\n        },\n        \"updater\": {\n          \"active\": true,\n          \"dialog\": false,\n          \"pubkey\": \"dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK\",\n          \"endpoints\": [\n            \"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}\"\n          ],\n          \"windows\": {\n            \"installerArgs\": [\"arg1\"],\n            \"installMode\": \"passive\"\n          }\n        },\n        \"allowlist\": {\n          \"all\": true,\n          \"fs\": {\n            \"scope\": {\n              \"allow\": [\"$APPDATA/db/**\", \"$DOWNLOAD/**\", \"$RESOURCE/**\"],\n              \"deny\": [\"$APPDATA/db/*.stronghold\"]\n            }\n          },\n          \"shell\": {\n            \"open\": true,\n            \"scope\": [\n              {\n                \"name\": \"sh\",\n                \"cmd\": \"sh\",\n                \"args\": [\"-c\", { \"validator\": \"\\\\S+\" }],\n                \"sidecar\": false\n              },\n              {\n                \"name\": \"cmd\",\n                \"cmd\": \"cmd\",\n                \"args\": [\"/C\", { \"validator\": \"\\\\S+\" }],\n                \"sidecar\": false\n              }\n            ]\n          },\n          \"protocol\": {\n            \"asset\": true,\n            \"assetScope\": {\n              \"allow\": [\"$APPDATA/db/**\", \"$RESOURCE/**\"],\n              \"deny\": [\"$APPDATA/db/*.stronghold\"]\n            }\n          },\n          \"http\": {\n            \"scope\": [\"http://localhost:3003/\"]\n          }\n        },\n        \"pattern\": { \"use\": \"brownfield\" },\n        \"security\": {\n          \"csp\": \"default-src 'self' tauri:\"\n        },\n        \"windows\": [{}]\n      }\n    });\n\n    let migrated = migrate(&original);\n\n    // $schema\n    assert_eq!(migrated[\"$schema\"], \"https://schema.tauri.app/config/2\");\n\n    // plugins > updater\n    assert_eq!(\n      migrated[\"plugins\"][\"updater\"][\"endpoints\"],\n      original[\"tauri\"][\"updater\"][\"endpoints\"]\n    );\n    assert_eq!(\n      migrated[\"plugins\"][\"updater\"][\"pubkey\"],\n      original[\"tauri\"][\"updater\"][\"pubkey\"]\n    );\n    assert_eq!(\n      migrated[\"plugins\"][\"updater\"][\"windows\"][\"installMode\"],\n      original[\"tauri\"][\"updater\"][\"windows\"][\"installMode\"]\n    );\n    assert_eq!(\n      migrated[\"plugins\"][\"updater\"][\"windows\"][\"installerArgs\"],\n      original[\"tauri\"][\"updater\"][\"windows\"][\"installerArgs\"]\n    );\n\n    // cli\n    assert_eq!(migrated[\"plugins\"][\"cli\"], original[\"tauri\"][\"cli\"]);\n\n    // asset scope\n    assert_eq!(\n      migrated[\"app\"][\"security\"][\"assetProtocol\"][\"enable\"],\n      original[\"tauri\"][\"allowlist\"][\"protocol\"][\"asset\"]\n    );\n    assert_eq!(\n      migrated[\"app\"][\"security\"][\"assetProtocol\"][\"scope\"][\"allow\"],\n      original[\"tauri\"][\"allowlist\"][\"protocol\"][\"assetScope\"][\"allow\"]\n    );\n    assert_eq!(\n      migrated[\"app\"][\"security\"][\"assetProtocol\"][\"scope\"][\"deny\"],\n      original[\"tauri\"][\"allowlist\"][\"protocol\"][\"assetScope\"][\"deny\"]\n    );\n\n    // security CSP\n    assert_eq!(\n      migrated[\"app\"][\"security\"][\"csp\"],\n      format!(\n        \"{}; connect-src ipc: http://ipc.localhost\",\n        original[\"tauri\"][\"security\"][\"csp\"].as_str().unwrap()\n      )\n    );\n\n    // security pattern\n    assert_eq!(\n      migrated[\"app\"][\"security\"][\"pattern\"],\n      original[\"tauri\"][\"pattern\"]\n    );\n\n    // license files\n    assert_eq!(\n      migrated[\"bundle\"][\"licenseFile\"],\n      original[\"tauri\"][\"bundle\"][\"macOS\"][\"license\"]\n    );\n    assert_eq!(\n      migrated[\"bundle\"][\"licenseFile\"],\n      original[\"tauri\"][\"bundle\"][\"windows\"][\"wix\"][\"license\"]\n    );\n    assert_eq!(\n      migrated[\"bundle\"][\"licenseFile\"],\n      original[\"tauri\"][\"bundle\"][\"windows\"][\"nsis\"][\"license\"]\n    );\n\n    // bundle appimage and deb\n    assert_eq!(\n      migrated[\"bundle\"][\"linux\"][\"deb\"],\n      original[\"tauri\"][\"bundle\"][\"deb\"]\n    );\n    assert_eq!(\n      migrated[\"bundle\"][\"linux\"][\"appimage\"],\n      original[\"tauri\"][\"bundle\"][\"appimage\"]\n    );\n\n    // app information\n    assert_eq!(migrated[\"productName\"], original[\"package\"][\"productName\"]);\n    assert_eq!(\n      migrated[\"mainBinaryName\"],\n      original[\"package\"][\"productName\"]\n    );\n    assert_eq!(migrated[\"version\"], original[\"package\"][\"version\"]);\n    assert_eq!(\n      migrated[\"identifier\"],\n      original[\"tauri\"][\"bundle\"][\"identifier\"]\n    );\n\n    // build object\n    assert_eq!(\n      migrated[\"build\"][\"frontendDist\"],\n      original[\"build\"][\"distDir\"]\n    );\n    assert_eq!(migrated[\"build\"][\"devUrl\"], original[\"build\"][\"devPath\"]);\n    assert_eq!(\n      migrated[\"app\"][\"withGlobalTauri\"],\n      original[\"build\"][\"withGlobalTauri\"]\n    );\n\n    assert_eq!(migrated[\"app\"][\"windows\"][0][\"useHttpsScheme\"], true);\n  }\n\n  #[test]\n  fn skips_migrating_updater() {\n    let original = serde_json::json!({\n      \"tauri\": {\n        \"updater\": {\n          \"active\": false\n        }\n      }\n    });\n\n    let migrated = migrate(&original);\n    assert_eq!(migrated[\"plugins\"][\"updater\"], serde_json::Value::Null);\n  }\n\n  #[test]\n  fn migrate_updater_pubkey() {\n    let original = serde_json::json!({\n      \"tauri\": {\n        \"updater\": {\n          \"active\": false,\n          \"pubkey\": \"somekey\"\n        }\n      }\n    });\n\n    let migrated = migrate(&original);\n    assert_eq!(\n      migrated[\"plugins\"][\"updater\"][\"pubkey\"],\n      original[\"tauri\"][\"updater\"][\"pubkey\"]\n    );\n  }\n\n  #[test]\n  fn migrate_dangerous_use_http_scheme() {\n    let original = serde_json::json!({\n      \"tauri\": {\n        \"windows\": [{}],\n        \"security\": {\n          \"dangerousUseHttpScheme\": true,\n        }\n      }\n    });\n\n    let migrated = migrate(&original);\n    assert_eq!(\n      !migrated[\"app\"][\"windows\"][0][\"useHttpsScheme\"]\n        .as_bool()\n        .unwrap(),\n      original[\"tauri\"][\"security\"][\"dangerousUseHttpScheme\"]\n        .as_bool()\n        .unwrap()\n    );\n  }\n\n  #[test]\n  fn can_migrate_default_config() {\n    let original = serde_json::to_value(tauri_utils::config_v1::Config::default()).unwrap();\n    migrate(&original);\n  }\n\n  #[test]\n  fn can_migrate_api_example_config() {\n    let original =\n      serde_json::from_str(include_str!(\"./fixtures/api-example.tauri.conf.json\")).unwrap();\n    migrate(&original);\n  }\n\n  #[test]\n  fn can_migrate_cli_template_config() {\n    let original =\n      serde_json::from_str(include_str!(\"./fixtures/cli-template.tauri.conf.json\")).unwrap();\n    migrate(&original);\n  }\n\n  #[test]\n  fn migrate_updater_target() {\n    let original = serde_json::json!({});\n\n    let migrated = migrate(&original);\n    assert_eq!(\n      migrated[\"bundle\"][\"createUpdaterArtifacts\"],\n      serde_json::Value::Null\n    );\n\n    let original = serde_json::json!({\n      \"tauri\": {\n        \"updater\": {\n          \"active\": true\n        }\n      }\n    });\n\n    let migrated = migrate(&original);\n    assert_eq!(migrated[\"bundle\"][\"createUpdaterArtifacts\"], \"v1Compatible\");\n\n    let original = serde_json::json!({\n      \"tauri\": {\n        \"bundle\": {\n          \"targets\": [\"nsis\", \"updater\"]\n        }\n      }\n    });\n\n    let migrated = migrate(&original);\n    assert_eq!(migrated[\"bundle\"][\"createUpdaterArtifacts\"], \"v1Compatible\");\n    assert_eq!(\n      migrated[\"bundle\"][\"targets\"].as_array(),\n      Some(&vec![\"nsis\".into()])\n    );\n\n    let original =\n      serde_json::from_str(include_str!(\"./fixtures/cli-template.tauri.conf.json\")).unwrap();\n    let migrated = migrate(&original);\n    assert_eq!(\n      migrated[\"bundle\"][\"createUpdaterArtifacts\"],\n      serde_json::Value::Null\n    );\n\n    let original = serde_json::json!({\n      \"tauri\": {\n        \"bundle\": {\n          \"targets\": \"all\"\n        }\n      }\n    });\n\n    let migrated = migrate(&original);\n    assert_eq!(\n      migrated[\"bundle\"][\"createUpdaterArtifacts\"],\n      serde_json::Value::Null\n    );\n    assert_eq!(migrated[\"bundle\"][\"targets\"], \"all\");\n\n    let original = serde_json::json!({\n      \"tauri\": {\n        \"bundle\": {\n          \"targets\": \"all\"\n        },\n        \"updater\": {\n          \"active\": true\n        }\n      }\n    });\n\n    let migrated = migrate(&original);\n    assert_eq!(migrated[\"bundle\"][\"createUpdaterArtifacts\"], \"v1Compatible\");\n    assert_eq!(migrated[\"bundle\"][\"targets\"], \"all\");\n\n    let original = serde_json::json!({\n      \"tauri\": {\n        \"bundle\": {\n          \"targets\": \"updater\"\n        }\n      }\n    });\n\n    let migrated = migrate(&original);\n    assert_eq!(migrated[\"bundle\"][\"createUpdaterArtifacts\"], \"v1Compatible\");\n    assert_eq!(migrated[\"bundle\"].get(\"targets\"), None);\n  }\n\n  #[test]\n  fn migrate_csp_object() {\n    let original = serde_json::json!({\n      \"tauri\": {\n        \"security\": {\n          \"csp\": {\n            \"default-src\": [\"self\", \"tauri:\"]\n          }\n        }\n      }\n    });\n\n    let migrated = migrate(&original);\n\n    assert_eq!(\n      migrated[\"app\"][\"security\"][\"csp\"][\"default-src\"],\n      original[\"tauri\"][\"security\"][\"csp\"][\"default-src\"]\n    );\n    assert!(migrated[\"app\"][\"security\"][\"csp\"][\"connect-src\"]\n      .as_array()\n      .expect(\"connect-src isn't an array\")\n      .contains(&\"ipc: http://ipc.localhost\".into()));\n  }\n\n  #[test]\n  fn migrate_csp_existing_connect_src_string() {\n    let original = serde_json::json!({\n      \"tauri\": {\n        \"security\": {\n          \"csp\": {\n            \"default-src\": [\"self\", \"tauri:\"],\n            \"connect-src\": \"self\"\n          }\n        }\n      }\n    });\n\n    let migrated = migrate(&original);\n\n    assert_eq!(\n      migrated[\"app\"][\"security\"][\"csp\"][\"default-src\"],\n      original[\"tauri\"][\"security\"][\"csp\"][\"default-src\"]\n    );\n    assert_eq!(\n      migrated[\"app\"][\"security\"][\"csp\"][\"connect-src\"]\n        .as_str()\n        .expect(\"connect-src isn't a string\"),\n      format!(\n        \"{} ipc: http://ipc.localhost\",\n        original[\"tauri\"][\"security\"][\"csp\"][\"connect-src\"]\n          .as_str()\n          .unwrap()\n      )\n    );\n  }\n\n  #[test]\n  fn migrate_csp_existing_connect_src_array() {\n    let original = serde_json::json!({\n      \"tauri\": {\n        \"security\": {\n          \"csp\": {\n            \"default-src\": [\"self\", \"tauri:\"],\n            \"connect-src\": [\"self\", \"asset:\"]\n          }\n        }\n      }\n    });\n\n    let migrated = migrate(&original);\n\n    assert_eq!(\n      migrated[\"app\"][\"security\"][\"csp\"][\"default-src\"],\n      original[\"tauri\"][\"security\"][\"csp\"][\"default-src\"]\n    );\n\n    let migrated_connect_src = migrated[\"app\"][\"security\"][\"csp\"][\"connect-src\"]\n      .as_array()\n      .expect(\"connect-src isn't an array\");\n    let original_connect_src = original[\"tauri\"][\"security\"][\"csp\"][\"connect-src\"]\n      .as_array()\n      .unwrap();\n    assert!(\n      migrated_connect_src\n        .iter()\n        .zip(original_connect_src.iter())\n        .all(|(a, b)| a == b),\n      \"connect-src migration failed\"\n    );\n  }\n\n  #[test]\n  fn migrate_invalid_url_dev_path() {\n    let original = serde_json::json!({\n      \"build\": {\n        \"devPath\": \"../src\",\n        \"distDir\": \"../src\"\n      }\n    });\n\n    let migrated = migrate(&original);\n\n    assert!(migrated[\"build\"].get(\"devUrl\").is_none());\n    assert_eq!(\n      migrated[\"build\"][\"distDir\"],\n      original[\"build\"][\"frontendDist\"]\n    );\n  }\n\n  #[test]\n  fn migrate_webview_fixed_runtime_path() {\n    let original = serde_json::json!({\n      \"tauri\": {\n        \"bundle\": {\n          \"windows\": {\n            \"webviewFixedRuntimePath\": \"./path/to/runtime\"\n          }\n        }\n      }\n    });\n\n    let migrated = migrate(&original);\n\n    assert_eq!(\n      migrated[\"bundle\"][\"windows\"][\"webviewInstallMode\"][\"type\"],\n      \"fixedRuntime\"\n    );\n\n    assert_eq!(\n      migrated[\"bundle\"][\"windows\"][\"webviewInstallMode\"][\"path\"],\n      original[\"tauri\"][\"bundle\"][\"windows\"][\"webviewFixedRuntimePath\"]\n    );\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/migrate/migrations/v1/fixtures/api-example.tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../../../../../tauri-schema-generator/schemas/config.schema.json\",\n  \"build\": {\n    \"distDir\": \"../dist\",\n    \"devPath\": \"http://localhost:5173\",\n    \"beforeDevCommand\": \"pnpm dev\",\n    \"beforeBuildCommand\": \"pnpm build\"\n  },\n  \"package\": {\n    \"productName\": \"Tauri API\",\n    \"version\": \"1.0.0\"\n  },\n  \"tauri\": {\n    \"pattern\": {\n      \"use\": \"isolation\",\n      \"options\": {\n        \"dir\": \"../isolation-dist/\"\n      }\n    },\n    \"macOSPrivateApi\": true,\n    \"cli\": {\n      \"description\": \"Tauri API example\",\n      \"args\": [\n        {\n          \"short\": \"c\",\n          \"name\": \"config\",\n          \"takesValue\": true,\n          \"description\": \"Config path\"\n        },\n        {\n          \"short\": \"t\",\n          \"name\": \"theme\",\n          \"takesValue\": true,\n          \"description\": \"App theme\",\n          \"possibleValues\": [\"light\", \"dark\", \"system\"]\n        },\n        {\n          \"short\": \"v\",\n          \"name\": \"verbose\",\n          \"multipleOccurrences\": true,\n          \"description\": \"Verbosity level\"\n        }\n      ],\n      \"subcommands\": {\n        \"update\": {\n          \"description\": \"Updates the app\",\n          \"args\": [\n            {\n              \"short\": \"b\",\n              \"name\": \"background\",\n              \"description\": \"Update in background\"\n            }\n          ]\n        }\n      }\n    },\n    \"bundle\": {\n      \"active\": true,\n      \"identifier\": \"com.tauri.api\",\n      \"icon\": [\n        \"../../.icons/32x32.png\",\n        \"../../.icons/128x128.png\",\n        \"../../.icons/128x128@2x.png\",\n        \"../../.icons/icon.icns\",\n        \"../../.icons/icon.ico\"\n      ],\n      \"windows\": {\n        \"wix\": {\n          \"language\": {\n            \"en-US\": {},\n            \"pt-BR\": {\n              \"localePath\": \"locales/pt-BR.wxl\"\n            }\n          }\n        }\n      }\n    },\n    \"updater\": {\n      \"active\": true,\n      \"dialog\": false,\n      \"pubkey\": \"dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK\",\n      \"endpoints\": [\n        \"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}\"\n      ]\n    },\n    \"allowlist\": {\n      \"all\": true,\n      \"fs\": {\n        \"scope\": {\n          \"allow\": [\"$APPDATA/db/**\", \"$DOWNLOAD/**\", \"$RESOURCE/**\"],\n          \"deny\": [\"$APPDATA/db/*.stronghold\"]\n        }\n      },\n      \"shell\": {\n        \"open\": true,\n        \"scope\": [\n          {\n            \"name\": \"sh\",\n            \"cmd\": \"sh\",\n            \"args\": [\"-c\", { \"validator\": \"\\\\S+\" }]\n          },\n          {\n            \"name\": \"cmd\",\n            \"cmd\": \"cmd\",\n            \"args\": [\"/C\", { \"validator\": \"\\\\S+\" }]\n          }\n        ]\n      },\n      \"protocol\": {\n        \"asset\": true,\n        \"assetScope\": {\n          \"allow\": [\"$APPDATA/db/**\", \"$RESOURCE/**\"],\n          \"deny\": [\"$APPDATA/db/*.stronghold\"]\n        }\n      },\n      \"http\": {\n        \"scope\": [\"http://localhost:3003\"]\n      }\n    },\n    \"windows\": [],\n    \"security\": {\n      \"csp\": {\n        \"default-src\": \"'self' customprotocol: asset:\",\n        \"font-src\": [\"https://fonts.gstatic.com\"],\n        \"img-src\": \"'self' asset: https://asset.localhost blob: data:\",\n        \"style-src\": \"'unsafe-inline' 'self' https://fonts.googleapis.com\"\n      },\n      \"freezePrototype\": true\n    },\n    \"systemTray\": {\n      \"iconPath\": \"../../.icons/tray_icon_with_transparency.png\",\n      \"iconAsTemplate\": true,\n      \"menuOnLeftClick\": false\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/migrate/migrations/v1/fixtures/cli-template.tauri.conf.json",
    "content": "{\n  \"package\": {\n    \"productName\": \"{{ app_name }}\",\n    \"version\": \"0.1.0\"\n  },\n  \"build\": {\n    \"distDir\": \"{{ dist_dir }}\",\n    \"devPath\": \"{{ dev_path }}\",\n    \"beforeDevCommand\": \"{{ before_dev_command }}\",\n    \"beforeBuildCommand\": \"{{ before_build_command }}\"\n  },\n  \"tauri\": {\n    \"bundle\": {\n      \"active\": true,\n      \"targets\": \"all\",\n      \"identifier\": \"com.tauri.dev\",\n      \"icon\": [\n        \"icons/32x32.png\",\n        \"icons/128x128.png\",\n        \"icons/128x128@2x.png\",\n        \"icons/icon.icns\",\n        \"icons/icon.ico\"\n      ],\n      \"resources\": [],\n      \"externalBin\": [],\n      \"copyright\": \"\",\n      \"category\": \"DeveloperTool\",\n      \"shortDescription\": \"\",\n      \"longDescription\": \"\",\n      \"deb\": {\n        \"depends\": []\n      },\n      \"macOS\": {\n        \"frameworks\": [],\n        \"exceptionDomain\": \"\",\n        \"signingIdentity\": null,\n        \"providerShortName\": null,\n        \"entitlements\": null\n      },\n      \"windows\": {\n        \"certificateThumbprint\": null,\n        \"digestAlgorithm\": \"sha256\",\n        \"timestampUrl\": \"\"\n      }\n    },\n    \"updater\": {\n      \"active\": false\n    },\n    \"allowlist\": {\n      \"all\": false\n    },\n    \"windows\": [\n      {\n        \"title\": \"{{ window_title }}\",\n        \"width\": 800,\n        \"height\": 600,\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ],\n    \"security\": {\n      \"csp\": null\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/migrate/migrations/v1/frontend/partial_loader/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// taken from https://github.com/oxc-project/oxc/blob/main/crates/oxc_linter/src/partial_loader/mod.rs\n\nmod svelte;\nmod vue;\n\nuse oxc_span::SourceType;\n\npub use self::{svelte::SveltePartialLoader, vue::VuePartialLoader};\n\nconst SCRIPT_START: &str = \"<script\";\nconst SCRIPT_END: &str = \"</script>\";\n\n#[derive(Debug, Clone, Copy)]\npub struct JavaScriptSource<'a> {\n  pub source_text: &'a str,\n  pub source_type: SourceType,\n  /// The javascript source could be embedded in some file,\n  /// use `start` to record start offset of js block in the original file.\n  pub start: usize,\n}\n\nimpl<'a> JavaScriptSource<'a> {\n  pub fn new(source_text: &'a str, source_type: SourceType, start: usize) -> Self {\n    Self {\n      source_text,\n      source_type,\n      start,\n    }\n  }\n}\n\npub struct PartialLoader;\n\nimpl PartialLoader {\n  /// Extract js section of specifial files.\n  /// Returns `None` if the specifial file does not have a js section.\n  pub fn parse<'a>(ext: &str, source_text: &'a str) -> Option<Vec<JavaScriptSource<'a>>> {\n    match ext {\n      \"vue\" => Some(VuePartialLoader::new(source_text).parse()),\n      \"svelte\" => Some(SveltePartialLoader::new(source_text).parse()),\n      _ => None,\n    }\n  }\n}\n\n/// Find closing angle for situations where there is another `>` in between.\n/// e.g. `<script generic=\"T extends Record<string, string>\">`\nfn find_script_closing_angle(source_text: &str, pointer: usize) -> Option<usize> {\n  let mut numbers_of_open_angle = 0;\n  for (offset, c) in source_text[pointer..].char_indices() {\n    match c {\n      '>' => {\n        if numbers_of_open_angle == 0 {\n          return Some(offset);\n        }\n        numbers_of_open_angle -= 1;\n      }\n      '<' => {\n        numbers_of_open_angle += 1;\n      }\n      _ => {}\n    }\n  }\n  None\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/migrate/migrations/v1/frontend/partial_loader/svelte.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// taken from https://github.com/oxc-project/oxc/blob/main/crates/oxc_linter/src/partial_loader/svelte.rs\n\nuse memchr::memmem::Finder;\nuse oxc_span::SourceType;\n\nuse super::{find_script_closing_angle, JavaScriptSource, SCRIPT_END, SCRIPT_START};\n\npub struct SveltePartialLoader<'a> {\n  source_text: &'a str,\n}\n\nimpl<'a> SveltePartialLoader<'a> {\n  pub fn new(source_text: &'a str) -> Self {\n    Self { source_text }\n  }\n\n  pub fn parse(self) -> Vec<JavaScriptSource<'a>> {\n    self\n      .parse_script()\n      .map_or_else(Vec::new, |source| vec![source])\n  }\n\n  fn parse_script(&self) -> Option<JavaScriptSource<'a>> {\n    let script_start_finder = Finder::new(SCRIPT_START);\n    let script_end_finder = Finder::new(SCRIPT_END);\n\n    let mut pointer = 0;\n\n    // find opening \"<script\"\n    let offset = script_start_finder.find(&self.source_text.as_bytes()[pointer..])?;\n    pointer += offset + SCRIPT_START.len();\n\n    // find closing \">\"\n    let offset = find_script_closing_angle(self.source_text, pointer)?;\n\n    // get lang=\"ts\" attribute\n    let content = &self.source_text[pointer..pointer + offset];\n    let is_ts = content.contains(\"ts\");\n\n    pointer += offset + 1;\n    let js_start = pointer;\n\n    // find \"</script>\"\n    let offset = script_end_finder.find(&self.source_text.as_bytes()[pointer..])?;\n    let js_end = pointer + offset;\n\n    let source_text = &self.source_text[js_start..js_end];\n    let source_type = SourceType::default()\n      .with_module(true)\n      .with_typescript(is_ts);\n    Some(JavaScriptSource::new(source_text, source_type, js_start))\n  }\n}\n\n#[cfg(test)]\nmod test {\n  use super::{JavaScriptSource, SveltePartialLoader};\n\n  fn parse_svelte(source_text: &str) -> JavaScriptSource<'_> {\n    let sources = SveltePartialLoader::new(source_text).parse();\n    *sources.first().unwrap()\n  }\n\n  #[test]\n  fn test_parse_svelte() {\n    let source_text = r#\"\n        <script>\n          console.log(\"hi\");\n        </script>\n        <h1>Hello World</h1>\n        \"#;\n\n    let result = parse_svelte(source_text);\n    assert_eq!(result.source_text.trim(), r#\"console.log(\"hi\");\"#);\n  }\n\n  #[test]\n  fn test_parse_svelte_ts_with_generic() {\n    let source_text = r#\"\n        <script lang=\"ts\" generics=\"T extends Record<string, unknown>\">\n          console.log(\"hi\");\n        </script>\n        <h1>Hello World</h1>\n        \"#;\n\n    let result = parse_svelte(source_text);\n    assert_eq!(result.source_text.trim(), r#\"console.log(\"hi\");\"#);\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/migrate/migrations/v1/frontend/partial_loader/vue.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// taken from https://github.com/oxc-project/oxc/blob/main/crates/oxc_linter/src/partial_loader/vue.rs\n\nuse memchr::memmem::Finder;\nuse oxc_span::SourceType;\n\nuse super::{find_script_closing_angle, JavaScriptSource, SCRIPT_END, SCRIPT_START};\n\npub struct VuePartialLoader<'a> {\n  source_text: &'a str,\n}\n\nimpl<'a> VuePartialLoader<'a> {\n  pub fn new(source_text: &'a str) -> Self {\n    Self { source_text }\n  }\n\n  pub fn parse(self) -> Vec<JavaScriptSource<'a>> {\n    self.parse_scripts()\n  }\n\n  /// Each *.vue file can contain at most\n  ///  * one `<script>` block (excluding `<script setup>`).\n  ///  * one `<script setup>` block (excluding normal `<script>`).\n  ///    <https://vuejs.org/api/sfc-spec.html#script>\n  fn parse_scripts(&self) -> Vec<JavaScriptSource<'a>> {\n    let mut pointer = 0;\n    let Some(result1) = self.parse_script(&mut pointer) else {\n      return vec![];\n    };\n    let Some(result2) = self.parse_script(&mut pointer) else {\n      return vec![result1];\n    };\n    vec![result1, result2]\n  }\n\n  fn parse_script(&self, pointer: &mut usize) -> Option<JavaScriptSource<'a>> {\n    let script_start_finder = Finder::new(SCRIPT_START);\n    let script_end_finder = Finder::new(SCRIPT_END);\n\n    // find opening \"<script\"\n    let offset = script_start_finder.find(&self.source_text.as_bytes()[*pointer..])?;\n    *pointer += offset + SCRIPT_START.len();\n\n    // find closing \">\"\n    let offset = find_script_closing_angle(self.source_text, *pointer)?;\n\n    // get ts and jsx attribute\n    let content = &self.source_text[*pointer..*pointer + offset];\n    let is_ts = content.contains(\"ts\");\n    let is_jsx = content.contains(\"tsx\") || content.contains(\"jsx\");\n\n    *pointer += offset + 1;\n    let js_start = *pointer;\n\n    // find \"</script>\"\n    let offset = script_end_finder.find(&self.source_text.as_bytes()[*pointer..])?;\n    let js_end = *pointer + offset;\n    *pointer += offset + SCRIPT_END.len();\n\n    let source_text = &self.source_text[js_start..js_end];\n    let source_type = SourceType::default()\n      .with_module(true)\n      .with_typescript(is_ts)\n      .with_jsx(is_jsx);\n    Some(JavaScriptSource::new(source_text, source_type, js_start))\n  }\n}\n\n#[cfg(test)]\nmod test {\n  use super::{JavaScriptSource, VuePartialLoader};\n\n  fn parse_vue(source_text: &str) -> JavaScriptSource<'_> {\n    let sources = VuePartialLoader::new(source_text).parse();\n    *sources.first().unwrap()\n  }\n\n  #[test]\n  fn test_parse_vue_one_line() {\n    let source_text = r#\"\n        <template>\n          <h1>hello world</h1>\n        </template>\n        <script> console.log(\"hi\") </script>\n        \"#;\n\n    let result = parse_vue(source_text);\n    assert_eq!(result.source_text, r#\" console.log(\"hi\") \"#);\n  }\n\n  #[test]\n  fn test_build_vue_with_ts_flag_1() {\n    let source_text = r#\"\n        <script lang=\"ts\" setup generic=\"T extends Record<string, string>\">\n            1/1\n        </script>\n        \"#;\n\n    let result = parse_vue(source_text);\n    assert!(result.source_type.is_typescript());\n    assert_eq!(result.source_text.trim(), \"1/1\");\n  }\n\n  #[test]\n  fn test_build_vue_with_ts_flag_2() {\n    let source_text = r\"\n        <script lang=ts setup>\n            1/1\n        </script>\n        \";\n\n    let result = parse_vue(source_text);\n    assert!(result.source_type.is_typescript());\n    assert_eq!(result.source_text.trim(), \"1/1\");\n  }\n\n  #[test]\n  fn test_build_vue_with_ts_flag_3() {\n    let source_text = r\"\n        <script lang='ts' setup>\n            1/1\n        </script>\n        \";\n\n    let result = parse_vue(source_text);\n    assert!(result.source_type.is_typescript());\n    assert_eq!(result.source_text.trim(), \"1/1\");\n  }\n\n  #[test]\n  fn test_build_vue_with_tsx_flag() {\n    let source_text = r\"\n        <script lang=tsx setup>\n            1/1\n        </script>\n        \";\n\n    let result = parse_vue(source_text);\n    assert!(result.source_type.is_jsx());\n    assert!(result.source_type.is_typescript());\n    assert_eq!(result.source_text.trim(), \"1/1\");\n  }\n\n  #[test]\n  fn test_build_vue_with_escape_string() {\n    let source_text = r\"\n        <script setup>\n            a.replace(/&#39;/g, '\\''))\n        </script>\n        <template> </template>\n        \";\n\n    let result = parse_vue(source_text);\n    assert!(!result.source_type.is_typescript());\n    assert_eq!(result.source_text.trim(), r\"a.replace(/&#39;/g, '\\''))\");\n  }\n\n  #[test]\n  fn test_multi_level_template_literal() {\n    let source_text = r\"\n        <script setup>\n            `a${b( `c \\`${d}\\``)}`\n        </script>\n        \";\n\n    let result = parse_vue(source_text);\n    assert_eq!(result.source_text.trim(), r\"`a${b( `c \\`${d}\\``)}`\");\n  }\n\n  #[test]\n  fn test_brace_with_regex_in_template_literal() {\n    let source_text = r\"\n        <script setup>\n            `${/{/}`\n        </script>\n        \";\n\n    let result = parse_vue(source_text);\n    assert_eq!(result.source_text.trim(), r\"`${/{/}`\");\n  }\n\n  #[test]\n  fn test_no_script() {\n    let source_text = r\"\n            <template></template>\n        \";\n\n    let sources = VuePartialLoader::new(source_text).parse();\n    assert!(sources.is_empty());\n  }\n\n  #[test]\n  fn test_syntax_error() {\n    let source_text = r\"\n        <script>\n            console.log('error')\n        \";\n    let sources = VuePartialLoader::new(source_text).parse();\n    assert!(sources.is_empty());\n  }\n\n  #[test]\n  fn test_multiple_scripts() {\n    let source_text = r\"\n        <template></template>\n        <script>a</script>\n        <script setup>b</script>\n        \";\n    let sources = VuePartialLoader::new(source_text).parse();\n    assert_eq!(sources.len(), 2);\n    assert_eq!(sources[0].source_text, \"a\");\n    assert_eq!(sources[1].source_text, \"b\");\n  }\n\n  #[test]\n  fn test_unicode() {\n    let source_text = r\"\n        <script setup>\n        let 日历 = '2000年';\n        const t = useTranslate({\n            'zh-CN': {\n                calendar: '日历',\n                tiledDisplay: '平铺展示',\n            },\n        });\n        </script>\n        \";\n\n    let result = parse_vue(source_text);\n    assert_eq!(\n      result.source_text.trim(),\n      \"let 日历 = '2000年';\n        const t = useTranslate({\n            'zh-CN': {\n                calendar: '日历',\n                tiledDisplay: '平铺展示',\n            },\n        });\"\n        .trim()\n    );\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/migrate/migrations/v1/frontend.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::Context,\n  helpers::{app_paths::walk_builder, npm::PackageManager},\n  Error, ErrorExt, Result,\n};\nuse itertools::Itertools;\nuse magic_string::MagicString;\nuse oxc_allocator::Allocator;\nuse oxc_ast::ast::*;\nuse oxc_parser::Parser;\nuse oxc_span::SourceType;\n\nuse std::{fs, path::Path};\n\nmod partial_loader;\n\nconst RENAMED_MODULES: phf::Map<&str, &str> = phf::phf_map! {\n  \"tauri\" => \"core\",\n  \"window\" => \"webviewWindow\"\n};\nconst PLUGINIFIED_MODULES: [&str; 11] = [\n  \"cli\",\n  \"clipboard\",\n  \"dialog\",\n  \"fs\",\n  \"globalShortcut\",\n  \"http\",\n  \"notification\",\n  \"os\",\n  \"process\",\n  \"shell\",\n  \"updater\",\n];\n// (from, to)\nconst MODULES_MAP: phf::Map<&str, &str> = phf::phf_map! {\n  // renamed\n  \"@tauri-apps/api/tauri\" => \"@tauri-apps/api/core\",\n  \"@tauri-apps/api/window\" => \"@tauri-apps/api/webviewWindow\",\n  // pluginified\n  \"@tauri-apps/api/cli\" => \"@tauri-apps/plugin-cli\",\n  \"@tauri-apps/api/clipboard\" => \"@tauri-apps/plugin-clipboard-manager\",\n  \"@tauri-apps/api/dialog\" => \"@tauri-apps/plugin-dialog\",\n  \"@tauri-apps/api/fs\" => \"@tauri-apps/plugin-fs\",\n  \"@tauri-apps/api/globalShortcut\" => \"@tauri-apps/plugin-global-shortcut\",\n  \"@tauri-apps/api/http\" => \"@tauri-apps/plugin-http\",\n  \"@tauri-apps/api/notification\" => \"@tauri-apps/plugin-notification\",\n  \"@tauri-apps/api/os\" => \"@tauri-apps/plugin-os\",\n  \"@tauri-apps/api/process\" => \"@tauri-apps/plugin-process\",\n  \"@tauri-apps/api/shell\" => \"@tauri-apps/plugin-shell\",\n  \"@tauri-apps/api/updater\" => \"@tauri-apps/plugin-updater\",\n  // v1 plugins to v2\n  \"tauri-plugin-sql-api\" => \"@tauri-apps/plugin-sql\",\n  \"tauri-plugin-store-api\" => \"@tauri-apps/plugin-store\",\n  \"tauri-plugin-upload-api\" => \"@tauri-apps/plugin-upload\",\n  \"tauri-plugin-fs-extra-api\" => \"@tauri-apps/plugin-fs\",\n  \"tauri-plugin-fs-watch-api\" => \"@tauri-apps/plugin-fs\",\n  \"tauri-plugin-autostart-api\" => \"@tauri-apps/plugin-autostart\",\n  \"tauri-plugin-websocket-api\" => \"@tauri-apps/plugin-websocket\",\n  \"tauri-plugin-positioner-api\" => \"@tauri-apps/plugin-positioner\",\n  \"tauri-plugin-stronghold-api\" => \"@tauri-apps/plugin-stronghold\",\n  \"tauri-plugin-window-state-api\" => \"@tauri-apps/plugin-window-state\",\n  \"tauri-plugin-authenticator-api\" => \"@tauri-apps/plugin-authenticator\",\n};\nconst JS_EXTENSIONS: &[&str] = &[\"js\", \"mjs\", \"jsx\", \"ts\", \"mts\", \"tsx\", \"svelte\", \"vue\"];\n\n/// Returns a list of migrated plugins\npub fn migrate(frontend_dir: &Path) -> Result<Vec<String>> {\n  let mut new_npm_packages = Vec::new();\n  let mut new_plugins = Vec::new();\n  let mut npm_packages_to_remove = Vec::new();\n\n  let pre = env!(\"CARGO_PKG_VERSION_PRE\");\n  let npm_version = if pre.is_empty() {\n    format!(\"{}.0.0\", env!(\"CARGO_PKG_VERSION_MAJOR\"))\n  } else {\n    format!(\n      \"{}.0.0-{}.0\",\n      env!(\"CARGO_PKG_VERSION_MAJOR\"),\n      pre.split('.').next().unwrap()\n    )\n  };\n\n  let pm = PackageManager::from_project(frontend_dir);\n\n  for pkg in [\"@tauri-apps/cli\", \"@tauri-apps/api\"] {\n    let version = pm\n      .current_package_version(pkg, frontend_dir)\n      .unwrap_or_default()\n      .unwrap_or_default();\n    if version.starts_with('1') {\n      new_npm_packages.push(format!(\"{pkg}@^{npm_version}\"));\n    }\n  }\n\n  for entry in walk_builder(frontend_dir).build().flatten() {\n    if entry.file_type().map(|t| t.is_file()).unwrap_or_default() {\n      let path = entry.path();\n      let ext = path.extension().unwrap_or_default();\n      if JS_EXTENSIONS.iter().any(|e| e == &ext) {\n        let js_contents =\n          std::fs::read_to_string(path).fs_context(\"failed to read JS file\", path.to_path_buf())?;\n        let new_contents = migrate_imports(\n          path,\n          &js_contents,\n          &mut new_plugins,\n          &mut npm_packages_to_remove,\n        )?;\n        if new_contents != js_contents {\n          fs::write(path, new_contents)\n            .fs_context(\"failed to write JS file\", path.to_path_buf())?;\n        }\n      }\n    }\n  }\n\n  if !npm_packages_to_remove.is_empty() {\n    npm_packages_to_remove.sort();\n    npm_packages_to_remove.dedup();\n    pm.remove(&npm_packages_to_remove, frontend_dir)\n      .context(\"Error removing npm packages\")?;\n  }\n\n  if !new_npm_packages.is_empty() {\n    new_npm_packages.sort();\n    new_npm_packages.dedup();\n    pm.install(&new_npm_packages, frontend_dir)\n      .context(\"Error installing new npm packages\")?;\n  }\n\n  Ok(new_plugins)\n}\n\nfn migrate_imports<'a>(\n  path: &'a Path,\n  js_source: &'a str,\n  new_plugins: &mut Vec<String>,\n  npm_packages_to_remove: &mut Vec<String>,\n) -> crate::Result<String> {\n  let mut magic_js_source = MagicString::new(js_source);\n\n  let has_partial_js = path\n    .extension()\n    .is_some_and(|ext| ext == \"vue\" || ext == \"svelte\");\n\n  let sources = if !has_partial_js {\n    vec![(SourceType::from_path(path).unwrap(), js_source, 0i64)]\n  } else {\n    partial_loader::PartialLoader::parse(\n      path\n        .extension()\n        .unwrap_or_default()\n        .to_str()\n        .unwrap_or_default(),\n      js_source,\n    )\n    .unwrap()\n    .into_iter()\n    .map(|s| (s.source_type, s.source_text, s.start as i64))\n    .collect()\n  };\n\n  for (source_type, js_source, script_start) in sources {\n    let allocator = Allocator::default();\n    let ret = Parser::new(&allocator, js_source, source_type).parse();\n    if !ret.errors.is_empty() {\n      crate::error::bail!(\n        \"failed to parse {} as valid Javascript/Typescript file\",\n        path.display()\n      )\n    }\n\n    let mut program = ret.program;\n\n    let mut stmts_to_add = Vec::new();\n    let mut imports_to_add = Vec::new();\n\n    for import in program.body.iter_mut() {\n      if let Statement::ImportDeclaration(stmt) = import {\n        let module = stmt.source.value.as_str();\n\n        // convert module to its pluginfied module or renamed one\n        // import { ... } from \"@tauri-apps/api/window\" -> import { ... } from \"@tauri-apps/api/webviewWindow\"\n        // import { ... } from \"@tauri-apps/api/cli\" -> import { ... } from \"@tauri-apps/plugin-cli\"\n        if let Some(&new_module) = MODULES_MAP.get(module) {\n          // +1 and -1, to skip modifying the import quotes\n          magic_js_source\n            .overwrite(\n              script_start + stmt.source.span.start as i64 + 1,\n              script_start + stmt.source.span.end as i64 - 1,\n              new_module,\n              Default::default(),\n            )\n            .map_err(|e| {\n              Error::Context(\n                \"failed to replace import source\".to_string(),\n                e.to_string().into(),\n              )\n            })?;\n\n          // if module was pluginified, add to packages\n          if let Some(plugin_name) = new_module.strip_prefix(\"@tauri-apps/plugin-\") {\n            new_plugins.push(plugin_name.to_string());\n          }\n\n          // if the module is a v1 plugin, we should remove it\n          if module.starts_with(\"tauri-plugin-\") {\n            npm_packages_to_remove.push(module.to_string());\n          }\n        }\n\n        // skip parsing non @tauri-apps/api imports\n        if !module.starts_with(\"@tauri-apps/api\") {\n          continue;\n        }\n\n        let Some(specifiers) = &mut stmt.specifiers else {\n          continue;\n        };\n\n        for specifier in specifiers.iter() {\n          if let ImportDeclarationSpecifier::ImportSpecifier(specifier) = specifier {\n            let new_identifier = match specifier.imported.name().as_str() {\n              // migrate appWindow from:\n              // ```\n              // import { appWindow } from \"@tauri-apps/api/window\"\n              // ```\n              // to:\n              // ```\n              // import { getCurrentWebviewWindow } from \"@tauri-apps/api/webviewWindow\"\n              // const appWindow = getCurrentWebviewWindow()\n              // ```\n              \"appWindow\" if module == \"@tauri-apps/api/window\" => {\n                stmts_to_add.push(\"\\nconst appWindow = getCurrentWebviewWindow()\");\n                Some(\"getCurrentWebviewWindow\")\n              }\n\n              // migrate pluginified modules from:\n              // ```\n              // import { dialog, cli as superCli } from \"@tauri-apps/api\"\n              // ```\n              // to:\n              // ```\n              // import * as dialog from \"@tauri-apps/plugin-dialog\"\n              // import * as cli as superCli from \"@tauri-apps/plugin-cli\"\n              // ```\n              import if PLUGINIFIED_MODULES.contains(&import) && module == \"@tauri-apps/api\" => {\n                let js_plugin: &str = MODULES_MAP[&format!(\"@tauri-apps/api/{import}\")];\n                let (_, plugin_name) = js_plugin.split_once(\"plugin-\").unwrap();\n\n                new_plugins.push(plugin_name.to_string());\n\n                if specifier.local.name.as_str() != import {\n                  let local = &specifier.local.name;\n                  imports_to_add.push(format!(\n                    \"\\nimport * as {import} as {local} from \\\"{js_plugin}\\\"\"\n                  ));\n                } else {\n                  imports_to_add.push(format!(\"\\nimport * as {import} from \\\"{js_plugin}\\\"\"));\n                };\n                None\n              }\n\n              import if module == \"@tauri-apps/api\" => match RENAMED_MODULES.get(import) {\n                Some(m) => Some(*m),\n                None => continue,\n              },\n\n              // nothing to do, go to next specifier\n              _ => continue,\n            };\n\n            // if identifier was renamed, it will be Some()\n            // and so we convert the import\n            // import { appWindow } from \"@tauri-apps/api/window\" -> import { getCurrentWebviewWindow } from \"@tauri-apps/api/webviewWindow\"\n            if let Some(new_identifier) = new_identifier {\n              magic_js_source\n                .overwrite(\n                  script_start + specifier.span.start as i64,\n                  script_start + specifier.span.end as i64,\n                  new_identifier,\n                  Default::default(),\n                )\n                .map_err(|e| {\n                  Error::Context(\n                    \"failed to rename identifier\".to_string(),\n                    e.to_string().into(),\n                  )\n                })?;\n            } else {\n              // if None, we need to remove this specifier,\n              // it will also be replaced with an import from its new plugin below\n\n              // find the next comma or the bracket ending the import\n              let start = specifier.span.start as usize;\n              let sliced = &js_source[start..];\n              let comma_or_bracket = sliced.chars().find_position(|&c| c == ',' || c == '}');\n              let end = match comma_or_bracket {\n                Some((n, ',')) => n + start + 1,\n                Some((_, '}')) => specifier.span.end as _,\n                _ => continue,\n              };\n\n              magic_js_source\n                .remove(script_start + start as i64, script_start + end as i64)\n                .map_err(|e| {\n                  Error::Context(\n                    \"failed to remove identifier\".to_string(),\n                    e.to_string().into(),\n                  )\n                })?;\n            }\n          }\n        }\n      }\n    }\n\n    // find the end of import list\n    // fallback to the program start\n    let start = program\n      .body\n      .iter()\n      .rev()\n      .find(|s| matches!(s, Statement::ImportDeclaration(_)))\n      .map(|s| match s {\n        Statement::ImportDeclaration(s) => s.span.end,\n        _ => unreachable!(),\n      })\n      .unwrap_or(program.span.start);\n\n    if !imports_to_add.is_empty() {\n      for import in imports_to_add {\n        magic_js_source\n          .append_right(script_start as u32 + start, &import)\n          .map_err(|e| Error::Context(\"failed to add import\".to_string(), e.to_string().into()))?;\n      }\n    }\n\n    if !stmts_to_add.is_empty() {\n      for stmt in stmts_to_add {\n        magic_js_source\n          .append_right(script_start as u32 + start, stmt)\n          .map_err(|e| {\n            Error::Context(\"failed to add statement\".to_string(), e.to_string().into())\n          })?;\n      }\n    }\n  }\n\n  Ok(magic_js_source.to_string())\n}\n\n#[cfg(test)]\nmod tests {\n\n  use super::*;\n  use pretty_assertions::assert_eq;\n\n  #[test]\n  fn migrates_vue() {\n    let input = r#\"\n<template>\n    <div>Tauri!</div>\n</template>\n\n<script setup>\n  import { useState } from \"react\";\n  import reactLogo from \"./assets/react.svg\";\n  import { invoke, dialog, cli as superCli } from \"@tauri-apps/api\";\n  import { appWindow } from \"@tauri-apps/api/window\";\n  import { convertFileSrc } from \"@tauri-apps/api/tauri\";\n  import { open } from \"@tauri-apps/api/dialog\";\n  import { register } from \"@tauri-apps/api/globalShortcut\";\n  import clipboard from \"@tauri-apps/api/clipboard\";\n  import * as fs from \"@tauri-apps/api/fs\";\n  import \"./App.css\";\n</script>\n\n<style>\n.greeting {\n  color: red;\n  font-weight: bold;\n}\n</style>\n\"#;\n\n    let expected = r#\"\n<template>\n    <div>Tauri!</div>\n</template>\n\n<script setup>\n  import { useState } from \"react\";\n  import reactLogo from \"./assets/react.svg\";\n  import { invoke,   } from \"@tauri-apps/api\";\n  import { getCurrentWebviewWindow } from \"@tauri-apps/api/webviewWindow\";\n  import { convertFileSrc } from \"@tauri-apps/api/core\";\n  import { open } from \"@tauri-apps/plugin-dialog\";\n  import { register } from \"@tauri-apps/plugin-global-shortcut\";\n  import clipboard from \"@tauri-apps/plugin-clipboard-manager\";\n  import * as fs from \"@tauri-apps/plugin-fs\";\n  import \"./App.css\";\nimport * as dialog from \"@tauri-apps/plugin-dialog\"\nimport * as cli as superCli from \"@tauri-apps/plugin-cli\"\nconst appWindow = getCurrentWebviewWindow()\n</script>\n\n<style>\n.greeting {\n  color: red;\n  font-weight: bold;\n}\n</style>\n\"#;\n\n    let mut new_plugins = Vec::new();\n    let mut npm_packages_to_remove = Vec::new();\n\n    let migrated = migrate_imports(\n      Path::new(\"file.vue\"),\n      input,\n      &mut new_plugins,\n      &mut npm_packages_to_remove,\n    )\n    .unwrap();\n\n    assert_eq!(migrated, expected);\n\n    assert_eq!(\n      new_plugins,\n      vec![\n        \"dialog\",\n        \"cli\",\n        \"dialog\",\n        \"global-shortcut\",\n        \"clipboard-manager\",\n        \"fs\"\n      ]\n    );\n    assert_eq!(npm_packages_to_remove, Vec::<String>::new());\n  }\n\n  #[test]\n  fn migrates_svelte() {\n    let input = r#\"\n<form>\n</form>\n\n<script>\n  import { useState } from \"react\";\n  import reactLogo from \"./assets/react.svg\";\n  import { invoke, dialog, cli as superCli } from \"@tauri-apps/api\";\n  import { appWindow } from \"@tauri-apps/api/window\";\n  import { convertFileSrc } from \"@tauri-apps/api/tauri\";\n  import { open } from \"@tauri-apps/api/dialog\";\n  import { register } from \"@tauri-apps/api/globalShortcut\";\n  import clipboard from \"@tauri-apps/api/clipboard\";\n  import * as fs from \"@tauri-apps/api/fs\";\n  import \"./App.css\";\n</script>\n\"#;\n\n    let expected = r#\"\n<form>\n</form>\n\n<script>\n  import { useState } from \"react\";\n  import reactLogo from \"./assets/react.svg\";\n  import { invoke,   } from \"@tauri-apps/api\";\n  import { getCurrentWebviewWindow } from \"@tauri-apps/api/webviewWindow\";\n  import { convertFileSrc } from \"@tauri-apps/api/core\";\n  import { open } from \"@tauri-apps/plugin-dialog\";\n  import { register } from \"@tauri-apps/plugin-global-shortcut\";\n  import clipboard from \"@tauri-apps/plugin-clipboard-manager\";\n  import * as fs from \"@tauri-apps/plugin-fs\";\n  import \"./App.css\";\nimport * as dialog from \"@tauri-apps/plugin-dialog\"\nimport * as cli as superCli from \"@tauri-apps/plugin-cli\"\nconst appWindow = getCurrentWebviewWindow()\n</script>\n\"#;\n\n    let mut new_plugins = Vec::new();\n    let mut npm_packages_to_remove = Vec::new();\n\n    let migrated = migrate_imports(\n      Path::new(\"file.svelte\"),\n      input,\n      &mut new_plugins,\n      &mut npm_packages_to_remove,\n    )\n    .unwrap();\n\n    assert_eq!(migrated, expected);\n\n    assert_eq!(\n      new_plugins,\n      vec![\n        \"dialog\",\n        \"cli\",\n        \"dialog\",\n        \"global-shortcut\",\n        \"clipboard-manager\",\n        \"fs\"\n      ]\n    );\n    assert_eq!(npm_packages_to_remove, Vec::<String>::new());\n  }\n\n  #[test]\n  fn migrates_js() {\n    let input = r#\"\nimport { useState } from \"react\";\nimport reactLogo from \"./assets/react.svg\";\nimport { invoke, dialog, cli as superCli } from \"@tauri-apps/api\";\nimport { appWindow } from \"@tauri-apps/api/window\";\nimport { convertFileSrc } from \"@tauri-apps/api/tauri\";\nimport { open } from \"@tauri-apps/api/dialog\";\nimport { register } from \"@tauri-apps/api/globalShortcut\";\nimport clipboard from \"@tauri-apps/api/clipboard\";\nimport * as fs from \"@tauri-apps/api/fs\";\nimport { Store } from \"tauri-plugin-store-api\";\nimport Database from \"tauri-plugin-sql-api\";\nimport \"./App.css\";\n\nfunction App() {\n  const [greetMsg, setGreetMsg] = useState(\"\");\n  const [name, setName] = useState(\"\");\n\n  async function greet() {\n    // Learn more about Tauri commands at https://v2.tauri.app/develop/calling-rust/#commands\n    setGreetMsg(await invoke(\"greet\", { name }));\n    await open();\n    await dialog.save();\n    await convertFileSrc(\"\");\n    const a = appWindow.label;\n    superCli.getMatches();\n    clipboard.readText();\n    fs.exists(\"\");\n  }\n\n  return (\n    <div className=\"container\">\n      <h1>Welcome to Tauri!</h1>\n\n      <div className=\"row\">\n        <a href=\"https://vite.dev\" target=\"_blank\">\n          <img src=\"/vite.svg\" className=\"logo vite\" alt=\"Vite logo\" />\n        </a>\n        <a href=\"https://tauri.app\" target=\"_blank\">\n          <img src=\"/tauri.svg\" className=\"logo tauri\" alt=\"Tauri logo\" />\n        </a>\n        <a href=\"https://react.dev\" target=\"_blank\">\n          <img src={reactLogo} className=\"logo react\" alt=\"React logo\" />\n        </a>\n      </div>\n\n      <p>Click on the Tauri, Vite, and React logos to learn more.</p>\n\n      <form\n        className=\"row\"\n        onSubmit={(e) => {\n          e.preventDefault();\n          greet();\n        }}\n      >\n        <input\n          id=\"greet-input\"\n          onChange={(e) => setName(e.currentTarget.value)}\n          placeholder=\"Enter a name...\"\n        />\n        <button type=\"submit\">Greet</button>\n      </form>\n\n      <p>{greetMsg}</p>\n    </div>\n  );\n}\n\nexport default App;\n\"#;\n\n    let expected = r#\"\nimport { useState } from \"react\";\nimport reactLogo from \"./assets/react.svg\";\nimport { invoke,   } from \"@tauri-apps/api\";\nimport { getCurrentWebviewWindow } from \"@tauri-apps/api/webviewWindow\";\nimport { convertFileSrc } from \"@tauri-apps/api/core\";\nimport { open } from \"@tauri-apps/plugin-dialog\";\nimport { register } from \"@tauri-apps/plugin-global-shortcut\";\nimport clipboard from \"@tauri-apps/plugin-clipboard-manager\";\nimport * as fs from \"@tauri-apps/plugin-fs\";\nimport { Store } from \"@tauri-apps/plugin-store\";\nimport Database from \"@tauri-apps/plugin-sql\";\nimport \"./App.css\";\nimport * as dialog from \"@tauri-apps/plugin-dialog\"\nimport * as cli as superCli from \"@tauri-apps/plugin-cli\"\nconst appWindow = getCurrentWebviewWindow()\n\nfunction App() {\n  const [greetMsg, setGreetMsg] = useState(\"\");\n  const [name, setName] = useState(\"\");\n\n  async function greet() {\n    // Learn more about Tauri commands at https://v2.tauri.app/develop/calling-rust/#commands\n    setGreetMsg(await invoke(\"greet\", { name }));\n    await open();\n    await dialog.save();\n    await convertFileSrc(\"\");\n    const a = appWindow.label;\n    superCli.getMatches();\n    clipboard.readText();\n    fs.exists(\"\");\n  }\n\n  return (\n    <div className=\"container\">\n      <h1>Welcome to Tauri!</h1>\n\n      <div className=\"row\">\n        <a href=\"https://vite.dev\" target=\"_blank\">\n          <img src=\"/vite.svg\" className=\"logo vite\" alt=\"Vite logo\" />\n        </a>\n        <a href=\"https://tauri.app\" target=\"_blank\">\n          <img src=\"/tauri.svg\" className=\"logo tauri\" alt=\"Tauri logo\" />\n        </a>\n        <a href=\"https://react.dev\" target=\"_blank\">\n          <img src={reactLogo} className=\"logo react\" alt=\"React logo\" />\n        </a>\n      </div>\n\n      <p>Click on the Tauri, Vite, and React logos to learn more.</p>\n\n      <form\n        className=\"row\"\n        onSubmit={(e) => {\n          e.preventDefault();\n          greet();\n        }}\n      >\n        <input\n          id=\"greet-input\"\n          onChange={(e) => setName(e.currentTarget.value)}\n          placeholder=\"Enter a name...\"\n        />\n        <button type=\"submit\">Greet</button>\n      </form>\n\n      <p>{greetMsg}</p>\n    </div>\n  );\n}\n\nexport default App;\n\"#;\n\n    let mut new_plugins = Vec::new();\n    let mut npm_packages_to_remove = Vec::new();\n\n    let migrated = migrate_imports(\n      Path::new(\"file.js\"),\n      input,\n      &mut new_plugins,\n      &mut npm_packages_to_remove,\n    )\n    .unwrap();\n\n    assert_eq!(migrated, expected);\n\n    assert_eq!(\n      new_plugins,\n      vec![\n        \"dialog\",\n        \"cli\",\n        \"dialog\",\n        \"global-shortcut\",\n        \"clipboard-manager\",\n        \"fs\",\n        \"store\",\n        \"sql\"\n      ]\n    );\n    assert_eq!(\n      npm_packages_to_remove,\n      vec![\"tauri-plugin-store-api\", \"tauri-plugin-sql-api\"]\n    );\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/migrate/migrations/v1/manifest.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::ErrorExt,\n  interface::rust::manifest::{read_manifest, serialize_manifest},\n  Result,\n};\n\nuse tauri_utils::config_v1::Allowlist;\nuse toml_edit::{DocumentMut, Entry, Item, TableLike, Value};\n\nuse std::path::Path;\n\nconst CRATE_TYPES: [&str; 3] = [\"lib\", \"staticlib\", \"cdylib\"];\n\npub fn migrate(tauri_dir: &Path) -> Result<()> {\n  let manifest_path = tauri_dir.join(\"Cargo.toml\");\n  let (mut manifest, _) = read_manifest(&manifest_path)?;\n  migrate_manifest(&mut manifest)?;\n\n  std::fs::write(&manifest_path, serialize_manifest(&manifest))\n    .fs_context(\"failed to rewrite Cargo manifest\", &manifest_path)?;\n\n  Ok(())\n}\n\nfn migrate_manifest(manifest: &mut DocumentMut) -> Result<()> {\n  let version = dependency_version();\n\n  let remove_features = features_to_remove();\n  let rename_features = features_to_rename();\n  let rename_message = rename_features\n    .iter()\n    .map(|(from, to)| format!(\"{from} to {to}\"))\n    .collect::<Vec<_>>()\n    .join(\", \");\n\n  for (dependency, table) in [\n    // normal deps\n    (\"tauri\", \"dependencies\"),\n    (\"tauri-utils\", \"dependencies\"),\n    (\"tauri-runtime\", \"dependencies\"),\n    (\"tauri-codegen\", \"dependencies\"),\n    (\"tauri-macros\", \"dependencies\"),\n    (\"tauri-runtime-wry\", \"dependencies\"),\n    // normal deps - plugins\n    (\"tauri-plugin-authenticator\", \"dependencies\"),\n    (\"tauri-plugin-autostart\", \"dependencies\"),\n    (\"tauri-plugin-fs-extra\", \"dependencies\"),\n    (\"tauri-plugin-fs-watch\", \"dependencies\"),\n    (\"tauri-plugin-localhost\", \"dependencies\"),\n    (\"tauri-plugin-log\", \"dependencies\"),\n    (\"tauri-plugin-persisted-scope\", \"dependencies\"),\n    (\"tauri-plugin-positioner\", \"dependencies\"),\n    (\"tauri-plugin-single-instance\", \"dependencies\"),\n    (\"tauri-plugin-sql\", \"dependencies\"),\n    (\"tauri-plugin-store\", \"dependencies\"),\n    (\"tauri-plugin-stronghold\", \"dependencies\"),\n    (\"tauri-plugin-upload\", \"dependencies\"),\n    (\"tauri-plugin-websocket\", \"dependencies\"),\n    (\"tauri-plugin-window-state\", \"dependencies\"),\n    // dev\n    (\"tauri\", \"dev-dependencies\"),\n    (\"tauri-utils\", \"dev-dependencies\"),\n    (\"tauri-runtime\", \"dev-dependencies\"),\n    (\"tauri-codegen\", \"dev-dependencies\"),\n    (\"tauri-macros\", \"dev-dependencies\"),\n    (\"tauri-runtime-wry\", \"dev-dependencies\"),\n    // build\n    (\"tauri-build\", \"build-dependencies\"),\n  ] {\n    let items = find_dependency(manifest, dependency, table);\n\n    for item in items {\n      // do not rewrite if dependency uses workspace inheritance\n      if item\n        .get(\"workspace\")\n        .and_then(|v| v.as_bool())\n        .unwrap_or_default()\n      {\n        log::warn!(\"`{dependency}` dependency has workspace inheritance enabled. This migration must be manually migrated to v2 by changing its version to {version}, removing any of the {remove_features:?} and renaming [{}] Cargo features.\", rename_message);\n      } else {\n        migrate_dependency(item, &version, &remove_features, &rename_features);\n      }\n    }\n  }\n\n  if let Some(lib) = manifest\n    .as_table_mut()\n    .get_mut(\"lib\")\n    .and_then(|l| l.as_table_mut())\n  {\n    match lib.entry(\"crate-type\") {\n      Entry::Occupied(mut e) => {\n        if let Item::Value(Value::Array(types)) = e.get_mut() {\n          let mut crate_types_to_add = CRATE_TYPES.to_vec();\n          for t in types.iter() {\n            // type is already in the manifest, skip adding it\n            if let Some(i) = crate_types_to_add\n              .iter()\n              .position(|ty| Some(ty) == t.as_str().as_ref())\n            {\n              crate_types_to_add.remove(i);\n            }\n          }\n          for t in crate_types_to_add {\n            types.push(t);\n          }\n        }\n      }\n      Entry::Vacant(e) => {\n        let mut arr = toml_edit::Array::new();\n        arr.extend(CRATE_TYPES.to_vec());\n        e.insert(Item::Value(arr.into()));\n      }\n    }\n  }\n\n  Ok(())\n}\n\nfn find_dependency<'a>(\n  manifest: &'a mut DocumentMut,\n  name: &'a str,\n  table: &'a str,\n) -> Vec<&'a mut Item> {\n  let m = manifest.as_table_mut();\n  for (k, v) in m.iter_mut() {\n    if let Some(t) = v.as_table_mut() {\n      if k == table {\n        if let Some(item) = t.get_mut(name) {\n          return vec![item];\n        }\n      } else if k == \"target\" {\n        let mut matching_deps = Vec::new();\n        for (_, target_value) in t.iter_mut() {\n          if let Some(target_table) = target_value.as_table_mut() {\n            if let Some(deps) = target_table.get_mut(table) {\n              if let Some(item) = deps.as_table_mut().and_then(|t| t.get_mut(name)) {\n                matching_deps.push(item);\n              }\n            }\n          }\n        }\n        return matching_deps;\n      }\n    }\n  }\n\n  Vec::new()\n}\n\nfn features_to_rename() -> Vec<(&'static str, &'static str)> {\n  vec![\n    (\"window-data-url\", \"webview-data-url\"),\n    (\"reqwest-native-tls-vendored\", \"native-tls-vendored\"),\n    (\"system-tray\", \"tray-icon\"),\n    (\"icon-ico\", \"image-ico\"),\n    (\"icon-png\", \"image-png\"),\n  ]\n}\n\nfn features_to_remove() -> Vec<&'static str> {\n  let mut features_to_remove = tauri_utils::config_v1::AllowlistConfig::all_features();\n  features_to_remove.extend(&[\n    \"reqwest-client\",\n    \"http-multipart\",\n    \"process-command-api\",\n    \"shell-open-api\",\n    \"os-api\",\n    \"global-shortcut\",\n    \"clipboard\",\n    \"dialog\",\n    \"notification\",\n    \"fs-extract-api\",\n    \"windows7-compat\",\n    \"updater\",\n    \"cli\",\n    \"linux-protocol-headers\",\n    \"dox\",\n  ]);\n\n  // this allowlist feature was not removed\n  let index = features_to_remove\n    .iter()\n    .position(|x| x == &\"protocol-asset\")\n    .unwrap();\n  features_to_remove.remove(index);\n\n  features_to_remove\n}\n\nfn dependency_version() -> String {\n  let pre = env!(\"CARGO_PKG_VERSION_PRE\");\n  if pre.is_empty() {\n    env!(\"CARGO_PKG_VERSION_MAJOR\").to_string()\n  } else {\n    format!(\n      \"{}.0.0-{}\",\n      env!(\"CARGO_PKG_VERSION_MAJOR\"),\n      pre.split('.').next().unwrap()\n    )\n  }\n}\n\nfn migrate_dependency(item: &mut Item, version: &str, remove: &[&str], rename: &[(&str, &str)]) {\n  if let Some(dep) = item.as_table_mut() {\n    migrate_dependency_table(dep, version, remove, rename);\n  } else if let Some(Value::InlineTable(table)) = item.as_value_mut() {\n    migrate_dependency_table(table, version, remove, rename);\n  } else if item.as_str().is_some() {\n    *item = Item::Value(version.into());\n  }\n}\n\nfn migrate_dependency_table<D: TableLike>(\n  dep: &mut D,\n  version: &str,\n  remove: &[&str],\n  rename: &[(&str, &str)],\n) {\n  dep.remove(\"rev\");\n  dep.remove(\"git\");\n  dep.remove(\"branch\");\n  dep.remove(\"tag\");\n  *dep.entry(\"version\").or_insert(Item::None) = Item::Value(version.into());\n  let manifest_features = dep.entry(\"features\").or_insert(Item::None);\n  if let Some(features_array) = manifest_features.as_array_mut() {\n    // remove features that shouldn't be in the manifest anymore\n    let mut i = features_array.len();\n    let mut add_features = Vec::new();\n    while i != 0 {\n      let index = i - 1;\n      if let Some(f) = features_array.get(index).and_then(|f| f.as_str()) {\n        if remove.contains(&f) {\n          features_array.remove(index);\n        } else if let Some((_from, rename_to)) = rename.iter().find(|(from, _to)| *from == f) {\n          features_array.remove(index);\n          add_features.push(rename_to);\n        }\n      }\n      i -= 1;\n    }\n\n    for f in add_features {\n      features_array.push(f.to_string());\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use itertools::Itertools;\n\n  fn migrate_deps<F: FnOnce(&[&str]) -> String>(get_toml: F) {\n    let keep_features = vec![\"isolation\", \"protocol-asset\"];\n    let mut features = super::features_to_remove();\n    features.extend(keep_features.clone());\n    let toml = get_toml(&features);\n\n    let mut manifest = toml\n      .parse::<toml_edit::DocumentMut>()\n      .expect(\"invalid toml\");\n    super::migrate_manifest(&mut manifest).expect(\"failed to migrate manifest\");\n\n    let dependencies = manifest\n      .as_table()\n      .get(\"dependencies\")\n      .expect(\"missing manifest dependencies\")\n      .as_table()\n      .expect(\"manifest dependencies isn't a table\");\n\n    let tauri = dependencies\n      .get(\"tauri\")\n      .expect(\"missing tauri dependency in manifest\");\n\n    let tauri_table = if let Some(table) = tauri.as_table() {\n      table.clone()\n    } else if let Some(toml_edit::Value::InlineTable(table)) = tauri.as_value() {\n      table.clone().into_table()\n    } else if let Some(version) = tauri.as_str() {\n      // convert the value to a table for the assert logic below\n      let mut table = toml_edit::Table::new();\n      table.insert(\n        \"version\",\n        toml_edit::Item::Value(version.to_string().into()),\n      );\n      table.insert(\n        \"features\",\n        toml_edit::Item::Value(toml_edit::Value::Array(Default::default())),\n      );\n      table\n    } else {\n      panic!(\"unexpected tauri dependency format\");\n    };\n\n    // assert version matches\n    let version = tauri_table\n      .get(\"version\")\n      .expect(\"missing version\")\n      .as_str()\n      .expect(\"version must be a string\");\n    assert_eq!(version, super::dependency_version());\n\n    // assert features matches\n    let features = tauri_table\n      .get(\"features\")\n      .expect(\"missing features\")\n      .as_array()\n      .expect(\"features must be an array\")\n      .clone();\n\n    if toml.contains(\"reqwest-native-tls-vendored\") {\n      assert!(\n        features\n          .iter()\n          .any(|f| f.as_str().expect(\"feature must be a string\") == \"native-tls-vendored\"),\n        \"reqwest-native-tls-vendored was not replaced with native-tls-vendored\"\n      );\n    }\n\n    if toml.contains(\"system-tray\") {\n      assert!(\n        features\n          .iter()\n          .any(|f| f.as_str().expect(\"feature must be a string\") == \"tray-icon\"),\n        \"system-tray was not replaced with tray-icon\"\n      );\n    }\n\n    for feature in features.iter() {\n      let feature = feature.as_str().expect(\"feature must be a string\");\n      assert!(\n        keep_features.contains(&feature)\n          || feature == \"native-tls-vendored\"\n          || feature == \"tray-icon\",\n        \"feature {feature} should have been removed\"\n      );\n    }\n  }\n\n  #[test]\n  fn migrate_table() {\n    migrate_deps(|features| {\n      format!(\n        r#\"\n    [dependencies]\n    tauri = {{ version = \"1.0.0\", features = [{}] }}\n\"#,\n        features.iter().map(|f| format!(\"{f:?}\")).join(\", \")\n      )\n    });\n  }\n\n  #[test]\n  fn migrate_inline_table() {\n    migrate_deps(|features| {\n      format!(\n        r#\"\n    [dependencies.tauri]\n    version = \"1.0.0\"\n    features = [{}]\n\"#,\n        features.iter().map(|f| format!(\"{f:?}\")).join(\", \")\n      )\n    });\n  }\n\n  #[test]\n  fn migrate_str() {\n    migrate_deps(|_features| {\n      r#\"\n    [dependencies]\n    tauri = \"1.0.0\"\n\"#\n      .into()\n    })\n  }\n\n  #[test]\n  fn migrate_add_crate_types() {\n    let toml = r#\"\n    [lib]\n    crate-type = [\"something\"]\"#;\n\n    let mut manifest = toml\n      .parse::<toml_edit::DocumentMut>()\n      .expect(\"invalid toml\");\n    super::migrate_manifest(&mut manifest).expect(\"failed to migrate manifest\");\n\n    if let Some(crate_types) = manifest\n      .as_table()\n      .get(\"lib\")\n      .and_then(|l| l.get(\"crate-type\"))\n      .and_then(|c| c.as_array())\n    {\n      let mut not_added_crate_types = super::CRATE_TYPES.to_vec();\n      for t in crate_types {\n        let t = t.as_str().expect(\"crate-type must be a string\");\n        if let Some(i) = not_added_crate_types.iter().position(|ty| ty == &t) {\n          not_added_crate_types.remove(i);\n        }\n      }\n      assert!(\n        not_added_crate_types.is_empty(),\n        \"missing crate-type: {not_added_crate_types:?}\"\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/migrate/migrations/v1/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{error::Context, helpers::app_paths::Dirs, Result};\n\nmod config;\nmod frontend;\nmod manifest;\n\npub fn run(dirs: &Dirs) -> Result<()> {\n  let mut migrated = config::migrate(dirs.tauri).context(\"Could not migrate config\")?;\n  manifest::migrate(dirs.tauri).context(\"Could not migrate manifest\")?;\n  let plugins = frontend::migrate(dirs.frontend)?;\n\n  migrated.plugins.extend(plugins);\n\n  // Add plugins\n  for plugin in migrated.plugins {\n    crate::add::run(\n      crate::add::Options {\n        plugin: plugin.clone(),\n        branch: None,\n        tag: None,\n        rev: None,\n        no_fmt: false,\n      },\n      dirs,\n    )\n    .with_context(|| format!(\"Could not migrate plugin '{plugin}'\"))?;\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/migrate/migrations/v2_beta.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::{Context, ErrorExt},\n  helpers::{app_paths::Dirs, npm::PackageManager},\n  interface::rust::manifest::{read_manifest, serialize_manifest},\n  Result,\n};\n\nuse std::{fs::read_to_string, path::Path};\n\nuse toml_edit::{DocumentMut, Item, Table, TableLike, Value};\n\npub fn run(dirs: &Dirs) -> Result<()> {\n  let manifest_path = dirs.tauri.join(\"Cargo.toml\");\n  let (mut manifest, _) = read_manifest(&manifest_path)?;\n  migrate_manifest(&mut manifest)?;\n\n  migrate_permissions(dirs.tauri)?;\n\n  migrate_npm_dependencies(dirs.frontend)?;\n\n  std::fs::write(&manifest_path, serialize_manifest(&manifest))\n    .fs_context(\"failed to rewrite Cargo manifest\", &manifest_path)?;\n\n  Ok(())\n}\n\nfn migrate_npm_dependencies(frontend_dir: &Path) -> Result<()> {\n  let pm = PackageManager::from_project(frontend_dir);\n\n  let mut install_deps = Vec::new();\n  for pkg in [\n    \"@tauri-apps/cli\",\n    \"@tauri-apps/api\",\n    \"@tauri-apps/plugin-authenticator\",\n    \"@tauri-apps/plugin-autostart\",\n    \"@tauri-apps/plugin-barcode-scanner\",\n    \"@tauri-apps/plugin-biometric\",\n    \"@tauri-apps/plugin-cli\",\n    \"@tauri-apps/plugin-clipboard-manager\",\n    \"@tauri-apps/plugin-deep-link\",\n    \"@tauri-apps/plugin-dialog\",\n    \"@tauri-apps/plugin-fs\",\n    \"@tauri-apps/plugin-global-shortcut\",\n    \"@tauri-apps/plugin-http\",\n    \"@tauri-apps/plugin-log\",\n    \"@tauri-apps/plugin-nfc\",\n    \"@tauri-apps/plugin-notification\",\n    \"@tauri-apps/plugin-os\",\n    \"@tauri-apps/plugin-positioner\",\n    \"@tauri-apps/plugin-process\",\n    \"@tauri-apps/plugin-shell\",\n    \"@tauri-apps/plugin-sql\",\n    \"@tauri-apps/plugin-store\",\n    \"@tauri-apps/plugin-stronghold\",\n    \"@tauri-apps/plugin-updater\",\n    \"@tauri-apps/plugin-upload\",\n    \"@tauri-apps/plugin-websocket\",\n    \"@tauri-apps/plugin-window-state\",\n  ] {\n    let version = pm\n      .current_package_version(pkg, frontend_dir)\n      .unwrap_or_default()\n      .unwrap_or_default();\n    if version.starts_with('1') {\n      install_deps.push(format!(\"{pkg}@^2.0.0\"));\n    }\n  }\n\n  if !install_deps.is_empty() {\n    pm.install(&install_deps, frontend_dir)?;\n  }\n\n  Ok(())\n}\n\nfn migrate_permissions(tauri_dir: &Path) -> Result<()> {\n  let core_plugins = [\n    \"app\",\n    \"event\",\n    \"image\",\n    \"menu\",\n    \"path\",\n    \"resources\",\n    \"tray\",\n    \"webview\",\n    \"window\",\n  ];\n\n  for entry in walkdir::WalkDir::new(tauri_dir.join(\"capabilities\")) {\n    let entry = entry.map_err(std::io::Error::other).fs_context(\n      \"failed to walk capabilities directory\",\n      tauri_dir.join(\"capabilities\"),\n    )?;\n    let path = entry.path();\n    if path.extension().is_some_and(|ext| ext == \"json\") {\n      let mut capability =\n        read_to_string(path).fs_context(\"failed to read capability\", path.to_path_buf())?;\n      for plugin in core_plugins {\n        capability = capability.replace(&format!(\"\\\"{plugin}:\"), &format!(\"\\\"core:{plugin}:\"));\n      }\n      std::fs::write(path, capability)\n        .fs_context(\"failed to rewrite capability\", path.to_path_buf())?;\n    }\n  }\n  Ok(())\n}\n\nfn migrate_manifest(manifest: &mut DocumentMut) -> Result<()> {\n  let version = \"2.0.0\";\n\n  let dependencies = manifest\n    .as_table_mut()\n    .entry(\"dependencies\")\n    .or_insert(Item::Table(Table::new()))\n    .as_table_mut()\n    .context(\"manifest dependencies isn't a table\")?;\n\n  for dep in [\n    \"tauri\",\n    \"tauri-plugin-authenticator\",\n    \"tauri-plugin-autostart\",\n    \"tauri-plugin-barcode-scanner\",\n    \"tauri-plugin-biometric\",\n    \"tauri-plugin-cli\",\n    \"tauri-plugin-clipboard-manager\",\n    \"tauri-plugin-deep-link\",\n    \"tauri-plugin-dialog\",\n    \"tauri-plugin-fs\",\n    \"tauri-plugin-global-shortcut\",\n    \"tauri-plugin-http\",\n    \"tauri-plugin-localhost\",\n    \"tauri-plugin-log\",\n    \"tauri-plugin-nfc\",\n    \"tauri-plugin-notification\",\n    \"tauri-plugin-os\",\n    \"tauri-plugin-persisted-scope\",\n    \"tauri-plugin-positioner\",\n    \"tauri-plugin-process\",\n    \"tauri-plugin-shell\",\n    \"tauri-plugin-single-instance\",\n    \"tauri-plugin-sql\",\n    \"tauri-plugin-store\",\n    \"tauri-plugin-stronghold\",\n    \"tauri-plugin-updater\",\n    \"tauri-plugin-upload\",\n    \"tauri-plugin-websocket\",\n    \"tauri-plugin-window-state\",\n  ] {\n    migrate_dependency(dependencies, dep, version);\n  }\n\n  let build_dependencies = manifest\n    .as_table_mut()\n    .entry(\"build-dependencies\")\n    .or_insert(Item::Table(Table::new()))\n    .as_table_mut()\n    .context(\"manifest build-dependencies isn't a table\")?;\n\n  migrate_dependency(build_dependencies, \"tauri-build\", version);\n\n  Ok(())\n}\n\nfn migrate_dependency(dependencies: &mut Table, name: &str, version: &str) {\n  let item = dependencies.entry(name).or_insert(Item::None);\n\n  // do not rewrite if dependency uses workspace inheritance\n  if item\n    .get(\"workspace\")\n    .and_then(|v| v.as_bool())\n    .unwrap_or_default()\n  {\n    log::info!(\"`{name}` dependency has workspace inheritance enabled. The features array won't be automatically rewritten.\");\n    return;\n  }\n\n  if let Some(dep) = item.as_table_mut() {\n    migrate_dependency_table(dep, version);\n  } else if let Some(Value::InlineTable(table)) = item.as_value_mut() {\n    migrate_dependency_table(table, version);\n  } else if item.as_str().is_some() {\n    *item = Item::Value(version.into());\n  }\n}\n\nfn migrate_dependency_table<D: TableLike>(dep: &mut D, version: &str) {\n  *dep.entry(\"version\").or_insert(Item::None) = Item::Value(version.into());\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/migrate/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::{bail, Context, ErrorExt},\n  helpers::cargo_manifest::{crate_version, CargoLock, CargoManifest},\n  interface::rust::get_workspace_dir,\n  Result,\n};\n\nuse std::{fs::read_to_string, str::FromStr};\n\nmod migrations;\n\npub fn command() -> Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n\n  let manifest_contents = read_to_string(dirs.tauri.join(\"Cargo.toml\")).fs_context(\n    \"failed to read Cargo manifest\",\n    dirs.tauri.join(\"Cargo.toml\"),\n  )?;\n  let manifest = toml::from_str::<CargoManifest>(&manifest_contents).with_context(|| {\n    format!(\n      \"failed to parse Cargo manifest {}\",\n      dirs.tauri.join(\"Cargo.toml\").display()\n    )\n  })?;\n\n  let workspace_dir = get_workspace_dir(dirs.tauri)?;\n  let lock_path = workspace_dir.join(\"Cargo.lock\");\n  let lock = if lock_path.exists() {\n    let lockfile_contents =\n      read_to_string(&lock_path).fs_context(\"failed to read Cargo lockfile\", &lock_path)?;\n    let lock = toml::from_str::<CargoLock>(&lockfile_contents)\n      .with_context(|| format!(\"failed to parse Cargo lockfile {}\", lock_path.display()))?;\n    Some(lock)\n  } else {\n    None\n  };\n\n  let tauri_version = crate_version(dirs.tauri, Some(&manifest), lock.as_ref(), \"tauri\")\n    .version\n    .context(\"failed to get tauri version\")?;\n  let tauri_version = semver::Version::from_str(&tauri_version)\n    .with_context(|| format!(\"failed to parse tauri version {tauri_version}\"))?;\n\n  if tauri_version.major == 1 {\n    migrations::v1::run(&dirs).context(\"failed to migrate from v1\")?;\n  } else if tauri_version.major == 2 {\n    if let Some((pre, _number)) = tauri_version.pre.as_str().split_once('.') {\n      match pre {\n        \"beta\" => {\n          migrations::v2_beta::run(&dirs).context(\"failed to migrate from v2 beta\")?;\n        }\n        \"alpha\" => {\n          bail!(\n            \"Migrating from v2 alpha ({tauri_version}) to v2 stable is not supported yet, \\\n             if your project started early, try downgrading to v1 and then try again\"\n          )\n        }\n        _ => {\n          bail!(\"Migrating from {tauri_version} to v2 stable is not supported yet\")\n        }\n      }\n    } else {\n      log::info!(\"Nothing to do, the tauri version is already at v2 stable\");\n    }\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/android/android_studio_script.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{detect_target_ok, ensure_init, env, get_app, get_config, read_options, MobileTarget};\nuse crate::{\n  error::{Context, ErrorExt},\n  helpers::config::{get_config as get_tauri_config, reload_config as reload_tauri_config},\n  interface::AppInterface,\n  mobile::CliOptions,\n  Error, Result,\n};\nuse clap::{ArgAction, Parser};\n\nuse cargo_mobile2::{\n  android::{adb, target::Target},\n  opts::Profile,\n  target::{call_for_targets_with_fallback, TargetTrait},\n};\n\nuse std::path::Path;\n\n#[derive(Debug, Parser)]\npub struct Options {\n  /// Targets to build.\n  #[clap(\n    short,\n    long = \"target\",\n    action = ArgAction::Append,\n    num_args(0..),\n    default_value = Target::DEFAULT_KEY,\n    value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))\n  )]\n  targets: Option<Vec<String>>,\n  /// Builds with the release flag\n  #[clap(short, long)]\n  release: bool,\n}\n\npub fn command(options: Options) -> Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n\n  let profile = if options.release {\n    Profile::Release\n  } else {\n    Profile::Debug\n  };\n\n  let mut tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, &[], dirs.tauri)?;\n  let cli_options = read_options(&tauri_config);\n\n  if !cli_options.config.is_empty() {\n    // reload config with merges from the android dev|build script\n    reload_tauri_config(\n      &mut tauri_config,\n      &cli_options\n        .config\n        .iter()\n        .map(|conf| &conf.0)\n        .collect::<Vec<_>>(),\n      dirs.tauri,\n    )?\n  };\n\n  let (config, metadata) = get_config(\n    &get_app(\n      MobileTarget::Android,\n      &tauri_config,\n      &AppInterface::new(&tauri_config, None, dirs.tauri)?,\n      dirs.tauri,\n    ),\n    &tauri_config,\n    &[],\n    &cli_options,\n  );\n\n  ensure_init(\n    &tauri_config,\n    config.app(),\n    config.project_dir(),\n    MobileTarget::Android,\n    std::env::var(\"CI\").is_ok(),\n  )?;\n\n  if !cli_options.config.is_empty() {\n    crate::helpers::config::merge_config_with(\n      &mut tauri_config,\n      &cli_options\n        .config\n        .iter()\n        .map(|conf| &conf.0)\n        .collect::<Vec<_>>(),\n    )?;\n  }\n\n  let env = env(std::env::var(\"CI\").is_ok())?;\n\n  if cli_options.dev {\n    if let Some(url) = &tauri_config.build.dev_url {\n      let localhost = match url.host() {\n        Some(url::Host::Domain(d)) => d == \"localhost\",\n        Some(url::Host::Ipv4(i)) => i == std::net::Ipv4Addr::LOCALHOST,\n        _ => false,\n      };\n\n      if localhost {\n        if let Some(port) = url.port_or_known_default() {\n          adb_forward_port(port, &env, &cli_options)?;\n        }\n      }\n    }\n  }\n\n  let mut validated_lib = false;\n\n  let installed_targets =\n    crate::interface::rust::installation::installed_targets().unwrap_or_default();\n\n  call_for_targets_with_fallback(\n    options.targets.unwrap_or_default().iter(),\n    &detect_target_ok,\n    &env,\n    |target: &Target| {\n      if !installed_targets.contains(&target.triple().into()) {\n        log::info!(\"Installing target {}\", target.triple());\n        target\n          .install()\n          .map_err(|error| Error::CommandFailed {\n            command: \"rustup target add\".to_string(),\n            error,\n          })\n          .context(\"failed to install target\")?;\n      }\n\n      target\n        .build(\n          &config,\n          &metadata,\n          &env,\n          cli_options.noise_level,\n          true,\n          profile,\n        )\n        .context(\"failed to build Android app\")?;\n\n      if !validated_lib {\n        validated_lib = true;\n\n        let lib_path = config\n          .app()\n          .target_dir(target.triple, profile)\n          .join(config.so_name());\n\n        validate_lib(&lib_path).context(\"failed to validate library\")?;\n      }\n\n      Ok(())\n    },\n  )\n  .map_err(|e| Error::GenericError(e.to_string()))?\n}\n\nfn validate_lib(path: &Path) -> Result<()> {\n  let so_bytes = std::fs::read(path).fs_context(\"failed to read library\", path.to_path_buf())?;\n  let elf = elf::ElfBytes::<elf::endian::AnyEndian>::minimal_parse(&so_bytes)\n    .context(\"failed to parse ELF\")?;\n  let (symbol_table, string_table) = elf\n    .dynamic_symbol_table()\n    .context(\"failed to read dynsym section\")?\n    .context(\"missing dynsym tables\")?;\n\n  let mut symbols = Vec::new();\n  for s in symbol_table.iter() {\n    if let Ok(symbol) = string_table.get(s.st_name as usize) {\n      symbols.push(symbol);\n    }\n  }\n\n  if !symbols.contains(&\"Java_app_tauri_plugin_PluginManager_handlePluginResponse\") {\n    crate::error::bail!(\n      \"Library from {} does not include required runtime symbols. This means you are likely missing the tauri::mobile_entry_point macro usage, see the documentation for more information: https://v2.tauri.app/start/migrate/from-tauri-1\",\n      path.display()\n    );\n  }\n\n  Ok(())\n}\n\nfn adb_forward_port(\n  port: u16,\n  env: &cargo_mobile2::android::env::Env,\n  cli_options: &CliOptions,\n) -> Result<()> {\n  let forward = format!(\"tcp:{port}\");\n  log::info!(\"Forwarding port {port} with adb\");\n\n  let mut devices = adb::device_list(env).unwrap_or_default();\n  // if we could not detect any running device, let's wait a few seconds, it might be booting up\n  if devices.is_empty() {\n    log::warn!(\n      \"ADB device list is empty, waiting a few seconds to see if there's any booting device...\"\n    );\n\n    let max = 5;\n    let mut count = 0;\n    loop {\n      std::thread::sleep(std::time::Duration::from_secs(1));\n\n      devices = adb::device_list(env).unwrap_or_default();\n      if !devices.is_empty() {\n        break;\n      }\n\n      count += 1;\n      if count == max {\n        break;\n      }\n    }\n  }\n\n  let target_device = if let Some(target_device) = &cli_options.target_device {\n    Some((target_device.id.clone(), target_device.name.clone()))\n  } else if devices.len() == 1 {\n    let device = devices.first().unwrap();\n    Some((device.serial_no().to_string(), device.name().to_string()))\n  } else if devices.len() > 1 {\n    crate::error::bail!(\"Multiple Android devices are connected ({}), please disconnect devices you do not intend to use so Tauri can determine which to use\",\n      devices.iter().map(|d| d.name()).collect::<Vec<_>>().join(\", \"));\n  } else {\n    // when building the app without running to a device, we might have an empty devices list\n    None\n  };\n\n  if let Some((target_device_serial_no, target_device_name)) = target_device {\n    let mut already_forwarded = false;\n\n    // clear port forwarding for all devices\n    for device in &devices {\n      let reverse_list_output =\n        adb_reverse_list(env, device.serial_no()).map_err(|error| Error::CommandFailed {\n          command: \"adb reverse --list\".to_string(),\n          error,\n        })?;\n\n      // check if the device has the port forwarded\n      if String::from_utf8_lossy(&reverse_list_output.stdout).contains(&forward) {\n        // device matches our target, we can skip forwarding\n        if device.serial_no() == target_device_serial_no {\n          log::debug!(\n            \"device {} already has the forward for {}\",\n            device.name(),\n            forward\n          );\n          already_forwarded = true;\n        }\n        break;\n      }\n    }\n\n    // if there's a known target, we should forward the port to it\n    if already_forwarded {\n      log::info!(\"{forward} already forwarded to {target_device_name}\");\n    } else {\n      loop {\n        run_adb_reverse(env, &target_device_serial_no, &forward, &forward).map_err(|error| {\n          Error::CommandFailed {\n            command: format!(\"adb reverse {forward} {forward}\"),\n            error,\n          }\n        })?;\n\n        let reverse_list_output =\n          adb_reverse_list(env, &target_device_serial_no).map_err(|error| {\n            Error::CommandFailed {\n              command: \"adb reverse --list\".to_string(),\n              error,\n            }\n          })?;\n        // wait and retry until the port has actually been forwarded\n        if String::from_utf8_lossy(&reverse_list_output.stdout).contains(&forward) {\n          break;\n        } else {\n          log::warn!(\n            \"waiting for the port to be forwarded to {}...\",\n            target_device_name\n          );\n          std::thread::sleep(std::time::Duration::from_secs(1));\n        }\n      }\n    }\n  } else {\n    log::warn!(\"no running devices detected with ADB; skipping port forwarding\");\n  }\n\n  Ok(())\n}\n\nfn run_adb_reverse(\n  env: &cargo_mobile2::android::env::Env,\n  device_serial_no: &str,\n  remote: &str,\n  local: &str,\n) -> std::io::Result<std::process::Output> {\n  adb::adb(env, [\"-s\", device_serial_no, \"reverse\", remote, local])\n    .stdin_file(os_pipe::dup_stdin().unwrap())\n    .stdout_file(os_pipe::dup_stdout().unwrap())\n    .stderr_file(os_pipe::dup_stdout().unwrap())\n    .run()\n}\n\nfn adb_reverse_list(\n  env: &cargo_mobile2::android::env::Env,\n  device_serial_no: &str,\n) -> std::io::Result<std::process::Output> {\n  adb::adb(env, [\"-s\", device_serial_no, \"reverse\", \"--list\"])\n    .stdin_file(os_pipe::dup_stdin().unwrap())\n    .stdout_capture()\n    .stderr_capture()\n    .run()\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/android/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{\n  configure_cargo, delete_codegen_vars, ensure_init, env, get_app, get_config, inject_resources,\n  log_finished, open_and_wait, MobileTarget, OptionsHandle,\n};\nuse crate::{\n  build::Options as BuildOptions,\n  error::Context,\n  helpers::{\n    app_paths::Dirs,\n    config::{get_config as get_tauri_config, ConfigMetadata},\n    flock,\n  },\n  interface::{AppInterface, Options as InterfaceOptions},\n  mobile::{android::generate_tauri_properties, write_options, CliOptions, TargetDevice},\n  ConfigValue, Error, Result,\n};\nuse clap::{ArgAction, Parser};\n\nuse cargo_mobile2::{\n  android::{aab, apk, config::Config as AndroidConfig, env::Env, target::Target},\n  opts::{NoiseLevel, Profile},\n  target::TargetTrait,\n};\n\nuse std::env::set_current_dir;\nuse std::path::Path;\n\n#[derive(Debug, Clone, Parser)]\n#[clap(\n  about = \"Build your app in release mode for Android and generate APKs and AABs\",\n  long_about = \"Build your app in release mode for Android and generate APKs and AABs. It makes use of the `build.frontendDist` property from your `tauri.conf.json` file. It also runs your `build.beforeBuildCommand` which usually builds your frontend into `build.frontendDist`.\"\n)]\npub struct Options {\n  /// Builds with the debug flag\n  #[clap(short, long)]\n  pub debug: bool,\n  /// Which targets to build (all by default).\n  #[clap(\n    short,\n    long = \"target\",\n    action = ArgAction::Append,\n    num_args(0..),\n    value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))\n  )]\n  pub targets: Option<Vec<String>>,\n  /// List of cargo features to activate\n  #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]\n  pub features: Vec<String>,\n  /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file\n  ///\n  /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.\n  ///\n  /// Note that a platform-specific file is looked up and merged with the default file by default\n  /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)\n  /// but you can use this for more specific use cases such as different build flavors.\n  #[clap(short, long)]\n  pub config: Vec<ConfigValue>,\n  /// Whether to split the APKs and AABs per ABIs.\n  #[clap(long)]\n  pub split_per_abi: bool,\n  /// Build APKs.\n  #[clap(long)]\n  pub apk: bool,\n  /// Build AABs.\n  #[clap(long)]\n  pub aab: bool,\n  #[clap(skip)]\n  pub skip_bundle: bool,\n  /// Open Android Studio\n  #[clap(short, long)]\n  pub open: bool,\n  /// Skip prompting for values\n  #[clap(long, env = \"CI\")]\n  pub ci: bool,\n  /// Command line arguments passed to the runner.\n  /// Use `--` to explicitly mark the start of the arguments.\n  /// e.g. `tauri android build -- [runnerArgs]`.\n  #[clap(last(true))]\n  pub args: Vec<String>,\n  /// Do not error out if a version mismatch is detected on a Tauri package.\n  ///\n  /// Only use this when you are sure the mismatch is incorrectly detected as version mismatched Tauri packages can lead to unknown behavior.\n  #[clap(long)]\n  pub ignore_version_mismatches: bool,\n  /// Target device of this build\n  #[clap(skip)]\n  pub target_device: Option<TargetDevice>,\n}\n\nimpl From<Options> for BuildOptions {\n  fn from(options: Options) -> Self {\n    Self {\n      runner: None,\n      debug: options.debug,\n      target: None,\n      features: options.features,\n      bundles: None,\n      no_bundle: false,\n      config: options.config,\n      args: options.args,\n      ci: options.ci,\n      skip_stapling: false,\n      ignore_version_mismatches: options.ignore_version_mismatches,\n      no_sign: false,\n    }\n  }\n}\n\npub struct BuiltApplication {\n  pub config: AndroidConfig,\n  pub interface: AppInterface,\n  // prevent drop\n  #[allow(dead_code)]\n  options_handle: OptionsHandle,\n}\n\npub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplication> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n  let tauri_config = get_tauri_config(\n    tauri_utils::platform::Target::Android,\n    &options\n      .config\n      .iter()\n      .map(|conf| &conf.0)\n      .collect::<Vec<_>>(),\n    dirs.tauri,\n  )?;\n  run(options, noise_level, &dirs, &tauri_config)\n}\n\npub fn run(\n  options: Options,\n  noise_level: NoiseLevel,\n  dirs: &Dirs,\n  tauri_config: &ConfigMetadata,\n) -> Result<BuiltApplication> {\n  delete_codegen_vars();\n\n  let mut build_options: BuildOptions = options.clone().into();\n\n  let first_target = Target::all()\n    .get(\n      options\n        .targets\n        .as_ref()\n        .and_then(|l| l.first().map(|t| t.as_str()))\n        .unwrap_or(Target::DEFAULT_KEY),\n    )\n    .unwrap();\n  build_options.target = Some(first_target.triple.into());\n\n  let interface = AppInterface::new(tauri_config, build_options.target.clone(), dirs.tauri)?;\n  interface.build_options(&mut build_options.args, &mut build_options.features, true);\n\n  let app = get_app(MobileTarget::Android, tauri_config, &interface, dirs.tauri);\n  let (config, metadata) = get_config(\n    &app,\n    tauri_config,\n    &build_options.features,\n    &CliOptions {\n      dev: false,\n      features: build_options.features.clone(),\n      args: build_options.args.clone(),\n      noise_level,\n      vars: Default::default(),\n      config: build_options.config.clone(),\n      target_device: None,\n    },\n  );\n\n  let profile = if options.debug {\n    Profile::Debug\n  } else {\n    Profile::Release\n  };\n\n  set_current_dir(dirs.tauri).context(\"failed to set current directory to Tauri directory\")?;\n\n  ensure_init(\n    tauri_config,\n    config.app(),\n    config.project_dir(),\n    MobileTarget::Android,\n    options.ci,\n  )?;\n\n  let mut env = env(options.ci)?;\n  configure_cargo(&mut env, &config)?;\n\n  generate_tauri_properties(&config, tauri_config, false)?;\n\n  crate::build::setup(&interface, &mut build_options, tauri_config, dirs, true)?;\n\n  let installed_targets =\n    crate::interface::rust::installation::installed_targets().unwrap_or_default();\n\n  if !installed_targets.contains(&first_target.triple().into()) {\n    log::info!(\"Installing target {}\", first_target.triple());\n    first_target\n      .install()\n      .map_err(|error| Error::CommandFailed {\n        command: \"rustup target add\".to_string(),\n        error,\n      })\n      .context(\"failed to install target\")?;\n  }\n  // run an initial build to initialize plugins\n  first_target\n    .build(&config, &metadata, &env, noise_level, true, profile)\n    .context(\"failed to build Android app\")?;\n\n  let open = options.open;\n  let options_handle = run_build(\n    &interface,\n    options,\n    build_options,\n    tauri_config,\n    profile,\n    &config,\n    &mut env,\n    noise_level,\n    dirs.tauri,\n  )?;\n\n  if open {\n    open_and_wait(&config, &env);\n  }\n\n  Ok(BuiltApplication {\n    config,\n    interface,\n    options_handle,\n  })\n}\n\n#[allow(clippy::too_many_arguments)]\nfn run_build(\n  interface: &AppInterface,\n  mut options: Options,\n  build_options: BuildOptions,\n  tauri_config: &ConfigMetadata,\n  profile: Profile,\n  config: &AndroidConfig,\n  env: &mut Env,\n  noise_level: NoiseLevel,\n  tauri_dir: &Path,\n) -> Result<OptionsHandle> {\n  if !(options.skip_bundle || options.apk || options.aab) {\n    // if the user didn't specify the format to build, we'll do both\n    options.apk = true;\n    options.aab = true;\n  }\n\n  let interface_options = InterfaceOptions {\n    debug: build_options.debug,\n    target: build_options.target.clone(),\n    args: build_options.args.clone(),\n    ..Default::default()\n  };\n\n  let app_settings = interface.app_settings();\n  let out_dir = app_settings.out_dir(&interface_options, tauri_dir)?;\n  let _lock = flock::open_rw(out_dir.join(\"lock\").with_extension(\"android\"), \"Android\")?;\n\n  let cli_options = CliOptions {\n    dev: false,\n    features: build_options.features.clone(),\n    args: build_options.args.clone(),\n    noise_level,\n    vars: Default::default(),\n    config: build_options.config,\n    target_device: options.target_device.clone(),\n  };\n  let handle = write_options(tauri_config, cli_options)?;\n\n  inject_resources(config, tauri_config)?;\n\n  let apk_outputs = if options.apk {\n    apk::build(\n      config,\n      env,\n      noise_level,\n      profile,\n      get_targets_or_all(options.targets.clone().unwrap_or_default())?,\n      options.split_per_abi,\n    )\n    .context(\"failed to build APK\")?\n  } else {\n    Vec::new()\n  };\n\n  let aab_outputs = if options.aab {\n    aab::build(\n      config,\n      env,\n      noise_level,\n      profile,\n      get_targets_or_all(options.targets.unwrap_or_default())?,\n      options.split_per_abi,\n    )\n    .context(\"failed to build AAB\")?\n  } else {\n    Vec::new()\n  };\n\n  if !apk_outputs.is_empty() {\n    log_finished(apk_outputs, \"APK\");\n  }\n  if !aab_outputs.is_empty() {\n    log_finished(aab_outputs, \"AAB\");\n  }\n\n  Ok(handle)\n}\n\nfn get_targets_or_all<'a>(targets: Vec<String>) -> Result<Vec<&'a Target<'a>>> {\n  if targets.is_empty() {\n    Ok(Target::all().iter().map(|t| t.1).collect())\n  } else {\n    let mut outs = Vec::new();\n\n    let possible_targets = Target::all()\n      .keys()\n      .map(|key| key.to_string())\n      .collect::<Vec<String>>()\n      .join(\",\");\n\n    for t in targets {\n      let target = Target::for_name(&t).with_context(|| {\n        format!(\"Target {t} is invalid; the possible targets are {possible_targets}\",)\n      })?;\n      outs.push(target);\n    }\n    Ok(outs)\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/android/dev.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{\n  configure_cargo, delete_codegen_vars, device_prompt, ensure_init, env, get_app, get_config,\n  inject_resources, open_and_wait, MobileTarget,\n};\nuse crate::{\n  dev::Options as DevOptions,\n  error::{Context, ErrorExt},\n  helpers::{\n    app_paths::Dirs,\n    config::{get_config as get_tauri_config, ConfigMetadata},\n    flock,\n  },\n  interface::{AppInterface, MobileOptions, Options as InterfaceOptions},\n  mobile::{\n    android::generate_tauri_properties, use_network_address_for_dev_url, write_options, CliOptions,\n    DevChild, DevHost, DevProcess, TargetDevice,\n  },\n  ConfigValue, Error, Result,\n};\nuse clap::{ArgAction, Parser};\n\nuse cargo_mobile2::{\n  android::{\n    config::{Config as AndroidConfig, Metadata as AndroidMetadata},\n    device::Device,\n    env::Env,\n    target::Target,\n  },\n  opts::{FilterLevel, NoiseLevel, Profile},\n  target::TargetTrait,\n};\nuse url::Host;\n\nuse std::{env::set_current_dir, net::Ipv4Addr, path::PathBuf};\n\n#[derive(Debug, Clone, Parser)]\n#[clap(\n  about = \"Run your app in development mode on Android\",\n  long_about = \"Run your app in development mode on Android with hot-reloading for the Rust code. It makes use of the `build.devUrl` property from your `tauri.conf.json` file. It also runs your `build.beforeDevCommand` which usually starts your frontend devServer.\"\n)]\npub struct Options {\n  /// List of cargo features to activate\n  #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]\n  pub features: Vec<String>,\n  /// Exit on panic\n  #[clap(short, long)]\n  exit_on_panic: bool,\n  /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file\n  ///\n  /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.\n  ///\n  /// Note that a platform-specific file is looked up and merged with the default file by default\n  /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)\n  /// but you can use this for more specific use cases such as different build flavors.\n  #[clap(short, long)]\n  pub config: Vec<ConfigValue>,\n  /// Run the code in release mode\n  #[clap(long = \"release\")]\n  pub release_mode: bool,\n  /// Skip waiting for the frontend dev server to start before building the tauri application.\n  #[clap(long, env = \"TAURI_CLI_NO_DEV_SERVER_WAIT\")]\n  pub no_dev_server_wait: bool,\n  /// Disable the file watcher\n  #[clap(long)]\n  pub no_watch: bool,\n  /// Additional paths to watch for changes.\n  #[clap(long)]\n  pub additional_watch_folders: Vec<PathBuf>,\n  /// Open Android Studio instead of trying to run on a connected device\n  #[clap(short, long)]\n  pub open: bool,\n  /// Runs on the given device name\n  pub device: Option<String>,\n  /// Force prompting for an IP to use to connect to the dev server on mobile.\n  #[clap(long)]\n  pub force_ip_prompt: bool,\n  /// Use the public network address for the development server.\n  /// If an actual address it provided, it is used instead of prompting to pick one.\n  ///\n  /// On Windows we use the public network address by default.\n  ///\n  /// This option is particularly useful along the `--open` flag when you intend on running on a physical device.\n  ///\n  /// This replaces the devUrl configuration value to match the public network address host,\n  /// it is your responsibility to set up your development server to listen on this address\n  /// by using 0.0.0.0 as host for instance.\n  ///\n  /// When this is set or when running on an iOS device the CLI sets the `TAURI_DEV_HOST`\n  /// environment variable so you can check this on your framework's configuration to expose the development server\n  /// on the public network address.\n  #[clap(long, default_value_t, default_missing_value(\"\"), num_args(0..=1))]\n  pub host: DevHost,\n  /// Disable the built-in dev server for static files.\n  #[clap(long)]\n  pub no_dev_server: bool,\n  /// Specify port for the built-in dev server for static files. Defaults to 1430.\n  #[clap(long, env = \"TAURI_CLI_PORT\")]\n  pub port: Option<u16>,\n  /// Command line arguments passed to the runner.\n  /// Use `--` to explicitly mark the start of the arguments.\n  /// e.g. `tauri android dev -- [runnerArgs]`.\n  #[clap(last(true))]\n  pub args: Vec<String>,\n  /// Path to the certificate file used by your dev server. Required for mobile dev when using HTTPS.\n  #[clap(long, env = \"TAURI_DEV_ROOT_CERTIFICATE_PATH\")]\n  pub root_certificate_path: Option<PathBuf>,\n}\n\nimpl From<Options> for DevOptions {\n  fn from(options: Options) -> Self {\n    Self {\n      runner: None,\n      target: None,\n      features: options.features,\n      exit_on_panic: options.exit_on_panic,\n      config: options.config,\n      args: options.args,\n      no_watch: options.no_watch,\n      additional_watch_folders: options.additional_watch_folders,\n      no_dev_server_wait: options.no_dev_server_wait,\n      no_dev_server: options.no_dev_server,\n      port: options.port,\n      release_mode: options.release_mode,\n      host: options.host.0.unwrap_or_default(),\n    }\n  }\n}\n\npub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n\n  let result = run_command(options, noise_level, dirs);\n  if result.is_err() {\n    crate::dev::kill_before_dev_process();\n  }\n  result\n}\n\nfn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<()> {\n  delete_codegen_vars();\n  // setup env additions before calling env()\n  if let Some(root_certificate_path) = &options.root_certificate_path {\n    std::env::set_var(\n      \"TAURI_DEV_ROOT_CERTIFICATE\",\n      std::fs::read_to_string(root_certificate_path).fs_context(\n        \"failed to read certificate file\",\n        root_certificate_path.clone(),\n      )?,\n    );\n  }\n\n  let tauri_config = get_tauri_config(\n    tauri_utils::platform::Target::Android,\n    &options\n      .config\n      .iter()\n      .map(|conf| &conf.0)\n      .collect::<Vec<_>>(),\n    dirs.tauri,\n  )?;\n\n  let env = env(false)?;\n  let device = if options.open {\n    None\n  } else {\n    match device_prompt(&env, options.device.as_deref()) {\n      Ok(d) => Some(d),\n      Err(e) => {\n        log::error!(\"{e}\");\n        None\n      }\n    }\n  };\n\n  let mut dev_options: DevOptions = options.clone().into();\n  let target_triple = device\n    .as_ref()\n    .map(|d| d.target().triple.to_string())\n    .unwrap_or_else(|| Target::all().values().next().unwrap().triple.into());\n  dev_options.target = Some(target_triple);\n  dev_options.args.push(\"--lib\".into());\n\n  let interface = AppInterface::new(&tauri_config, dev_options.target.clone(), dirs.tauri)?;\n\n  let app = get_app(MobileTarget::Android, &tauri_config, &interface, dirs.tauri);\n  let (config, metadata) = get_config(\n    &app,\n    &tauri_config,\n    dev_options.features.as_ref(),\n    &CliOptions {\n      dev: true,\n      features: dev_options.features.clone(),\n      args: dev_options.args.clone(),\n      noise_level,\n      vars: Default::default(),\n      config: dev_options.config.clone(),\n      target_device: None,\n    },\n  );\n\n  set_current_dir(dirs.tauri).context(\"failed to set current directory to Tauri directory\")?;\n\n  ensure_init(\n    &tauri_config,\n    config.app(),\n    config.project_dir(),\n    MobileTarget::Android,\n    false,\n  )?;\n  run_dev(\n    interface,\n    options,\n    dev_options,\n    tauri_config,\n    device,\n    env,\n    &config,\n    &metadata,\n    noise_level,\n    &dirs,\n  )\n}\n\n#[allow(clippy::too_many_arguments)]\nfn run_dev(\n  mut interface: AppInterface,\n  options: Options,\n  mut dev_options: DevOptions,\n  mut tauri_config: ConfigMetadata,\n  device: Option<Device>,\n  mut env: Env,\n  config: &AndroidConfig,\n  metadata: &AndroidMetadata,\n  noise_level: NoiseLevel,\n  dirs: &Dirs,\n) -> Result<()> {\n  // when --host is provided or running on a physical device or resolving 0.0.0.0 we must use the network IP\n  if options.host.0.is_some()\n    || device\n      .as_ref()\n      .map(|device| !device.serial_no().starts_with(\"emulator\"))\n      .unwrap_or(false)\n    || tauri_config.build.dev_url.as_ref().is_some_and(|url| {\n      matches!(\n        url.host(),\n        Some(Host::Ipv4(i)) if i == Ipv4Addr::UNSPECIFIED\n      )\n    })\n  {\n    use_network_address_for_dev_url(\n      &mut tauri_config,\n      &mut dev_options,\n      options.force_ip_prompt,\n      dirs.tauri,\n    )?;\n  }\n\n  crate::dev::setup(&interface, &mut dev_options, &mut tauri_config, dirs)?;\n\n  let interface_options = InterfaceOptions {\n    debug: !dev_options.release_mode,\n    target: dev_options.target.clone(),\n    ..Default::default()\n  };\n\n  let app_settings = interface.app_settings();\n  let out_dir = app_settings.out_dir(&interface_options, dirs.tauri)?;\n  let _lock = flock::open_rw(out_dir.join(\"lock\").with_extension(\"android\"), \"Android\")?;\n\n  configure_cargo(&mut env, config)?;\n\n  generate_tauri_properties(config, &tauri_config, true)?;\n\n  let installed_targets =\n    crate::interface::rust::installation::installed_targets().unwrap_or_default();\n\n  // run an initial build to initialize plugins\n  let target_triple = dev_options.target.as_ref().unwrap();\n  let target = Target::all()\n    .values()\n    .find(|t| t.triple == target_triple)\n    .unwrap_or_else(|| Target::all().values().next().unwrap());\n  if !installed_targets.contains(&target.triple().into()) {\n    log::info!(\"Installing target {}\", target.triple());\n    target.install().map_err(|error| Error::CommandFailed {\n      command: \"rustup target add\".to_string(),\n      error,\n    })?;\n  }\n\n  target\n    .build(\n      config,\n      metadata,\n      &env,\n      noise_level,\n      true,\n      if options.release_mode {\n        Profile::Release\n      } else {\n        Profile::Debug\n      },\n    )\n    .context(\"failed to build Android app\")?;\n\n  let open = options.open;\n  interface.mobile_dev(\n    &mut tauri_config,\n    MobileOptions {\n      debug: !options.release_mode,\n      features: options.features,\n      args: options.args,\n      config: dev_options.config.clone(),\n      no_watch: options.no_watch,\n      additional_watch_folders: options.additional_watch_folders,\n    },\n    |options, tauri_config| {\n      let cli_options = CliOptions {\n        dev: true,\n        features: options.features.clone(),\n        args: options.args.clone(),\n        noise_level,\n        vars: Default::default(),\n        config: dev_options.config.clone(),\n        target_device: device.as_ref().map(|d| TargetDevice {\n          id: d.serial_no().to_string(),\n          name: d.name().to_string(),\n        }),\n      };\n\n      let _handle = write_options(tauri_config, cli_options)?;\n\n      inject_resources(config, tauri_config)?;\n\n      if open {\n        open_and_wait(config, &env)\n      } else if let Some(device) = &device {\n        match run(device, options, config, &env, metadata, noise_level) {\n          Ok(c) => Ok(Box::new(c) as Box<dyn DevProcess + Send>),\n          Err(e) => {\n            crate::dev::kill_before_dev_process();\n            Err(e)\n          }\n        }\n      } else {\n        open_and_wait(config, &env)\n      }\n    },\n    dirs,\n  )\n}\n\nfn run(\n  device: &Device<'_>,\n  options: MobileOptions,\n  config: &AndroidConfig,\n  env: &Env,\n  metadata: &AndroidMetadata,\n  noise_level: NoiseLevel,\n) -> crate::Result<DevChild> {\n  let profile = if options.debug {\n    Profile::Debug\n  } else {\n    Profile::Release\n  };\n\n  let build_app_bundle = metadata.asset_packs().is_some();\n\n  device\n    .run(\n      config,\n      env,\n      noise_level,\n      profile,\n      Some(match noise_level {\n        NoiseLevel::Polite => FilterLevel::Info,\n        NoiseLevel::LoudAndProud => FilterLevel::Debug,\n        NoiseLevel::FranklyQuitePedantic => FilterLevel::Verbose,\n      }),\n      build_app_bundle,\n      false,\n      \".MainActivity\".into(),\n    )\n    .map(DevChild::new)\n    .context(\"failed to run Android app\")\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/android/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse cargo_mobile2::{\n  android::{\n    adb,\n    config::{Config as AndroidConfig, Metadata as AndroidMetadata, Raw as RawAndroidConfig},\n    device::Device,\n    emulator,\n    env::Env,\n    target::Target,\n  },\n  config::app::{App, DEFAULT_ASSET_DIR},\n  opts::{FilterLevel, NoiseLevel},\n  os,\n  target::TargetTrait,\n  util::prompt,\n};\nuse clap::{Parser, Subcommand};\nuse semver::Version;\nuse std::{\n  env::set_var,\n  fs::{create_dir, create_dir_all, read_dir, write},\n  io::Cursor,\n  path::{Path, PathBuf},\n  process::{exit, Command},\n  thread::sleep,\n  time::Duration,\n};\nuse sublime_fuzzy::best_match;\nuse tauri_utils::resources::ResourcePaths;\n\nuse super::{\n  ensure_init, get_app, init::command as init_command, log_finished, read_options, CliOptions,\n  OptionsHandle, Target as MobileTarget, MIN_DEVICE_MATCH_SCORE,\n};\nuse crate::{\n  error::Context,\n  helpers::config::{BundleResources, Config as TauriConfig},\n  ConfigValue, Error, ErrorExt, Result,\n};\n\nmod android_studio_script;\nmod build;\nmod dev;\npub(crate) mod project;\nmod run;\n\nconst NDK_VERSION: &str = \"29.0.13846066\";\nconst SDK_VERSION: u8 = 36;\n\n#[cfg(target_os = \"macos\")]\nconst CMDLINE_TOOLS_URL: &str =\n  \"https://dl.google.com/android/repository/commandlinetools-mac-13114758_latest.zip\";\n#[cfg(target_os = \"linux\")]\nconst CMDLINE_TOOLS_URL: &str =\n  \"https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip\";\n#[cfg(windows)]\nconst CMDLINE_TOOLS_URL: &str =\n  \"https://dl.google.com/android/repository/commandlinetools-win-13114758_latest.zip\";\n\n#[derive(Parser)]\n#[clap(\n  author,\n  version,\n  about = \"Android commands\",\n  subcommand_required(true),\n  arg_required_else_help(true)\n)]\npub struct Cli {\n  #[clap(subcommand)]\n  command: Commands,\n}\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Initialize Android target in the project\")]\npub struct InitOptions {\n  /// Skip prompting for values\n  #[clap(long, env = \"CI\")]\n  ci: bool,\n  /// Skips installing rust toolchains via rustup\n  #[clap(long)]\n  skip_targets_install: bool,\n  /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file\n  ///\n  /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.\n  ///\n  /// Note that a platform-specific file is looked up and merged with the default file by default\n  /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)\n  /// but you can use this for more specific use cases such as different build flavors.\n  #[clap(short, long)]\n  pub config: Vec<ConfigValue>,\n}\n\n#[derive(Subcommand)]\nenum Commands {\n  Init(InitOptions),\n  Dev(dev::Options),\n  Build(build::Options),\n  Run(run::Options),\n  #[clap(hide(true))]\n  AndroidStudioScript(android_studio_script::Options),\n}\n\npub fn command(cli: Cli, verbosity: u8) -> Result<()> {\n  let noise_level = NoiseLevel::from_occurrences(verbosity as u64);\n  match cli.command {\n    Commands::Init(options) => init_command(\n      MobileTarget::Android,\n      options.ci,\n      false,\n      options.skip_targets_install,\n      options.config,\n    )?,\n    Commands::Dev(options) => dev::command(options, noise_level)?,\n    Commands::Build(options) => build::command(options, noise_level).map(|_| ())?,\n    Commands::Run(options) => run::command(options, noise_level)?,\n    Commands::AndroidStudioScript(options) => android_studio_script::command(options)?,\n  }\n\n  Ok(())\n}\n\npub fn get_config(\n  app: &App,\n  config: &TauriConfig,\n  features: &[String],\n  cli_options: &CliOptions,\n) -> (AndroidConfig, AndroidMetadata) {\n  let mut android_options = cli_options.clone();\n  android_options.features.extend_from_slice(features);\n\n  let raw = RawAndroidConfig {\n    features: Some(android_options.features.clone()),\n    logcat_filter_specs: vec![\n      \"RustStdoutStderr\".into(),\n      format!(\n        \"*:{}\",\n        match cli_options.noise_level {\n          NoiseLevel::Polite => FilterLevel::Info,\n          NoiseLevel::LoudAndProud => FilterLevel::Debug,\n          NoiseLevel::FranklyQuitePedantic => FilterLevel::Verbose,\n        }\n        .logcat()\n      ),\n    ],\n    min_sdk_version: Some(config.bundle.android.min_sdk_version),\n    ..Default::default()\n  };\n  let config = AndroidConfig::from_raw(app.clone(), Some(raw)).unwrap();\n\n  let metadata = AndroidMetadata {\n    supported: true,\n    cargo_args: Some(android_options.args),\n    features: Some(android_options.features),\n    ..Default::default()\n  };\n\n  set_var(\n    \"WRY_ANDROID_PACKAGE\",\n    app.android_identifier_escape_kotlin_keyword(),\n  );\n  set_var(\"TAURI_ANDROID_PACKAGE_UNESCAPED\", app.identifier());\n  set_var(\"WRY_ANDROID_LIBRARY\", app.lib_name());\n  set_var(\"TAURI_ANDROID_PROJECT_PATH\", config.project_dir());\n\n  let src_main_dir = config\n    .project_dir()\n    .join(\"app/src/main\")\n    .join(format!(\"java/{}\", app.identifier().replace('.', \"/\"),));\n  if config.project_dir().exists() {\n    if src_main_dir.exists() {\n      let _ = create_dir(src_main_dir.join(\"generated\"));\n    } else {\n      log::error!(\n      \"Project directory {} does not exist. Did you update the package name in `Cargo.toml` or the bundle identifier in `tauri.conf.json > identifier`? Save your changes, delete the `gen/android` folder and run `tauri android init` to recreate the Android project.\",\n      src_main_dir.display()\n    );\n      exit(1);\n    }\n  }\n  set_var(\n    \"WRY_ANDROID_KOTLIN_FILES_OUT_DIR\",\n    src_main_dir.join(\"generated\"),\n  );\n\n  (config, metadata)\n}\n\npub fn env(non_interactive: bool) -> Result<Env> {\n  let env = super::env().context(\"failed to setup Android environment\")?;\n  ensure_env(non_interactive).context(\"failed to ensure Android environment\")?;\n  cargo_mobile2::android::env::Env::from_env(env).context(\"failed to load Android environment\")\n}\n\nfn download_cmdline_tools(extract_path: &Path) -> Result<()> {\n  log::info!(\"Downloading Android command line tools...\");\n\n  let mut response = crate::helpers::http::get(CMDLINE_TOOLS_URL)\n    .context(\"failed to download Android command line tools\")?;\n  let body = response\n    .body_mut()\n    .with_config()\n    .limit(200 * 1024 * 1024 /* 200MB */)\n    .read_to_vec()\n    .context(\"failed to read Android command line tools download response\")?;\n\n  let mut zip = zip::ZipArchive::new(Cursor::new(body))\n    .context(\"failed to create zip archive from Android command line tools download response\")?;\n\n  log::info!(\n    \"Extracting Android command line tools to {}\",\n    extract_path.display()\n  );\n  zip\n    .extract(extract_path)\n    .context(\"failed to extract Android command line tools\")?;\n\n  Ok(())\n}\n\nfn ensure_env(non_interactive: bool) -> Result<()> {\n  ensure_java()?;\n  ensure_sdk(non_interactive)?;\n  ensure_ndk(non_interactive)?;\n  Ok(())\n}\n\nfn ensure_java() -> Result<()> {\n  if std::env::var_os(\"JAVA_HOME\").is_none() {\n    #[cfg(windows)]\n    let default_java_home = \"C:\\\\Program Files\\\\Android\\\\Android Studio\\\\jbr\";\n    #[cfg(target_os = \"macos\")]\n    let default_java_home = \"/Applications/Android Studio.app/Contents/jbr/Contents/Home\";\n    #[cfg(target_os = \"linux\")]\n    let default_java_home = \"/opt/android-studio/jbr\";\n\n    if Path::new(default_java_home).exists() {\n      log::info!(\"Using Android Studio's default Java installation: {default_java_home}\");\n      std::env::set_var(\"JAVA_HOME\", default_java_home);\n    } else if which::which(\"java\").is_err() {\n      crate::error::bail!(\"Java not found in PATH, default Android Studio Java installation not found at {default_java_home} and JAVA_HOME environment variable not set. Please install Java before proceeding\");\n    }\n  }\n\n  Ok(())\n}\n\nfn ensure_sdk(non_interactive: bool) -> Result<()> {\n  let android_home = std::env::var_os(\"ANDROID_HOME\")\n    .or_else(|| std::env::var_os(\"ANDROID_SDK_ROOT\"))\n    .map(PathBuf::from);\n  if !android_home.as_ref().is_some_and(|v| v.exists()) {\n    log::info!(\n      \"ANDROID_HOME {}, trying to locate Android SDK...\",\n      if let Some(v) = &android_home {\n        format!(\"not found at {}\", v.display())\n      } else {\n        \"not set\".into()\n      }\n    );\n\n    #[cfg(target_os = \"macos\")]\n    let default_android_home = dirs::home_dir().unwrap().join(\"Library/Android/sdk\");\n    #[cfg(target_os = \"linux\")]\n    let default_android_home = dirs::home_dir().unwrap().join(\"Android/Sdk\");\n    #[cfg(windows)]\n    let default_android_home = dirs::data_local_dir().unwrap().join(\"Android/Sdk\");\n\n    if default_android_home.exists() {\n      log::info!(\n        \"Using installed Android SDK: {}\",\n        default_android_home.display()\n      );\n    } else if non_interactive {\n      crate::error::bail!(\"Android SDK not found. Make sure the SDK and NDK are installed and the ANDROID_HOME and NDK_HOME environment variables are set.\");\n    } else {\n      log::error!(\n        \"Android SDK not found at {}\",\n        default_android_home.display()\n      );\n\n      let extract_path = if create_dir_all(&default_android_home).is_ok() {\n        default_android_home.clone()\n      } else {\n        std::env::current_dir().context(\"failed to get current directory\")?\n      };\n\n      let sdk_manager_path = extract_path\n        .join(\"cmdline-tools/bin/sdkmanager\")\n        .with_extension(if cfg!(windows) { \"bat\" } else { \"\" });\n\n      let mut granted_permission_to_install = false;\n\n      if !sdk_manager_path.exists() {\n        granted_permission_to_install = crate::helpers::prompts::confirm(\n          \"Do you want to install the Android Studio command line tools to setup the Android SDK?\",\n          Some(false),\n        )\n        .unwrap_or_default();\n\n        if !granted_permission_to_install {\n          crate::error::bail!(\"Skipping Android Studio command line tools installation. Please go through the manual setup process described in the documentation: https://tauri.app/start/prerequisites/#android\");\n        }\n\n        download_cmdline_tools(&extract_path)?;\n      }\n\n      if !granted_permission_to_install {\n        granted_permission_to_install = crate::helpers::prompts::confirm(\n          \"Do you want to install the Android SDK using the command line tools?\",\n          Some(false),\n        )\n        .unwrap_or_default();\n\n        if !granted_permission_to_install {\n          crate::error::bail!(\"Skipping Android Studio SDK installation. Please go through the manual setup process described in the documentation: https://tauri.app/start/prerequisites/#android\");\n        }\n      }\n\n      log::info!(\"Running sdkmanager to install platform-tools, android-{SDK_VERSION} and ndk-{NDK_VERSION} on {}...\", default_android_home.display());\n      let status = Command::new(&sdk_manager_path)\n        .arg(format!(\"--sdk_root={}\", default_android_home.display()))\n        .arg(\"--install\")\n        .arg(\"platform-tools\")\n        .arg(format!(\"platforms;android-{SDK_VERSION}\"))\n        .arg(format!(\"ndk;{NDK_VERSION}\"))\n        .status()\n        .map_err(|error| Error::CommandFailed {\n          command: format!(\"{} --sdk_root={} --install platform-tools platforms;android-{SDK_VERSION} ndk;{NDK_VERSION}\", sdk_manager_path.display(), default_android_home.display()),\n          error,\n        })?;\n\n      if !status.success() {\n        crate::error::bail!(\"Failed to install Android SDK\");\n      }\n    }\n\n    std::env::set_var(\"ANDROID_HOME\", default_android_home);\n  }\n\n  Ok(())\n}\n\nfn ensure_ndk(non_interactive: bool) -> Result<()> {\n  // re-evaluate ANDROID_HOME\n  let android_home = std::env::var_os(\"ANDROID_HOME\")\n    .or_else(|| std::env::var_os(\"ANDROID_SDK_ROOT\"))\n    .map(PathBuf::from)\n    .context(\"Failed to locate Android SDK\")?;\n  let mut installed_ndks = read_dir(android_home.join(\"ndk\"))\n    .map(|dir| {\n      dir\n        .into_iter()\n        .flat_map(|e| e.ok().map(|e| e.path()))\n        .collect::<Vec<_>>()\n    })\n    .unwrap_or_default();\n  installed_ndks.sort();\n\n  if let Some(ndk) = installed_ndks.last() {\n    log::info!(\"Using installed NDK: {}\", ndk.display());\n    std::env::set_var(\"NDK_HOME\", ndk);\n  } else if non_interactive {\n    crate::error::bail!(\"Android NDK not found. Make sure the NDK is installed and the NDK_HOME environment variable is set.\");\n  } else {\n    let sdk_manager_path = android_home\n      .join(\"cmdline-tools/bin/sdkmanager\")\n      .with_extension(if cfg!(windows) { \"bat\" } else { \"\" });\n\n    let mut granted_permission_to_install = false;\n\n    if !sdk_manager_path.exists() {\n      granted_permission_to_install = crate::helpers::prompts::confirm(\n        \"Do you want to install the Android Studio command line tools to setup the Android NDK?\",\n        Some(false),\n      )\n      .unwrap_or_default();\n\n      if !granted_permission_to_install {\n        crate::error::bail!(\"Skipping Android Studio command line tools installation. Please go through the manual setup process described in the documentation: https://tauri.app/start/prerequisites/#android\");\n      }\n\n      download_cmdline_tools(&android_home)?;\n    }\n\n    if !granted_permission_to_install {\n      granted_permission_to_install = crate::helpers::prompts::confirm(\n        \"Do you want to install the Android NDK using the command line tools?\",\n        Some(false),\n      )\n      .unwrap_or_default();\n\n      if !granted_permission_to_install {\n        crate::error::bail!(\"Skipping Android Studio NDK installation. Please go through the manual setup process described in the documentation: https://tauri.app/start/prerequisites/#android\");\n      }\n    }\n\n    log::info!(\n      \"Running sdkmanager to install ndk-{NDK_VERSION} on {}...\",\n      android_home.display()\n    );\n    let status = Command::new(&sdk_manager_path)\n      .arg(format!(\"--sdk_root={}\", android_home.display()))\n      .arg(\"--install\")\n      .arg(format!(\"ndk;{NDK_VERSION}\"))\n      .status()\n      .map_err(|error| Error::CommandFailed {\n        command: format!(\n          \"{} --sdk_root={} --install ndk;{NDK_VERSION}\",\n          sdk_manager_path.display(),\n          android_home.display()\n        ),\n        error,\n      })?;\n\n    if !status.success() {\n      crate::error::bail!(\"Failed to install Android NDK\");\n    }\n\n    let ndk_path = android_home.join(\"ndk\").join(NDK_VERSION);\n    log::info!(\"Installed NDK: {}\", ndk_path.display());\n    std::env::set_var(\"NDK_HOME\", ndk_path);\n  }\n\n  Ok(())\n}\n\nfn delete_codegen_vars() {\n  for (k, _) in std::env::vars() {\n    if k.starts_with(\"WRY_\") && (k.ends_with(\"CLASS_EXTENSION\") || k.ends_with(\"CLASS_INIT\")) {\n      std::env::remove_var(k);\n    }\n  }\n}\n\nfn adb_device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {\n  let device_list = adb::device_list(env).context(\"failed to detect connected Android devices\")?;\n  if !device_list.is_empty() {\n    let device = if let Some(t) = target {\n      let (device, score) = device_list\n        .into_iter()\n        .rev()\n        .map(|d| {\n          let score = best_match(t, d.name()).map_or(0, |m| m.score());\n          (d, score)\n        })\n        .max_by_key(|(_, score)| *score)\n        // we already checked the list is not empty\n        .unwrap();\n      if score > MIN_DEVICE_MATCH_SCORE {\n        device\n      } else {\n        crate::error::bail!(\"Could not find an Android device matching {t}\")\n      }\n    } else if device_list.len() > 1 {\n      let index = prompt::list(\n        concat!(\"Detected \", \"Android\", \" devices\"),\n        device_list.iter(),\n        \"device\",\n        None,\n        \"Device\",\n      )\n      .context(\"failed to prompt for device\")?;\n      device_list.into_iter().nth(index).unwrap()\n    } else {\n      device_list.into_iter().next().unwrap()\n    };\n\n    log::info!(\n      \"Detected connected device: {} with target {:?}\",\n      device,\n      device.target().triple,\n    );\n    Ok(device)\n  } else {\n    Err(Error::GenericError(\n      \"No connected Android devices detected\".to_string(),\n    ))\n  }\n}\n\nfn emulator_prompt(env: &'_ Env, target: Option<&str>) -> Result<emulator::Emulator> {\n  let emulator_list = emulator::avd_list(env).unwrap_or_default();\n  if !emulator_list.is_empty() {\n    let emulator = if let Some(t) = target {\n      let (device, score) = emulator_list\n        .into_iter()\n        .rev()\n        .map(|d| {\n          let score = best_match(t, d.name()).map_or(0, |m| m.score());\n          (d, score)\n        })\n        .max_by_key(|(_, score)| *score)\n        // we already checked the list is not empty\n        .unwrap();\n      if score > MIN_DEVICE_MATCH_SCORE {\n        device\n      } else {\n        crate::error::bail!(\"Could not find an Android Emulator matching {t}\")\n      }\n    } else if emulator_list.len() > 1 {\n      let index = prompt::list(\n        concat!(\"Detected \", \"Android\", \" emulators\"),\n        emulator_list.iter(),\n        \"emulator\",\n        None,\n        \"Emulator\",\n      )\n      .context(\"failed to prompt for emulator\")?;\n      emulator_list.into_iter().nth(index).unwrap()\n    } else {\n      emulator_list.into_iter().next().unwrap()\n    };\n\n    Ok(emulator)\n  } else {\n    Err(Error::GenericError(\n      \"No available Android Emulator detected\".to_string(),\n    ))\n  }\n}\n\nfn device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {\n  if let Ok(device) = adb_device_prompt(env, target) {\n    Ok(device)\n  } else {\n    let emulator = emulator_prompt(env, target)?;\n    log::info!(\"Starting emulator {}\", emulator.name());\n    emulator\n      .start_detached(env)\n      .context(\"failed to start emulator\")?;\n    let mut tries = 0;\n    loop {\n      sleep(Duration::from_secs(2));\n      if let Ok(device) = adb_device_prompt(env, Some(emulator.name())) {\n        return Ok(device);\n      }\n      if tries >= 3 {\n        log::info!(\"Waiting for emulator to start... (maybe the emulator is unauthorized or offline, run `adb devices` to check)\");\n      } else {\n        log::info!(\"Waiting for emulator to start...\");\n      }\n      tries += 1;\n    }\n  }\n}\n\nfn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {\n  device_prompt(env, None).map(|device| device.target()).ok()\n}\n\nfn open_and_wait(config: &AndroidConfig, env: &Env) -> ! {\n  log::info!(\"Opening Android Studio\");\n  if let Err(e) = os::open_file_with(\"Android Studio\", config.project_dir(), &env.base) {\n    log::error!(\"{e}\");\n  }\n  loop {\n    sleep(Duration::from_secs(24 * 60 * 60));\n  }\n}\n\nfn inject_resources(config: &AndroidConfig, tauri_config: &TauriConfig) -> Result<()> {\n  let asset_dir = config\n    .project_dir()\n    .join(\"app/src/main\")\n    .join(DEFAULT_ASSET_DIR);\n  create_dir_all(&asset_dir).fs_context(\"failed to create asset directory\", asset_dir.clone())?;\n\n  write(\n    asset_dir.join(\"tauri.conf.json\"),\n    serde_json::to_string(&tauri_config).with_context(|| \"failed to serialize tauri config\")?,\n  )\n  .fs_context(\n    \"failed to write tauri config\",\n    asset_dir.join(\"tauri.conf.json\"),\n  )?;\n\n  let resources = match &tauri_config.bundle.resources {\n    Some(BundleResources::List(paths)) => Some(ResourcePaths::new(paths.as_slice(), true)),\n    Some(BundleResources::Map(map)) => Some(ResourcePaths::from_map(map, true)),\n    None => None,\n  };\n  if let Some(resources) = resources {\n    for resource in resources.iter() {\n      let resource = resource.context(\"failed to get resource\")?;\n      let dest = asset_dir.join(resource.target());\n      crate::helpers::fs::copy_file(resource.path(), dest).context(\"failed to copy resource\")?;\n    }\n  }\n\n  Ok(())\n}\n\nfn configure_cargo(env: &mut Env, config: &AndroidConfig) -> Result<()> {\n  for target in Target::all().values() {\n    let config = target\n      .generate_cargo_config(config, env)\n      .context(\"failed to find Android tool\")?;\n    let target_var_name = target.triple.replace('-', \"_\").to_uppercase();\n    if let Some(linker) = config.linker {\n      env.base.insert_env_var(\n        format!(\"CARGO_TARGET_{target_var_name}_LINKER\"),\n        linker.into(),\n      );\n    }\n    env.base.insert_env_var(\n      format!(\"CARGO_TARGET_{target_var_name}_RUSTFLAGS\"),\n      config.rustflags.join(\" \").into(),\n    );\n  }\n\n  Ok(())\n}\n\nfn generate_tauri_properties(\n  config: &AndroidConfig,\n  tauri_config: &TauriConfig,\n  dev: bool,\n) -> Result<()> {\n  let app_tauri_properties_path = config.project_dir().join(\"app\").join(\"tauri.properties\");\n\n  let mut app_tauri_properties = Vec::new();\n  if let Some(version) = tauri_config.version.as_ref() {\n    app_tauri_properties.push(format!(\"tauri.android.versionName={version}\"));\n    if tauri_config.bundle.android.auto_increment_version_code && !dev {\n      let last_version_code = std::fs::read_to_string(&app_tauri_properties_path)\n        .ok()\n        .and_then(|content| {\n          content\n            .lines()\n            .find(|line| line.starts_with(\"tauri.android.versionCode=\"))\n            .and_then(|line| line.split('=').nth(1))\n            .and_then(|s| s.trim().parse::<u32>().ok())\n        });\n      let new_version_code = last_version_code.map(|v| v.saturating_add(1)).unwrap_or(1);\n      app_tauri_properties.push(format!(\"tauri.android.versionCode={new_version_code}\"));\n    } else if let Some(version_code) = tauri_config.bundle.android.version_code.as_ref() {\n      app_tauri_properties.push(format!(\"tauri.android.versionCode={version_code}\"));\n    } else if let Ok(version) = Version::parse(version) {\n      let mut version_code = version.major * 1000000 + version.minor * 1000 + version.patch;\n\n      if dev {\n        version_code = version_code.clamp(1, 2100000000);\n      }\n\n      if version_code == 0 {\n        crate::error::bail!(\n          \"You must change the `version` in `tauri.conf.json`. The default value `0.0.0` is not allowed for Android package and must be at least `0.0.1`.\"\n        );\n      } else if version_code > 2100000000 {\n        crate::error::bail!(\n          \"Invalid version code {}. Version code must be between 1 and 2100000000. You must change the `version` in `tauri.conf.json`.\",\n          version_code\n        );\n      }\n\n      app_tauri_properties.push(format!(\"tauri.android.versionCode={version_code}\"));\n    }\n  }\n\n  if !app_tauri_properties.is_empty() {\n    let app_tauri_properties_content = format!(\n      \"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\\n{}\",\n      app_tauri_properties.join(\"\\n\")\n    );\n    if std::fs::read_to_string(&app_tauri_properties_path)\n      .map(|o| o != app_tauri_properties_content)\n      .unwrap_or(true)\n    {\n      write(&app_tauri_properties_path, app_tauri_properties_content)\n        .context(\"failed to write tauri.properties\")?;\n    }\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/android/project.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::{Context, ErrorExt},\n  helpers::template,\n  Error, Result,\n};\nuse cargo_mobile2::{\n  android::{\n    config::{Config, Metadata},\n    target::Target,\n  },\n  config::app::DEFAULT_ASSET_DIR,\n  os,\n  target::TargetTrait as _,\n  util::{\n    self,\n    cli::{Report, TextWrapper},\n    prefix_path,\n  },\n};\nuse handlebars::Handlebars;\nuse include_dir::{include_dir, Dir};\n\nuse std::{\n  ffi::OsStr,\n  fs,\n  path::{Path, PathBuf},\n};\n\nconst TEMPLATE_DIR: Dir<'_> = include_dir!(\"$CARGO_MANIFEST_DIR/templates/mobile/android\");\n\npub fn gen(\n  config: &Config,\n  metadata: &Metadata,\n  (handlebars, mut map): (Handlebars, template::JsonMap),\n  wrapper: &TextWrapper,\n  skip_targets_install: bool,\n) -> Result<()> {\n  if !skip_targets_install {\n    let installed_targets =\n      crate::interface::rust::installation::installed_targets().unwrap_or_default();\n    let missing_targets = Target::all()\n      .values()\n      .filter(|t| !installed_targets.contains(&t.triple().into()))\n      .collect::<Vec<&Target>>();\n\n    if !missing_targets.is_empty() {\n      log::info!(\"Installing Android Rust targets...\");\n      for target in missing_targets {\n        log::info!(\"Installing target {}\", target.triple());\n        target.install().map_err(|error| Error::CommandFailed {\n          command: \"rustup target add\".to_string(),\n          error,\n        })?;\n      }\n    }\n  }\n  println!(\"Generating Android Studio project...\");\n  let dest = config.project_dir();\n  let asset_packs = metadata.asset_packs().unwrap_or_default();\n\n  map.insert(\n    \"root-dir-rel\",\n    Path::new(&os::replace_path_separator(\n      util::relativize_path(\n        config.app().root_dir(),\n        config.project_dir().join(config.app().name_snake()),\n      )\n      .into_os_string(),\n    )),\n  );\n  map.insert(\"root-dir\", config.app().root_dir());\n  map.insert(\n    \"abi-list\",\n    Target::all()\n      .values()\n      .map(|target| target.abi)\n      .collect::<Vec<_>>(),\n  );\n  map.insert(\"target-list\", Target::all().keys().collect::<Vec<_>>());\n  map.insert(\n    \"arch-list\",\n    Target::all()\n      .values()\n      .map(|target| target.arch)\n      .collect::<Vec<_>>(),\n  );\n  map.insert(\"android-app-plugins\", metadata.app_plugins());\n  map.insert(\n    \"android-project-dependencies\",\n    metadata.project_dependencies(),\n  );\n  map.insert(\"android-app-dependencies\", metadata.app_dependencies());\n  map.insert(\n    \"android-app-dependencies-platform\",\n    metadata.app_dependencies_platform(),\n  );\n  map.insert(\n    \"has-code\",\n    metadata.project_dependencies().is_some()\n      || metadata.app_dependencies().is_some()\n      || metadata.app_dependencies_platform().is_some(),\n  );\n  map.insert(\"has-asset-packs\", !asset_packs.is_empty());\n  map.insert(\n    \"asset-packs\",\n    asset_packs\n      .iter()\n      .map(|p| p.name.as_str())\n      .collect::<Vec<_>>(),\n  );\n  map.insert(\"windows\", cfg!(windows));\n\n  let identifier = config.app().identifier().replace('.', \"/\");\n  let package_path = format!(\"java/{identifier}\");\n\n  map.insert(\"package-path\", &package_path);\n\n  let mut created_dirs = Vec::new();\n  template::render_with_generator(\n    &handlebars,\n    map.inner(),\n    &TEMPLATE_DIR,\n    &dest,\n    &mut |path| generate_out_file(&path, &dest, &package_path, &mut created_dirs),\n  )\n  .with_context(|| \"failed to process template\")?;\n\n  if !asset_packs.is_empty() {\n    Report::action_request(\n      \"When running from Android Studio, you must first set your deployment option to \\\"APK from app bundle\\\".\",\n      \"Android Studio will not be able to find your asset packs otherwise. The option can be found under \\\"Run > Edit Configurations > Deploy\\\".\"\n    ).print(wrapper);\n  }\n\n  let source_dest = dest.join(\"app\");\n  for source in metadata.app_sources() {\n    let source_src = config.app().root_dir().join(source);\n    let source_file = source_src\n      .file_name()\n      .with_context(|| format!(\"asset source {} is invalid\", source_src.display()))?;\n    fs::copy(&source_src, source_dest.join(source_file))\n      .fs_context(\"failed to copy asset\", source_src)?;\n  }\n\n  let dest = prefix_path(dest, \"app/src/main/\");\n  fs::create_dir_all(&dest).fs_context(\"failed to create directory\", dest.clone())?;\n\n  let asset_dir = dest.join(DEFAULT_ASSET_DIR);\n  if !asset_dir.is_dir() {\n    fs::create_dir_all(&asset_dir).fs_context(\"failed to create asset dir\", asset_dir)?;\n  }\n\n  Ok(())\n}\n\nfn generate_out_file(\n  path: &Path,\n  dest: &Path,\n  package_path: &str,\n  created_dirs: &mut Vec<PathBuf>,\n) -> std::io::Result<Option<fs::File>> {\n  let mut iter = path.iter();\n  let root = iter.next().unwrap().to_str().unwrap();\n  let path_without_root: std::path::PathBuf = iter.collect();\n  let path = match (\n    root,\n    path.extension().and_then(|o| o.to_str()),\n    path_without_root.strip_prefix(\"src/main\"),\n  ) {\n    (\"app\" | \"buildSrc\", Some(\"kt\"), Ok(path)) => {\n      let parent = path.parent().unwrap();\n      let file_name = path.file_name().unwrap();\n      let out_dir = dest\n        .join(root)\n        .join(\"src/main\")\n        .join(package_path)\n        .join(parent);\n      out_dir.join(file_name)\n    }\n    _ => dest.join(path),\n  };\n\n  let parent = path.parent().unwrap().to_path_buf();\n  if !created_dirs.contains(&parent) {\n    fs::create_dir_all(&parent)?;\n    created_dirs.push(parent);\n  }\n\n  let mut options = fs::OpenOptions::new();\n  options.write(true);\n\n  #[cfg(unix)]\n  if path.file_name().unwrap() == OsStr::new(\"gradlew\") {\n    use std::os::unix::fs::OpenOptionsExt;\n    options.mode(0o755);\n  }\n\n  if path.file_name().unwrap() == OsStr::new(\"BuildTask.kt\") {\n    options.truncate(true).create(true).open(path).map(Some)\n  } else if !path.exists() {\n    options.create(true).open(path).map(Some)\n  } else {\n    Ok(None)\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/android/run.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse cargo_mobile2::{\n  android::target::Target,\n  opts::{FilterLevel, NoiseLevel, Profile},\n  target::TargetTrait,\n};\nuse clap::{ArgAction, Parser};\nuse std::path::PathBuf;\n\nuse super::{configure_cargo, device_prompt, env};\nuse crate::{\n  error::Context,\n  helpers::config::ConfigMetadata,\n  interface::{DevProcess, WatcherOptions},\n  mobile::{DevChild, TargetDevice},\n  ConfigValue, Result,\n};\n\n#[derive(Debug, Clone, Parser)]\n#[clap(\n  about = \"Run your app in production mode on Android\",\n  long_about = \"Run your app in production mode on Android. It makes use of the `build.frontendDist` property from your `tauri.conf.json` file. It also runs your `build.beforeBuildCommand` which usually builds your frontend into `build.frontendDist`.\"\n)]\npub struct Options {\n  /// Run the app in release mode\n  #[clap(short, long)]\n  pub release: bool,\n  /// List of cargo features to activate\n  #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]\n  pub features: Vec<String>,\n  /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file\n  ///\n  /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.\n  ///\n  /// Note that a platform-specific file is looked up and merged with the default file by default\n  /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)\n  /// but you can use this for more specific use cases such as different build flavors.\n  #[clap(short, long)]\n  pub config: Vec<ConfigValue>,\n  /// Disable the file watcher\n  #[clap(long)]\n  pub no_watch: bool,\n  /// Additional paths to watch for changes.\n  #[clap(long)]\n  pub additional_watch_folders: Vec<PathBuf>,\n  /// Open Android Studio\n  #[clap(short, long)]\n  pub open: bool,\n  /// Runs on the given device name\n  pub device: Option<String>,\n  /// Command line arguments passed to the runner.\n  /// Use `--` to explicitly mark the start of the arguments.\n  /// e.g. `tauri android build -- [runnerArgs]`.\n  #[clap(last(true))]\n  pub args: Vec<String>,\n  /// Do not error out if a version mismatch is detected on a Tauri package.\n  ///\n  /// Only use this when you are sure the mismatch is incorrectly detected as version mismatched Tauri packages can lead to unknown behavior.\n  #[clap(long)]\n  pub ignore_version_mismatches: bool,\n}\n\npub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {\n  let mut env = env(false)?;\n\n  let device = if options.open {\n    None\n  } else {\n    match device_prompt(&env, options.device.as_deref()) {\n      Ok(d) => Some(d),\n      Err(e) => {\n        log::error!(\"{e}\");\n        None\n      }\n    }\n  };\n\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n  let mut tauri_config = crate::helpers::config::get_config(\n    tauri_utils::platform::Target::Android,\n    &options\n      .config\n      .iter()\n      .map(|conf| &conf.0)\n      .collect::<Vec<_>>(),\n    dirs.tauri,\n  )?;\n  let mut built_application = super::build::run(\n    super::build::Options {\n      debug: !options.release,\n      targets: device.as_ref().map(|d| {\n        vec![Target::all()\n          .iter()\n          .find(|(_key, t)| t.arch == d.target().arch)\n          .map(|(key, _t)| key.to_string())\n          .expect(\"Target not found\")]\n      }),\n      features: options.features,\n      config: options.config.clone(),\n      split_per_abi: true,\n      apk: false,\n      aab: false,\n      skip_bundle: false,\n      open: options.open,\n      ci: false,\n      args: options.args,\n      ignore_version_mismatches: options.ignore_version_mismatches,\n      target_device: device.as_ref().map(|d| TargetDevice {\n        id: d.serial_no().to_string(),\n        name: d.name().to_string(),\n      }),\n    },\n    noise_level,\n    &dirs,\n    &tauri_config,\n  )?;\n\n  configure_cargo(&mut env, &built_application.config)?;\n\n  // options.open is handled by the build command\n  // so all we need to do here is run the app on the selected device\n  if let Some(device) = device {\n    let config = built_application.config.clone();\n    let release = options.release;\n    let runner = move |_tauri_config: &ConfigMetadata| {\n      device\n        .run(\n          &config,\n          &env,\n          noise_level,\n          if !release {\n            Profile::Debug\n          } else {\n            Profile::Release\n          },\n          Some(match noise_level {\n            NoiseLevel::Polite => FilterLevel::Info,\n            NoiseLevel::LoudAndProud => FilterLevel::Debug,\n            NoiseLevel::FranklyQuitePedantic => FilterLevel::Verbose,\n          }),\n          false,\n          false,\n          \".MainActivity\".into(),\n        )\n        .map(|c| Box::new(DevChild::new(c)) as Box<dyn DevProcess + Send>)\n        .context(\"failed to run Android app\")\n    };\n\n    if options.no_watch {\n      runner(&tauri_config)?;\n    } else {\n      built_application.interface.watch(\n        &mut tauri_config,\n        WatcherOptions {\n          config: options.config,\n          additional_watch_folders: options.additional_watch_folders,\n        },\n        runner,\n        &dirs,\n      )?;\n    }\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/init.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{get_app, Target};\nuse crate::{\n  helpers::app_paths::Dirs,\n  helpers::{config::get_config as get_tauri_config, template::JsonMap},\n  interface::AppInterface,\n  ConfigValue, Result,\n};\nuse cargo_mobile2::{\n  config::app::App,\n  reserved_names::KOTLIN_ONLY_KEYWORDS,\n  util::{\n    self,\n    cli::{Report, TextWrapper},\n  },\n};\nuse handlebars::{\n  Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError, RenderErrorReason,\n};\n\nuse std::{env::var_os, path::PathBuf};\n\npub fn command(\n  target: Target,\n  ci: bool,\n  reinstall_deps: bool,\n  skip_targets_install: bool,\n  config: Vec<ConfigValue>,\n) -> Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n  let wrapper = TextWrapper::default();\n\n  exec(\n    target,\n    &wrapper,\n    ci,\n    reinstall_deps,\n    skip_targets_install,\n    config,\n    dirs,\n  )?;\n  Ok(())\n}\n\nfn exec(\n  target: Target,\n  wrapper: &TextWrapper,\n  #[allow(unused_variables)] non_interactive: bool,\n  #[allow(unused_variables)] reinstall_deps: bool,\n  skip_targets_install: bool,\n  config: Vec<ConfigValue>,\n  dirs: Dirs,\n) -> Result<App> {\n  let tauri_config = get_tauri_config(\n    target.platform_target(),\n    &config.iter().map(|conf| &conf.0).collect::<Vec<_>>(),\n    dirs.tauri,\n  )?;\n\n  let app = get_app(\n    target,\n    &tauri_config,\n    &AppInterface::new(&tauri_config, None, dirs.tauri)?,\n    dirs.tauri,\n  );\n\n  let (handlebars, mut map) = handlebars(&app);\n\n  let mut args = std::env::args_os();\n\n  let (binary, mut build_args) = args\n    .next()\n    .map(|bin| {\n      let bin_path = PathBuf::from(&bin);\n      let mut build_args = vec![\"tauri\"];\n\n      if let Some(bin_stem) = bin_path.file_stem() {\n        let r = regex::Regex::new(\"(nodejs|node)\\\\-?([1-9]*)*$\").unwrap();\n        if r.is_match(&bin_stem.to_string_lossy()) {\n          if var_os(\"PNPM_PACKAGE_NAME\").is_some() {\n            return (\"pnpm\".into(), build_args);\n          } else if is_pnpm_dlx() {\n            return (\"pnpm\".into(), vec![\"dlx\", \"@tauri-apps/cli\"]);\n          } else if let Some(npm_execpath) = var_os(\"npm_execpath\") {\n            let manager_stem = PathBuf::from(&npm_execpath)\n              .file_stem()\n              .unwrap()\n              .to_os_string();\n            let is_npm = manager_stem == \"npm-cli\";\n            let binary = if is_npm {\n              \"npm\".into()\n            } else if manager_stem == \"npx-cli\" {\n              \"npx\".into()\n            } else {\n              manager_stem\n            };\n\n            if is_npm {\n              build_args.insert(0, \"run\");\n              build_args.insert(1, \"--\");\n            }\n\n            return (binary, build_args);\n          }\n        } else if bin_stem == \"deno\" {\n          build_args.insert(0, \"task\");\n          return (std::ffi::OsString::from(\"deno\"), build_args);\n        } else if !cfg!(debug_assertions) && bin_stem == \"cargo-tauri\" {\n          return (std::ffi::OsString::from(\"cargo\"), build_args);\n        }\n      }\n\n      (bin, build_args)\n    })\n    .unwrap_or_else(|| (std::ffi::OsString::from(\"cargo\"), vec![\"tauri\"]));\n\n  build_args.push(target.command_name());\n  build_args.push(target.ide_build_script_name());\n\n  let mut binary = binary.to_string_lossy().to_string();\n  if binary.ends_with(\".exe\") || binary.ends_with(\".cmd\") || binary.ends_with(\".bat\") {\n    // remove Windows-only extension\n    binary.pop();\n    binary.pop();\n    binary.pop();\n    binary.pop();\n  }\n\n  map.insert(\"tauri-binary\", binary);\n  map.insert(\"tauri-binary-args\", &build_args);\n  map.insert(\"tauri-binary-args-str\", build_args.join(\" \"));\n\n  let app = match target {\n    // Generate Android Studio project\n    Target::Android => {\n      let _env = super::android::env(non_interactive)?;\n      let (config, metadata) =\n        super::android::get_config(&app, &tauri_config, &[], &Default::default());\n      map.insert(\"android\", &config);\n      super::android::project::gen(\n        &config,\n        &metadata,\n        (handlebars, map),\n        wrapper,\n        skip_targets_install,\n      )?;\n      app\n    }\n    #[cfg(target_os = \"macos\")]\n    // Generate Xcode project\n    Target::Ios => {\n      let (config, metadata) =\n        super::ios::get_config(&app, &tauri_config, &[], &Default::default(), dirs.tauri)?;\n      map.insert(\"apple\", &config);\n      super::ios::project::gen(\n        &tauri_config,\n        &config,\n        &metadata,\n        (handlebars, map),\n        wrapper,\n        non_interactive,\n        reinstall_deps,\n        skip_targets_install,\n      )?;\n      app\n    }\n  };\n\n  Report::victory(\n    \"Project generated successfully!\",\n    \"Make cool apps! 🌻 🐕 🎉\",\n  )\n  .print(wrapper);\n  Ok(app)\n}\n\nfn handlebars(app: &App) -> (Handlebars<'static>, JsonMap) {\n  let mut h = Handlebars::new();\n  h.register_escape_fn(handlebars::no_escape);\n\n  h.register_helper(\"html-escape\", Box::new(html_escape));\n  h.register_helper(\"join\", Box::new(join));\n  h.register_helper(\"quote-and-join\", Box::new(quote_and_join));\n  h.register_helper(\n    \"quote-and-join-colon-prefix\",\n    Box::new(quote_and_join_colon_prefix),\n  );\n  h.register_helper(\"snake-case\", Box::new(snake_case));\n  h.register_helper(\"escape-kotlin-keyword\", Box::new(escape_kotlin_keyword));\n  // don't mix these up or very bad things will happen to all of us\n  h.register_helper(\"prefix-path\", Box::new(prefix_path));\n  h.register_helper(\"unprefix-path\", Box::new(unprefix_path));\n\n  let mut map = JsonMap::default();\n  map.insert(\"app\", app);\n\n  (h, map)\n}\n\nfn get_str<'a>(helper: &'a Helper) -> &'a str {\n  helper\n    .param(0)\n    .and_then(|v| v.value().as_str())\n    .unwrap_or(\"\")\n}\n\nfn get_str_array(helper: &Helper, formatter: impl Fn(&str) -> String) -> Option<Vec<String>> {\n  helper.param(0).and_then(|v| {\n    v.value()\n      .as_array()\n      .and_then(|arr| arr.iter().map(|val| val.as_str().map(&formatter)).collect())\n  })\n}\n\nfn html_escape(\n  helper: &Helper,\n  _: &Handlebars,\n  _ctx: &Context,\n  _: &mut RenderContext,\n  out: &mut dyn Output,\n) -> HelperResult {\n  out\n    .write(&handlebars::html_escape(get_str(helper)))\n    .map_err(Into::into)\n}\n\nfn join(\n  helper: &Helper,\n  _: &Handlebars,\n  _: &Context,\n  _: &mut RenderContext,\n  out: &mut dyn Output,\n) -> HelperResult {\n  out\n    .write(\n      &get_str_array(helper, |s| s.to_string())\n        .ok_or_else(|| {\n          RenderErrorReason::ParamTypeMismatchForName(\"join\", \"0\".to_owned(), \"array\".to_owned())\n        })?\n        .join(\", \"),\n    )\n    .map_err(Into::into)\n}\n\nfn quote_and_join(\n  helper: &Helper,\n  _: &Handlebars,\n  _: &Context,\n  _: &mut RenderContext,\n  out: &mut dyn Output,\n) -> HelperResult {\n  out\n    .write(\n      &get_str_array(helper, |s| format!(\"{s:?}\"))\n        .ok_or_else(|| {\n          RenderErrorReason::ParamTypeMismatchForName(\n            \"quote-and-join\",\n            \"0\".to_owned(),\n            \"array\".to_owned(),\n          )\n        })?\n        .join(\", \"),\n    )\n    .map_err(Into::into)\n}\n\nfn quote_and_join_colon_prefix(\n  helper: &Helper,\n  _: &Handlebars,\n  _: &Context,\n  _: &mut RenderContext,\n  out: &mut dyn Output,\n) -> HelperResult {\n  out\n    .write(\n      &get_str_array(helper, |s| format!(\"{:?}\", format!(\":{s}\")))\n        .ok_or_else(|| {\n          RenderErrorReason::ParamTypeMismatchForName(\n            \"quote-and-join-colon-prefix\",\n            \"0\".to_owned(),\n            \"array\".to_owned(),\n          )\n        })?\n        .join(\", \"),\n    )\n    .map_err(Into::into)\n}\n\nfn snake_case(\n  helper: &Helper,\n  _: &Handlebars,\n  _: &Context,\n  _: &mut RenderContext,\n  out: &mut dyn Output,\n) -> HelperResult {\n  use heck::ToSnekCase as _;\n  out\n    .write(&get_str(helper).to_snek_case())\n    .map_err(Into::into)\n}\n\nfn escape_kotlin_keyword(\n  helper: &Helper,\n  _: &Handlebars,\n  _: &Context,\n  _: &mut RenderContext,\n  out: &mut dyn Output,\n) -> HelperResult {\n  let escaped_result = get_str(helper)\n    .split('.')\n    .map(|s| {\n      if KOTLIN_ONLY_KEYWORDS.contains(&s) {\n        format!(\"`{s}`\")\n      } else {\n        s.to_string()\n      }\n    })\n    .collect::<Vec<_>>()\n    .join(\".\");\n\n  out.write(&escaped_result).map_err(Into::into)\n}\n\nfn app_root(ctx: &Context) -> std::result::Result<&str, RenderError> {\n  let app_root = ctx\n    .data()\n    .get(\"app\")\n    .ok_or_else(|| RenderErrorReason::Other(\"`app` missing from template data.\".to_owned()))?\n    .get(\"root-dir\")\n    .ok_or_else(|| {\n      RenderErrorReason::Other(\"`app.root-dir` missing from template data.\".to_owned())\n    })?;\n  app_root.as_str().ok_or_else(|| {\n    RenderErrorReason::Other(\"`app.root-dir` contained invalid UTF-8.\".to_owned()).into()\n  })\n}\n\nfn prefix_path(\n  helper: &Helper,\n  _: &Handlebars,\n  ctx: &Context,\n  _: &mut RenderContext,\n  out: &mut dyn Output,\n) -> HelperResult {\n  out\n    .write(\n      util::prefix_path(app_root(ctx)?, get_str(helper))\n        .to_str()\n        .ok_or_else(|| {\n          RenderErrorReason::Other(\n            \"Either the `app.root-dir` or the specified path contained invalid UTF-8.\".to_owned(),\n          )\n        })?,\n    )\n    .map_err(Into::into)\n}\n\nfn unprefix_path(\n  helper: &Helper,\n  _: &Handlebars,\n  ctx: &Context,\n  _: &mut RenderContext,\n  out: &mut dyn Output,\n) -> HelperResult {\n  out\n    .write(\n      util::unprefix_path(app_root(ctx)?, get_str(helper))\n        .map_err(|_| {\n          RenderErrorReason::Other(\n            \"Attempted to unprefix a path that wasn't in the app root dir.\".to_owned(),\n          )\n        })?\n        .to_str()\n        .ok_or_else(|| {\n          RenderErrorReason::Other(\n            \"Either the `app.root-dir` or the specified path contained invalid UTF-8.\".to_owned(),\n          )\n        })?,\n    )\n    .map_err(Into::into)\n}\n\nfn is_pnpm_dlx() -> bool {\n  var_os(\"NODE_PATH\")\n    .map(PathBuf::from)\n    .is_some_and(|node_path| {\n      let mut iter = node_path.components().peekable();\n      while let Some(c) = iter.next() {\n        if c.as_os_str() == \"pnpm\" && iter.peek().is_some_and(|c| c.as_os_str() == \"dlx\") {\n          return true;\n        }\n      }\n      false\n    })\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/ios/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{\n  detect_target_ok, ensure_init, env, get_app, get_config, inject_resources, load_pbxproj,\n  log_finished, open_and_wait, project_config, synchronize_project_config, MobileTarget,\n  OptionsHandle,\n};\nuse crate::{\n  build::Options as BuildOptions,\n  error::{Context, ErrorExt},\n  helpers::{\n    app_paths::Dirs,\n    config::{get_config as get_tauri_config, ConfigMetadata},\n    flock,\n    plist::merge_plist,\n  },\n  interface::{AppInterface, Options as InterfaceOptions},\n  mobile::{ios::ensure_ios_runtime_installed, write_options, CliOptions, TargetDevice},\n  ConfigValue, Error, Result,\n};\nuse clap::{ArgAction, Parser, ValueEnum};\n\nuse cargo_mobile2::{\n  apple::{\n    config::Config as AppleConfig,\n    target::{ArchiveConfig, BuildConfig, ExportConfig, Target},\n  },\n  env::Env,\n  opts::{NoiseLevel, Profile},\n  target::{call_for_targets_with_fallback, TargetInvalid, TargetTrait},\n};\nuse rand::distr::{Alphanumeric, SampleString};\n\nuse std::{\n  env::{set_current_dir, var, var_os},\n  fs,\n  path::PathBuf,\n};\n\n#[derive(Debug, Clone, Parser)]\n#[clap(\n  about = \"Build your app in release mode for iOS and generate IPAs\",\n  long_about = \"Build your app in release mode for iOS and generate IPAs. It makes use of the `build.frontendDist` property from your `tauri.conf.json` file. It also runs your `build.beforeBuildCommand` which usually builds your frontend into `build.frontendDist`.\"\n)]\npub struct Options {\n  /// Builds with the debug flag\n  #[clap(short, long)]\n  pub debug: bool,\n  /// Which targets to build.\n  #[clap(\n    short,\n    long = \"target\",\n    action = ArgAction::Append,\n    num_args(0..),\n    default_value = Target::DEFAULT_KEY,\n    value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))\n  )]\n  pub targets: Option<Vec<String>>,\n  /// List of cargo features to activate\n  #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]\n  pub features: Vec<String>,\n  /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file\n  ///\n  /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.\n  ///\n  /// Note that a platform-specific file is looked up and merged with the default file by default\n  /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)\n  /// but you can use this for more specific use cases such as different build flavors.\n  #[clap(short, long)]\n  pub config: Vec<ConfigValue>,\n  /// Build number to append to the app version.\n  #[clap(long)]\n  pub build_number: Option<u32>,\n  /// Open Xcode\n  #[clap(short, long)]\n  pub open: bool,\n  /// Skip prompting for values\n  #[clap(long, env = \"CI\")]\n  pub ci: bool,\n  /// Describes how Xcode should export the archive.\n  ///\n  /// Use this to create a package ready for the App Store (app-store-connect option) or TestFlight (release-testing option).\n  #[clap(long, value_enum)]\n  pub export_method: Option<ExportMethod>,\n  /// Command line arguments passed to the runner.\n  /// Use `--` to explicitly mark the start of the arguments.\n  /// e.g. `tauri ios build -- [runnerArgs]`.\n  #[clap(last(true))]\n  pub args: Vec<String>,\n  /// Do not error out if a version mismatch is detected on a Tauri package.\n  ///\n  /// Only use this when you are sure the mismatch is incorrectly detected as version mismatched Tauri packages can lead to unknown behavior.\n  #[clap(long)]\n  pub ignore_version_mismatches: bool,\n  /// Target device of this build\n  #[clap(skip)]\n  pub target_device: Option<TargetDevice>,\n}\n\n#[derive(Debug, Clone, Copy, ValueEnum)]\npub enum ExportMethod {\n  AppStoreConnect,\n  ReleaseTesting,\n  Debugging,\n}\n\nimpl ExportMethod {\n  /// Xcode 15.4 deprecated these names (in this case we should use the Display impl).\n  pub fn pre_xcode_15_4_name(&self) -> String {\n    match self {\n      Self::AppStoreConnect => \"app-store\".to_string(),\n      Self::ReleaseTesting => \"ad-hoc\".to_string(),\n      Self::Debugging => \"development\".to_string(),\n    }\n  }\n}\n\nimpl std::fmt::Display for ExportMethod {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    match self {\n      Self::AppStoreConnect => write!(f, \"app-store-connect\"),\n      Self::ReleaseTesting => write!(f, \"release-testing\"),\n      Self::Debugging => write!(f, \"debugging\"),\n    }\n  }\n}\n\nimpl std::str::FromStr for ExportMethod {\n  type Err = &'static str;\n\n  fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n    match s {\n      \"app-store-connect\" => Ok(Self::AppStoreConnect),\n      \"release-testing\" => Ok(Self::ReleaseTesting),\n      \"debugging\" => Ok(Self::Debugging),\n      _ => Err(\"unknown ios target\"),\n    }\n  }\n}\n\nimpl From<Options> for BuildOptions {\n  fn from(options: Options) -> Self {\n    Self {\n      runner: None,\n      debug: options.debug,\n      target: None,\n      features: options.features,\n      bundles: None,\n      no_bundle: false,\n      config: options.config,\n      args: options.args,\n      ci: options.ci,\n      skip_stapling: false,\n      ignore_version_mismatches: options.ignore_version_mismatches,\n      no_sign: false,\n    }\n  }\n}\n\npub struct BuiltApplication {\n  pub config: AppleConfig,\n  pub interface: AppInterface,\n  // prevent drop\n  #[allow(dead_code)]\n  options_handle: OptionsHandle,\n}\n\npub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplication> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n  run(options, noise_level, &dirs)\n}\n\npub fn run(options: Options, noise_level: NoiseLevel, dirs: &Dirs) -> Result<BuiltApplication> {\n  let mut build_options: BuildOptions = options.clone().into();\n  build_options.target = Some(\n    Target::all()\n      .get(\n        options\n          .targets\n          .as_ref()\n          .and_then(|t| t.first())\n          .map(|t| t.as_str())\n          .unwrap_or(Target::DEFAULT_KEY),\n      )\n      .unwrap()\n      .triple\n      .into(),\n  );\n\n  let tauri_config = get_tauri_config(\n    tauri_utils::platform::Target::Ios,\n    &options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),\n    dirs.tauri,\n  )?;\n  let interface = AppInterface::new(&tauri_config, build_options.target.clone(), dirs.tauri)?;\n  interface.build_options(&mut build_options.args, &mut build_options.features, true);\n\n  let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri);\n  let (mut config, _) = get_config(\n    &app,\n    &tauri_config,\n    &build_options.features,\n    &CliOptions {\n      dev: false,\n      features: build_options.features.clone(),\n      args: build_options.args.clone(),\n      noise_level,\n      vars: Default::default(),\n      config: build_options.config.clone(),\n      target_device: None,\n    },\n    dirs.tauri,\n  )?;\n\n  set_current_dir(dirs.tauri).context(\"failed to set current directory\")?;\n\n  ensure_init(\n    &tauri_config,\n    config.app(),\n    config.project_dir(),\n    MobileTarget::Ios,\n    options.ci,\n  )?;\n  inject_resources(&config, &tauri_config)?;\n\n  let mut plist = plist::Dictionary::new();\n  plist.insert(\n    \"CFBundleShortVersionString\".into(),\n    config.bundle_version_short().into(),\n  );\n\n  let info_plist_path = config\n    .project_dir()\n    .join(config.scheme())\n    .join(\"Info.plist\");\n  let mut src_plists = vec![info_plist_path.clone().into()];\n  src_plists.push(plist::Value::Dictionary(plist).into());\n  if dirs.tauri.join(\"Info.plist\").exists() {\n    src_plists.push(dirs.tauri.join(\"Info.plist\").into());\n  }\n  if dirs.tauri.join(\"Info.ios.plist\").exists() {\n    src_plists.push(dirs.tauri.join(\"Info.ios.plist\").into());\n  }\n  if let Some(info_plist) = &tauri_config.bundle.ios.info_plist {\n    src_plists.push(info_plist.clone().into());\n  }\n  let merged_info_plist = merge_plist(src_plists)?;\n  merged_info_plist\n    .to_file_xml(&info_plist_path)\n    .map_err(std::io::Error::other)\n    .fs_context(\"failed to save merged Info.plist file\", info_plist_path)?;\n\n  let mut env = env().context(\"failed to load iOS environment\")?;\n\n  if !options.open {\n    ensure_ios_runtime_installed()?;\n  }\n\n  let mut export_options_plist = plist::Dictionary::new();\n  if let Some(method) = options.export_method {\n    let xcode_version =\n      crate::info::env_system::xcode_version().context(\"failed to determine Xcode version\")?;\n    let mut iter = xcode_version.split('.');\n    let major = iter.next().context(format!(\n      \"failed to parse Xcode version `{xcode_version}` as semver\"\n    ))?;\n    let minor = iter.next().context(format!(\n      \"failed to parse Xcode version `{xcode_version}` as semver\"\n    ))?;\n    let major = major.parse::<u64>().ok().context(format!(\n      \"failed to parse Xcode version `{xcode_version}` as semver: major is not a number\"\n    ))?;\n    let minor = minor.parse::<u64>().ok().context(format!(\n      \"failed to parse Xcode version `{xcode_version}` as semver: minor is not a number\"\n    ))?;\n\n    if major < 15 || (major == 15 && minor < 4) {\n      export_options_plist.insert(\"method\".to_string(), method.pre_xcode_15_4_name().into());\n    } else {\n      export_options_plist.insert(\"method\".to_string(), method.to_string().into());\n    }\n  }\n\n  let (keychain, provisioning_profile) = super::signing_from_env()?;\n  let project_config = project_config(keychain.as_ref(), provisioning_profile.as_ref())?;\n  let mut pbxproj = load_pbxproj(&config)?;\n\n  // synchronize pbxproj and exportoptions\n  synchronize_project_config(\n    &config,\n    &tauri_config,\n    &mut pbxproj,\n    &mut export_options_plist,\n    &project_config,\n    options.debug,\n  )?;\n  if pbxproj.has_changes() {\n    pbxproj\n      .save()\n      .fs_context(\"failed to save pbxproj file\", pbxproj.path)?;\n  }\n\n  // merge export options and write to temp file\n  let _export_options_tmp = if !export_options_plist.is_empty() {\n    let export_options_plist_path = config.project_dir().join(\"ExportOptions.plist\");\n    let export_options =\n      tempfile::NamedTempFile::new().context(\"failed to create temporary file\")?;\n\n    let merged_plist = merge_plist(vec![\n      export_options_plist_path.into(),\n      plist::Value::from(export_options_plist).into(),\n    ])?;\n    merged_plist\n      .to_file_xml(export_options.path())\n      .map_err(std::io::Error::other)\n      .fs_context(\n        \"failed to save export options plist file\",\n        export_options.path().to_path_buf(),\n      )?;\n\n    config.set_export_options_plist_path(export_options.path());\n\n    Some(export_options)\n  } else {\n    None\n  };\n\n  let open = options.open;\n  let options_handle = run_build(\n    &interface,\n    options,\n    build_options,\n    tauri_config,\n    &mut config,\n    &mut env,\n    noise_level,\n    dirs,\n  )?;\n\n  if open {\n    open_and_wait(&config, &env);\n  }\n\n  Ok(BuiltApplication {\n    config,\n    interface,\n    options_handle,\n  })\n}\n\n#[allow(clippy::too_many_arguments)]\nfn run_build(\n  interface: &AppInterface,\n  options: Options,\n  mut build_options: BuildOptions,\n  tauri_config: ConfigMetadata,\n  config: &mut AppleConfig,\n  env: &mut Env,\n  noise_level: NoiseLevel,\n  dirs: &Dirs,\n) -> Result<OptionsHandle> {\n  let profile = if options.debug {\n    Profile::Debug\n  } else {\n    Profile::Release\n  };\n\n  crate::build::setup(interface, &mut build_options, &tauri_config, dirs, true)?;\n\n  let app_settings = interface.app_settings();\n  let out_dir = app_settings.out_dir(\n    &InterfaceOptions {\n      debug: build_options.debug,\n      target: build_options.target.clone(),\n      args: build_options.args.clone(),\n      ..Default::default()\n    },\n    dirs.tauri,\n  )?;\n  let _lock = flock::open_rw(out_dir.join(\"lock\").with_extension(\"ios\"), \"iOS\")?;\n\n  let cli_options = CliOptions {\n    dev: false,\n    features: build_options.features.clone(),\n    args: build_options.args.clone(),\n    noise_level,\n    vars: Default::default(),\n    config: build_options.config.clone(),\n    target_device: options.target_device.clone(),\n  };\n  let handle = write_options(&tauri_config, cli_options)?;\n\n  if options.open {\n    return Ok(handle);\n  }\n\n  let mut out_files = Vec::new();\n\n  let force_skip_target_fallback = options.targets.as_ref().is_some_and(|t| t.is_empty());\n\n  call_for_targets_with_fallback(\n    options.targets.unwrap_or_default().iter(),\n    if force_skip_target_fallback {\n      &|_| None\n    } else {\n      &detect_target_ok\n    },\n    env,\n    |target: &Target| -> Result<()> {\n      let mut app_version = config.bundle_version().to_string();\n      if let Some(build_number) = options.build_number {\n        app_version.push('.');\n        app_version.push_str(&build_number.to_string());\n      }\n\n      let credentials = auth_credentials_from_env()?;\n      let skip_signing = credentials.is_some();\n\n      let mut build_config = BuildConfig::new().allow_provisioning_updates();\n      if let Some(credentials) = &credentials {\n        build_config = build_config\n          .authentication_credentials(credentials.clone())\n          .skip_codesign();\n      }\n\n      target\n        .build(None, config, env, noise_level, profile, build_config)\n        .context(\"failed to build iOS app\")?;\n\n      let mut archive_config = ArchiveConfig::new();\n      if skip_signing {\n        archive_config = archive_config.skip_codesign();\n      }\n\n      target\n        .archive(\n          config,\n          env,\n          noise_level,\n          profile,\n          Some(app_version),\n          archive_config,\n        )\n        .context(\"failed to archive iOS app\")?;\n\n      let out_dir = config.export_dir().join(target.arch);\n\n      if target.sdk == \"iphonesimulator\" {\n        fs::create_dir_all(&out_dir)\n          .fs_context(\"failed to create Xcode output directory\", out_dir.clone())?;\n\n        let app_path = config\n          .archive_dir()\n          .join(format!(\"{}.xcarchive\", config.scheme()))\n          .join(\"Products\")\n          .join(\"Applications\")\n          .join(config.app().stylized_name())\n          .with_extension(\"app\");\n\n        let path = out_dir.join(app_path.file_name().unwrap());\n        fs::rename(&app_path, &path).fs_context(\"failed to rename app\", app_path)?;\n        out_files.push(path);\n      } else {\n        // if we skipped code signing, we do not have the entitlements applied to our exported IPA\n        // we must force sign the app binary with a dummy certificate just to preserve the entitlements\n        // target.export() will sign it with an actual certificate for us\n        if skip_signing {\n          let password = Alphanumeric.sample_string(&mut rand::rng(), 16);\n          let certificate = tauri_macos_sign::certificate::generate_self_signed(\n            tauri_macos_sign::certificate::SelfSignedCertificateRequest {\n              algorithm: \"rsa\".to_string(),\n              profile: tauri_macos_sign::certificate::CertificateProfile::AppleDistribution,\n              team_id: \"unset\".to_string(),\n              person_name: \"Tauri\".to_string(),\n              country_name: \"NL\".to_string(),\n              validity_days: 365,\n              password: password.clone(),\n            },\n          )\n          .map_err(Box::new)?;\n          let tmp_dir = tempfile::tempdir().context(\"failed to create temporary directory\")?;\n          let cert_path = tmp_dir.path().join(\"cert.p12\");\n          std::fs::write(&cert_path, certificate)\n            .fs_context(\"failed to write certificate\", cert_path.clone())?;\n          let self_signed_cert_keychain =\n            tauri_macos_sign::Keychain::with_certificate_file(&cert_path, &password.into())\n              .map_err(Box::new)?;\n\n          let app_dir = config\n            .export_dir()\n            .join(format!(\"{}.xcarchive\", config.scheme()))\n            .join(\"Products/Applications\")\n            .join(format!(\"{}.app\", config.app().stylized_name()));\n\n          self_signed_cert_keychain\n            .sign(\n              &app_dir.join(config.app().stylized_name()),\n              Some(\n                &config\n                  .project_dir()\n                  .join(config.scheme())\n                  .join(format!(\"{}.entitlements\", config.scheme())),\n              ),\n              false,\n            )\n            .map_err(Box::new)?;\n        }\n\n        let mut export_config = ExportConfig::new().allow_provisioning_updates();\n        if let Some(credentials) = &credentials {\n          export_config = export_config.authentication_credentials(credentials.clone());\n        }\n\n        target\n          .export(config, env, noise_level, export_config)\n          .context(\"failed to export iOS app\")?;\n\n        if let Ok(ipa_path) = config.ipa_path() {\n          fs::create_dir_all(&out_dir)\n            .fs_context(\"failed to create Xcode output directory\", out_dir.clone())?;\n          let path = out_dir.join(ipa_path.file_name().unwrap());\n          fs::rename(&ipa_path, &path).fs_context(\"failed to rename IPA\", ipa_path)?;\n          out_files.push(path);\n        }\n      }\n\n      Ok(())\n    },\n  )\n  .map_err(|e: TargetInvalid| Error::GenericError(e.to_string()))??;\n\n  if !out_files.is_empty() {\n    log_finished(out_files, \"iOS Bundle\");\n  }\n\n  Ok(handle)\n}\n\nfn auth_credentials_from_env() -> Result<Option<cargo_mobile2::apple::AuthCredentials>> {\n  match (\n    var(\"APPLE_API_KEY\"),\n    var(\"APPLE_API_ISSUER\"),\n    var_os(\"APPLE_API_KEY_PATH\").map(PathBuf::from),\n  ) {\n    (Ok(key_id), Ok(key_issuer_id), Some(key_path)) => {\n      Ok(Some(cargo_mobile2::apple::AuthCredentials {\n        key_path,\n        key_id,\n        key_issuer_id,\n      }))\n    }\n    (Err(_), Err(_), None) => Ok(None),\n    _ => crate::error::bail!(\n      \"APPLE_API_KEY, APPLE_API_ISSUER and APPLE_API_KEY_PATH must be provided for code signing\"\n    ),\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/ios/dev.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{\n  device_prompt, ensure_init, env, get_app, get_config, inject_resources, load_pbxproj,\n  open_and_wait, synchronize_project_config, MobileTarget, ProjectConfig,\n};\nuse crate::{\n  dev::Options as DevOptions,\n  error::{Context, ErrorExt},\n  helpers::{\n    app_paths::Dirs,\n    config::{get_config as get_tauri_config, ConfigMetadata},\n    flock,\n    plist::merge_plist,\n  },\n  interface::{AppInterface, MobileOptions, Options as InterfaceOptions},\n  mobile::{\n    ios::ensure_ios_runtime_installed, use_network_address_for_dev_url, write_options, CliOptions,\n    DevChild, DevHost, DevProcess,\n  },\n  ConfigValue, Result,\n};\nuse clap::{ArgAction, Parser};\n\nuse cargo_mobile2::{\n  apple::{\n    config::Config as AppleConfig,\n    device::{Device, DeviceKind, RunError},\n    target::BuildError,\n  },\n  env::Env,\n  opts::{NoiseLevel, Profile},\n};\nuse url::Host;\n\nuse std::{env::set_current_dir, net::Ipv4Addr, path::PathBuf};\n\nconst PHYSICAL_IPHONE_DEV_WARNING: &str = \"To develop on physical phones you need the `--host` option (not required for Simulators). See the documentation for more information: https://v2.tauri.app/develop/#development-server\";\n\n#[derive(Debug, Clone, Parser)]\n#[clap(\n  about = \"Run your app in development mode on iOS\",\n  long_about = \"Run your app in development mode on iOS with hot-reloading for the Rust code.\nIt makes use of the `build.devUrl` property from your `tauri.conf.json` file.\nIt also runs your `build.beforeDevCommand` which usually starts your frontend devServer.\n\nWhen connected to a physical iOS device, the public network address must be used instead of `localhost`\nfor the devUrl property. Tauri makes that change automatically, but your dev server might need\na different configuration to listen on the public address. You can check the `TAURI_DEV_HOST`\nenvironment variable to determine whether the public network should be used or not.\"\n)]\npub struct Options {\n  /// List of cargo features to activate\n  #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]\n  pub features: Vec<String>,\n  /// Exit on panic\n  #[clap(short, long)]\n  exit_on_panic: bool,\n  /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file\n  ///\n  /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.\n  ///\n  /// Note that a platform-specific file is looked up and merged with the default file by default\n  /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)\n  /// but you can use this for more specific use cases such as different build flavors.\n  #[clap(short, long)]\n  pub config: Vec<ConfigValue>,\n  /// Run the code in release mode\n  #[clap(long = \"release\")]\n  pub release_mode: bool,\n  /// Skip waiting for the frontend dev server to start before building the tauri application.\n  #[clap(long, env = \"TAURI_CLI_NO_DEV_SERVER_WAIT\")]\n  pub no_dev_server_wait: bool,\n  /// Disable the file watcher\n  #[clap(long)]\n  pub no_watch: bool,\n  /// Additional paths to watch for changes.\n  #[clap(long)]\n  pub additional_watch_folders: Vec<PathBuf>,\n  /// Open Xcode instead of trying to run on a connected device\n  #[clap(short, long)]\n  pub open: bool,\n  /// Runs on the given device name\n  pub device: Option<String>,\n  /// Force prompting for an IP to use to connect to the dev server on mobile.\n  #[clap(long)]\n  pub force_ip_prompt: bool,\n  /// Use the public network address for the development server.\n  /// If an actual address it provided, it is used instead of prompting to pick one.\n  ///\n  /// This option is particularly useful along the `--open` flag when you intend on running on a physical device.\n  ///\n  /// This replaces the devUrl configuration value to match the public network address host,\n  /// it is your responsibility to set up your development server to listen on this address\n  /// by using 0.0.0.0 as host for instance.\n  ///\n  /// When this is set or when running on an iOS device the CLI sets the `TAURI_DEV_HOST`\n  /// environment variable so you can check this on your framework's configuration to expose the development server\n  /// on the public network address.\n  #[clap(long, default_value_t, default_missing_value(\"\"), num_args(0..=1))]\n  pub host: DevHost,\n  /// Disable the built-in dev server for static files.\n  #[clap(long)]\n  pub no_dev_server: bool,\n  /// Specify port for the built-in dev server for static files. Defaults to 1430.\n  #[clap(long, env = \"TAURI_CLI_PORT\")]\n  pub port: Option<u16>,\n  /// Command line arguments passed to the runner.\n  /// Use `--` to explicitly mark the start of the arguments.\n  /// e.g. `tauri ios dev -- [runnerArgs]`.\n  #[clap(last(true))]\n  pub args: Vec<String>,\n  /// Path to the certificate file used by your dev server. Required for mobile dev when using HTTPS.\n  #[clap(long, env = \"TAURI_DEV_ROOT_CERTIFICATE_PATH\")]\n  pub root_certificate_path: Option<PathBuf>,\n}\n\nimpl From<Options> for DevOptions {\n  fn from(options: Options) -> Self {\n    Self {\n      runner: None,\n      target: None,\n      features: options.features,\n      exit_on_panic: options.exit_on_panic,\n      config: options.config,\n      release_mode: options.release_mode,\n      args: options.args,\n      no_watch: options.no_watch,\n      additional_watch_folders: options.additional_watch_folders,\n      no_dev_server: options.no_dev_server,\n      no_dev_server_wait: options.no_dev_server_wait,\n      port: options.port,\n      host: options.host.0.unwrap_or_default(),\n    }\n  }\n}\n\npub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n\n  let result = run_command(options, noise_level, dirs);\n  if result.is_err() {\n    crate::dev::kill_before_dev_process();\n  }\n  result\n}\n\nfn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<()> {\n  // setup env additions before calling env()\n  if let Some(root_certificate_path) = &options.root_certificate_path {\n    std::env::set_var(\n      \"TAURI_DEV_ROOT_CERTIFICATE\",\n      std::fs::read_to_string(root_certificate_path).fs_context(\n        \"failed to read root certificate file\",\n        root_certificate_path.clone(),\n      )?,\n    );\n  }\n\n  let env = env().context(\"failed to load iOS environment\")?;\n  let device = if options.open {\n    None\n  } else {\n    match device_prompt(&env, options.device.as_deref()) {\n      Ok(d) => Some(d),\n      Err(e) => {\n        log::error!(\"{e}\");\n        None\n      }\n    }\n  };\n\n  if device.is_some() {\n    ensure_ios_runtime_installed()?;\n  }\n\n  let mut dev_options: DevOptions = options.clone().into();\n  let target_triple = device\n    .as_ref()\n    .map(|d| d.target().triple.to_string())\n    .unwrap_or_else(|| \"aarch64-apple-ios\".into());\n  dev_options.target = Some(target_triple.clone());\n  dev_options.args.push(\"--lib\".into());\n\n  let tauri_config = get_tauri_config(\n    tauri_utils::platform::Target::Ios,\n    &options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),\n    dirs.tauri,\n  )?;\n  let interface = AppInterface::new(&tauri_config, Some(target_triple), dirs.tauri)?;\n\n  let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri);\n  let (config, _) = get_config(\n    &app,\n    &tauri_config,\n    &dev_options.features,\n    &CliOptions {\n      dev: true,\n      features: dev_options.features.clone(),\n      args: dev_options.args.clone(),\n      noise_level,\n      vars: Default::default(),\n      config: dev_options.config.clone(),\n      target_device: None,\n    },\n    dirs.tauri,\n  )?;\n\n  set_current_dir(dirs.tauri).context(\"failed to set current directory to Tauri directory\")?;\n\n  ensure_init(\n    &tauri_config,\n    config.app(),\n    config.project_dir(),\n    MobileTarget::Ios,\n    false,\n  )?;\n  inject_resources(&config, &tauri_config)?;\n\n  let info_plist_path = config\n    .project_dir()\n    .join(config.scheme())\n    .join(\"Info.plist\");\n  let mut src_plists = vec![info_plist_path.clone().into()];\n  if dirs.tauri.join(\"Info.plist\").exists() {\n    src_plists.push(dirs.tauri.join(\"Info.plist\").into());\n  }\n  if dirs.tauri.join(\"Info.ios.plist\").exists() {\n    src_plists.push(dirs.tauri.join(\"Info.ios.plist\").into());\n  }\n  if let Some(info_plist) = &tauri_config.bundle.ios.info_plist {\n    src_plists.push(info_plist.clone().into());\n  }\n  let merged_info_plist = merge_plist(src_plists)?;\n  merged_info_plist\n    .to_file_xml(&info_plist_path)\n    .map_err(std::io::Error::other)\n    .fs_context(\"failed to save merged Info.plist file\", info_plist_path)?;\n\n  let mut pbxproj = load_pbxproj(&config)?;\n\n  // synchronize pbxproj\n  synchronize_project_config(\n    &config,\n    &tauri_config,\n    &mut pbxproj,\n    &mut plist::Dictionary::new(),\n    &ProjectConfig {\n      code_sign_identity: None,\n      team_id: None,\n      provisioning_profile_uuid: None,\n    },\n    !options.release_mode,\n  )?;\n  if pbxproj.has_changes() {\n    pbxproj\n      .save()\n      .fs_context(\"failed to save pbxproj file\", pbxproj.path)?;\n  }\n\n  run_dev(\n    interface,\n    options,\n    dev_options,\n    tauri_config,\n    device,\n    env,\n    &config,\n    noise_level,\n    &dirs,\n  )\n}\n\n#[allow(clippy::too_many_arguments)]\nfn run_dev(\n  mut interface: AppInterface,\n  options: Options,\n  mut dev_options: DevOptions,\n  mut tauri_config: ConfigMetadata,\n  device: Option<Device>,\n  env: Env,\n  config: &AppleConfig,\n  noise_level: NoiseLevel,\n  dirs: &Dirs,\n) -> Result<()> {\n  // when --host is provided or running on a physical device or resolving 0.0.0.0 we must use the network IP\n  if options.host.0.is_some()\n    || device\n      .as_ref()\n      .map(|device| !matches!(device.kind(), DeviceKind::Simulator))\n      .unwrap_or(false)\n    || tauri_config.build.dev_url.as_ref().is_some_and(|url| {\n      matches!(\n          url.host(),\n          Some(Host::Ipv4(i)) if i == Ipv4Addr::UNSPECIFIED\n      )\n    })\n  {\n    use_network_address_for_dev_url(\n      &mut tauri_config,\n      &mut dev_options,\n      options.force_ip_prompt,\n      dirs.tauri,\n    )?;\n  }\n\n  crate::dev::setup(&interface, &mut dev_options, &mut tauri_config, dirs)?;\n\n  let app_settings = interface.app_settings();\n  let out_dir = app_settings.out_dir(\n    &InterfaceOptions {\n      debug: !dev_options.release_mode,\n      target: dev_options.target.clone(),\n      ..Default::default()\n    },\n    dirs.tauri,\n  )?;\n  let _lock = flock::open_rw(out_dir.join(\"lock\").with_extension(\"ios\"), \"iOS\")?;\n\n  let set_host = options.host.0.is_some();\n\n  let open = options.open;\n  interface.mobile_dev(\n    &mut tauri_config,\n    MobileOptions {\n      debug: true,\n      features: options.features,\n      args: options.args,\n      config: dev_options.config.clone(),\n      no_watch: options.no_watch,\n      additional_watch_folders: options.additional_watch_folders,\n    },\n    |options, tauri_config| {\n      let cli_options = CliOptions {\n        dev: true,\n        features: options.features.clone(),\n        args: options.args.clone(),\n        noise_level,\n        vars: Default::default(),\n        config: dev_options.config.clone(),\n        target_device: None,\n      };\n      let _handle = write_options(tauri_config, cli_options)?;\n\n      let open_xcode = || {\n        if !set_host {\n          log::warn!(\"{PHYSICAL_IPHONE_DEV_WARNING}\");\n        }\n        open_and_wait(config, &env)\n      };\n\n      if open {\n        open_xcode()\n      } else if let Some(device) = &device {\n        match run(device, options, config, noise_level, &env) {\n          Ok(c) => Ok(Box::new(c) as Box<dyn DevProcess + Send>),\n          Err(RunError::BuildFailed(BuildError::Sdk(sdk_err))) => {\n            log::warn!(\"{sdk_err}\");\n            open_xcode()\n          }\n          Err(e) => {\n            crate::dev::kill_before_dev_process();\n            crate::error::bail!(\"failed to run iOS app: {}\", e)\n          }\n        }\n      } else {\n        open_xcode()\n      }\n    },\n    dirs,\n  )\n}\n\nfn run(\n  device: &Device<'_>,\n  options: MobileOptions,\n  config: &AppleConfig,\n  noise_level: NoiseLevel,\n  env: &Env,\n) -> std::result::Result<DevChild, RunError> {\n  let profile = if options.debug {\n    Profile::Debug\n  } else {\n    Profile::Release\n  };\n\n  device\n    .run(\n      config,\n      env,\n      noise_level,\n      false, // do not quit on app exit\n      profile,\n    )\n    .map(DevChild::new)\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/ios/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse cargo_mobile2::{\n  apple::{\n    config::{\n      Config as AppleConfig, Metadata as AppleMetadata, Platform as ApplePlatform,\n      Raw as RawAppleConfig,\n    },\n    device::{self, Device},\n    target::Target,\n    teams::find_development_teams,\n  },\n  config::app::{App, DEFAULT_ASSET_DIR},\n  env::Env,\n  opts::NoiseLevel,\n  os,\n  util::{prompt, relativize_path},\n};\nuse clap::{Parser, Subcommand};\nuse serde::Deserialize;\nuse sublime_fuzzy::best_match;\nuse tauri_utils::resources::ResourcePaths;\n\nuse super::{\n  ensure_init, env, get_app, init::command as init_command, log_finished, read_options, CliOptions,\n  OptionsHandle, Target as MobileTarget, MIN_DEVICE_MATCH_SCORE,\n};\nuse crate::{\n  error::{Context, ErrorExt},\n  helpers::{\n    config::{BundleResources, Config as TauriConfig, ConfigMetadata},\n    pbxproj, strip_semver_prerelease_tag,\n  },\n  ConfigValue, Error, Result,\n};\n\nuse std::{\n  env::{set_var, var_os},\n  fs::create_dir_all,\n  path::Path,\n  str::FromStr,\n  thread::sleep,\n  time::Duration,\n};\n\nmod build;\nmod dev;\npub(crate) mod project;\nmod run;\nmod xcode_script;\n\npub const APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME: &str = \"APPLE_DEVELOPMENT_TEAM\";\npub const LIB_OUTPUT_FILE_NAME: &str = \"libapp.a\";\n\n#[derive(Parser)]\n#[clap(\n  author,\n  version,\n  about = \"iOS commands\",\n  subcommand_required(true),\n  arg_required_else_help(true)\n)]\npub struct Cli {\n  #[clap(subcommand)]\n  command: Commands,\n}\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Initialize iOS target in the project\")]\npub struct InitOptions {\n  /// Skip prompting for values\n  #[clap(long, env = \"CI\")]\n  ci: bool,\n  /// Reinstall dependencies\n  #[clap(short, long)]\n  reinstall_deps: bool,\n  /// Skips installing rust toolchains via rustup\n  #[clap(long)]\n  skip_targets_install: bool,\n  /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file\n  ///\n  /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.\n  ///\n  /// Note that a platform-specific file is looked up and merged with the default file by default\n  /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)\n  /// but you can use this for more specific use cases such as different build flavors.\n  #[clap(short, long)]\n  pub config: Vec<ConfigValue>,\n}\n\n#[derive(Subcommand)]\nenum Commands {\n  Init(InitOptions),\n  Dev(dev::Options),\n  Build(build::Options),\n  Run(run::Options),\n  #[clap(hide(true))]\n  XcodeScript(xcode_script::Options),\n}\n\npub fn command(cli: Cli, verbosity: u8) -> Result<()> {\n  let noise_level = NoiseLevel::from_occurrences(verbosity as u64);\n  match cli.command {\n    Commands::Init(options) => init_command(\n      MobileTarget::Ios,\n      options.ci,\n      options.reinstall_deps,\n      options.skip_targets_install,\n      options.config,\n    )?,\n    Commands::Dev(options) => dev::command(options, noise_level)?,\n    Commands::Build(options) => build::command(options, noise_level).map(|_| ())?,\n    Commands::Run(options) => run::command(options, noise_level)?,\n    Commands::XcodeScript(options) => xcode_script::command(options)?,\n  }\n\n  Ok(())\n}\n\npub fn get_config(\n  app: &App,\n  tauri_config: &TauriConfig,\n  features: &[String],\n  cli_options: &CliOptions,\n  tauri_dir: &Path,\n) -> Result<(AppleConfig, AppleMetadata)> {\n  let mut ios_options = cli_options.clone();\n  ios_options.features.extend_from_slice(features);\n\n  let bundle_version = if let Some(bundle_version) = tauri_config\n    .bundle\n    .ios\n    .bundle_version\n    .clone()\n    .or_else(|| tauri_config.version.clone())\n  {\n    // if it's a semver string, we must strip the prerelease tag\n    if let Ok(mut version) = semver::Version::from_str(&bundle_version) {\n      if !version.pre.is_empty() {\n        log::warn!(\"CFBundleVersion cannot have prerelease tag; stripping from {bundle_version}\");\n        strip_semver_prerelease_tag(&mut version)?;\n      }\n      // correctly serialize version - cannot contain `+` as build metadata separator\n      Some(format!(\n        \"{}.{}.{}{}\",\n        version.major,\n        version.minor,\n        version.patch,\n        if version.build.is_empty() {\n          \"\".to_string()\n        } else {\n          format!(\".{}\", version.build.as_str())\n        }\n      ))\n    } else {\n      // let it go as is - cargo-mobile2 will validate it\n      Some(bundle_version)\n    }\n  } else {\n    None\n  };\n  let full_bundle_version_short = if let Some(app_version) = &tauri_config.version {\n    if let Ok(mut version) = semver::Version::from_str(app_version) {\n      if !version.pre.is_empty() {\n        log::warn!(\n          \"CFBundleShortVersionString cannot have prerelease tag; stripping from {app_version}\"\n        );\n        strip_semver_prerelease_tag(&mut version)?;\n      }\n      // correctly serialize version - cannot contain `+` as build metadata separator\n      Some(format!(\n        \"{}.{}.{}{}\",\n        version.major,\n        version.minor,\n        version.patch,\n        if version.build.is_empty() {\n          \"\".to_string()\n        } else {\n          format!(\".{}\", version.build.as_str())\n        }\n      ))\n    } else {\n      // let it go as is - cargo-mobile2 will validate it\n      Some(app_version.clone())\n    }\n  } else {\n    bundle_version.clone()\n  };\n  let bundle_version_short = if let Some(full_version) = full_bundle_version_short.as_deref() {\n    let mut s = full_version.split('.');\n    let short_version = format!(\n      \"{}.{}.{}\",\n      s.next().unwrap_or(\"0\"),\n      s.next().unwrap_or(\"0\"),\n      s.next().unwrap_or(\"0\")\n    );\n\n    if short_version != full_version {\n      log::warn!(\"{full_version:?} is not a valid CFBundleShortVersionString since it must contain exactly three dot separated integers; setting it to {short_version} instead\");\n    }\n\n    Some(short_version)\n  } else {\n    None\n  };\n\n  let raw = RawAppleConfig {\n    development_team: std::env::var(APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME)\n        .ok()\n        .or_else(|| tauri_config.bundle.ios.development_team.clone())\n        .or_else(|| {\n          let teams = find_development_teams().unwrap_or_default();\n          match teams.len() {\n            0 => {\n              log::warn!(\"No code signing certificates found. You must add one and set the certificate development team ID on the `bundle > iOS > developmentTeam` config value or the `{APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME}` environment variable. To list the available certificates, run `tauri info`.\");\n              None\n            }\n            1 => None,\n            _ => {\n              log::warn!(\"You must set the code signing certificate development team ID on the `bundle > iOS > developmentTeam` config value or the `{APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME}` environment variable. Available certificates: {}\", teams.iter().map(|t| format!(\"{} (ID: {})\", t.name, t.id)).collect::<Vec<String>>().join(\", \"));\n              None\n            }\n          }\n        }),\n    ios_features: Some(ios_options.features.clone()),\n    bundle_version,\n    bundle_version_short,\n    ios_version: Some(tauri_config.bundle.ios.minimum_system_version.clone()),\n    ..Default::default()\n  };\n  let config = AppleConfig::from_raw(app.clone(), Some(raw))\n    .context(\"failed to create Apple configuration\")?;\n\n  let mut vendor_frameworks = Vec::new();\n  let mut frameworks = Vec::new();\n  for framework in tauri_config\n    .bundle\n    .ios\n    .frameworks\n    .clone()\n    .unwrap_or_default()\n  {\n    let framework_path = Path::new(&framework);\n    let ext = framework_path.extension().unwrap_or_default();\n    if ext.is_empty() {\n      frameworks.push(framework);\n    } else if ext == \"framework\" {\n      frameworks.push(\n        framework_path\n          .file_stem()\n          .unwrap()\n          .to_string_lossy()\n          .to_string(),\n      );\n    } else {\n      vendor_frameworks.push(\n        relativize_path(tauri_dir.join(framework_path), config.project_dir())\n          .to_string_lossy()\n          .to_string(),\n      );\n    }\n  }\n\n  let metadata = AppleMetadata {\n    supported: true,\n    ios: ApplePlatform {\n      cargo_args: Some(ios_options.args),\n      features: Some(ios_options.features),\n      frameworks: Some(frameworks),\n      vendor_frameworks: Some(vendor_frameworks),\n      ..Default::default()\n    },\n    macos: Default::default(),\n  };\n\n  set_var(\"TAURI_IOS_PROJECT_PATH\", config.project_dir());\n  set_var(\"TAURI_IOS_APP_NAME\", config.app().name());\n\n  Ok((config, metadata))\n}\n\nfn connected_device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {\n  let device_list = device::list_devices(env).map_err(|cause| {\n    Error::GenericError(format!(\"Failed to detect connected iOS devices: {cause}\"))\n  })?;\n  if !device_list.is_empty() {\n    let device = if let Some(t) = target {\n      let (device, score) = device_list\n        .into_iter()\n        .rev()\n        .map(|d| {\n          let score = best_match(t, d.name()).map_or(0, |m| m.score());\n          (d, score)\n        })\n        .max_by_key(|(_, score)| *score)\n        // we already checked the list is not empty\n        .unwrap();\n      if score > MIN_DEVICE_MATCH_SCORE {\n        device\n      } else {\n        crate::error::bail!(\"Could not find an iOS device matching {t}\")\n      }\n    } else {\n      let index = if device_list.len() > 1 {\n        prompt::list(\n          concat!(\"Detected \", \"iOS\", \" devices\"),\n          device_list.iter(),\n          \"device\",\n          None,\n          \"Device\",\n        )\n        .context(\"failed to prompt for device\")?\n      } else {\n        0\n      };\n      device_list.into_iter().nth(index).unwrap()\n    };\n    println!(\n      \"Detected connected device: {} with target {:?}\",\n      device,\n      device.target().triple,\n    );\n\n    Ok(device)\n  } else {\n    crate::error::bail!(\"No connected iOS devices detected\")\n  }\n}\n\n#[derive(Default, Deserialize)]\nstruct InstalledRuntimesList {\n  runtimes: Vec<InstalledRuntime>,\n}\n\n#[derive(Deserialize)]\nstruct InstalledRuntime {\n  name: String,\n}\n\nfn simulator_prompt(env: &'_ Env, target: Option<&str>) -> Result<device::Simulator> {\n  let simulator_list = device::list_simulators(env).map_err(|cause| {\n    Error::GenericError(format!(\n      \"Failed to detect connected iOS Simulator devices: {cause}\"\n    ))\n  })?;\n  if !simulator_list.is_empty() {\n    let device = if let Some(t) = target {\n      let (device, score) = simulator_list\n        .into_iter()\n        .rev()\n        .map(|d| {\n          let score = best_match(t, d.name()).map_or(0, |m| m.score());\n          (d, score)\n        })\n        .max_by_key(|(_, score)| *score)\n        // we already checked the list is not empty\n        .unwrap();\n      if score > MIN_DEVICE_MATCH_SCORE {\n        device\n      } else {\n        crate::error::bail!(\"Could not find an iOS Simulator matching {t}\")\n      }\n    } else if simulator_list.len() > 1 {\n      let index = prompt::list(\n        concat!(\"Detected \", \"iOS\", \" simulators\"),\n        simulator_list.iter(),\n        \"simulator\",\n        None,\n        \"Simulator\",\n      )\n      .context(\"failed to prompt for simulator\")?;\n      simulator_list.into_iter().nth(index).unwrap()\n    } else {\n      simulator_list.into_iter().next().unwrap()\n    };\n    Ok(device)\n  } else {\n    log::warn!(\"No available iOS Simulator detected\");\n    let install_ios = crate::helpers::prompts::confirm(\n      \"Would you like to install the latest iOS runtime?\",\n      Some(false),\n    )\n    .unwrap_or_default();\n    if install_ios {\n      duct::cmd(\"xcodebuild\", [\"-downloadPlatform\", \"iOS\"])\n        .stdout_file(os_pipe::dup_stdout().unwrap())\n        .stderr_file(os_pipe::dup_stderr().unwrap())\n        .run()\n        .map_err(|e| Error::CommandFailed {\n          command: \"xcodebuild -downloadPlatform iOS\".to_string(),\n          error: e,\n        })?;\n      return simulator_prompt(env, target);\n    }\n    crate::error::bail!(\"No available iOS Simulator detected\")\n  }\n}\n\nfn device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {\n  if let Ok(device) = connected_device_prompt(env, target) {\n    Ok(device)\n  } else {\n    let simulator = simulator_prompt(env, target)?;\n    log::info!(\"Starting simulator {}\", simulator.name());\n    simulator\n      .start_detached(env)\n      .context(\"failed to start simulator\")?;\n    Ok(simulator.into())\n  }\n}\n\nfn ensure_ios_runtime_installed() -> Result<()> {\n  let installed_platforms_json = duct::cmd(\"xcrun\", [\"simctl\", \"list\", \"runtimes\", \"--json\"])\n    .read()\n    .map_err(|e| Error::CommandFailed {\n      command: \"xcrun simctl list runtimes --json\".to_string(),\n      error: e,\n    })?;\n  let installed_platforms: InstalledRuntimesList =\n    serde_json::from_str(&installed_platforms_json).unwrap_or_default();\n  if !installed_platforms\n    .runtimes\n    .iter()\n    .any(|r| r.name.starts_with(\"iOS\"))\n  {\n    log::warn!(\"iOS platform not installed\");\n    let install_ios = crate::helpers::prompts::confirm(\n      \"Would you like to install the latest iOS runtime?\",\n      Some(false),\n    )\n    .unwrap_or_default();\n    if install_ios {\n      duct::cmd(\"xcodebuild\", [\"-downloadPlatform\", \"iOS\"])\n        .stdout_file(os_pipe::dup_stdout().unwrap())\n        .stderr_file(os_pipe::dup_stderr().unwrap())\n        .run()\n        .map_err(|e| Error::CommandFailed {\n          command: \"xcodebuild -downloadPlatform iOS\".to_string(),\n          error: e,\n        })?;\n    } else {\n      crate::error::bail!(\"iOS platform not installed\");\n    }\n  }\n  Ok(())\n}\n\nfn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {\n  device_prompt(env, None).map(|device| device.target()).ok()\n}\n\nfn open_and_wait(config: &AppleConfig, env: &Env) -> ! {\n  log::info!(\"Opening Xcode\");\n  if let Err(e) = os::open_file_with(\"Xcode\", config.project_dir(), env) {\n    log::error!(\"{e}\");\n  }\n  loop {\n    sleep(Duration::from_secs(24 * 60 * 60));\n  }\n}\n\nfn inject_resources(config: &AppleConfig, tauri_config: &TauriConfig) -> Result<()> {\n  let asset_dir = config.project_dir().join(DEFAULT_ASSET_DIR);\n  create_dir_all(&asset_dir).fs_context(\"failed to create asset directory\", asset_dir.clone())?;\n\n  let resources = match &tauri_config.bundle.resources {\n    Some(BundleResources::List(paths)) => Some(ResourcePaths::new(paths.as_slice(), true)),\n    Some(BundleResources::Map(map)) => Some(ResourcePaths::from_map(map, true)),\n    None => None,\n  };\n  if let Some(resources) = resources {\n    for resource in resources.iter() {\n      let resource = resource.context(\"failed to get resource\")?;\n      let dest = asset_dir.join(resource.target());\n      crate::helpers::fs::copy_file(resource.path(), dest)?;\n    }\n  }\n\n  Ok(())\n}\n\npub fn signing_from_env() -> Result<(\n  Option<tauri_macos_sign::Keychain>,\n  Option<tauri_macos_sign::ProvisioningProfile>,\n)> {\n  let keychain = match (\n    var_os(\"IOS_CERTIFICATE\"),\n    var_os(\"IOS_CERTIFICATE_PASSWORD\"),\n  ) {\n    (Some(certificate), Some(certificate_password)) => {\n      log::info!(\"Reading iOS certificates from \");\n      tauri_macos_sign::Keychain::with_certificate(&certificate, &certificate_password)\n        .map(Some)\n        .map_err(Box::new)?\n    }\n    (Some(_), None) => {\n      log::warn!(\"The IOS_CERTIFICATE environment variable is set but not IOS_CERTIFICATE_PASSWORD. Ignoring the certificate...\");\n      None\n    }\n    _ => None,\n  };\n\n  let provisioning_profile = if let Some(provisioning_profile) = var_os(\"IOS_MOBILE_PROVISION\") {\n    tauri_macos_sign::ProvisioningProfile::from_base64(&provisioning_profile)\n      .map(Some)\n      .map_err(Box::new)?\n  } else {\n    if keychain.is_some() {\n      log::warn!(\"You have provided an iOS certificate via environment variables but the IOS_MOBILE_PROVISION environment variable is not set. This will fail when signing unless the profile is set in your Xcode project.\");\n    }\n    None\n  };\n\n  Ok((keychain, provisioning_profile))\n}\n\npub struct ProjectConfig {\n  pub code_sign_identity: Option<String>,\n  pub team_id: Option<String>,\n  pub provisioning_profile_uuid: Option<String>,\n}\n\npub fn project_config(\n  keychain: Option<&tauri_macos_sign::Keychain>,\n  provisioning_profile: Option<&tauri_macos_sign::ProvisioningProfile>,\n) -> Result<ProjectConfig> {\n  Ok(ProjectConfig {\n    code_sign_identity: keychain.map(|k| k.signing_identity()),\n    team_id: keychain.and_then(|k| k.team_id().map(ToString::to_string)),\n    provisioning_profile_uuid: provisioning_profile.and_then(|p| p.uuid().ok()),\n  })\n}\n\npub fn load_pbxproj(config: &AppleConfig) -> Result<pbxproj::Pbxproj> {\n  pbxproj::parse(\n    config\n      .project_dir()\n      .join(format!(\"{}.xcodeproj\", config.app().name()))\n      .join(\"project.pbxproj\"),\n  )\n}\n\npub fn synchronize_project_config(\n  config: &AppleConfig,\n  tauri_config: &ConfigMetadata,\n  pbxproj: &mut pbxproj::Pbxproj,\n  export_options_plist: &mut plist::Dictionary,\n  project_config: &ProjectConfig,\n  debug: bool,\n) -> Result<()> {\n  let identifier = tauri_config.identifier.clone();\n  let product_name = tauri_config.product_name.clone();\n\n  let manual_signing = project_config.code_sign_identity.is_some()\n    || project_config.provisioning_profile_uuid.is_some();\n\n  if let Some(xc_configuration_list) = pbxproj\n    .xc_configuration_list\n    .clone()\n    .into_values()\n    .find(|l| l.comment.contains(\"_iOS\"))\n  {\n    for build_configuration_ref in xc_configuration_list.build_configurations {\n      if manual_signing {\n        pbxproj.set_build_settings(&build_configuration_ref.id, \"CODE_SIGN_STYLE\", \"Manual\");\n      }\n\n      if let Some(team) = config.development_team() {\n        let team = format!(\"\\\"{team}\\\"\");\n        pbxproj.set_build_settings(&build_configuration_ref.id, \"DEVELOPMENT_TEAM\", &team);\n      }\n\n      pbxproj.set_build_settings(\n        &build_configuration_ref.id,\n        \"PRODUCT_BUNDLE_IDENTIFIER\",\n        &identifier,\n      );\n\n      if let Some(product_name) = &product_name {\n        pbxproj.set_build_settings(\n          &build_configuration_ref.id,\n          \"PRODUCT_NAME\",\n          &format!(\"\\\"{product_name}\\\"\"),\n        );\n      }\n\n      if let Some(identity) = &project_config.code_sign_identity {\n        let identity = format!(\"\\\"{identity}\\\"\");\n        pbxproj.set_build_settings(&build_configuration_ref.id, \"CODE_SIGN_IDENTITY\", &identity);\n        pbxproj.set_build_settings(\n          &build_configuration_ref.id,\n          \"\\\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\\\"\",\n          &identity,\n        );\n      }\n\n      if let Some(id) = &project_config.team_id {\n        let id = format!(\"\\\"{id}\\\"\");\n        pbxproj.set_build_settings(&build_configuration_ref.id, \"DEVELOPMENT_TEAM\", &id);\n        pbxproj.set_build_settings(\n          &build_configuration_ref.id,\n          \"\\\"DEVELOPMENT_TEAM[sdk=iphoneos*]\\\"\",\n          &id,\n        );\n      }\n\n      if let Some(profile_uuid) = &project_config.provisioning_profile_uuid {\n        let profile_uuid = format!(\"\\\"{profile_uuid}\\\"\");\n        pbxproj.set_build_settings(\n          &build_configuration_ref.id,\n          \"PROVISIONING_PROFILE_SPECIFIER\",\n          &profile_uuid,\n        );\n        pbxproj.set_build_settings(\n          &build_configuration_ref.id,\n          \"\\\"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]\\\"\",\n          &profile_uuid,\n        );\n      }\n    }\n  }\n\n  let build_configuration = {\n    if let Some(xc_configuration_list) = pbxproj\n      .xc_configuration_list\n      .clone()\n      .into_values()\n      .find(|l| l.comment.contains(\"_iOS\"))\n    {\n      let mut configuration = None;\n      let target = if debug { \"debug\" } else { \"release\" };\n      for build_configuration_ref in xc_configuration_list.build_configurations {\n        if build_configuration_ref.comments.contains(target) {\n          configuration = pbxproj\n            .xc_build_configuration\n            .get(&build_configuration_ref.id);\n          break;\n        }\n      }\n\n      configuration\n    } else {\n      None\n    }\n  };\n\n  if let Some(build_configuration) = build_configuration {\n    if let Some(style) = build_configuration.get_build_setting(\"CODE_SIGN_STYLE\") {\n      export_options_plist.insert(\n        \"signingStyle\".to_string(),\n        style.value.to_lowercase().into(),\n      );\n    } else {\n      export_options_plist.insert(\"signingStyle\".to_string(), \"automatic\".into());\n    }\n\n    if manual_signing {\n      if let Some(identity) = build_configuration\n        .get_build_setting(\"\\\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\\\"\")\n        .or_else(|| build_configuration.get_build_setting(\"CODE_SIGN_IDENTITY\"))\n      {\n        export_options_plist.insert(\n          \"signingCertificate\".to_string(),\n          identity.value.trim_matches('\"').into(),\n        );\n      }\n\n      let profile_uuid = project_config\n        .provisioning_profile_uuid\n        .clone()\n        .or_else(|| {\n          build_configuration\n            .get_build_setting(\"\\\"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]\\\"\")\n            .or_else(|| build_configuration.get_build_setting(\"PROVISIONING_PROFILE_SPECIFIER\"))\n            .map(|setting| setting.value.trim_matches('\"').to_string())\n        });\n      if let Some(profile_uuid) = profile_uuid {\n        let mut provisioning_profiles = plist::Dictionary::new();\n        provisioning_profiles.insert(config.app().identifier().to_string(), profile_uuid.into());\n        export_options_plist.insert(\n          \"provisioningProfiles\".to_string(),\n          provisioning_profiles.into(),\n        );\n      }\n    }\n\n    if let Some(id) = build_configuration\n      .get_build_setting(\"\\\"DEVELOPMENT_TEAM[sdk=iphoneos*]\\\"\")\n      .or_else(|| build_configuration.get_build_setting(\"DEVELOPMENT_TEAM\"))\n    {\n      export_options_plist.insert(\"teamID\".to_string(), id.value.trim_matches('\"').into());\n    }\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/ios/project.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::Context,\n  helpers::{config::Config as TauriConfig, template},\n  mobile::ios::LIB_OUTPUT_FILE_NAME,\n  Error, ErrorExt, Result,\n};\nuse cargo_mobile2::{\n  apple::{\n    config::{Config, Metadata},\n    deps,\n    target::Target,\n  },\n  config::app::DEFAULT_ASSET_DIR,\n  target::TargetTrait as _,\n  util::{self, cli::TextWrapper},\n};\nuse handlebars::Handlebars;\nuse include_dir::{include_dir, Dir};\nuse std::{\n  ffi::OsString,\n  fs::{create_dir_all, OpenOptions},\n  path::{Component, PathBuf},\n};\n\nconst TEMPLATE_DIR: Dir<'_> = include_dir!(\"$CARGO_MANIFEST_DIR/templates/mobile/ios\");\n\n// unprefixed app_root seems pretty dangerous!!\n// TODO: figure out what cargo-mobile meant by that\n#[allow(clippy::too_many_arguments)]\npub fn gen(\n  tauri_config: &TauriConfig,\n  config: &Config,\n  metadata: &Metadata,\n  (handlebars, mut map): (Handlebars, template::JsonMap),\n  wrapper: &TextWrapper,\n  non_interactive: bool,\n  reinstall_deps: bool,\n  skip_targets_install: bool,\n) -> Result<()> {\n  if !skip_targets_install {\n    let installed_targets =\n      crate::interface::rust::installation::installed_targets().unwrap_or_default();\n    let missing_targets = Target::all()\n      .values()\n      .filter(|t| !installed_targets.contains(&t.triple().into()))\n      .collect::<Vec<&Target>>();\n\n    if !missing_targets.is_empty() {\n      log::info!(\"Installing iOS Rust targets...\");\n      for target in missing_targets {\n        log::info!(\"Installing target {}\", target.triple());\n        target.install().map_err(|error| Error::CommandFailed {\n          command: \"rustup target add\".to_string(),\n          error,\n        })?;\n      }\n    }\n  }\n\n  deps::install_all(wrapper, non_interactive, true, reinstall_deps).map_err(|error| {\n    Error::CommandFailed {\n      command: \"pod install\".to_string(),\n      error: std::io::Error::other(error),\n    }\n  })?;\n\n  let dest = config.project_dir();\n  let rel_prefix = util::relativize_path(config.app().root_dir(), &dest);\n  let source_dirs = vec![rel_prefix.join(\"src\")];\n\n  let asset_catalogs = metadata.ios().asset_catalogs().unwrap_or_default();\n  let ios_pods = metadata.ios().pods().unwrap_or_default();\n  let macos_pods = metadata.macos().pods().unwrap_or_default();\n\n  #[cfg(target_arch = \"aarch64\")]\n  let default_archs = [\"arm64\"];\n  #[cfg(not(target_arch = \"aarch64\"))]\n  let default_archs = [\"arm64\", \"x86_64\"];\n\n  map.insert(\"lib-output-file-name\", LIB_OUTPUT_FILE_NAME);\n\n  map.insert(\"file-groups\", &source_dirs);\n  map.insert(\"ios-frameworks\", metadata.ios().frameworks());\n  map.insert(\"ios-valid-archs\", default_archs);\n  map.insert(\"ios-vendor-frameworks\", metadata.ios().vendor_frameworks());\n  map.insert(\"ios-vendor-sdks\", metadata.ios().vendor_sdks());\n  map.insert(\"macos-frameworks\", metadata.macos().frameworks());\n  map.insert(\n    \"macos-vendor-frameworks\",\n    metadata.macos().vendor_frameworks(),\n  );\n  map.insert(\"macos-vendor-sdks\", metadata.macos().vendor_frameworks());\n  map.insert(\"asset-catalogs\", asset_catalogs);\n  map.insert(\"ios-pods\", ios_pods);\n  map.insert(\"macos-pods\", macos_pods);\n  map.insert(\n    \"ios-additional-targets\",\n    metadata.ios().additional_targets(),\n  );\n  map.insert(\n    \"macos-additional-targets\",\n    metadata.macos().additional_targets(),\n  );\n  map.insert(\"ios-pre-build-scripts\", metadata.ios().pre_build_scripts());\n  map.insert(\n    \"ios-post-compile-scripts\",\n    metadata.ios().post_compile_scripts(),\n  );\n  map.insert(\n    \"ios-post-build-scripts\",\n    metadata.ios().post_build_scripts(),\n  );\n  map.insert(\n    \"macos-pre-build-scripts\",\n    metadata.macos().pre_build_scripts(),\n  );\n  map.insert(\n    \"macos-post-compile-scripts\",\n    metadata.macos().post_compile_scripts(),\n  );\n  map.insert(\n    \"macos-post-build-scripts\",\n    metadata.macos().post_build_scripts(),\n  );\n  map.insert(\n    \"ios-command-line-arguments\",\n    metadata.ios().command_line_arguments(),\n  );\n  map.insert(\n    \"macos-command-line-arguments\",\n    metadata.macos().command_line_arguments(),\n  );\n\n  let mut created_dirs = Vec::new();\n  template::render_with_generator(\n    &handlebars,\n    map.inner(),\n    &TEMPLATE_DIR,\n    &dest,\n    &mut |path| {\n      let mut components: Vec<_> = path.components().collect();\n      let mut new_component = None;\n      for component in &mut components {\n        if let Component::Normal(c) = component {\n          let c = c.to_string_lossy();\n          if c.contains(\"{{app.name}}\") {\n            new_component.replace(OsString::from(\n              &c.replace(\"{{app.name}}\", config.app().name()),\n            ));\n            *component = Component::Normal(new_component.as_ref().unwrap());\n            break;\n          }\n        }\n      }\n      let path = dest.join(components.iter().collect::<PathBuf>());\n\n      let parent = path.parent().unwrap().to_path_buf();\n      if !created_dirs.contains(&parent) {\n        create_dir_all(&parent)?;\n        created_dirs.push(parent);\n      }\n\n      let mut options = OpenOptions::new();\n      options.write(true);\n\n      if !path.exists() {\n        options.create(true).open(path).map(Some)\n      } else {\n        Ok(None)\n      }\n    },\n  )\n  .with_context(|| \"failed to process template\")?;\n\n  if let Some(template_path) = tauri_config.bundle.ios.template.as_ref() {\n    let template = std::fs::read_to_string(template_path).fs_context(\n      \"failed to read custom Xcode project template\",\n      template_path.to_path_buf(),\n    )?;\n    let mut output_file = std::fs::File::create(dest.join(\"project.yml\")).fs_context(\n      \"failed to create project.yml file\",\n      dest.join(\"project.yml\"),\n    )?;\n    handlebars\n      .render_template_to_write(&template, map.inner(), &mut output_file)\n      .expect(\"Failed to render template\");\n  }\n\n  let mut dirs_to_create = asset_catalogs.to_vec();\n  dirs_to_create.push(dest.join(DEFAULT_ASSET_DIR));\n  dirs_to_create.push(dest.join(\"Externals\"));\n  dirs_to_create.push(dest.join(format!(\"{}_iOS\", config.app().name())));\n\n  // Create all required project directories if they don't already exist\n  for dir in &dirs_to_create {\n    std::fs::create_dir_all(dir).fs_context(\"failed to create directory\", dir.to_path_buf())?;\n  }\n\n  // Note that Xcode doesn't always reload the project nicely; reopening is\n  // often necessary.\n  println!(\"Generating Xcode project...\");\n  duct::cmd(\n    \"xcodegen\",\n    [\n      \"generate\",\n      \"--spec\",\n      &dest.join(\"project.yml\").to_string_lossy(),\n    ],\n  )\n  .stdout_file(os_pipe::dup_stdout().unwrap())\n  .stderr_file(os_pipe::dup_stderr().unwrap())\n  .run()\n  .map_err(|error| Error::CommandFailed {\n    command: \"xcodegen\".to_string(),\n    error,\n  })?;\n\n  if !ios_pods.is_empty() || !macos_pods.is_empty() {\n    duct::cmd(\n      \"pod\",\n      [\n        \"install\",\n        &format!(\"--project-directory={}\", dest.display()),\n      ],\n    )\n    .stdout_file(os_pipe::dup_stdout().unwrap())\n    .stderr_file(os_pipe::dup_stderr().unwrap())\n    .run()\n    .map_err(|error| Error::CommandFailed {\n      command: \"pod install\".to_string(),\n      error,\n    })?;\n  }\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/ios/run.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::PathBuf;\n\nuse cargo_mobile2::opts::{NoiseLevel, Profile};\nuse clap::{ArgAction, Parser};\n\nuse super::{device_prompt, env};\nuse crate::{\n  error::Context,\n  helpers::config::{get_config as get_tauri_config, ConfigMetadata},\n  interface::{DevProcess, WatcherOptions},\n  mobile::{DevChild, TargetDevice},\n  ConfigValue, Result,\n};\n\n#[derive(Debug, Clone, Parser)]\n#[clap(\n  about = \"Run your app in production mode on iOS\",\n  long_about = \"Run your app in production mode on iOS. It makes use of the `build.frontendDist` property from your `tauri.conf.json` file. It also runs your `build.beforeBuildCommand` which usually builds your frontend into `build.frontendDist`.\"\n)]\npub struct Options {\n  /// Run the app in release mode\n  #[clap(short, long)]\n  pub release: bool,\n  /// List of cargo features to activate\n  #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]\n  pub features: Vec<String>,\n  /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file\n  ///\n  /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.\n  ///\n  /// Note that a platform-specific file is looked up and merged with the default file by default\n  /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)\n  /// but you can use this for more specific use cases such as different build flavors.\n  #[clap(short, long)]\n  pub config: Vec<ConfigValue>,\n  /// Disable the file watcher\n  #[clap(long)]\n  pub no_watch: bool,\n  /// Additional paths to watch for changes.\n  #[clap(long)]\n  pub additional_watch_folders: Vec<PathBuf>,\n  /// Open Xcode\n  #[clap(short, long)]\n  pub open: bool,\n  /// Runs on the given device name\n  pub device: Option<String>,\n  /// Command line arguments passed to the runner.\n  /// Use `--` to explicitly mark the start of the arguments.\n  /// e.g. `tauri android build -- [runnerArgs]`.\n  #[clap(last(true))]\n  pub args: Vec<String>,\n  /// Do not error out if a version mismatch is detected on a Tauri package.\n  ///\n  /// Only use this when you are sure the mismatch is incorrectly detected as version mismatched Tauri packages can lead to unknown behavior.\n  #[clap(long)]\n  pub ignore_version_mismatches: bool,\n}\n\npub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {\n  let env = env().context(\"failed to load iOS environment\")?;\n  let device = if options.open {\n    None\n  } else {\n    match device_prompt(&env, options.device.as_deref()) {\n      Ok(d) => Some(d),\n      Err(e) => {\n        log::error!(\"{e}\");\n        None\n      }\n    }\n  };\n\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n\n  let mut built_application = super::build::run(\n    super::build::Options {\n      debug: !options.release,\n      targets: Some(vec![]), /* skips IPA build since there's no target */\n      features: Vec::new(),\n      config: options.config.clone(),\n      build_number: None,\n      open: options.open,\n      ci: false,\n      export_method: None,\n      args: options.args,\n      ignore_version_mismatches: options.ignore_version_mismatches,\n      target_device: device.as_ref().map(|d| TargetDevice {\n        id: d.id().to_string(),\n        name: d.name().to_string(),\n      }),\n    },\n    noise_level,\n    &dirs,\n  )?;\n\n  let mut tauri_config = get_tauri_config(\n    tauri_utils::platform::Target::Ios,\n    &options.config.iter().map(|c| &c.0).collect::<Vec<_>>(),\n    dirs.tauri,\n  )?;\n\n  // options.open is handled by the build command\n  // so all we need to do here is run the app on the selected device\n  if let Some(device) = device {\n    let runner = move |_tauri_config: &ConfigMetadata| {\n      device\n        .run(\n          &built_application.config,\n          &env,\n          noise_level,\n          false, // do not quit on app exit\n          if !options.release {\n            Profile::Debug\n          } else {\n            Profile::Release\n          },\n        )\n        .map(|c| Box::new(DevChild::new(c)) as Box<dyn DevProcess + Send>)\n        .context(\"failed to run iOS app\")\n    };\n\n    if options.no_watch {\n      runner(&tauri_config)?;\n    } else {\n      built_application.interface.watch(\n        &mut tauri_config,\n        WatcherOptions {\n          config: options.config,\n          additional_watch_folders: options.additional_watch_folders,\n        },\n        runner,\n        &dirs,\n      )?;\n    }\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/ios/xcode_script.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::{ensure_init, env, get_app, get_config, read_options, MobileTarget};\nuse crate::{\n  error::{Context, ErrorExt},\n  helpers::config::{get_config as get_tauri_config, reload_config as reload_tauri_config},\n  interface::{AppInterface, Options as InterfaceOptions},\n  mobile::ios::LIB_OUTPUT_FILE_NAME,\n  Error, Result,\n};\n\nuse cargo_mobile2::{apple::target::Target, opts::Profile, target::TargetTrait};\nuse clap::{ArgAction, Parser};\nuse object::{Object, ObjectSymbol};\n\nuse std::{\n  collections::HashMap,\n  env::{current_dir, set_current_dir, var, var_os},\n  ffi::OsStr,\n  fs::read_to_string,\n  io::Read,\n  path::{Path, PathBuf},\n};\n\n#[derive(Debug, Parser)]\npub struct Options {\n  /// Value of `PLATFORM_DISPLAY_NAME` env var\n  #[clap(long)]\n  platform: String,\n  /// Value of `SDKROOT` env var\n  #[clap(long)]\n  sdk_root: PathBuf,\n  /// Value of `FRAMEWORK_SEARCH_PATHS` env var\n  #[clap(long, action = ArgAction::Append, num_args(0..))]\n  framework_search_paths: Vec<String>,\n  /// Value of `GCC_PREPROCESSOR_DEFINITIONS` env var\n  #[clap(long, action = ArgAction::Append, num_args(0..))]\n  gcc_preprocessor_definitions: Vec<String>,\n  /// Value of `HEADER_SEARCH_PATHS` env var\n  #[clap(long, action = ArgAction::Append, num_args(0..))]\n  header_search_paths: Vec<String>,\n  /// Value of `CONFIGURATION` env var\n  #[clap(long)]\n  configuration: String,\n  /// Value of `FORCE_COLOR` env var\n  #[clap(long)]\n  force_color: bool,\n  /// Value of `ARCHS` env var\n  #[clap(index = 1, required = true)]\n  arches: Vec<String>,\n}\n\npub fn command(options: Options) -> Result<()> {\n  fn macos_from_platform(platform: &str) -> bool {\n    platform == \"macOS\"\n  }\n\n  fn profile_from_configuration(configuration: &str) -> Profile {\n    if configuration == \"release\" {\n      Profile::Release\n    } else {\n      Profile::Debug\n    }\n  }\n\n  let process_path = std::env::current_exe().unwrap_or_default();\n\n  // `xcode-script` is ran from the `gen/apple` folder when not using NPM/yarn/pnpm/deno.\n  // so we must change working directory to the src-tauri folder to resolve the tauri dir\n  // additionally, bun@<1.2 does not modify the current working directory, so it is also runs this script from `gen/apple`\n  // bun@>1.2 now actually moves the CWD to the package root so we shouldn't modify CWD in that case\n  // see https://bun.sh/blog/bun-v1.2#bun-run-uses-the-correct-directory\n  if (var_os(\"npm_lifecycle_event\").is_none()\n    && var_os(\"PNPM_PACKAGE_NAME\").is_none()\n    && process_path.file_stem().unwrap_or_default() != \"deno\")\n    || var(\"npm_config_user_agent\")\n      .is_ok_and(|agent| agent.starts_with(\"bun/1.0\") || agent.starts_with(\"bun/1.1\"))\n  {\n    set_current_dir(\n      current_dir()\n        .context(\"failed to resolve current directory\")?\n        .parent()\n        .unwrap()\n        .parent()\n        .unwrap(),\n    )\n    .unwrap();\n  }\n\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n\n  let profile = profile_from_configuration(&options.configuration);\n  let macos = macos_from_platform(&options.platform);\n\n  let mut tauri_config = get_tauri_config(tauri_utils::platform::Target::Ios, &[], dirs.tauri)?;\n  let cli_options = read_options(&tauri_config);\n  if !cli_options.config.is_empty() {\n    // reload config with merges from the ios dev|build script\n    reload_tauri_config(\n      &mut tauri_config,\n      &cli_options\n        .config\n        .iter()\n        .map(|conf| &conf.0)\n        .collect::<Vec<_>>(),\n      dirs.tauri,\n    )?\n  };\n\n  let (config, metadata) = get_config(\n    &get_app(\n      MobileTarget::Ios,\n      &tauri_config,\n      &AppInterface::new(&tauri_config, None, dirs.tauri)?,\n      dirs.tauri,\n    ),\n    &tauri_config,\n    &[],\n    &cli_options,\n    dirs.tauri,\n  )?;\n\n  ensure_init(\n    &tauri_config,\n    config.app(),\n    config.project_dir(),\n    MobileTarget::Ios,\n    std::env::var(\"CI\").is_ok(),\n  )?;\n\n  if !cli_options.config.is_empty() {\n    crate::helpers::config::merge_config_with(\n      &mut tauri_config,\n      &cli_options\n        .config\n        .iter()\n        .map(|conf| &conf.0)\n        .collect::<Vec<_>>(),\n    )?;\n  }\n\n  let env = env()\n    .context(\"failed to load iOS environment\")?\n    .explicit_env_vars(cli_options.vars);\n\n  if !options.sdk_root.is_dir() {\n    crate::error::bail!(\n      \"SDK root provided by Xcode was invalid. {} doesn't exist or isn't a directory\",\n      options.sdk_root.display(),\n    );\n  }\n  let include_dir = options.sdk_root.join(\"usr/include\");\n  if !include_dir.is_dir() {\n    crate::error::bail!(\n      \"Include dir was invalid. {} doesn't exist or isn't a directory\",\n      include_dir.display()\n    );\n  }\n\n  // Host flags that are used by build scripts\n  let macos_isysroot = {\n    let macos_sdk_root = options\n      .sdk_root\n      .join(\"../../../../MacOSX.platform/Developer/SDKs/MacOSX.sdk\");\n    if !macos_sdk_root.is_dir() {\n      crate::error::bail!(\"Invalid SDK root {}\", macos_sdk_root.display());\n    }\n    format!(\"-isysroot {}\", macos_sdk_root.display())\n  };\n\n  let mut host_env = HashMap::<&str, &OsStr>::new();\n\n  host_env.insert(\"RUST_BACKTRACE\", \"1\".as_ref());\n\n  host_env.insert(\"CFLAGS_x86_64_apple_darwin\", macos_isysroot.as_ref());\n  host_env.insert(\"CXXFLAGS_x86_64_apple_darwin\", macos_isysroot.as_ref());\n\n  host_env.insert(\n    \"OBJC_INCLUDE_PATH_x86_64_apple_darwin\",\n    include_dir.as_os_str(),\n  );\n\n  let framework_search_paths = options.framework_search_paths.join(\" \");\n  host_env.insert(\"FRAMEWORK_SEARCH_PATHS\", framework_search_paths.as_ref());\n\n  let gcc_preprocessor_definitions = options.gcc_preprocessor_definitions.join(\" \");\n  host_env.insert(\n    \"GCC_PREPROCESSOR_DEFINITIONS\",\n    gcc_preprocessor_definitions.as_ref(),\n  );\n\n  let header_search_paths = options.header_search_paths.join(\" \");\n  host_env.insert(\"HEADER_SEARCH_PATHS\", header_search_paths.as_ref());\n\n  let macos_target = Target::macos();\n\n  let isysroot = format!(\"-isysroot {}\", options.sdk_root.display());\n\n  let simulator =\n    options.platform == \"iOS Simulator\" || options.arches.contains(&\"Simulator\".to_string());\n  let arches = if simulator {\n    // when compiling for the simulator, we don't need to build other targets\n    vec![if cfg!(target_arch = \"aarch64\") {\n      \"arm64\"\n    } else {\n      \"x86_64\"\n    }\n    .to_string()]\n  } else {\n    options.arches\n  };\n\n  let installed_targets =\n    crate::interface::rust::installation::installed_targets().unwrap_or_default();\n\n  for arch in arches {\n    // Set target-specific flags\n    let (env_triple, rust_triple) = match arch.as_str() {\n      \"arm64\" if !simulator => (\"aarch64_apple_ios\", \"aarch64-apple-ios\"),\n      \"arm64\" if simulator => (\"aarch64_apple_ios_sim\", \"aarch64-apple-ios-sim\"),\n      \"x86_64\" => (\"x86_64_apple_ios\", \"x86_64-apple-ios\"),\n      _ => {\n        crate::error::bail!(\"Arch specified by Xcode was invalid. {arch} isn't a known arch\")\n      }\n    };\n\n    let interface = AppInterface::new(&tauri_config, Some(rust_triple.into()), dirs.tauri)?;\n\n    let cflags = format!(\"CFLAGS_{env_triple}\");\n    let cxxflags = format!(\"CFLAGS_{env_triple}\");\n    let objc_include_path = format!(\"OBJC_INCLUDE_PATH_{env_triple}\");\n    let mut target_env = host_env.clone();\n    target_env.insert(cflags.as_ref(), isysroot.as_ref());\n    target_env.insert(cxxflags.as_ref(), isysroot.as_ref());\n    target_env.insert(objc_include_path.as_ref(), include_dir.as_ref());\n\n    let target = if macos {\n      &macos_target\n    } else {\n      Target::for_arch(if arch == \"arm64\" && simulator {\n        \"arm64-sim\"\n      } else {\n        &arch\n      })\n      .with_context(|| format!(\"Arch specified by Xcode was invalid. {arch} isn't a known arch\"))?\n    };\n\n    if !installed_targets.contains(&rust_triple.into()) {\n      log::info!(\"Installing target {}\", target.triple());\n      target.install().map_err(|error| Error::CommandFailed {\n        command: \"rustup target add\".to_string(),\n        error,\n      })?;\n    }\n\n    target\n      .compile_lib(\n        &config,\n        &metadata,\n        cli_options.noise_level,\n        true,\n        profile,\n        &env,\n        target_env,\n      )\n      .context(\"failed to compile iOS app\")?;\n\n    let out_dir = interface.app_settings().out_dir(\n      &InterfaceOptions {\n        debug: matches!(profile, Profile::Debug),\n        target: Some(rust_triple.into()),\n        ..Default::default()\n      },\n      dirs.tauri,\n    )?;\n\n    let lib_path = out_dir.join(format!(\"lib{}.a\", config.app().lib_name()));\n    if !lib_path.exists() {\n      crate::error::bail!(\"Library not found at {}. Make sure your Cargo.toml file has a [lib] block with `crate-type = [\\\"staticlib\\\", \\\"cdylib\\\", \\\"lib\\\"]`\", lib_path.display());\n    }\n\n    validate_lib(&lib_path)?;\n\n    let project_dir = config.project_dir();\n    let externals_lib_dir = project_dir.join(format!(\"Externals/{arch}/{}\", profile.as_str()));\n    std::fs::create_dir_all(&externals_lib_dir).fs_context(\n      \"failed to create externals lib directory\",\n      externals_lib_dir.clone(),\n    )?;\n\n    // backwards compatible lib output file name\n    let uses_new_lib_output_file_name = {\n      let pbxproj_path = project_dir\n        .join(format!(\"{}.xcodeproj\", config.app().name()))\n        .join(\"project.pbxproj\");\n      let pbxproj_contents = read_to_string(&pbxproj_path)\n        .fs_context(\"failed to read project.pbxproj file\", pbxproj_path)?;\n\n      pbxproj_contents.contains(LIB_OUTPUT_FILE_NAME)\n    };\n\n    let lib_output_file_name = if uses_new_lib_output_file_name {\n      LIB_OUTPUT_FILE_NAME.to_string()\n    } else {\n      format!(\"lib{}.a\", config.app().lib_name())\n    };\n\n    std::fs::copy(&lib_path, externals_lib_dir.join(lib_output_file_name)).fs_context(\n      \"failed to copy mobile lib file to Externals directory\",\n      lib_path.to_path_buf(),\n    )?;\n  }\n  Ok(())\n}\n\nfn validate_lib(path: &Path) -> Result<()> {\n  let mut archive = ar::Archive::new(\n    std::fs::File::open(path).fs_context(\"failed to open mobile lib file\", path.to_path_buf())?,\n  );\n  // Iterate over all entries in the archive:\n  while let Some(entry) = archive.next_entry() {\n    let Ok(mut entry) = entry else {\n      continue;\n    };\n    let mut obj_bytes = Vec::new();\n    entry\n      .read_to_end(&mut obj_bytes)\n      .fs_context(\"failed to read mobile lib entry\", path.to_path_buf())?;\n\n    let file = object::File::parse(&*obj_bytes)\n      .map_err(std::io::Error::other)\n      .fs_context(\"failed to parse mobile lib entry\", path.to_path_buf())?;\n    for symbol in file.symbols() {\n      let Ok(name) = symbol.name() else {\n        continue;\n      };\n      if name.contains(\"start_app\") {\n        return Ok(());\n      }\n    }\n  }\n\n  crate::error::bail!(\n    \"Library from {} does not include required runtime symbols. This means you are likely missing the tauri::mobile_entry_point macro usage, see the documentation for more information: https://v2.tauri.app/start/migrate/from-tauri-1\",\n    path.display()\n  )\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/mobile/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::{Context, ErrorExt},\n  helpers::config::{reload_config, Config as TauriConfig, ConfigMetadata},\n  interface::{AppInterface, AppSettings, DevProcess, Options as InterfaceOptions},\n  ConfigValue, Error, Result,\n};\nuse heck::ToSnekCase;\nuse jsonrpsee::core::client::{Client, ClientBuilder, ClientT};\nuse jsonrpsee::server::{RpcModule, ServerBuilder, ServerHandle};\nuse jsonrpsee_client_transport::ws::WsTransportClientBuilder;\nuse jsonrpsee_core::rpc_params;\nuse serde::{Deserialize, Serialize};\n\nuse cargo_mobile2::{\n  config::app::{App, Raw as RawAppConfig},\n  env::Error as EnvError,\n  opts::{NoiseLevel, Profile},\n  ChildHandle,\n};\nuse std::{\n  collections::HashMap,\n  env::{set_var, temp_dir},\n  ffi::OsString,\n  fmt::{Display, Write},\n  fs::{read_to_string, write},\n  net::{AddrParseError, IpAddr, Ipv4Addr, SocketAddr},\n  path::{Path, PathBuf},\n  process::{exit, ExitStatus},\n  str::FromStr,\n  sync::{\n    atomic::{AtomicBool, Ordering},\n    Arc, OnceLock,\n  },\n};\nuse tokio::runtime::Runtime;\n\n#[cfg(not(windows))]\nuse cargo_mobile2::env::Env;\n#[cfg(windows)]\nuse cargo_mobile2::os::Env;\n\npub mod android;\nmod init;\n#[cfg(target_os = \"macos\")]\npub mod ios;\n\nconst MIN_DEVICE_MATCH_SCORE: isize = 0;\n\n#[derive(Clone)]\npub struct DevChild {\n  child: Arc<ChildHandle>,\n  manually_killed_process: Arc<AtomicBool>,\n}\n\nimpl DevChild {\n  fn new(handle: ChildHandle) -> Self {\n    Self {\n      child: Arc::new(handle),\n      manually_killed_process: Default::default(),\n    }\n  }\n}\n\nimpl DevProcess for DevChild {\n  fn kill(&self) -> std::io::Result<()> {\n    self.child.kill()?;\n    self.manually_killed_process.store(true, Ordering::SeqCst);\n    Ok(())\n  }\n\n  fn wait(&self) -> std::io::Result<ExitStatus> {\n    self.child.wait().map(|o| o.status)\n  }\n\n  fn manually_killed_process(&self) -> bool {\n    self.manually_killed_process.load(Ordering::SeqCst)\n  }\n}\n\n#[derive(PartialEq, Eq, Copy, Clone)]\npub enum Target {\n  Android,\n  #[cfg(target_os = \"macos\")]\n  Ios,\n}\n\nimpl Target {\n  fn ide_name(&self) -> &'static str {\n    match self {\n      Self::Android => \"Android Studio\",\n      #[cfg(target_os = \"macos\")]\n      Self::Ios => \"Xcode\",\n    }\n  }\n\n  fn command_name(&self) -> &'static str {\n    match self {\n      Self::Android => \"android\",\n      #[cfg(target_os = \"macos\")]\n      Self::Ios => \"ios\",\n    }\n  }\n\n  fn ide_build_script_name(&self) -> &'static str {\n    match self {\n      Self::Android => \"android-studio-script\",\n      #[cfg(target_os = \"macos\")]\n      Self::Ios => \"xcode-script\",\n    }\n  }\n\n  fn platform_target(&self) -> tauri_utils::platform::Target {\n    match self {\n      Self::Android => tauri_utils::platform::Target::Android,\n      #[cfg(target_os = \"macos\")]\n      Self::Ios => tauri_utils::platform::Target::Ios,\n    }\n  }\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct TargetDevice {\n  id: String,\n  name: String,\n}\n\n#[derive(Debug, Clone)]\npub struct DevHost(Option<Option<IpAddr>>);\n\nimpl FromStr for DevHost {\n  type Err = AddrParseError;\n  fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n    if s.is_empty() || s == \"<public network address>\" {\n      Ok(Self(Some(None)))\n    } else if s == \"<none>\" {\n      Ok(Self(None))\n    } else {\n      IpAddr::from_str(s).map(|addr| Self(Some(Some(addr))))\n    }\n  }\n}\n\nimpl Display for DevHost {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    match self.0 {\n      Some(None) => write!(f, \"<public network address>\"),\n      Some(Some(addr)) => write!(f, \"{addr}\"),\n      None => write!(f, \"<none>\"),\n    }\n  }\n}\n\nimpl Default for DevHost {\n  fn default() -> Self {\n    // on Windows we want to force using the public network address for the development server\n    // because the adb port forwarding does not work well\n    if cfg!(windows) {\n      Self(Some(None))\n    } else {\n      Self(None)\n    }\n  }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct CliOptions {\n  pub dev: bool,\n  pub features: Vec<String>,\n  pub args: Vec<String>,\n  pub noise_level: NoiseLevel,\n  pub vars: HashMap<String, OsString>,\n  pub config: Vec<ConfigValue>,\n  pub target_device: Option<TargetDevice>,\n}\n\nfn local_ip_address(force: bool) -> &'static IpAddr {\n  static LOCAL_IP: OnceLock<IpAddr> = OnceLock::new();\n  LOCAL_IP.get_or_init(|| {\n    let prompt_for_ip = || {\n      let addresses: Vec<IpAddr> = local_ip_address::list_afinet_netifas()\n        .expect(\"failed to list networks\")\n        .into_iter()\n        .map(|(_, ipaddr)| ipaddr)\n        .filter(|ipaddr| match ipaddr {\n          IpAddr::V4(i) => i != &Ipv4Addr::LOCALHOST,\n          IpAddr::V6(i) => i.to_string().ends_with(\"::2\"),\n\n        })\n        .collect();\n      match addresses.as_slice() {\n        [] => panic!(\"No external IP detected.\"),\n        [ipaddr] => *ipaddr,\n        _ => {\n          let selected = dialoguer::Select::with_theme(&dialoguer::theme::ColorfulTheme::default())\n            .with_prompt(\n              \"Failed to detect external IP, What IP should we use to access your development server?\",\n            )\n            .items(&addresses)\n            .default(0)\n            .interact()\n            .expect(\"failed to select external IP\");\n          *addresses.get(selected).unwrap()\n        }\n      }\n    };\n\n    let ip = if force {\n      prompt_for_ip()\n    } else {\n      local_ip_address::local_ip().unwrap_or_else(|_| prompt_for_ip())\n    };\n    log::info!(\"Using {ip} to access the development server.\");\n    ip\n  })\n}\n\nstruct DevUrlConfig {\n  no_dev_server_wait: bool,\n}\n\nfn use_network_address_for_dev_url(\n  config: &mut ConfigMetadata,\n  dev_options: &mut crate::dev::Options,\n  force_ip_prompt: bool,\n  tauri_dir: &Path,\n) -> crate::Result<DevUrlConfig> {\n  let mut dev_url = config.build.dev_url.clone();\n\n  let ip = if let Some(url) = &mut dev_url {\n    let localhost = match url.host() {\n      Some(url::Host::Domain(d)) => d == \"localhost\",\n      Some(url::Host::Ipv4(i)) => i == Ipv4Addr::LOCALHOST || i == Ipv4Addr::UNSPECIFIED,\n      _ => false,\n    };\n\n    if localhost {\n      let ip = dev_options\n        .host\n        .unwrap_or_else(|| *local_ip_address(force_ip_prompt));\n      log::info!(\n        \"Replacing devUrl host with {ip}. {}.\",\n        \"If your frontend is not listening on that address, try configuring your development server to use the `TAURI_DEV_HOST` environment variable or 0.0.0.0 as host\"\n      );\n\n      let url_str = format!(\n        \"{}://{}{}\",\n        url.scheme(),\n        SocketAddr::new(ip, url.port_or_known_default().unwrap()),\n        url.path()\n      );\n      *url =\n        url::Url::parse(&url_str).with_context(|| format!(\"failed to parse URL: {url_str}\"))?;\n\n      dev_options\n        .config\n        .push(crate::ConfigValue(serde_json::json!({\n          \"build\": {\n            \"devUrl\": url\n          }\n        })));\n\n      reload_config(\n        config,\n        &dev_options\n          .config\n          .iter()\n          .map(|conf| &conf.0)\n          .collect::<Vec<_>>(),\n        tauri_dir,\n      )?;\n\n      Some(ip)\n    } else {\n      None\n    }\n  } else if !dev_options.no_dev_server {\n    let ip = dev_options\n      .host\n      .unwrap_or_else(|| *local_ip_address(force_ip_prompt));\n    dev_options.host.replace(ip);\n    Some(ip)\n  } else {\n    None\n  };\n\n  let mut dev_url_config = DevUrlConfig {\n    no_dev_server_wait: false,\n  };\n\n  if let Some(ip) = ip {\n    std::env::set_var(\"TAURI_DEV_HOST\", ip.to_string());\n    std::env::set_var(\"TRUNK_SERVE_ADDRESS\", ip.to_string());\n    if ip.is_ipv6() {\n      // in this case we can't ping the server for some reason\n      dev_url_config.no_dev_server_wait = true;\n    }\n  }\n\n  Ok(dev_url_config)\n}\n\nfn env_vars() -> HashMap<String, OsString> {\n  let mut vars = HashMap::new();\n  vars.insert(\"RUST_LOG_STYLE\".into(), \"always\".into());\n  for (k, v) in std::env::vars_os() {\n    let k = k.to_string_lossy();\n    if (k.starts_with(\"TAURI\")\n      && k != \"TAURI_SIGNING_PRIVATE_KEY\"\n      && k != \"TAURI_SIGNING_PRIVATE_KEY_PASSWORD\")\n      || k.starts_with(\"WRY\")\n      || k.starts_with(\"CARGO_\")\n      || k.starts_with(\"RUST_\")\n      || k == \"TMPDIR\"\n      || k == \"PATH\"\n    {\n      vars.insert(k.into_owned(), v);\n    }\n  }\n  vars\n}\n\nfn env() -> std::result::Result<Env, EnvError> {\n  let env = Env::new()?.explicit_env_vars(env_vars());\n  Ok(env)\n}\n\npub struct OptionsHandle(#[allow(unused)] Runtime, #[allow(unused)] ServerHandle);\n\n/// Writes CLI options to be used later on the Xcode and Android Studio build commands\npub fn write_options(\n  config: &ConfigMetadata,\n  mut options: CliOptions,\n) -> crate::Result<OptionsHandle> {\n  options.vars.extend(env_vars());\n\n  let runtime = Runtime::new().unwrap();\n  let r: crate::Result<(ServerHandle, SocketAddr)> = runtime.block_on(async move {\n    let server = ServerBuilder::default()\n      .build(\"127.0.0.1:0\")\n      .await\n      .context(\"failed to build WebSocket server\")?;\n    let addr = server.local_addr().context(\"failed to get local address\")?;\n\n    let mut module = RpcModule::new(());\n    module\n      .register_method(\"options\", move |_, _, _| Some(options.clone()))\n      .context(\"failed to register options method\")?;\n\n    let handle = server.start(module);\n\n    Ok((handle, addr))\n  });\n  let (handle, addr) = r?;\n\n  let server_addr_path = temp_dir().join(format!(\n    \"{}-server-addr\",\n    config\n      .original_identifier()\n      .context(\"app configuration is missing an identifier\")?\n  ));\n\n  write(&server_addr_path, addr.to_string())\n    .fs_context(\"failed to write server address file\", server_addr_path)?;\n\n  Ok(OptionsHandle(runtime, handle))\n}\n\nfn read_options(config: &ConfigMetadata) -> CliOptions {\n  let runtime = tokio::runtime::Runtime::new().unwrap();\n  let options = runtime\n    .block_on(async move {\n      let addr_path = temp_dir().join(format!(\n        \"{}-server-addr\",\n        config\n          .original_identifier()\n          .context(\"app configuration is missing an identifier\")?\n      ));\n      let (tx, rx) = WsTransportClientBuilder::default()\n        .build(\n          format!(\n            \"ws://{}\",\n            read_to_string(&addr_path).unwrap_or_else(|e| panic!(\n              \"failed to read missing addr file {}: {e}\",\n              addr_path.display()\n            ))\n          )\n          .parse()\n          .unwrap(),\n        )\n        .await\n        .context(\"failed to build WebSocket client\")?;\n      let client: Client = ClientBuilder::default().build_with_tokio(tx, rx);\n      let options: CliOptions = client\n        .request(\"options\", rpc_params![])\n        .await\n        .context(\"failed to request options\")?;\n      Ok::<CliOptions, Error>(options)\n    })\n    .expect(\"failed to read CLI options\");\n\n  for (k, v) in &options.vars {\n    set_var(k, v);\n  }\n  options\n}\n\npub fn get_app(\n  target: Target,\n  config: &TauriConfig,\n  interface: &AppInterface,\n  tauri_dir: &Path,\n) -> App {\n  let identifier = match target {\n    Target::Android => config.identifier.replace('-', \"_\"),\n    #[cfg(target_os = \"macos\")]\n    Target::Ios => config.identifier.replace('_', \"-\"),\n  };\n\n  if identifier.is_empty() {\n    log::error!(\"Bundle identifier set in `tauri.conf.json > identifier` cannot be empty\");\n    exit(1);\n  }\n\n  let app_name = interface\n    .app_settings()\n    .app_name()\n    .unwrap_or_else(|| \"app\".into());\n  let lib_name = interface\n    .app_settings()\n    .lib_name()\n    .unwrap_or_else(|| app_name.to_snek_case());\n\n  if config.product_name.is_none() {\n    log::warn!(\n      \"`productName` is not set in the Tauri configuration. Using `{app_name}` as the app name.\"\n    );\n  }\n\n  let raw = RawAppConfig {\n    name: app_name,\n    lib_name: Some(lib_name),\n    stylized_name: config.product_name.clone(),\n    identifier,\n    asset_dir: None,\n    template_pack: None,\n  };\n\n  let app_settings = interface.app_settings();\n  let tauri_dir = tauri_dir.to_path_buf();\n  App::from_raw(tauri_dir.to_path_buf(), raw)\n    .unwrap()\n    .with_target_dir_resolver(move |target, profile| {\n      app_settings\n        .out_dir(\n          &InterfaceOptions {\n            debug: matches!(profile, Profile::Debug),\n            target: Some(target.into()),\n            ..Default::default()\n          },\n          &tauri_dir,\n        )\n        .expect(\"failed to resolve target directory\")\n    })\n}\n\n#[allow(unused_variables)]\nfn ensure_init(\n  tauri_config: &ConfigMetadata,\n  app: &App,\n  project_dir: PathBuf,\n  target: Target,\n  noninteractive: bool,\n) -> Result<()> {\n  if !project_dir.exists() {\n    crate::error::bail!(\n      \"{} project directory {} doesn't exist. Please run `tauri {} init` and try again.\",\n      target.ide_name(),\n      project_dir.display(),\n      target.command_name(),\n    )\n  }\n\n  let mut project_outdated_reasons = Vec::new();\n\n  match target {\n    Target::Android => {\n      let java_folder = project_dir\n        .join(\"app/src/main/java\")\n        .join(tauri_config.identifier.replace('.', \"/\").replace('-', \"_\"));\n      if java_folder.exists() {\n        #[cfg(unix)]\n        ensure_gradlew(&project_dir)?;\n      } else {\n        project_outdated_reasons\n          .push(\"you have modified your \\\"identifier\\\" in the Tauri configuration\");\n      }\n    }\n    #[cfg(target_os = \"macos\")]\n    Target::Ios => {\n      let xcodeproj_path = crate::helpers::fs::find_in_directory(&project_dir, \"*.xcodeproj\")\n        .with_context(|| format!(\"failed to locate xcodeproj in {}\", project_dir.display()))?;\n\n      let xcodeproj_name = xcodeproj_path.file_stem().unwrap().to_str().unwrap();\n      if xcodeproj_name != app.name() {\n        let rename_targets = vec![\n          // first rename the entitlements\n          (\n            format!(\"{xcodeproj_name}_iOS/{xcodeproj_name}_iOS.entitlements\"),\n            format!(\"{xcodeproj_name}_iOS/{}_iOS.entitlements\", app.name()),\n          ),\n          // then the scheme folder\n          (\n            format!(\"{xcodeproj_name}_iOS\"),\n            format!(\"{}_iOS\", app.name()),\n          ),\n          (\n            format!(\"{xcodeproj_name}.xcodeproj\"),\n            format!(\"{}.xcodeproj\", app.name()),\n          ),\n        ];\n        let rename_info = rename_targets\n          .iter()\n          .map(|(from, to)| format!(\"- {from} to {to}\"))\n          .collect::<Vec<_>>()\n          .join(\"\\n\");\n        log::error!(\n          \"you have modified your package name from {current_project_name} to {new_project_name}\\nWe need to apply the name change to the Xcode project, renaming:\\n{rename_info}\",\n          new_project_name = app.name(),\n          current_project_name = xcodeproj_name,\n        );\n        if noninteractive {\n          project_outdated_reasons\n            .push(\"you have modified your [lib.name] or [package.name] in the Cargo.toml file\");\n        } else {\n          let confirm = crate::helpers::prompts::confirm(\n            \"Do you want to apply the name change to the Xcode project?\",\n            Some(true),\n          )\n          .unwrap_or_default();\n          if confirm {\n            for (from, to) in rename_targets {\n              std::fs::rename(project_dir.join(&from), project_dir.join(&to))\n                .with_context(|| format!(\"failed to rename {from} to {to}\"))?;\n            }\n\n            // update scheme name in pbxproj\n            // identifier / product name are synchronized by the dev/build commands\n            let pbxproj_path =\n              project_dir.join(format!(\"{}.xcodeproj/project.pbxproj\", app.name()));\n            let pbxproj_contents = std::fs::read_to_string(&pbxproj_path)\n              .with_context(|| format!(\"failed to read {}\", pbxproj_path.display()))?;\n            std::fs::write(\n              &pbxproj_path,\n              pbxproj_contents.replace(\n                &format!(\"{xcodeproj_name}_iOS\"),\n                &format!(\"{}_iOS\", app.name()),\n              ),\n            )\n            .with_context(|| format!(\"failed to write {}\", pbxproj_path.display()))?;\n          } else {\n            project_outdated_reasons\n              .push(\"you have modified your [lib.name] or [package.name] in the Cargo.toml file\");\n          }\n        }\n      }\n\n      // note: pbxproj is synchronied by the dev/build commands\n    }\n  }\n\n  if !project_outdated_reasons.is_empty() {\n    let reason = project_outdated_reasons.join(\" and \");\n    crate::error::bail!(\n        \"{} project directory is outdated because {reason}. Please delete {}, run `tauri {} init` and try again.\",\n        target.ide_name(),\n        project_dir.display(),\n        target.command_name(),\n      )\n  }\n\n  Ok(())\n}\n\n#[cfg(unix)]\nfn ensure_gradlew(project_dir: &std::path::Path) -> Result<()> {\n  use std::os::unix::fs::PermissionsExt;\n\n  let gradlew_path = project_dir.join(\"gradlew\");\n  if let Ok(metadata) = gradlew_path.metadata() {\n    let mut permissions = metadata.permissions();\n    let is_executable = permissions.mode() & 0o111 != 0;\n    if !is_executable {\n      permissions.set_mode(permissions.mode() | 0o111);\n      std::fs::set_permissions(&gradlew_path, permissions)\n        .fs_context(\"failed to mark gradlew as executable\", gradlew_path.clone())?;\n    }\n    std::fs::write(\n      &gradlew_path,\n      std::fs::read_to_string(&gradlew_path)\n        .fs_context(\"failed to read gradlew\", gradlew_path.clone())?\n        .replace(\"\\r\\n\", \"\\n\"),\n    )\n    .fs_context(\"failed to replace gradlew CRLF with LF\", gradlew_path)?;\n  }\n\n  Ok(())\n}\n\nfn log_finished(outputs: Vec<PathBuf>, kind: &str) {\n  if !outputs.is_empty() {\n    let mut printable_paths = String::new();\n    for path in &outputs {\n      writeln!(printable_paths, \"        {}\", path.display()).unwrap();\n    }\n\n    log::info!(action = \"Finished\"; \"{} {}{} at:\\n{}\", outputs.len(), kind, if outputs.len() == 1 { \"\" } else { \"s\" }, printable_paths);\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/plugin/android.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  error::Context,\n  helpers::{prompts, template},\n  Result,\n};\nuse clap::{Parser, Subcommand};\nuse handlebars::Handlebars;\n\nuse std::{\n  collections::BTreeMap,\n  env::current_dir,\n  ffi::OsStr,\n  path::{Component, PathBuf},\n};\n\n#[derive(Parser)]\n#[clap(\n  author,\n  version,\n  about = \"Manage the Android project for a Tauri plugin\",\n  subcommand_required(true),\n  arg_required_else_help(true)\n)]\npub struct Cli {\n  #[clap(subcommand)]\n  command: Commands,\n}\n\n#[derive(Subcommand)]\nenum Commands {\n  Init(InitOptions),\n}\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Initializes the Android project for an existing Tauri plugin\")]\npub struct InitOptions {\n  /// Name of your Tauri plugin. Must match the current plugin's name.\n  /// If not specified, it will be inferred from the current directory.\n  plugin_name: Option<String>,\n  /// The output directory.\n  #[clap(short, long)]\n  #[clap(default_value_t = current_dir().expect(\"failed to read cwd\").to_string_lossy().into_owned())]\n  out_dir: String,\n}\n\npub fn command(cli: Cli) -> Result<()> {\n  match cli.command {\n    Commands::Init(options) => {\n      let plugin_name = match options.plugin_name {\n        None => super::infer_plugin_name(\n          std::env::current_dir().context(\"failed to get current directory\")?,\n        )?,\n        Some(name) => name,\n      };\n\n      let out_dir = PathBuf::from(options.out_dir);\n      if out_dir.join(\"android\").exists() {\n        crate::error::bail!(\"Android folder already exists\");\n      }\n\n      let plugin_id = prompts::input(\n        \"What should be the Android Package ID for your plugin?\",\n        Some(format!(\"com.plugin.{plugin_name}\")),\n        false,\n        false,\n      )?\n      .unwrap();\n\n      let handlebars = Handlebars::new();\n\n      let mut data = BTreeMap::new();\n      super::init::plugin_name_data(&mut data, &plugin_name);\n      data.insert(\"android_package_id\", handlebars::to_json(&plugin_id));\n\n      let mut created_dirs = Vec::new();\n      template::render_with_generator(\n        &handlebars,\n        &data,\n        &super::init::TEMPLATE_DIR,\n        &out_dir,\n        &mut |path| {\n          let mut components = path.components();\n          let root = components.next().unwrap();\n          if let Component::Normal(component) = root {\n            if component == OsStr::new(\"android\") {\n              return super::init::generate_android_out_file(\n                &path,\n                &out_dir,\n                &plugin_id.replace('.', \"/\"),\n                &mut created_dirs,\n              );\n            }\n          }\n\n          Ok(None)\n        },\n      )?;\n\n      let metadata = super::init::crates_metadata()?;\n\n      let cargo_toml_addition = format!(\n        r#\"\n[build-dependencies]\ntauri-build = \"{}\"\n\"#,\n        metadata.tauri_build\n      );\n      let build_file = super::init::TEMPLATE_DIR\n        .get_file(\"build.rs\")\n        .unwrap()\n        .contents_utf8()\n        .unwrap();\n      let init_fn = format!(\n        r#\"\npub fn init<R: Runtime>() -> TauriPlugin<R> {{\n  Builder::new(\"{plugin_name}\")\n    .setup(|app, api| {{\n      #[cfg(target_os = \"android\")]\n      let handle = api.register_android_plugin(\"{plugin_id}\", \"ExamplePlugin\")?;\n      Ok(())\n    }})\n    .build()\n}}\n\"#\n      );\n\n      log::info!(\"Android project added\");\n      println!(\"You must add the following to the Cargo.toml file:\\n{cargo_toml_addition}\",);\n      println!(\"You must add the following code to the build.rs file:\\n\\n{build_file}\",);\n      println!(\"Your plugin's init function under src/lib.rs must initialize the Android plugin:\\n{init_fn}\");\n    }\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/plugin/init.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::PluginIosFramework;\nuse crate::Result;\nuse crate::{\n  error::{Context, ErrorExt},\n  helpers::{prompts, resolve_tauri_path, template},\n  VersionMetadata,\n};\nuse clap::Parser;\nuse handlebars::{to_json, Handlebars};\nuse heck::{ToKebabCase, ToPascalCase, ToSnakeCase};\nuse include_dir::{include_dir, Dir};\nuse std::ffi::{OsStr, OsString};\nuse std::{\n  collections::BTreeMap,\n  env::current_dir,\n  fs::{create_dir_all, remove_dir_all, File, OpenOptions},\n  path::{Component, Path, PathBuf},\n};\n\npub const TEMPLATE_DIR: Dir<'_> = include_dir!(\"$CARGO_MANIFEST_DIR/templates/plugin\");\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Initialize a Tauri plugin project on an existing directory\")]\npub struct Options {\n  /// Name of your Tauri plugin.\n  /// If not specified, it will be inferred from the current directory.\n  pub(crate) plugin_name: Option<String>,\n  /// Initializes a Tauri plugin without the TypeScript API\n  #[clap(long)]\n  pub(crate) no_api: bool,\n  /// Initialize without an example project.\n  #[clap(long)]\n  pub(crate) no_example: bool,\n  /// Set target directory for init\n  #[clap(short, long)]\n  #[clap(default_value_t = current_dir().expect(\"failed to read cwd\").display().to_string())]\n  pub(crate) directory: String,\n  /// Author name\n  #[clap(short, long)]\n  pub(crate) author: Option<String>,\n  /// Whether to initialize an Android project for the plugin.\n  #[clap(long)]\n  pub(crate) android: bool,\n  /// Whether to initialize an iOS project for the plugin.\n  #[clap(long)]\n  pub(crate) ios: bool,\n  /// Whether to initialize Android and iOS projects for the plugin.\n  #[clap(long)]\n  pub(crate) mobile: bool,\n  /// Type of framework to use for the iOS project.\n  #[clap(long)]\n  #[clap(default_value_t = PluginIosFramework::default())]\n  pub(crate) ios_framework: PluginIosFramework,\n  /// Generate github workflows\n  #[clap(long)]\n  pub(crate) github_workflows: bool,\n\n  /// Initializes a Tauri core plugin (internal usage)\n  #[clap(long, hide(true))]\n  pub(crate) tauri: bool,\n  /// Path of the Tauri project to use (relative to the cwd)\n  #[clap(short, long)]\n  pub(crate) tauri_path: Option<PathBuf>,\n}\n\nimpl Options {\n  fn load(&mut self) {\n    if self.author.is_none() {\n      self.author.replace(if self.tauri {\n        \"Tauri Programme within The Commons Conservancy\".into()\n      } else {\n        \"You\".into()\n      });\n    }\n  }\n}\n\npub fn command(mut options: Options) -> Result<()> {\n  options.load();\n\n  let plugin_name = match options.plugin_name {\n    None => super::infer_plugin_name(&options.directory)?,\n    Some(name) => name,\n  };\n\n  let template_target_path = PathBuf::from(options.directory);\n  let metadata = crates_metadata()?;\n  if std::fs::read_dir(&template_target_path)\n    .fs_context(\n      \"failed to read target directory\",\n      template_target_path.clone(),\n    )?\n    .count()\n    > 0\n  {\n    log::warn!(\"Plugin dir ({:?}) not empty.\", template_target_path);\n  } else {\n    let (tauri_dep, tauri_example_dep, tauri_build_dep, tauri_plugin_dep) =\n      if let Some(tauri_path) = options.tauri_path {\n        (\n          format!(\n            r#\"{{  path = {:?} }}\"#,\n            resolve_tauri_path(&tauri_path, \"crates/tauri\")\n          ),\n          format!(\n            r#\"{{  path = {:?} }}\"#,\n            resolve_tauri_path(&tauri_path, \"crates/tauri\")\n          ),\n          format!(\n            \"{{  path = {:?}, default-features = false }}\",\n            resolve_tauri_path(&tauri_path, \"crates/tauri-build\")\n          ),\n          format!(\n            r#\"{{  path = {:?}, features = [\"build\"] }}\"#,\n            resolve_tauri_path(&tauri_path, \"crates/tauri-plugin\")\n          ),\n        )\n      } else {\n        (\n          format!(r#\"{{ version = \"{}\" }}\"#, metadata.tauri),\n          format!(r#\"{{ version = \"{}\" }}\"#, metadata.tauri),\n          format!(\n            r#\"{{ version = \"{}\", default-features = false }}\"#,\n            metadata.tauri_build\n          ),\n          format!(\n            r#\"{{ version = \"{}\", features = [\"build\"] }}\"#,\n            metadata.tauri_plugin\n          ),\n        )\n      };\n\n    let _ = remove_dir_all(&template_target_path);\n    let mut handlebars = Handlebars::new();\n    handlebars.register_escape_fn(handlebars::no_escape);\n\n    let mut data = BTreeMap::new();\n    plugin_name_data(&mut data, &plugin_name);\n    data.insert(\"tauri_dep\", to_json(tauri_dep));\n    data.insert(\"tauri_example_dep\", to_json(tauri_example_dep));\n    data.insert(\"tauri_build_dep\", to_json(tauri_build_dep));\n    data.insert(\"tauri_plugin_dep\", to_json(tauri_plugin_dep));\n    data.insert(\"author\", to_json(options.author));\n\n    if options.tauri {\n      data.insert(\n        \"license_header\",\n        to_json(\n          \"// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n             // SPDX-License-Identifier: Apache-2.0\n             // SPDX-License-Identifier: MIT\\n\\n\"\n            .replace(\"  \", \"\")\n            .replace(\" //\", \"//\"),\n        ),\n      );\n    }\n\n    let plugin_id = if options.android || options.mobile {\n      let plugin_id = prompts::input(\n        \"What should be the Android Package ID for your plugin?\",\n        Some(format!(\"com.plugin.{plugin_name}\")),\n        false,\n        false,\n      )?\n      .unwrap();\n\n      data.insert(\"android_package_id\", to_json(&plugin_id));\n      Some(plugin_id)\n    } else {\n      None\n    };\n\n    let ios_framework = options.ios_framework;\n\n    let mut created_dirs = Vec::new();\n    template::render_with_generator(\n      &handlebars,\n      &data,\n      &TEMPLATE_DIR,\n      &template_target_path,\n      &mut |mut path| {\n        let mut components = path.components();\n        let root = components.next().unwrap();\n\n        if let Component::Normal(component) = root {\n          match component.to_str().unwrap() {\n            \"__example-api\" => {\n              if options.no_api || options.no_example {\n                return Ok(None);\n              } else {\n                path = Path::new(\"examples\").join(components.collect::<PathBuf>());\n              }\n            }\n            \"__example-basic\" => {\n              if options.no_api && !options.no_example {\n                path = Path::new(\"examples\").join(components.collect::<PathBuf>());\n              } else {\n                return Ok(None);\n              }\n            }\n            \".github\" if !options.github_workflows => return Ok(None),\n            \"android\" => {\n              if options.android || options.mobile {\n                return generate_android_out_file(\n                  &path,\n                  &template_target_path,\n                  &plugin_id.as_ref().unwrap().replace('.', \"/\"),\n                  &mut created_dirs,\n                );\n              } else {\n                return Ok(None);\n              }\n            }\n            \"ios-spm\" | \"ios-xcode\" if !(options.ios || options.mobile) => return Ok(None),\n            \"ios-spm\" if !matches!(ios_framework, PluginIosFramework::Spm) => return Ok(None),\n            \"ios-xcode\" if !matches!(ios_framework, PluginIosFramework::Xcode) => return Ok(None),\n            \"ios-spm\" | \"ios-xcode\" => {\n              let folder_name = components.next().unwrap().as_os_str().to_string_lossy();\n              let new_folder_name = folder_name.replace(\"{{ plugin_name }}\", &plugin_name);\n              let new_folder_name = OsString::from(&new_folder_name);\n\n              path = [\n                Component::Normal(OsStr::new(\"ios\")),\n                Component::Normal(&new_folder_name),\n              ]\n              .into_iter()\n              .chain(components)\n              .collect::<PathBuf>();\n            }\n            \"guest-js\" | \"rollup.config.js\" | \"tsconfig.json\" | \"package.json\"\n              if options.no_api =>\n            {\n              return Ok(None);\n            }\n            _ => (),\n          }\n        }\n\n        let path = template_target_path.join(path);\n        let parent = path.parent().unwrap().to_path_buf();\n        if !created_dirs.contains(&parent) {\n          create_dir_all(&parent)?;\n          created_dirs.push(parent);\n        }\n        File::create(path).map(Some)\n      },\n    )\n    .with_context(|| \"failed to render plugin template\")?;\n  }\n\n  let permissions_dir = template_target_path.join(\"permissions\");\n  std::fs::create_dir(&permissions_dir).fs_context(\n    \"failed to create `permissions` directory\",\n    permissions_dir.clone(),\n  )?;\n\n  let default_permissions = r#\"[default]\ndescription = \"Default permissions for the plugin\"\npermissions = [\"allow-ping\"]\n\"#;\n  std::fs::write(permissions_dir.join(\"default.toml\"), default_permissions).fs_context(\n    \"failed to write default permissions file\",\n    permissions_dir.join(\"default.toml\"),\n  )?;\n\n  Ok(())\n}\n\npub fn plugin_name_data(data: &mut BTreeMap<&'static str, serde_json::Value>, plugin_name: &str) {\n  data.insert(\"plugin_name_original\", to_json(plugin_name));\n  data.insert(\"plugin_name\", to_json(plugin_name.to_kebab_case()));\n  data.insert(\n    \"plugin_name_snake_case\",\n    to_json(plugin_name.to_snake_case()),\n  );\n  data.insert(\n    \"plugin_name_pascal_case\",\n    to_json(plugin_name.to_pascal_case()),\n  );\n}\n\npub fn crates_metadata() -> Result<VersionMetadata> {\n  serde_json::from_str::<VersionMetadata>(include_str!(\"../../metadata-v2.json\"))\n    .context(\"failed to parse Tauri version metadata\")\n}\n\npub fn generate_android_out_file(\n  path: &Path,\n  dest: &Path,\n  package_path: &str,\n  created_dirs: &mut Vec<PathBuf>,\n) -> std::io::Result<Option<File>> {\n  let mut iter = path.iter();\n  let root = iter.next().unwrap().to_str().unwrap();\n  let path = match (root, path.extension().and_then(|o| o.to_str())) {\n    (\"src\", Some(\"kt\")) => {\n      let parent = path.parent().unwrap();\n      let file_name = path.file_name().unwrap();\n      let out_dir = dest.join(parent).join(package_path);\n      out_dir.join(file_name)\n    }\n    _ => dest.join(path),\n  };\n\n  let parent = path.parent().unwrap().to_path_buf();\n  if !created_dirs.contains(&parent) {\n    create_dir_all(&parent)?;\n    created_dirs.push(parent);\n  }\n\n  let mut options = OpenOptions::new();\n  options.write(true);\n\n  #[cfg(unix)]\n  if path.file_name().unwrap() == std::ffi::OsStr::new(\"gradlew\") {\n    use std::os::unix::fs::OpenOptionsExt;\n    options.mode(0o755);\n  }\n\n  if !path.exists() {\n    options.create(true).open(path).map(Some)\n  } else {\n    Ok(None)\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/plugin/ios.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::PluginIosFramework;\nuse crate::{error::Context, helpers::template, Result};\nuse clap::{Parser, Subcommand};\nuse handlebars::Handlebars;\n\nuse std::{\n  collections::BTreeMap,\n  env::current_dir,\n  ffi::{OsStr, OsString},\n  fs::{create_dir_all, File},\n  path::{Component, PathBuf},\n};\n\n#[derive(Parser)]\n#[clap(\n  author,\n  version,\n  about = \"Manage the iOS project for a Tauri plugin\",\n  subcommand_required(true),\n  arg_required_else_help(true)\n)]\npub struct Cli {\n  #[clap(subcommand)]\n  command: Commands,\n}\n\n#[derive(Subcommand)]\nenum Commands {\n  Init(InitOptions),\n}\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Initializes the iOS project for an existing Tauri plugin\")]\npub struct InitOptions {\n  /// Name of your Tauri plugin. Must match the current plugin's name.\n  /// If not specified, it will be inferred from the current directory.\n  plugin_name: Option<String>,\n  /// The output directory.\n  #[clap(short, long)]\n  #[clap(default_value_t = current_dir().expect(\"failed to read cwd\").to_string_lossy().into_owned())]\n  out_dir: String,\n  /// Type of framework to use for the iOS project.\n  #[clap(long)]\n  #[clap(default_value_t = PluginIosFramework::default())]\n  ios_framework: PluginIosFramework,\n}\n\npub fn command(cli: Cli) -> Result<()> {\n  match cli.command {\n    Commands::Init(options) => {\n      let plugin_name = match options.plugin_name {\n        None => super::infer_plugin_name(\n          std::env::current_dir().context(\"failed to get current directory\")?,\n        )?,\n        Some(name) => name,\n      };\n\n      let out_dir = PathBuf::from(options.out_dir);\n      if out_dir.join(\"ios\").exists() {\n        crate::error::bail!(\"iOS folder already exists\");\n      }\n\n      let handlebars = Handlebars::new();\n\n      let mut data = BTreeMap::new();\n      super::init::plugin_name_data(&mut data, &plugin_name);\n\n      let ios_folder_name = match options.ios_framework {\n        PluginIosFramework::Spm => OsStr::new(\"ios-spm\"),\n        PluginIosFramework::Xcode => OsStr::new(\"ios-xcode\"),\n      };\n\n      let mut created_dirs = Vec::new();\n      template::render_with_generator(\n        &handlebars,\n        &data,\n        &super::init::TEMPLATE_DIR,\n        &out_dir,\n        &mut |path| {\n          let mut components = path.components();\n          let root = components.next().unwrap();\n          if let Component::Normal(component) = root {\n            if component == ios_folder_name {\n              let folder_name = components.next().unwrap().as_os_str().to_string_lossy();\n              let new_folder_name = folder_name.replace(\"{{ plugin_name }}\", &plugin_name);\n              let new_folder_name = OsString::from(&new_folder_name);\n\n              let path = [\n                Component::Normal(OsStr::new(\"ios\")),\n                Component::Normal(&new_folder_name),\n              ]\n              .into_iter()\n              .chain(components)\n              .collect::<PathBuf>();\n\n              let path = out_dir.join(path);\n              let parent = path.parent().unwrap().to_path_buf();\n              if !created_dirs.contains(&parent) {\n                create_dir_all(&parent)?;\n                created_dirs.push(parent);\n              }\n              return File::create(path).map(Some);\n            }\n          }\n\n          Ok(None)\n        },\n      )?;\n\n      let metadata = super::init::crates_metadata()?;\n\n      let cargo_toml_addition = format!(\n        r#\"\n[build-dependencies]\ntauri-build = \"{}\"\n\"#,\n        metadata.tauri_build\n      );\n      let build_file = super::init::TEMPLATE_DIR\n        .get_file(\"build.rs\")\n        .unwrap()\n        .contents_utf8()\n        .unwrap();\n      let init_fn = format!(\n        r#\"\n#[cfg(target_os = \"ios\")]\ntauri::ios_plugin_binding!(init_plugin_{plugin_name});\n\npub fn init<R: Runtime>() -> TauriPlugin<R> {{\n  Builder::new(\"{plugin_name}\")\n    .setup(|app| {{\n      #[cfg(target_os = \"ios\")]\n      app.register_ios_plugin(init_plugin_{plugin_name})?;\n      Ok(())\n    }})\n    .build()\n}}\n\"#,\n      );\n\n      log::info!(\"iOS project added\");\n      println!(\"You must add the following to the Cargo.toml file:\\n{cargo_toml_addition}\",);\n      println!(\"You must add the following code to the build.rs file:\\n\\n{build_file}\",);\n      println!(\n        \"Your plugin's init function under src/lib.rs must initialize the iOS plugin:\\n{init_fn}\"\n      );\n    }\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/plugin/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{fmt::Display, path::Path};\n\nuse clap::{Parser, Subcommand, ValueEnum};\n\nuse crate::{\n  error::{Context, ErrorExt},\n  Result,\n};\n\nmod android;\nmod init;\nmod ios;\nmod new;\n\n#[derive(Debug, Clone, ValueEnum, Default)]\npub enum PluginIosFramework {\n  /// Swift Package Manager project\n  #[default]\n  Spm,\n  /// Xcode project\n  Xcode,\n}\n\nimpl Display for PluginIosFramework {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    match self {\n      Self::Spm => write!(f, \"spm\"),\n      Self::Xcode => write!(f, \"xcode\"),\n    }\n  }\n}\n\n#[derive(Parser)]\n#[clap(\n  author,\n  version,\n  about = \"Manage or create Tauri plugins\",\n  subcommand_required(true),\n  arg_required_else_help(true)\n)]\npub struct Cli {\n  #[clap(subcommand)]\n  command: Commands,\n}\n\n#[derive(Subcommand)]\nenum Commands {\n  New(new::Options),\n  Init(init::Options),\n  Android(android::Cli),\n  Ios(ios::Cli),\n}\n\npub fn command(cli: Cli) -> Result<()> {\n  match cli.command {\n    Commands::New(options) => new::command(options)?,\n    Commands::Init(options) => init::command(options)?,\n    Commands::Android(cli) => android::command(cli)?,\n    Commands::Ios(cli) => ios::command(cli)?,\n  }\n\n  Ok(())\n}\n\nfn infer_plugin_name<P: AsRef<Path>>(directory: P) -> Result<String> {\n  let dir = directory.as_ref();\n  let cargo_toml_path = dir.join(\"Cargo.toml\");\n  let name = if cargo_toml_path.exists() {\n    let contents = std::fs::read_to_string(&cargo_toml_path)\n      .fs_context(\"failed to read Cargo manifest\", cargo_toml_path)?;\n    let cargo_toml: toml::Value =\n      toml::from_str(&contents).context(\"failed to parse Cargo.toml\")?;\n    cargo_toml\n      .get(\"package\")\n      .and_then(|v| v.get(\"name\"))\n      .map(|v| v.as_str().unwrap_or_default())\n      .unwrap_or_default()\n      .to_string()\n  } else {\n    dir\n      .file_name()\n      .unwrap_or_default()\n      .to_string_lossy()\n      .to_string()\n  };\n  Ok(\n    name\n      .strip_prefix(\"tauri-plugin-\")\n      .unwrap_or(&name)\n      .to_string(),\n  )\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/plugin/new.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::PluginIosFramework;\nuse crate::{\n  error::{Context, ErrorExt},\n  Result,\n};\nuse clap::Parser;\nuse std::path::PathBuf;\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Initializes a new Tauri plugin project\")]\npub struct Options {\n  /// Name of your Tauri plugin\n  plugin_name: String,\n  /// Initializes a Tauri plugin without the TypeScript API\n  #[clap(long)]\n  no_api: bool,\n  /// Initialize without an example project.\n  #[clap(long)]\n  no_example: bool,\n  /// Set target directory for init\n  #[clap(short, long)]\n  directory: Option<String>,\n  /// Author name\n  #[clap(short, long)]\n  author: Option<String>,\n  /// Whether to initialize an Android project for the plugin.\n  #[clap(long)]\n  android: bool,\n  /// Whether to initialize an iOS project for the plugin.\n  #[clap(long)]\n  ios: bool,\n  /// Whether to initialize Android and iOS projects for the plugin.\n  #[clap(long)]\n  mobile: bool,\n  /// Type of framework to use for the iOS project.\n  #[clap(long)]\n  #[clap(default_value_t = PluginIosFramework::default())]\n  pub(crate) ios_framework: PluginIosFramework,\n  /// Generate github workflows\n  #[clap(long)]\n  github_workflows: bool,\n\n  /// Initializes a Tauri core plugin (internal usage)\n  #[clap(long, hide(true))]\n  tauri: bool,\n  /// Path of the Tauri project to use (relative to the cwd)\n  #[clap(short, long)]\n  tauri_path: Option<PathBuf>,\n}\n\nimpl From<Options> for super::init::Options {\n  fn from(o: Options) -> Self {\n    Self {\n      plugin_name: Some(o.plugin_name),\n      no_api: o.no_api,\n      no_example: o.no_example,\n      directory: o.directory.unwrap(),\n      author: o.author,\n      android: o.android,\n      ios: o.ios,\n      mobile: o.mobile,\n      ios_framework: o.ios_framework,\n      github_workflows: o.github_workflows,\n\n      tauri: o.tauri,\n      tauri_path: o.tauri_path,\n    }\n  }\n}\n\npub fn command(mut options: Options) -> Result<()> {\n  let cwd = std::env::current_dir().context(\"failed to get current directory\")?;\n  if let Some(dir) = &options.directory {\n    std::fs::create_dir_all(cwd.join(dir))\n      .fs_context(\"failed to create crate directory\", cwd.join(dir))?;\n  } else {\n    let target = cwd.join(format!(\"tauri-plugin-{}\", options.plugin_name));\n    std::fs::create_dir_all(&target)\n      .fs_context(\"failed to create crate directory\", target.clone())?;\n    options.directory.replace(target.display().to_string());\n  }\n\n  super::init::command(options.into())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/remove.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse clap::Parser;\n\nuse crate::{\n  acl,\n  helpers::{app_paths::resolve_frontend_dir, cargo, npm::PackageManager},\n  Result,\n};\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Remove a tauri plugin from the project\")]\npub struct Options {\n  /// The plugin to remove.\n  pub plugin: String,\n}\n\npub fn command(options: Options) -> Result<()> {\n  let dirs = crate::helpers::app_paths::resolve_dirs();\n  let plugin = options.plugin;\n\n  let crate_name = format!(\"tauri-plugin-{plugin}\");\n\n  let mut plugins = crate::helpers::plugins::known_plugins();\n  let metadata = plugins.remove(plugin.as_str()).unwrap_or_default();\n\n  let frontend_dir = resolve_frontend_dir();\n\n  let target_str = metadata\n    .desktop_only\n    .then_some(r#\"cfg(not(any(target_os = \"android\", target_os = \"ios\")))\"#)\n    .or_else(|| {\n      metadata\n        .mobile_only\n        .then_some(r#\"cfg(any(target_os = \"android\", target_os = \"ios\"))\"#)\n    });\n\n  cargo::uninstall_one(cargo::CargoUninstallOptions {\n    name: &crate_name,\n    cwd: Some(dirs.tauri),\n    target: target_str,\n  })?;\n\n  if !metadata.rust_only {\n    if let Some(manager) = frontend_dir.map(PackageManager::from_project) {\n      let npm_name = format!(\"@tauri-apps/plugin-{plugin}\");\n      manager.remove(&[npm_name], dirs.tauri)?;\n    }\n\n    acl::permission::rm::command(acl::permission::rm::Options {\n      identifier: format!(\"{plugin}:*\"),\n    })?;\n  }\n\n  log::info!(\"Now, you must manually remove the plugin from your Rust code.\",);\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/signer/generate.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  helpers::updater_signature::{generate_key, save_keypair},\n  Result,\n};\nuse clap::Parser;\nuse std::path::PathBuf;\nuse tauri_utils::display_path;\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Generate a new signing key to sign files\")]\npub struct Options {\n  /// Set private key password when signing\n  #[clap(short, long)]\n  password: Option<String>,\n  /// Write private key to a file\n  #[clap(short, long)]\n  write_keys: Option<PathBuf>,\n  /// Overwrite private key even if it exists on the specified path\n  #[clap(short, long)]\n  force: bool,\n  /// Skip prompting for values\n  #[clap(long, env = \"CI\")]\n  ci: bool,\n}\n\npub fn command(mut options: Options) -> Result<()> {\n  if options.ci && options.password.is_none() {\n    log::warn!(\"Generating new private key without password. For security reasons, we recommend setting a password instead.\");\n    options.password.replace(\"\".into());\n  }\n  let keypair = generate_key(options.password).expect(\"Failed to generate key\");\n\n  if let Some(output_path) = options.write_keys {\n    let (secret_path, public_path) =\n      save_keypair(options.force, output_path, &keypair.sk, &keypair.pk)\n        .expect(\"Unable to write keypair\");\n\n    println!();\n    println!(\"Your keypair was generated successfully:\");\n    println!(\"Private: {} (Keep it secret!)\", display_path(secret_path));\n    println!(\"Public: {}\", display_path(public_path));\n    println!(\"---------------------------\")\n  } else {\n    println!();\n    println!(\"Your keys were generated successfully!\",);\n    println!();\n    println!(\"Private: (Keep it secret!)\");\n    println!(\"{}\", keypair.sk);\n    println!();\n    println!(\"Public:\");\n    println!(\"{}\", keypair.pk);\n  }\n\n  println!();\n  println!(\"Environment variables used to sign:\");\n  println!(\"- `TAURI_SIGNING_PRIVATE_KEY`: String of your private key\");\n  println!(\"- `TAURI_SIGNING_PRIVATE_KEY_PATH`: Path to your private key file\");\n  println!(\"- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`:  Your private key password (optional if key has no password)\");\n  println!();\n  println!(\"ATTENTION: If you lose your private key OR password, you'll not be able to sign your update package and updates will not work\");\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/signer/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::Result;\nuse clap::{Parser, Subcommand};\n\nmod generate;\nmod sign;\n\n#[derive(Parser)]\n#[clap(\n  author,\n  version,\n  about = \"Generate signing keys for Tauri updater or sign files\",\n  subcommand_required(true),\n  arg_required_else_help(true)\n)]\npub struct Cli {\n  #[clap(subcommand)]\n  command: Commands,\n}\n\n#[derive(Subcommand)]\nenum Commands {\n  Sign(sign::Options),\n  Generate(generate::Options),\n}\n\npub fn command(cli: Cli) -> Result<()> {\n  match cli.command {\n    Commands::Sign(options) => sign::command(options)?,\n    Commands::Generate(options) => generate::command(options)?,\n  }\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/src/signer/sign.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::{Path, PathBuf};\n\nuse crate::{\n  error::Context,\n  helpers::updater_signature::{secret_key, sign_file},\n  Result,\n};\nuse base64::Engine;\nuse clap::Parser;\nuse tauri_utils::display_path;\n\n#[derive(Debug, Parser)]\n#[clap(about = \"Sign a file\")]\npub struct Options {\n  /// Load the private key from a string\n  #[clap(\n    short = 'k',\n    long,\n    conflicts_with(\"private_key_path\"),\n    env = \"TAURI_SIGNING_PRIVATE_KEY\"\n  )]\n  private_key: Option<String>,\n  /// Load the private key from a file\n  #[clap(\n    short = 'f',\n    long,\n    conflicts_with(\"private_key\"),\n    env = \"TAURI_SIGNING_PRIVATE_KEY_PATH\"\n  )]\n  private_key_path: Option<PathBuf>,\n  /// Set private key password when signing\n  #[clap(short, long, env = \"TAURI_SIGNING_PRIVATE_KEY_PASSWORD\")]\n  password: Option<String>,\n  /// Sign the specified file\n  file: PathBuf,\n}\n\n// Backwards compatibility with old env vars\n// TODO: remove in v3.0\nfn backward_env_vars(mut options: Options) -> Options {\n  let get_env = |old, new| {\n    if let Ok(old_value) = std::env::var(old) {\n      println!(\n      \"\\x1b[33mWarning: The environment variable '{old}' is deprecated. Please use '{new}' instead.\\x1b[0m\",\n    );\n      Some(old_value)\n    } else {\n      None\n    }\n  };\n\n  options.private_key = options\n    .private_key\n    .or_else(|| get_env(\"TAURI_PRIVATE_KEY\", \"TAURI_SIGNING_PRIVATE_KEY\"));\n\n  options.private_key_path = options.private_key_path.or_else(|| {\n    get_env(\"TAURI_PRIVATE_KEY_PATH\", \"TAURI_SIGNING_PRIVATE_KEY_PATH\").map(PathBuf::from)\n  });\n\n  options.password = options.password.or_else(|| {\n    get_env(\n      \"TAURI_PRIVATE_KEY_PASSWORD\",\n      \"TAURI_SIGNING_PRIVATE_KEY_PASSWORD\",\n    )\n  });\n  options\n}\n\npub fn command(mut options: Options) -> Result<()> {\n  options = backward_env_vars(options);\n\n  options.private_key = if let Some(private_key) = options.private_key_path {\n    Some(std::fs::read_to_string(Path::new(&private_key)).expect(\"Unable to extract private key\"))\n  } else {\n    options.private_key\n  };\n  let private_key = if let Some(pk) = options.private_key {\n    pk\n  } else {\n    crate::error::bail!(\"Key generation aborted: Unable to find the private key\");\n  };\n\n  if options.password.is_none() {\n    println!(\"Signing without password.\");\n  }\n\n  let (manifest_dir, signature) =\n    sign_file(&secret_key(private_key, options.password)?, options.file)\n      .with_context(|| \"failed to sign file\")?;\n\n  println!(\n           \"\\nYour file was signed successfully, You can find the signature here:\\n{}\\n\\nPublic signature:\\n{}\\n\\nMake sure to include this into the signature field of your update server.\",\n           display_path(manifest_dir),\n           base64::engine::general_purpose::STANDARD.encode(signature.to_string())\n         );\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-cli/tauri-dev-watcher.gitignore",
    "content": "node_modules/\ntarget/\ngen/\nCargo.lock\n.DS_Store\n"
  },
  {
    "path": "crates/tauri-cli/tauri.config.schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"title\": \"Config\",\n  \"description\": \"The Tauri configuration object.\\n It is read from a file where you can define your frontend assets,\\n configure the bundler and define a tray icon.\\n\\n The configuration file is generated by the\\n [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in\\n your Tauri application source directory (src-tauri).\\n\\n Once generated, you may modify it at will to customize your Tauri application.\\n\\n ## File Formats\\n\\n By default, the configuration is defined as a JSON file named `tauri.conf.json`.\\n\\n Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.\\n The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.\\n The TOML file name is `Tauri.toml`.\\n\\n ## Platform-Specific Configuration\\n\\n In addition to the default configuration file, Tauri can\\n read a platform-specific configuration from `tauri.linux.conf.json`,\\n `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`\\n (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),\\n which gets merged with the main configuration object.\\n\\n ## Configuration Structure\\n\\n The configuration is composed of the following objects:\\n\\n - [`app`](#appconfig): The Tauri configuration\\n - [`build`](#buildconfig): The build configuration\\n - [`bundle`](#bundleconfig): The bundle configurations\\n - [`plugins`](#pluginconfig): The plugins configuration\\n\\n Example tauri.config.json file:\\n\\n ```json\\n {\\n   \\\"productName\\\": \\\"tauri-app\\\",\\n   \\\"version\\\": \\\"0.1.0\\\",\\n   \\\"build\\\": {\\n     \\\"beforeBuildCommand\\\": \\\"\\\",\\n     \\\"beforeDevCommand\\\": \\\"\\\",\\n     \\\"devUrl\\\": \\\"../dist\\\",\\n     \\\"frontendDist\\\": \\\"../dist\\\"\\n   },\\n   \\\"app\\\": {\\n     \\\"security\\\": {\\n       \\\"csp\\\": null\\n     },\\n     \\\"windows\\\": [\\n       {\\n         \\\"fullscreen\\\": false,\\n         \\\"height\\\": 600,\\n         \\\"resizable\\\": true,\\n         \\\"title\\\": \\\"Tauri App\\\",\\n         \\\"width\\\": 800\\n       }\\n     ]\\n   },\\n   \\\"bundle\\\": {},\\n   \\\"plugins\\\": {}\\n }\\n ```\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"$schema\": {\n      \"description\": \"The JSON schema for the Tauri config.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"productName\": {\n      \"description\": \"App name.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ],\n      \"pattern\": \"^[^/\\\\:*?\\\"<>|]+$\"\n    },\n    \"version\": {\n      \"description\": \"App version. It is a semver version number or a path to a `package.json` file containing the `version` field. If removed the version number from `Cargo.toml` is used.\\n\\n By default version 1.0 is used on Android.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"identifier\": {\n      \"description\": \"The application identifier in reverse domain name notation (e.g. `com.tauri.example`).\\n This string must be unique across applications since it is used in system configurations like\\n the bundle ID and path to the webview data directory.\\n This string must contain only alphanumeric characters (A–Z, a–z, and 0–9), hyphens (-),\\n and periods (.).\",\n      \"default\": \"\",\n      \"type\": \"string\"\n    },\n    \"app\": {\n      \"description\": \"The App configuration.\",\n      \"default\": {\n        \"enableGTKAppId\": false,\n        \"macOSPrivateApi\": false,\n        \"security\": {\n          \"assetProtocol\": {\n            \"enable\": false,\n            \"scope\": []\n          },\n          \"capabilities\": [],\n          \"dangerousDisableAssetCspModification\": false,\n          \"freezePrototype\": false,\n          \"pattern\": {\n            \"use\": \"brownfield\"\n          }\n        },\n        \"windows\": [],\n        \"withGlobalTauri\": false\n      },\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/AppConfig\"\n        }\n      ]\n    },\n    \"build\": {\n      \"description\": \"The build configuration.\",\n      \"default\": {},\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/BuildConfig\"\n        }\n      ]\n    },\n    \"bundle\": {\n      \"description\": \"The bundler configuration.\",\n      \"default\": {\n        \"active\": false,\n        \"android\": {\n          \"minSdkVersion\": 24\n        },\n        \"createUpdaterArtifacts\": false,\n        \"iOS\": {\n          \"minimumSystemVersion\": \"13.0\"\n        },\n        \"icon\": [],\n        \"linux\": {\n          \"appimage\": {\n            \"bundleMediaFramework\": false,\n            \"files\": {}\n          },\n          \"deb\": {\n            \"files\": {}\n          },\n          \"rpm\": {\n            \"epoch\": 0,\n            \"files\": {},\n            \"release\": \"1\"\n          }\n        },\n        \"macOS\": {\n          \"dmg\": {\n            \"appPosition\": {\n              \"x\": 180,\n              \"y\": 170\n            },\n            \"applicationFolderPosition\": {\n              \"x\": 480,\n              \"y\": 170\n            },\n            \"windowSize\": {\n              \"height\": 400,\n              \"width\": 660\n            }\n          },\n          \"files\": {},\n          \"hardenedRuntime\": true,\n          \"minimumSystemVersion\": \"10.13\"\n        },\n        \"targets\": \"all\",\n        \"windows\": {\n          \"allowDowngrades\": true,\n          \"certificateThumbprint\": null,\n          \"digestAlgorithm\": null,\n          \"nsis\": null,\n          \"signCommand\": null,\n          \"timestampUrl\": null,\n          \"tsp\": false,\n          \"webviewInstallMode\": {\n            \"silent\": true,\n            \"type\": \"downloadBootstrapper\"\n          },\n          \"wix\": null\n        }\n      },\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/BundleConfig\"\n        }\n      ]\n    },\n    \"plugins\": {\n      \"description\": \"The plugins config.\",\n      \"default\": {},\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/PluginConfig\"\n        }\n      ]\n    }\n  },\n  \"additionalProperties\": false,\n  \"definitions\": {\n    \"AppConfig\": {\n      \"description\": \"The App configuration object.\\n\\n See more: <https://tauri.app/v1/api/config#appconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"windows\": {\n          \"description\": \"The windows configuration.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/WindowConfig\"\n          }\n        },\n        \"security\": {\n          \"description\": \"Security configuration.\",\n          \"default\": {\n            \"assetProtocol\": {\n              \"enable\": false,\n              \"scope\": []\n            },\n            \"capabilities\": [],\n            \"dangerousDisableAssetCspModification\": false,\n            \"freezePrototype\": false,\n            \"pattern\": {\n              \"use\": \"brownfield\"\n            }\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/SecurityConfig\"\n            }\n          ]\n        },\n        \"trayIcon\": {\n          \"description\": \"Configuration for app tray icon.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/TrayIconConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"macOSPrivateApi\": {\n          \"description\": \"MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"withGlobalTauri\": {\n          \"description\": \"Whether we should inject the Tauri API on `window.__TAURI__` or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"enableGTKAppId\": {\n          \"description\": \"If set to true \\\"identifier\\\" will be set as GTK app ID (on systems that use GTK).\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WindowConfig\": {\n      \"description\": \"The window configuration object.\\n\\n See more: <https://tauri.app/v1/api/config#windowconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"label\": {\n          \"description\": \"The window identifier. It must be alphanumeric.\",\n          \"default\": \"main\",\n          \"type\": \"string\"\n        },\n        \"url\": {\n          \"description\": \"The window webview URL.\",\n          \"default\": \"index.html\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WebviewUrl\"\n            }\n          ]\n        },\n        \"userAgent\": {\n          \"description\": \"The user agent for the webview\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dragDropEnabled\": {\n          \"description\": \"Whether the drag and drop is enabled or not on the webview. By default it is enabled.\\n\\n Disabling it is required to use HTML5 drag and drop on the frontend on Windows.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"center\": {\n          \"description\": \"Whether or not the window starts centered or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"x\": {\n          \"description\": \"The horizontal position of the window's top left corner\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"y\": {\n          \"description\": \"The vertical position of the window's top left corner\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"width\": {\n          \"description\": \"The window width.\",\n          \"default\": 800.0,\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"height\": {\n          \"description\": \"The window height.\",\n          \"default\": 600.0,\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"minWidth\": {\n          \"description\": \"The min window width.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"minHeight\": {\n          \"description\": \"The min window height.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"maxWidth\": {\n          \"description\": \"The max window width.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"maxHeight\": {\n          \"description\": \"The max window height.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"preventOverflow\": {\n          \"description\": \"Whether or not to prevent window overflow\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/PreventOverflowMarginConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"resizable\": {\n          \"description\": \"Whether the window is resizable or not. When resizable is set to false, native window's maximize button is automatically disabled.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"maximizable\": {\n          \"description\": \"Whether the window's native maximize button is enabled or not.\\n If resizable is set to false, this setting is ignored.\\n\\n ## Platform-specific\\n\\n - **macOS:** Disables the \\\"zoom\\\" button in the window titlebar, which is also used to enter fullscreen mode.\\n - **Linux / iOS / Android:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"minimizable\": {\n          \"description\": \"Whether the window's native minimize button is enabled or not.\\n\\n ## Platform-specific\\n\\n - **Linux / iOS / Android:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"closable\": {\n          \"description\": \"Whether the window's native close button is enabled or not.\\n\\n ## Platform-specific\\n\\n - **Linux:** \\\"GTK+ will do its best to convince the window manager not to show a close button.\\n   Depending on the system, this function may not have any effect when called on a window that is already visible\\\"\\n - **iOS / Android:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"title\": {\n          \"description\": \"The window title.\",\n          \"default\": \"Tauri App\",\n          \"type\": \"string\"\n        },\n        \"fullscreen\": {\n          \"description\": \"Whether the window starts as fullscreen or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"focus\": {\n          \"description\": \"Whether the window will be initially focused or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"focusable\": {\n          \"description\": \"Whether the window will be focusable or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"transparent\": {\n          \"description\": \"Whether the window is transparent or not.\\n\\n Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri > macOSPrivateApi`.\\n WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"maximized\": {\n          \"description\": \"Whether the window is maximized or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"visible\": {\n          \"description\": \"Whether the window is visible or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"decorations\": {\n          \"description\": \"Whether the window should have borders and bars.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"alwaysOnBottom\": {\n          \"description\": \"Whether the window should always be below other windows.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"alwaysOnTop\": {\n          \"description\": \"Whether the window should always be on top of other windows.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"visibleOnAllWorkspaces\": {\n          \"description\": \"Whether the window should be visible on all workspaces or virtual desktops.\\n\\n ## Platform-specific\\n\\n - **Windows / iOS / Android:** Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"contentProtected\": {\n          \"description\": \"Prevents the window contents from being captured by other apps.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"skipTaskbar\": {\n          \"description\": \"If `true`, hides the window icon from the taskbar on Windows and Linux.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"theme\": {\n          \"description\": \"The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Theme\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"titleBarStyle\": {\n          \"description\": \"The style of the macOS title bar.\",\n          \"default\": \"Visible\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/TitleBarStyle\"\n            }\n          ]\n        },\n        \"hiddenTitle\": {\n          \"description\": \"If `true`, sets the window title to be hidden on macOS.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"acceptFirstMouse\": {\n          \"description\": \"Whether clicking an inactive window also clicks through to the webview on macOS.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"tabbingIdentifier\": {\n          \"description\": \"Defines the window [tabbing identifier] for macOS.\\n\\n Windows with matching tabbing identifiers will be grouped together.\\n If the tabbing identifier is not set, automatic tabbing will be disabled.\\n\\n [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"additionalBrowserArgs\": {\n          \"description\": \"Defines additional browser arguments on Windows. By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`\\n so if you use this method, you also need to disable these components by yourself if you want.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"shadow\": {\n          \"description\": \"Whether or not the window has shadow.\\n\\n ## Platform-specific\\n\\n - **Windows:**\\n   - `false` has no effect on decorated window, shadow are always ON.\\n   - `true` will make undecorated window have a 1px white border,\\n and on Windows 11, it will have a rounded corners.\\n - **Linux:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"windowEffects\": {\n          \"description\": \"Window effects.\\n\\n Requires the window to be transparent.\\n\\n ## Platform-specific:\\n\\n - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>\\n - **Linux**: Unsupported\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowEffectsConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"incognito\": {\n          \"description\": \"Whether or not the webview should be launched in incognito  mode.\\n\\n  ## Platform-specific:\\n\\n  - **Android**: Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"parent\": {\n          \"description\": \"Sets the window associated with this label to be the parent of the window to be created.\\n\\n ## Platform-specific\\n\\n - **Windows**: This sets the passed parent as an owner window to the window to be created.\\n   From [MSDN owned windows docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows):\\n     - An owned window is always above its owner in the z-order.\\n     - The system automatically destroys an owned window when its owner is destroyed.\\n     - An owned window is hidden when its owner is minimized.\\n - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\\n - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"proxyUrl\": {\n          \"description\": \"The proxy URL for the WebView for all network requests.\\n\\n Must be either a `http://` or a `socks5://` URL.\\n\\n ## Platform-specific\\n\\n - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"format\": \"uri\"\n        },\n        \"zoomHotkeysEnabled\": {\n          \"description\": \"Whether page zooming by hotkeys is enabled\\n\\n ## Platform-specific:\\n\\n - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.\\n - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,\\n 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission\\n\\n - **Android / iOS**: Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"backgroundThrottling\": {\n          \"description\": \"Change the default background throttling behaviour.\\n\\n By default, browsers use a suspend policy that will throttle timers and even unload\\n the whole tab (view) to free resources after roughly 5 minutes when a view became\\n minimized or hidden. This will pause all tasks until the documents visibility state\\n changes back from hidden to visible by bringing the view back to the foreground.\\n\\n ## Platform-specific\\n\\n - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.\\n - **iOS**: Supported since version 17.0+.\\n - **macOS**: Supported since version 14.0+.\\n\\n see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"disabled\",\n            \"throttle\",\n            \"suspend\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WebviewUrl\": {\n      \"description\": \"An URL to open on a Tauri webview window.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An external URL. Must use either the `http` or `https` schemes.\",\n          \"type\": \"string\",\n          \"format\": \"uri\"\n        },\n        {\n          \"description\": \"The path portion of an app URL.\\n For instance, to load `tauri://localhost/users/john`,\\n you can simply provide `users/john` in this configuration.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A custom protocol url, for example, `doom://index.html`\",\n          \"type\": \"string\",\n          \"format\": \"uri\"\n        }\n      ]\n    },\n    \"PreventOverflowMarginConfig\": {\n      \"description\": \"Prevent overflow with a margin\",\n      \"anyOf\": [\n        {\n          \"description\": \"Enable prevent overflow or not\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Enable prevent overflow with a margin\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/PreventOverflowMargin\"\n            }\n          ]\n        }\n      ]\n    },\n    \"PreventOverflowMargin\": {\n      \"description\": \"Enable prevent overflow with a margin\",\n      \"type\": \"object\",\n      \"required\": [\n        \"height\",\n        \"width\"\n      ],\n      \"properties\": {\n        \"width\": {\n          \"description\": \"Horizontal margin in physical unit\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"height\": {\n          \"description\": \"Vertical margin in physical unit\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Theme\": {\n      \"description\": \"System theme.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Light theme.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Light\"\n          ]\n        },\n        {\n          \"description\": \"Dark theme.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Dark\"\n          ]\n        }\n      ]\n    },\n    \"TitleBarStyle\": {\n      \"description\": \"How the window title bar should be displayed on macOS.\",\n      \"oneOf\": [\n        {\n          \"description\": \"A normal title bar.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Visible\"\n          ]\n        },\n        {\n          \"description\": \"Makes the title bar transparent, so the window background color is shown instead.\\n\\n Useful if you don't need to have actual HTML under the title bar. This lets you avoid the caveats of using `TitleBarStyle::Overlay`. Will be more useful when Tauri lets you set a custom window background color.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Transparent\"\n          ]\n        },\n        {\n          \"description\": \"Shows the title bar as a transparent overlay over the window's content.\\n\\n Keep in mind:\\n - The height of the title bar is different on different OS versions, which can lead to window the controls and title not being where you don't expect.\\n - You need to define a custom drag region to make your window draggable, however due to a limitation you can't drag the window when it's not in focus <https://github.com/tauri-apps/tauri/issues/4316>.\\n - The color of the window title depends on the system theme.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Overlay\"\n          ]\n        }\n      ]\n    },\n    \"WindowEffectsConfig\": {\n      \"description\": \"The window effects configuration object\",\n      \"type\": \"object\",\n      \"required\": [\n        \"effects\"\n      ],\n      \"properties\": {\n        \"effects\": {\n          \"description\": \"List of Window effects to apply to the Window.\\n Conflicting effects will apply the first one and ignore the rest.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/WindowEffect\"\n          }\n        },\n        \"state\": {\n          \"description\": \"Window effect state **macOS Only**\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowEffectState\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"radius\": {\n          \"description\": \"Window effect corner radius **macOS Only**\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"color\": {\n          \"description\": \"Window effect color. Affects [`WindowEffect::Blur`] and [`WindowEffect::Acrylic`] only\\n on Windows 10 v1903+. Doesn't have any effect on Windows 7 or Windows 11.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Color\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WindowEffect\": {\n      \"description\": \"Platform-specific window effects\",\n      \"oneOf\": [\n        {\n          \"description\": \"A default material appropriate for the view's effectiveAppearance. **macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"appearanceBased\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"light\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"dark\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"mediumLight\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"ultraDark\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.10+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"titlebar\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.10+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"selection\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.11+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"menu\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.11+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"popover\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.11+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"sidebar\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"headerView\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"sheet\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"windowBackground\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"hudWindow\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"fullScreenUI\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tooltip\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"contentBackground\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"underWindowBackground\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"underPageBackground\"\n          ]\n        },\n        {\n          \"description\": \"Mica effect that matches the system dark perefence **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"mica\"\n          ]\n        },\n        {\n          \"description\": \"Mica effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"micaDark\"\n          ]\n        },\n        {\n          \"description\": \"Mica effect with light mode **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"micaLight\"\n          ]\n        },\n        {\n          \"description\": \"Tabbed effect that matches the system dark perefence **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tabbed\"\n          ]\n        },\n        {\n          \"description\": \"Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tabbedDark\"\n          ]\n        },\n        {\n          \"description\": \"Tabbed effect with light mode **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tabbedLight\"\n          ]\n        },\n        {\n          \"description\": \"**Windows 7/10/11(22H1) Only**\\n\\n ## Notes\\n\\n This effect has bad performance when resizing/dragging the window on Windows 11 build 22621.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"blur\"\n          ]\n        },\n        {\n          \"description\": \"**Windows 10/11 Only**\\n\\n ## Notes\\n\\n This effect has bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"acrylic\"\n          ]\n        }\n      ]\n    },\n    \"WindowEffectState\": {\n      \"description\": \"Window effect state **macOS only**\\n\\n <https://developer.apple.com/documentation/appkit/nsvisualeffectview/state>\",\n      \"oneOf\": [\n        {\n          \"description\": \"Make window effect state follow the window's active state\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"followsWindowActiveState\"\n          ]\n        },\n        {\n          \"description\": \"Make window effect state always active\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"active\"\n          ]\n        },\n        {\n          \"description\": \"Make window effect state always inactive\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"inactive\"\n          ]\n        }\n      ]\n    },\n    \"Color\": {\n      \"description\": \"a tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255.\",\n      \"type\": \"array\",\n      \"items\": [\n        {\n          \"type\": \"integer\",\n          \"format\": \"uint8\",\n          \"minimum\": 0.0\n        },\n        {\n          \"type\": \"integer\",\n          \"format\": \"uint8\",\n          \"minimum\": 0.0\n        },\n        {\n          \"type\": \"integer\",\n          \"format\": \"uint8\",\n          \"minimum\": 0.0\n        },\n        {\n          \"type\": \"integer\",\n          \"format\": \"uint8\",\n          \"minimum\": 0.0\n        }\n      ],\n      \"maxItems\": 4,\n      \"minItems\": 4\n    },\n    \"SecurityConfig\": {\n      \"description\": \"Security configuration.\\n\\n See more: <https://tauri.app/v1/api/config#securityconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"csp\": {\n          \"description\": \"The Content Security Policy that will be injected on all HTML files on the built application.\\n If [`dev_csp`](#SecurityConfig.devCsp) is not specified, this value is also injected on dev.\\n\\n This is a really important part of the configuration since it helps you ensure your WebView is secured.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Csp\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"devCsp\": {\n          \"description\": \"The Content Security Policy that will be injected on all HTML files on development.\\n\\n This is a really important part of the configuration since it helps you ensure your WebView is secured.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Csp\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"freezePrototype\": {\n          \"description\": \"Freeze the `Object.prototype` when using the custom protocol.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"dangerousDisableAssetCspModification\": {\n          \"description\": \"Disables the Tauri-injected CSP sources.\\n\\n At compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy\\n to only allow loading of your own scripts and styles by injecting nonce and hash sources.\\n This stricts your CSP, which may introduce issues when using along with other flexing sources.\\n\\n This configuration option allows both a boolean and a list of strings as value.\\n A boolean instructs Tauri to disable the injection for all CSP injections,\\n and a list of strings indicates the CSP directives that Tauri cannot inject.\\n\\n **WARNING:** Only disable this if you know what you are doing and have properly configured the CSP.\\n Your application might be vulnerable to XSS attacks without this Tauri protection.\",\n          \"default\": false,\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/DisabledCspModificationKind\"\n            }\n          ]\n        },\n        \"assetProtocol\": {\n          \"description\": \"Custom protocol config.\",\n          \"default\": {\n            \"enable\": false,\n            \"scope\": []\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/AssetProtocolConfig\"\n            }\n          ]\n        },\n        \"pattern\": {\n          \"description\": \"The pattern to use.\",\n          \"default\": {\n            \"use\": \"brownfield\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/PatternKind\"\n            }\n          ]\n        },\n        \"capabilities\": {\n          \"description\": \"List of capabilities that are enabled on the application.\\n\\n If the list is empty, all capabilities are included.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/CapabilityEntry\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Csp\": {\n      \"description\": \"A Content-Security-Policy definition.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\",\n      \"anyOf\": [\n        {\n          \"description\": \"The entire CSP policy in a single text string.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An object mapping a directive with its sources values as a list of strings.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/CspDirectiveSources\"\n          }\n        }\n      ]\n    },\n    \"CspDirectiveSources\": {\n      \"description\": \"A Content-Security-Policy directive source list.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources>.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An inline list of CSP sources. Same as [`Self::List`], but concatenated with a space separator.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A list of CSP sources. The collection will be concatenated with a space separator for the CSP string.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"DisabledCspModificationKind\": {\n      \"description\": \"The possible values for the `dangerous_disable_asset_csp_modification` config option.\",\n      \"anyOf\": [\n        {\n          \"description\": \"If `true`, disables all CSP modification.\\n `false` is the default value and it configures Tauri to control the CSP.\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Disables the given list of CSP directives modifications.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"AssetProtocolConfig\": {\n      \"description\": \"Config for the asset custom protocol.\\n\\n See more: <https://tauri.app/v1/api/config#assetprotocolconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"scope\": {\n          \"description\": \"The access scope for the asset protocol.\",\n          \"default\": [],\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/FsScope\"\n            }\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Enables the asset protocol.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"FsScope\": {\n      \"description\": \"Protocol scope definition.\\n It is a list of glob patterns that restrict the API access from the webview.\\n\\n Each pattern can start with a variable that resolves to a system base directory.\\n The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,\\n `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,\\n `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,\\n `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A list of paths that are allowed by this scope.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A complete scope configuration.\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"allow\": {\n              \"description\": \"A list of paths that are allowed by this scope.\",\n              \"default\": [],\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            },\n            \"deny\": {\n              \"description\": \"A list of paths that are not allowed by this scope.\\n This gets precedence over the [`Self::Scope::allow`] list.\",\n              \"default\": [],\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            },\n            \"requireLiteralLeadingDot\": {\n              \"description\": \"Whether or not paths that contain components that start with a `.`\\n will require that `.` appears literally in the pattern; `*`, `?`, `**`,\\n or `[...]` will not match. This is useful because such files are\\n conventionally considered hidden on Unix systems and it might be\\n desirable to skip them when listing files.\\n\\n Defaults to `true` on Unix systems and `false` on Windows\",\n              \"type\": [\n                \"boolean\",\n                \"null\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"PatternKind\": {\n      \"description\": \"The application pattern.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Brownfield pattern.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"use\"\n          ],\n          \"properties\": {\n            \"use\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"brownfield\"\n              ]\n            }\n          }\n        },\n        {\n          \"description\": \"Isolation pattern. Recommended for security purposes.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"options\",\n            \"use\"\n          ],\n          \"properties\": {\n            \"use\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"isolation\"\n              ]\n            },\n            \"options\": {\n              \"type\": \"object\",\n              \"required\": [\n                \"dir\"\n              ],\n              \"properties\": {\n                \"dir\": {\n                  \"description\": \"The dir containing the index.html file that contains the secure isolation application.\",\n                  \"type\": \"string\"\n                }\n              }\n            }\n          }\n        }\n      ]\n    },\n    \"CapabilityEntry\": {\n      \"description\": \"A capability entry which can be either an inlined capability or a reference to a capability defined on its own file.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An inlined capability.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Capability\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Reference to a capability identifier.\",\n          \"type\": \"string\"\n        }\n      ]\n    },\n    \"Capability\": {\n      \"description\": \"A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\\n\\n It controls application windows fine grained access to the Tauri core, application, or plugin commands.\\n If a window is not matching any capability then it has no access to the IPC layer at all.\\n\\n This can be done to create groups of windows, based on their required system access, which can reduce\\n impact of frontend vulnerabilities in less privileged windows.\\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\\n A Window can have none, one, or multiple associated capabilities.\\n\\n ## Example\\n\\n ```json\\n {\\n   \\\"identifier\\\": \\\"main-user-files-write\\\",\\n   \\\"description\\\": \\\"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\\\",\\n   \\\"windows\\\": [\\n     \\\"main\\\"\\n   ],\\n  \\\"permissions\\\": [\\n   \\\"core:default\\\",\\n   \\\"dialog:open\\\",\\n   {\\n     \\\"identifier\\\": \\\"fs:allow-write-text-file\\\",\\n     \\\"allow\\\": [{ \\\"path\\\": \\\"$HOME/test.txt\\\" }]\\n   },\\n  \\\"platforms\\\": [\\\"macOS\\\",\\\"windows\\\"]\\n }\\n ```\",\n      \"type\": \"object\",\n      \"required\": [\n        \"identifier\",\n        \"permissions\"\n      ],\n      \"properties\": {\n        \"identifier\": {\n          \"description\": \"Identifier of the capability.\\n\\n ## Example\\n\\n `main-user-files-write`\",\n          \"type\": \"string\"\n        },\n        \"description\": {\n          \"description\": \"Description of what the capability is intended to allow on associated windows.\\n\\n It should contain a description of what the grouped permissions should allow.\\n\\n ## Example\\n\\n This capability allows the `main` window access to `filesystem` write related\\n commands and `dialog` commands to enable programatic access to files selected by the user.\",\n          \"default\": \"\",\n          \"type\": \"string\"\n        },\n        \"remote\": {\n          \"description\": \"Configure remote URLs that can use the capability permissions.\\n\\n This setting is optional and defaults to not being set, as our\\n default use case is that the content is served from our local application.\\n\\n :::caution\\n Make sure you understand the security implications of providing remote\\n sources with local system access.\\n :::\\n\\n ## Example\\n\\n ```json\\n {\\n   \\\"urls\\\": [\\\"https://*.mydomain.dev\\\"]\\n }\\n ```\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/CapabilityRemote\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"local\": {\n          \"description\": \"Whether this capability is enabled for local app URLs or not. Defaults to `true`.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"windows\": {\n          \"description\": \"List of windows that are affected by this capability. Can be a glob pattern.\\n\\n On multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\\n\\n ## Example\\n\\n `[\\\"main\\\"]`\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"webviews\": {\n          \"description\": \"List of webviews that are affected by this capability. Can be a glob pattern.\\n\\n This is only required when using on multiwebview contexts, by default\\n all child webviews of a window that matches [`Self::windows`] are linked.\\n\\n ## Example\\n\\n `[\\\"sub-webview-one\\\", \\\"sub-webview-two\\\"]`\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"permissions\": {\n          \"description\": \"List of permissions attached to this capability.\\n\\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\\n For commands directly implemented in the application itself only `${permission-name}`\\n is required.\\n\\n ## Example\\n\\n ```json\\n [\\n  \\\"core:default\\\",\\n  \\\"shell:allow-open\\\",\\n  \\\"dialog:open\\\",\\n  {\\n    \\\"identifier\\\": \\\"fs:allow-write-text-file\\\",\\n    \\\"allow\\\": [{ \\\"path\\\": \\\"$HOME/test.txt\\\" }]\\n  }\\n ```\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/PermissionEntry\"\n          },\n          \"uniqueItems\": true\n        },\n        \"platforms\": {\n          \"description\": \"Limit which target platforms this capability applies to.\\n\\n By default all platforms are targeted.\\n\\n ## Example\\n\\n `[\\\"macOS\\\",\\\"windows\\\"]`\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/definitions/Target\"\n          }\n        }\n      }\n    },\n    \"CapabilityRemote\": {\n      \"description\": \"Configuration for remote URLs that are associated with the capability.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"urls\"\n      ],\n      \"properties\": {\n        \"urls\": {\n          \"description\": \"Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\\n\\n ## Examples\\n\\n - \\\"https://*.mydomain.dev\\\": allows subdomains of mydomain.dev\\n - \\\"https://mydomain.dev/api/*\\\": allows any subpath of mydomain.dev/api\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"PermissionEntry\": {\n      \"description\": \"An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`]\\n or an object that references a permission and extends its scope.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Reference a permission or permission set by identifier.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Identifier\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Reference a permission or permission set by identifier and extends its scope.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"identifier\"\n          ],\n          \"properties\": {\n            \"identifier\": {\n              \"description\": \"Identifier of the permission or permission set.\",\n              \"allOf\": [\n                {\n                  \"$ref\": \"#/definitions/Identifier\"\n                }\n              ]\n            },\n            \"allow\": {\n              \"description\": \"Data that defines what is allowed by the scope.\",\n              \"type\": [\n                \"array\",\n                \"null\"\n              ],\n              \"items\": {\n                \"$ref\": \"#/definitions/Value\"\n              }\n            },\n            \"deny\": {\n              \"description\": \"Data that defines what is denied by the scope. This should be prioritized by validation logic.\",\n              \"type\": [\n                \"array\",\n                \"null\"\n              ],\n              \"items\": {\n                \"$ref\": \"#/definitions/Value\"\n              }\n            }\n          }\n        }\n      ]\n    },\n    \"Identifier\": {\n      \"type\": \"string\"\n    },\n    \"Value\": {\n      \"description\": \"All supported ACL values.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents a null JSON value.\",\n          \"type\": \"null\"\n        },\n        {\n          \"description\": \"Represents a [`bool`].\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Represents a valid ACL [`Number`].\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Number\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Represents a [`String`].\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Represents a list of other [`Value`]s.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        },\n        {\n          \"description\": \"Represents a map of [`String`] keys to [`Value`]s.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        }\n      ]\n    },\n    \"Number\": {\n      \"description\": \"A valid ACL number.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents an [`i64`].\",\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        {\n          \"description\": \"Represents a [`f64`].\",\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      ]\n    },\n    \"Target\": {\n      \"description\": \"Platform target.\",\n      \"oneOf\": [\n        {\n          \"description\": \"MacOS.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"macOS\"\n          ]\n        },\n        {\n          \"description\": \"Windows.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"windows\"\n          ]\n        },\n        {\n          \"description\": \"Linux.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"linux\"\n          ]\n        },\n        {\n          \"description\": \"Android.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"android\"\n          ]\n        },\n        {\n          \"description\": \"iOS.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"iOS\"\n          ]\n        }\n      ]\n    },\n    \"TrayIconConfig\": {\n      \"description\": \"Configuration for application tray icon.\\n\\n See more: <https://tauri.app/v1/api/config#trayiconconfig>\",\n      \"type\": \"object\",\n      \"required\": [\n        \"iconPath\"\n      ],\n      \"properties\": {\n        \"id\": {\n          \"description\": \"Set an id for this tray icon so you can reference it later, defaults to `main`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"iconPath\": {\n          \"description\": \"Path to the default icon to use for the tray icon.\\n\\n Note: this stores the image in raw pixels to the final binary,\\n so keep the icon size (width and height) small\\n or else it's going to bloat your final executable\",\n          \"type\": \"string\"\n        },\n        \"iconAsTemplate\": {\n          \"description\": \"A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"menuOnLeftClick\": {\n          \"description\": \"A Boolean value that determines whether the menu should appear when the tray icon receives a left click on macOS.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"title\": {\n          \"description\": \"Title for MacOS tray\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tooltip\": {\n          \"description\": \"Tray icon tooltip on Windows and macOS\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"BuildConfig\": {\n      \"description\": \"The Build configuration object.\\n\\n See more: <https://tauri.app/v1/api/config#buildconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"runner\": {\n          \"description\": \"The binary used to build and run the application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"devUrl\": {\n          \"description\": \"The URL to load in development.\\n\\n This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.\\n Most modern JavaScript bundlers like [Vite](https://vite.dev/guide/) provides a way to start a dev server by default.\\n\\n If you don't have a dev server or don't want to use one, ignore this option and use [`frontendDist`](BuildConfig::frontend_dist)\\n and point to a web assets directory, and Tauri CLI will run its built-in dev server and provide a simple hot-reload experience.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"format\": \"uri\"\n        },\n        \"frontendDist\": {\n          \"description\": \"The path to the application assets (usually the `dist` folder of your javascript bundler)\\n or a URL that could be either a custom protocol registered in the tauri app (for example: `myprotocol://`)\\n or a remote URL (for example: `https://site.com/app`).\\n\\n When a path relative to the configuration file is provided,\\n it is read recursively and all files are embedded in the application binary.\\n Tauri then looks for an `index.html` and serves it as the default entry point for your application.\\n\\n You can also provide a list of paths to be embedded, which allows granular control over what files are added to the binary.\\n In this case, all files are added to the root and you must reference it that way in your HTML files.\\n\\n When a URL is provided, the application won't have bundled assets\\n and the application will load that URL by default.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/FrontendDist\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"beforeDevCommand\": {\n          \"description\": \"A shell command to run before `tauri dev` kicks in.\\n\\n The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BeforeDevCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"beforeBuildCommand\": {\n          \"description\": \"A shell command to run before `tauri build` kicks in.\\n\\n The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HookCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"beforeBundleCommand\": {\n          \"description\": \"A shell command to run before the bundling phase in `tauri build` kicks in.\\n\\n The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HookCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"features\": {\n          \"description\": \"Features passed to `cargo` commands.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"FrontendDist\": {\n      \"description\": \"Defines the URL or assets to embed in the application.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An external URL that should be used as the default application URL.\",\n          \"type\": \"string\",\n          \"format\": \"uri\"\n        },\n        {\n          \"description\": \"Path to a directory containing the frontend dist assets.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An array of files to embed on the app.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"BeforeDevCommand\": {\n      \"description\": \"Describes the shell command to run before `tauri dev`.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Run the given script with the default options.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Run the given script with custom options.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"script\"\n          ],\n          \"properties\": {\n            \"script\": {\n              \"description\": \"The script to execute.\",\n              \"type\": \"string\"\n            },\n            \"cwd\": {\n              \"description\": \"The current working directory.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            },\n            \"wait\": {\n              \"description\": \"Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`.\",\n              \"default\": false,\n              \"type\": \"boolean\"\n            }\n          }\n        }\n      ]\n    },\n    \"HookCommand\": {\n      \"description\": \"Describes a shell command to be executed when a CLI hook is triggered.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Run the given script with the default options.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Run the given script with custom options.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"script\"\n          ],\n          \"properties\": {\n            \"script\": {\n              \"description\": \"The script to execute.\",\n              \"type\": \"string\"\n            },\n            \"cwd\": {\n              \"description\": \"The current working directory.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"BundleConfig\": {\n      \"description\": \"Configuration for tauri-bundler.\\n\\n See more: <https://tauri.app/v1/api/config#bundleconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"active\": {\n          \"description\": \"Whether Tauri should bundle your application or just output the executable.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"targets\": {\n          \"description\": \"The bundle targets, currently supports [\\\"deb\\\", \\\"rpm\\\", \\\"appimage\\\", \\\"nsis\\\", \\\"msi\\\", \\\"app\\\", \\\"dmg\\\"] or \\\"all\\\".\",\n          \"default\": \"all\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleTarget\"\n            }\n          ]\n        },\n        \"createUpdaterArtifacts\": {\n          \"description\": \"Produce updaters and their signatures or not\",\n          \"default\": false,\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Updater\"\n            }\n          ]\n        },\n        \"publisher\": {\n          \"description\": \"The application's publisher. Defaults to the second element in the identifier string.\\n Currently maps to the Manufacturer property of the Windows Installer.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"homepage\": {\n          \"description\": \"A url to the home page of your application. If unset, will\\n fallback to `homepage` defined in `Cargo.toml`.\\n\\n Supported bundle targets: `deb`, `rpm`, `nsis` and `msi`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"icon\": {\n          \"description\": \"The app's icons\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"resources\": {\n          \"description\": \"App resources to bundle.\\n Each resource is a path to a file or directory.\\n Glob patterns are supported.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleResources\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"copyright\": {\n          \"description\": \"A copyright string associated with your application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"license\": {\n          \"description\": \"The package's license identifier to be included in the appropriate bundles.\\n If not set, defaults to the license from the Cargo.toml file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"licenseFile\": {\n          \"description\": \"The path to the license file to be included in the appropriate bundles.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"category\": {\n          \"description\": \"The application kind.\\n\\n Should be one of the following:\\n Business, DeveloperTool, Education, Entertainment, Finance, Game, ActionGame, AdventureGame, ArcadeGame, BoardGame, CardGame, CasinoGame, DiceGame, EducationalGame, FamilyGame, KidsGame, MusicGame, PuzzleGame, RacingGame, RolePlayingGame, SimulationGame, SportsGame, StrategyGame, TriviaGame, WordGame, GraphicsAndDesign, HealthcareAndFitness, Lifestyle, Medical, Music, News, Photography, Productivity, Reference, SocialNetworking, Sports, Travel, Utility, Video, Weather.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fileAssociations\": {\n          \"description\": \"File associations to application.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/definitions/FileAssociation\"\n          }\n        },\n        \"shortDescription\": {\n          \"description\": \"A short description of your application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"longDescription\": {\n          \"description\": \"A longer, multi-line description of the application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"externalBin\": {\n          \"description\": \"A list of—either absolute or relative—paths to binaries to embed with your application.\\n\\n Note that Tauri will look for system-specific binaries following the pattern \\\"binary-name{-target-triple}{.system-extension}\\\".\\n\\n E.g. for the external binary \\\"my-binary\\\", Tauri looks for:\\n\\n - \\\"my-binary-x86_64-pc-windows-msvc.exe\\\" for Windows\\n - \\\"my-binary-x86_64-apple-darwin\\\" for macOS\\n - \\\"my-binary-x86_64-unknown-linux-gnu\\\" for Linux\\n\\n so don't forget to provide binaries for all targeted platforms.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"windows\": {\n          \"description\": \"Configuration for the Windows bundles.\",\n          \"default\": {\n            \"allowDowngrades\": true,\n            \"certificateThumbprint\": null,\n            \"digestAlgorithm\": null,\n            \"nsis\": null,\n            \"signCommand\": null,\n            \"timestampUrl\": null,\n            \"tsp\": false,\n            \"webviewInstallMode\": {\n              \"silent\": true,\n              \"type\": \"downloadBootstrapper\"\n            },\n            \"wix\": null\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowsConfig\"\n            }\n          ]\n        },\n        \"linux\": {\n          \"description\": \"Configuration for the Linux bundles.\",\n          \"default\": {\n            \"appimage\": {\n              \"bundleMediaFramework\": false,\n              \"files\": {}\n            },\n            \"deb\": {\n              \"files\": {}\n            },\n            \"rpm\": {\n              \"epoch\": 0,\n              \"files\": {},\n              \"release\": \"1\"\n            }\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/LinuxConfig\"\n            }\n          ]\n        },\n        \"macOS\": {\n          \"description\": \"Configuration for the macOS bundles.\",\n          \"default\": {\n            \"dmg\": {\n              \"appPosition\": {\n                \"x\": 180,\n                \"y\": 170\n              },\n              \"applicationFolderPosition\": {\n                \"x\": 480,\n                \"y\": 170\n              },\n              \"windowSize\": {\n                \"height\": 400,\n                \"width\": 660\n              }\n            },\n            \"files\": {},\n            \"hardenedRuntime\": true,\n            \"minimumSystemVersion\": \"10.13\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/MacConfig\"\n            }\n          ]\n        },\n        \"iOS\": {\n          \"description\": \"iOS configuration.\",\n          \"default\": {\n            \"minimumSystemVersion\": \"13.0\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/IosConfig\"\n            }\n          ]\n        },\n        \"android\": {\n          \"description\": \"Android configuration.\",\n          \"default\": {\n            \"minSdkVersion\": 24\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/AndroidConfig\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"BundleTarget\": {\n      \"description\": \"Targets to bundle. Each value is case insensitive.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Bundle all targets.\",\n          \"enum\": [\n            \"all\"\n          ]\n        },\n        {\n          \"description\": \"A list of bundle targets.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/BundleType\"\n          }\n        },\n        {\n          \"description\": \"A single bundle target.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleType\"\n            }\n          ]\n        }\n      ]\n    },\n    \"BundleType\": {\n      \"description\": \"A bundle referenced by tauri-bundler.\",\n      \"oneOf\": [\n        {\n          \"description\": \"The debian bundle (.deb).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"deb\"\n          ]\n        },\n        {\n          \"description\": \"The RPM bundle (.rpm).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"rpm\"\n          ]\n        },\n        {\n          \"description\": \"The AppImage bundle (.appimage).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"appimage\"\n          ]\n        },\n        {\n          \"description\": \"The Microsoft Installer bundle (.msi).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"msi\"\n          ]\n        },\n        {\n          \"description\": \"The NSIS bundle (.exe).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"nsis\"\n          ]\n        },\n        {\n          \"description\": \"The macOS application bundle (.app).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"app\"\n          ]\n        },\n        {\n          \"description\": \"The Apple Disk Image bundle (.dmg).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"dmg\"\n          ]\n        }\n      ]\n    },\n    \"Updater\": {\n      \"description\": \"Updater type\",\n      \"anyOf\": [\n        {\n          \"description\": \"Generates lagacy zipped v1 compatible updaters\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/V1Compatible\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Produce updaters and their signatures or not\",\n          \"type\": \"boolean\"\n        }\n      ]\n    },\n    \"V1Compatible\": {\n      \"description\": \"Generates lagacy zipped v1 compatible updaters\",\n      \"oneOf\": [\n        {\n          \"description\": \"Generates lagacy zipped v1 compatible updaters\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"v1Compatible\"\n          ]\n        }\n      ]\n    },\n    \"BundleResources\": {\n      \"description\": \"Definition for bundle resources.\\n Can be either a list of paths to include or a map of source to target paths.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A list of paths to include.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A map of source to target paths.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"FileAssociation\": {\n      \"description\": \"File association\",\n      \"type\": \"object\",\n      \"required\": [\n        \"ext\"\n      ],\n      \"properties\": {\n        \"ext\": {\n          \"description\": \"File extensions to associate with this app. e.g. 'png'\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/AssociationExt\"\n          }\n        },\n        \"name\": {\n          \"description\": \"The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"description\": {\n          \"description\": \"The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"role\": {\n          \"description\": \"The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.\",\n          \"default\": \"Editor\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleTypeRole\"\n            }\n          ]\n        },\n        \"mimeType\": {\n          \"description\": \"The mime-type e.g. 'image/png' or 'text/plain'. Linux-only.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"rank\": {\n          \"description\": \"The ranking of this app among apps that declare themselves as editors or viewers of the given file type.  Maps to `LSHandlerRank` on macOS.\",\n          \"default\": \"Default\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/HandlerRank\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"AssociationExt\": {\n      \"description\": \"An extension for a [`FileAssociation`].\\n\\n A leading `.` is automatically stripped.\",\n      \"type\": \"string\"\n    },\n    \"BundleTypeRole\": {\n      \"description\": \"macOS-only. Corresponds to CFBundleTypeRole\",\n      \"oneOf\": [\n        {\n          \"description\": \"CFBundleTypeRole.Editor. Files can be read and edited.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Editor\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.Viewer. Files can be read.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Viewer\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.Shell\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Shell\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.QLGenerator\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"QLGenerator\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.None\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"None\"\n          ]\n        }\n      ]\n    },\n    \"HandlerRank\": {\n      \"description\": \"Corresponds to LSHandlerRank\",\n      \"oneOf\": [\n        {\n          \"description\": \"LSHandlerRank.Default. This app is an opener of files of this type; this value is also used if no rank is specified.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Default\"\n          ]\n        },\n        {\n          \"description\": \"LSHandlerRank.Owner. This app is the primary creator of files of this type.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Owner\"\n          ]\n        },\n        {\n          \"description\": \"LSHandlerRank.Alternate. This app is a secondary viewer of files of this type.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Alternate\"\n          ]\n        },\n        {\n          \"description\": \"LSHandlerRank.None. This app is never selected to open files of this type, but it accepts drops of files of this type.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"None\"\n          ]\n        }\n      ]\n    },\n    \"WindowsConfig\": {\n      \"description\": \"Windows bundler configuration.\\n\\n See more: <https://tauri.app/v1/api/config#windowsconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"digestAlgorithm\": {\n          \"description\": \"Specifies the file digest algorithm to use for creating file signatures.\\n Required for code signing. SHA-256 is recommended.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"certificateThumbprint\": {\n          \"description\": \"Specifies the SHA1 hash of the signing certificate.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"timestampUrl\": {\n          \"description\": \"Server to use during timestamping.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tsp\": {\n          \"description\": \"Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may\\n use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"webviewInstallMode\": {\n          \"description\": \"The installation mode for the Webview2 runtime.\",\n          \"default\": {\n            \"silent\": true,\n            \"type\": \"downloadBootstrapper\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WebviewInstallMode\"\n            }\n          ]\n        },\n        \"allowDowngrades\": {\n          \"description\": \"Validates a second app installation, blocking the user from installing an older version if set to `false`.\\n\\n For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\\n\\n The default value of this flag is `true`.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"wix\": {\n          \"description\": \"Configuration for the MSI generated with WiX.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WixConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"nsis\": {\n          \"description\": \"Configuration for the installer generated with NSIS.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/NsisConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"signCommand\": {\n          \"description\": \"Specify a custom command to sign the binaries.\\n This command needs to have a `%1` in args which is just a placeholder for the binary path,\\n which we will detect and replace before calling the command.\\n\\n By Default we use `signtool.exe` which can be found only on Windows so\\n if you are on another platform and want to cross-compile and sign you will\\n need to use another tool like `osslsigncode`.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/CustomSignCommandConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WebviewInstallMode\": {\n      \"description\": \"Install modes for the Webview2 runtime.\\n Note that for the updater bundle [`Self::DownloadBootstrapper`] is used.\\n\\n For more information see <https://tauri.app/v1/guides/building/windows>.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Do not install the Webview2 as part of the Windows Installer.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"skip\"\n              ]\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Download the bootstrapper and run it.\\n Requires an internet connection.\\n Results in a smaller installer size, but is not recommended on Windows 7.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"downloadBootstrapper\"\n              ]\n            },\n            \"silent\": {\n              \"description\": \"Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.\",\n              \"default\": true,\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Embed the bootstrapper and run it.\\n Requires an internet connection.\\n Increases the installer size by around 1.8MB, but offers better support on Windows 7.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"embedBootstrapper\"\n              ]\n            },\n            \"silent\": {\n              \"description\": \"Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.\",\n              \"default\": true,\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Embed the offline installer and run it.\\n Does not require an internet connection.\\n Increases the installer size by around 127MB.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"offlineInstaller\"\n              ]\n            },\n            \"silent\": {\n              \"description\": \"Instructs the installer to run the installer in silent mode. Defaults to `true`.\",\n              \"default\": true,\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Embed a fixed webview2 version and use it at runtime.\\n Increases the installer size by around 180MB.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"path\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"fixedRuntime\"\n              ]\n            },\n            \"path\": {\n              \"description\": \"The path to the fixed runtime to use.\\n\\n The fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section).\\n The `.cab` file must be extracted to a folder and this folder path must be defined on this field.\",\n              \"type\": \"string\"\n            }\n          },\n          \"additionalProperties\": false\n        }\n      ]\n    },\n    \"WixConfig\": {\n      \"description\": \"Configuration for the MSI bundle using WiX.\\n\\n See more: <https://tauri.app/v1/api/config#wixconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"language\": {\n          \"description\": \"The installer languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.\",\n          \"default\": \"en-US\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WixLanguage\"\n            }\n          ]\n        },\n        \"template\": {\n          \"description\": \"A custom .wxs template to use.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fragmentPaths\": {\n          \"description\": \"A list of paths to .wxs files with WiX fragments to use.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"componentGroupRefs\": {\n          \"description\": \"The ComponentGroup element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"componentRefs\": {\n          \"description\": \"The Component element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"featureGroupRefs\": {\n          \"description\": \"The FeatureGroup element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"featureRefs\": {\n          \"description\": \"The Feature element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"mergeRefs\": {\n          \"description\": \"The Merge element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"enableElevatedUpdateTask\": {\n          \"description\": \"Create an elevated update task within Windows Task Scheduler.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"bannerPath\": {\n          \"description\": \"Path to a bitmap file to use as the installation user interface banner.\\n This bitmap will appear at the top of all but the first page of the installer.\\n\\n The required dimensions are 493px × 58px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dialogImagePath\": {\n          \"description\": \"Path to a bitmap file to use on the installation user interface dialogs.\\n It is used on the welcome and completion dialogs.\\n The required dimensions are 493px × 312px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fipsCompliant\": {\n          \"description\": \"Enables FIPS compliant algorithms.\",\n          \"default\": null,\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WixLanguage\": {\n      \"description\": \"The languages to build using WiX.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A single language to build, without configuration.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A list of languages to build, without configuration.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A map of languages and its configuration.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/WixLanguageConfig\"\n          }\n        }\n      ]\n    },\n    \"WixLanguageConfig\": {\n      \"description\": \"Configuration for a target language for the WiX build.\\n\\n See more: <https://tauri.app/v1/api/config#wixlanguageconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"localePath\": {\n          \"description\": \"The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NsisConfig\": {\n      \"description\": \"Configuration for the Installer bundle using NSIS.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"template\": {\n          \"description\": \"A custom .nsi template to use.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"headerImage\": {\n          \"description\": \"The path to a bitmap file to display on the header of installers pages.\\n\\n The recommended dimensions are 150px x 57px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"sidebarImage\": {\n          \"description\": \"The path to a bitmap file for the Welcome page and the Finish page.\\n\\n The recommended dimensions are 164px x 314px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installerIcon\": {\n          \"description\": \"The path to an icon file used as the installer icon.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installMode\": {\n          \"description\": \"Whether the installation will be for all users or just the current user.\",\n          \"default\": \"currentUser\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/NSISInstallerMode\"\n            }\n          ]\n        },\n        \"languages\": {\n          \"description\": \"A list of installer languages.\\n By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.\\n To allow the user to select the language, set `display_language_selector` to `true`.\\n\\n See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"customLanguageFiles\": {\n          \"description\": \"A key-value pair where the key is the language and the\\n value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.\\n\\n See <https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-bundler/src/bundle/windows/templates/nsis-languages/English.nsh> for an example `.nsh` file.\\n\\n **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array,\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"displayLanguageSelector\": {\n          \"description\": \"Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.\\n By default the OS language is selected, with a fallback to the first language in the `languages` array.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"compression\": {\n          \"description\": \"Set the compression algorithm used to compress files in the installer.\\n\\n See <https://nsis.sourceforge.io/Reference/SetCompressor>\",\n          \"default\": \"lzma\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/NsisCompression\"\n            }\n          ]\n        },\n        \"startMenuFolder\": {\n          \"description\": \"Set the folder name for the start menu shortcut.\\n\\n Use this option if you have multiple apps and wish to group their shortcuts under one folder\\n or if you generally prefer to set your shortcut inside a folder.\\n\\n Examples:\\n - `AwesomePublisher`, shortcut will be placed in `%AppData%\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\AwesomePublisher\\\\<your-app>.lnk`\\n - If unset, shortcut will be placed in `%AppData%\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\<your-app>.lnk`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installerHooks\": {\n          \"description\": \"A path to a `.nsh` file that contains special NSIS macros to be hooked into the\\n main installer.nsi script.\\n\\n Supported hooks are:\\n - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\\n - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\\n - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\\n - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\\n\\n\\n ### Example\\n\\n ```nsh\\n !macro NSIS_HOOK_PREINSTALL\\n   MessageBox MB_OK \\\"PreInstall\\\"\\n !macroend\\n\\n !macro NSIS_HOOK_POSTINSTALL\\n   MessageBox MB_OK \\\"PostInstall\\\"\\n !macroend\\n\\n !macro NSIS_HOOK_PREUNINSTALL\\n   MessageBox MB_OK \\\"PreUnInstall\\\"\\n !macroend\\n\\n !macro NSIS_HOOK_POSTUNINSTALL\\n   MessageBox MB_OK \\\"PostUninstall\\\"\\n !macroend\\n\\n ```\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimumWebview2Version\": {\n          \"description\": \"Try to ensure that the WebView2 version is equal to or newer than this version,\\n if the user's WebView2 is older than this version,\\n the installer will try to trigger a WebView2 update.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NSISInstallerMode\": {\n      \"description\": \"Install Modes for the NSIS installer.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Default mode for the installer.\\n\\n Install the app by default in a directory that doesn't require Administrator access.\\n\\n Installer metadata will be saved under the `HKCU` registry path.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"currentUser\"\n          ]\n        },\n        {\n          \"description\": \"Install the app by default in the `Program Files` folder directory requires Administrator\\n access for the installation.\\n\\n Installer metadata will be saved under the `HKLM` registry path.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"perMachine\"\n          ]\n        },\n        {\n          \"description\": \"Combines both modes and allows the user to choose at install time\\n whether to install for the current user or per machine. Note that this mode\\n will require Administrator access even if the user wants to install it for the current user only.\\n\\n Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"both\"\n          ]\n        }\n      ]\n    },\n    \"NsisCompression\": {\n      \"description\": \"Compression algorithms used in the NSIS installer.\\n\\n See <https://nsis.sourceforge.io/Reference/SetCompressor>\",\n      \"oneOf\": [\n        {\n          \"description\": \"ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"zlib\"\n          ]\n        },\n        {\n          \"description\": \"BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"bzip2\"\n          ]\n        },\n        {\n          \"description\": \"LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"lzma\"\n          ]\n        },\n        {\n          \"description\": \"Disable compression\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"none\"\n          ]\n        }\n      ]\n    },\n    \"CustomSignCommandConfig\": {\n      \"description\": \"Custom Signing Command configuration.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A string notation of the script to execute.\\n\\n \\\"%1\\\" will be replaced with the path to the binary to be signed.\\n\\n This is a simpler notation for the command.\\n Tauri will split the string with `' '` and use the first element as the command name and the rest as arguments.\\n\\n If you need to use whitespace in the command or arguments, use the object notation [`Self::ScriptWithOptions`].\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An object notation of the command.\\n\\n This is more complex notation for the command but\\n this allows you to use whitespace in the command and arguments.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"args\",\n            \"cmd\"\n          ],\n          \"properties\": {\n            \"cmd\": {\n              \"description\": \"The command to run to sign the binary.\",\n              \"type\": \"string\"\n            },\n            \"args\": {\n              \"description\": \"The arguments to pass to the command.\\n\\n \\\"%1\\\" will be replaced with the path to the binary to be signed.\",\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            }\n          },\n          \"additionalProperties\": false\n        }\n      ]\n    },\n    \"LinuxConfig\": {\n      \"description\": \"Configuration for Linux bundles.\\n\\n See more: <https://tauri.app/v1/api/config#linuxconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"appimage\": {\n          \"description\": \"Configuration for the AppImage bundle.\",\n          \"default\": {\n            \"bundleMediaFramework\": false,\n            \"files\": {}\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/AppImageConfig\"\n            }\n          ]\n        },\n        \"deb\": {\n          \"description\": \"Configuration for the Debian bundle.\",\n          \"default\": {\n            \"files\": {}\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/DebConfig\"\n            }\n          ]\n        },\n        \"rpm\": {\n          \"description\": \"Configuration for the RPM bundle.\",\n          \"default\": {\n            \"epoch\": 0,\n            \"files\": {},\n            \"release\": \"1\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/RpmConfig\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"AppImageConfig\": {\n      \"description\": \"Configuration for AppImage bundles.\\n\\n See more: <https://tauri.app/v1/api/config#appimageconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bundleMediaFramework\": {\n          \"description\": \"Include additional gstreamer dependencies needed for audio and video playback.\\n This increases the bundle size by ~15-35MB depending on your build system.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"files\": {\n          \"description\": \"The files to include in the Appimage Binary.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"DebConfig\": {\n      \"description\": \"Configuration for Debian (.deb) bundles.\\n\\n See more: <https://tauri.app/v1/api/config#debconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"depends\": {\n          \"description\": \"The list of deb dependencies your application relies on.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"provides\": {\n          \"description\": \"The list of dependencies the package provides.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"conflicts\": {\n          \"description\": \"The list of package conflicts.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"replaces\": {\n          \"description\": \"The list of package replaces.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"files\": {\n          \"description\": \"The files to include on the package.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"section\": {\n          \"description\": \"Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"priority\": {\n          \"description\": \"Change the priority of the Debian Package. By default, it is set to `optional`.\\n Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"changelog\": {\n          \"description\": \"Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\\n <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"desktopTemplate\": {\n          \"description\": \"Path to a custom desktop file Handlebars template.\\n\\n Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preInstallScript\": {\n          \"description\": \"Path to script that will be executed before the package is unpacked. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postInstallScript\": {\n          \"description\": \"Path to script that will be executed after the package is unpacked. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preRemoveScript\": {\n          \"description\": \"Path to script that will be executed before the package is removed. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postRemoveScript\": {\n          \"description\": \"Path to script that will be executed after the package is removed. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"RpmConfig\": {\n      \"description\": \"Configuration for RPM bundles.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"depends\": {\n          \"description\": \"The list of RPM dependencies your application relies on.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"provides\": {\n          \"description\": \"The list of RPM dependencies your application provides.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"conflicts\": {\n          \"description\": \"The list of RPM dependencies your application conflicts with. They must not be present\\n in order for the package to be installed.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"obsoletes\": {\n          \"description\": \"The list of RPM dependencies your application supersedes - if this package is installed,\\n packages listed as “obsoletes” will be automatically removed (if they are present).\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"release\": {\n          \"description\": \"The RPM release tag.\",\n          \"default\": \"1\",\n          \"type\": \"string\"\n        },\n        \"epoch\": {\n          \"description\": \"The RPM epoch.\",\n          \"default\": 0,\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"files\": {\n          \"description\": \"The files to include on the package.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"desktopTemplate\": {\n          \"description\": \"Path to a custom desktop file Handlebars template.\\n\\n Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preInstallScript\": {\n          \"description\": \"Path to script that will be executed before the package is unpacked. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postInstallScript\": {\n          \"description\": \"Path to script that will be executed after the package is unpacked. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preRemoveScript\": {\n          \"description\": \"Path to script that will be executed before the package is removed. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postRemoveScript\": {\n          \"description\": \"Path to script that will be executed after the package is removed. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"MacConfig\": {\n      \"description\": \"Configuration for the macOS bundles.\\n\\n See more: <https://tauri.app/v1/api/config#macconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"frameworks\": {\n          \"description\": \"A list of strings indicating any macOS X frameworks that need to be bundled with the application.\\n\\n If a name is used, \\\".framework\\\" must be omitted and it will look for standard install locations. You may also use a path to a specific framework.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"files\": {\n          \"description\": \"The files to include in the application relative to the Contents directory.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"minimumSystemVersion\": {\n          \"description\": \"A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\\n\\n Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`\\n and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\\n\\n An empty string is considered an invalid value so the default value is used.\",\n          \"default\": \"10.13\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"exceptionDomain\": {\n          \"description\": \"Allows your application to communicate with the outside world.\\n It should be a lowercase, without port and protocol domain name.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"signingIdentity\": {\n          \"description\": \"Identity to use for code signing.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"hardenedRuntime\": {\n          \"description\": \"Whether the codesign should enable [hardened runtime] (for executables) or not.\\n\\n [hardened runtime]: <https://developer.apple.com/documentation/security/hardened_runtime>\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"providerShortName\": {\n          \"description\": \"Provider short name for notarization.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"entitlements\": {\n          \"description\": \"Path to the entitlements file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dmg\": {\n          \"description\": \"DMG-specific settings.\",\n          \"default\": {\n            \"appPosition\": {\n              \"x\": 180,\n              \"y\": 170\n            },\n            \"applicationFolderPosition\": {\n              \"x\": 480,\n              \"y\": 170\n            },\n            \"windowSize\": {\n              \"height\": 400,\n              \"width\": 660\n            }\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/DmgConfig\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"DmgConfig\": {\n      \"description\": \"Configuration for Apple Disk Image (.dmg) bundles.\\n\\n See more: <https://tauri.app/v1/api/config#dmgconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"background\": {\n          \"description\": \"Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"windowPosition\": {\n          \"description\": \"Position of volume window on screen.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"windowSize\": {\n          \"description\": \"Size of volume window.\",\n          \"default\": {\n            \"height\": 400,\n            \"width\": 660\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Size\"\n            }\n          ]\n        },\n        \"appPosition\": {\n          \"description\": \"Position of app file on window.\",\n          \"default\": {\n            \"x\": 180,\n            \"y\": 170\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            }\n          ]\n        },\n        \"applicationFolderPosition\": {\n          \"description\": \"Position of application folder on window.\",\n          \"default\": {\n            \"x\": 480,\n            \"y\": 170\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Position\": {\n      \"description\": \"Position coordinates struct.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"x\",\n        \"y\"\n      ],\n      \"properties\": {\n        \"x\": {\n          \"description\": \"X coordinate.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"y\": {\n          \"description\": \"Y coordinate.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Size\": {\n      \"description\": \"Size of the window.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"height\",\n        \"width\"\n      ],\n      \"properties\": {\n        \"width\": {\n          \"description\": \"Width of the window.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"height\": {\n          \"description\": \"Height of the window.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"IosConfig\": {\n      \"description\": \"General configuration for the iOS target.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"template\": {\n          \"description\": \"A custom [XcodeGen] project.yml template to use.\\n\\n [XcodeGen]: <https://github.com/yonaskolb/XcodeGen>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"frameworks\": {\n          \"description\": \"A list of strings indicating any iOS frameworks that need to be bundled with the application.\\n\\n Note that you need to recreate the iOS project for the changes to be applied.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"developmentTeam\": {\n          \"description\": \"The development team. This value is required for iOS development because code signing is enforced.\\n The `APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimumSystemVersion\": {\n          \"description\": \"A version string indicating the minimum iOS version that the bundled application supports. Defaults to `13.0`.\\n\\n Maps to the IPHONEOS_DEPLOYMENT_TARGET value.\",\n          \"default\": \"13.0\",\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"AndroidConfig\": {\n      \"description\": \"General configuration for the iOS target.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"minSdkVersion\": {\n          \"description\": \"The minimum API level required for the application to run.\\n The Android system will prevent the user from installing the application if the system's API level is lower than the value specified.\",\n          \"default\": 24,\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"versionCode\": {\n          \"description\": \"The version code of the application.\\n It is limited to 2,100,000,000 as per Google Play Store requirements.\\n\\n By default we use your configured version and perform the following math:\\n versionCode = version.major * 1000000 + version.minor * 1000 + version.patch\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint32\",\n          \"maximum\": 2100000000.0,\n          \"minimum\": 1.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"PluginConfig\": {\n      \"description\": \"The plugin configs holds a HashMap mapping a plugin name to its configuration object.\\n\\n See more: <https://tauri.app/v1/api/config#pluginconfig>\",\n      \"type\": \"object\",\n      \"additionalProperties\": true\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/tauri.gitignore",
    "content": "node_modules/\ntarget/\n.DS_Store\n"
  },
  {
    "path": "crates/tauri-cli/templates/app/src-tauri/.gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n/gen/schemas\n"
  },
  {
    "path": "crates/tauri-cli/templates/app/src-tauri/Cargo.crate-manifest",
    "content": "[package]\nname = \"app\"\nversion = \"0.1.0\"\ndescription = \"A Tauri App\"\nauthors = [\"you\"]\nlicense = \"\"\nrepository = \"\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[lib]\nname = \"app_lib\"\ncrate-type = [\"staticlib\", \"cdylib\", \"rlib\"]\n\n[build-dependencies]\ntauri-build = {{  tauri_build_dep  }}\n\n[dependencies]\nserde_json = \"1.0\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nlog = \"0.4\"\ntauri = {{  tauri_dep  }}\ntauri-plugin-log = \"2\"\n{{#if patch_tauri_dep}}\n[patch.crates-io]\ntauri = {{ tauri_dep }}\ntauri-utils = {{ tauri_utils_dep }}\ntauri-plugin = {{ tauri_plugin_dep }}\n{{/if}}\n"
  },
  {
    "path": "crates/tauri-cli/templates/app/src-tauri/build.rs",
    "content": "fn main() {\n  tauri_build::build()\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/app/src-tauri/capabilities/default.json",
    "content": "{\n  \"$schema\": \"../gen/schemas/desktop-schema.json\",\n  \"identifier\": \"default\",\n  \"description\": \"enables the default permissions\",\n  \"windows\": [\n    \"main\"\n  ],\n  \"permissions\": [\n    \"core:default\"\n  ]\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/app/src-tauri/src/lib.rs",
    "content": "#[cfg_attr(mobile, tauri::mobile_entry_point)]\npub fn run() {\n  tauri::Builder::default()\n    .setup(|app| {\n      if cfg!(debug_assertions) {\n        app.handle().plugin(\n          tauri_plugin_log::Builder::default()\n            .level(log::LevelFilter::Info)\n            .build(),\n        )?;\n      }\n      Ok(())\n    })\n    .run(tauri::generate_context!())\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/app/src-tauri/src/main.rs",
    "content": "// Prevents additional console window on Windows in release, DO NOT REMOVE!!\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nfn main() {\n  app_lib::run();\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/app/src-tauri/tauri.conf.json",
    "content": "{{ tauri_config }}\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/.editorconfig",
    "content": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = false\ninsert_final_newline = false"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\nbuild\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\nkey.properties\nkeystore.properties\n\n/.tauri\n/tauri.settings.gradle"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/.gitignore",
    "content": "/src/main/**/generated\n/src/main/jniLibs/**/*.so\n/src/main/assets/tauri.conf.json\n/tauri.build.gradle.kts\n/proguard-tauri.pro\n/tauri.properties"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/build.gradle.kts",
    "content": "import java.util.Properties\n\nplugins {\n    id(\"com.android.application\")\n    id(\"org.jetbrains.kotlin.android\")\n    id(\"rust\")\n    {{~#each android-app-plugins}}\n    id(\"{{this}}\"){{/each}}\n}\n\nval tauriProperties = Properties().apply {\n    val propFile = file(\"tauri.properties\")\n    if (propFile.exists()) {\n        propFile.inputStream().use { load(it) }\n    }\n}\n\nandroid {\n    compileSdk = 36\n    namespace = \"{{app.identifier}}\"\n    defaultConfig {\n        manifestPlaceholders[\"usesCleartextTraffic\"] = \"false\"\n        applicationId = \"{{app.identifier}}\"\n        minSdk = {{android.min-sdk-version}}\n        targetSdk = 36\n        versionCode = tauriProperties.getProperty(\"tauri.android.versionCode\", \"1\").toInt()\n        versionName = tauriProperties.getProperty(\"tauri.android.versionName\", \"1.0\")\n    }\n    buildTypes {\n        getByName(\"debug\") {\n            manifestPlaceholders[\"usesCleartextTraffic\"] = \"true\"\n            isDebuggable = true\n            isJniDebuggable = true\n            isMinifyEnabled = false\n            packaging {\n                {{~#each abi-list}}\n                jniLibs.keepDebugSymbols.add(\"*/{{this}}/*.so\")\n                {{/each}}\n            }\n        }\n        getByName(\"release\") {\n            isMinifyEnabled = true\n            proguardFiles(\n                *fileTree(\".\") { include(\"**/*.pro\") }\n                    .plus(getDefaultProguardFile(\"proguard-android-optimize.txt\"))\n                    .toList().toTypedArray()\n            )\n        }\n    }\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n    buildFeatures {\n        buildConfig = true\n    }\n}\n\nrust {\n    rootDirRel = \"{{root-dir-rel}}\"\n}\n\ndependencies {\n    {{~#each android-app-dependencies-platform}}\n    implementation(platform(\"{{this}}\")){{/each}}\n    {{~#each android-app-dependencies}}\n    implementation(\"{{this}}\"){{/each}}\n    implementation(\"androidx.webkit:webkit:1.14.0\")\n    implementation(\"androidx.appcompat:appcompat:1.7.1\")\n    implementation(\"androidx.activity:activity-ktx:1.10.1\")\n    implementation(\"com.google.android.material:material:1.12.0\")\n    testImplementation(\"junit:junit:4.13.2\")\n    androidTestImplementation(\"androidx.test.ext:junit:1.1.4\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.5.0\")\n}\n\napply(from = \"tauri.build.gradle.kts\")"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n\n    <!-- AndroidTV support -->\n    <uses-feature android:name=\"android.software.leanback\" android:required=\"false\" />\n\n    <application\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/Theme.{{snake-case app.name}}\"\n        android:usesCleartextTraffic=\"${usesCleartextTraffic}\">\n        <activity\n            android:configChanges=\"orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode\"\n            android:launchMode=\"singleTask\"\n            android:label=\"@string/main_activity_title\"\n            android:name=\".MainActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n                <!-- AndroidTV support -->\n                <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <provider\n          android:name=\"androidx.core.content.FileProvider\"\n          android:authorities=\"${applicationId}.fileprovider\"\n          android:exported=\"false\"\n          android:grantUriPermissions=\"true\">\n          <meta-data\n            android:name=\"android.support.FILE_PROVIDER_PATHS\"\n            android:resource=\"@xml/file_paths\" />\n        </provider>\n    </application>\n</manifest>\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/src/main/MainActivity.kt",
    "content": "package {{escape-kotlin-keyword app.identifier}}\n\nimport android.os.Bundle\nimport androidx.activity.enableEdgeToEdge\n\nclass MainActivity : TauriActivity() {\n  override fun onCreate(savedInstanceState: Bundle?) {\n    enableEdgeToEdge()\n    super.onCreate(savedInstanceState)\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#3DDC84\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".MainActivity\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Hello World!\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"purple_200\">#FFBB86FC</color>\n    <color name=\"purple_500\">#FF6200EE</color>\n    <color name=\"purple_700\">#FF3700B3</color>\n    <color name=\"teal_200\">#FF03DAC5</color>\n    <color name=\"teal_700\">#FF018786</color>\n    <color name=\"black\">#FF000000</color>\n    <color name=\"white\">#FFFFFFFF</color>\n</resources>"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">{{app.stylized-name}}</string>\n    <string name=\"main_activity_title\">{{app.stylized-name}}</string>\n</resources>"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/src/main/res/values/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.{{snake-case app.name}}\" parent=\"Theme.MaterialComponents.DayNight.NoActionBar\">\n        <!-- Customize your theme here. -->\n    </style>\n</resources>\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/src/main/res/values-night/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.{{snake-case app.name}}\" parent=\"Theme.MaterialComponents.DayNight.NoActionBar\">\n        <!-- Customize your theme here. -->\n    </style>\n</resources>\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/app/src/main/res/xml/file_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <external-path name=\"my_images\" path=\".\" />\n  <cache-path name=\"my_cache_images\" path=\".\" />\n</paths>\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/build.gradle.kts",
    "content": "buildscript {\n    repositories {\n        google()\n        mavenCentral()\n    }\n    dependencies {\n        classpath(\"com.android.tools.build:gradle:8.11.0\")\n        classpath(\"org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25\")\n        {{~#each android-project-dependencies}}\n        classpath(\"{{this}}\"){{/each}}\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\ntasks.register(\"clean\").configure {\n    delete(\"build\")\n}\n\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/buildSrc/build.gradle.kts",
    "content": "plugins {\n    `kotlin-dsl`\n}\n\ngradlePlugin {\n    plugins {\n        create(\"pluginsForCoolKids\") {\n            id = \"rust\"\n            implementationClass = \"RustPlugin\"\n        }\n    }\n}\n\nrepositories {\n    google()\n    mavenCentral()\n}\n\ndependencies {\n    compileOnly(gradleApi())\n    implementation(\"com.android.tools.build:gradle:8.11.0\")\n}\n\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/buildSrc/src/main/kotlin/BuildTask.kt",
    "content": "import java.io.File\nimport org.apache.tools.ant.taskdefs.condition.Os\nimport org.gradle.api.DefaultTask\nimport org.gradle.api.GradleException\nimport org.gradle.api.logging.LogLevel\nimport org.gradle.api.tasks.Input\nimport org.gradle.api.tasks.TaskAction\n\nopen class BuildTask : DefaultTask() {\n    @Input\n    var rootDirRel: String? = null\n    @Input\n    var target: String? = null\n    @Input\n    var release: Boolean? = null\n\n    @TaskAction\n    fun assemble() {\n        val executable = \"\"\"{{tauri-binary}}\"\"\";\n        try {\n            runTauriCli(executable)\n        } catch (e: Exception) {\n            if (Os.isFamily(Os.FAMILY_WINDOWS)) {\n                // Try different Windows-specific extensions\n                val fallbacks = listOf(\n                    \"$executable.exe\",\n                    \"$executable.cmd\",\n                    \"$executable.bat\",\n                )\n                \n                var lastException: Exception = e\n                for (fallback in fallbacks) {\n                    try {\n                        runTauriCli(fallback)\n                        return\n                    } catch (fallbackException: Exception) {\n                        lastException = fallbackException\n                    }\n                }\n                throw lastException\n            } else {\n                throw e;\n            }\n        }\n    }\n\n    fun runTauriCli(executable: String) {\n        val rootDirRel = rootDirRel ?: throw GradleException(\"rootDirRel cannot be null\")\n        val target = target ?: throw GradleException(\"target cannot be null\")\n        val release = release ?: throw GradleException(\"release cannot be null\")\n        val args = listOf({{quote-and-join tauri-binary-args}});\n\n        project.exec {\n            workingDir(File(project.projectDir, rootDirRel))\n            executable(executable)\n            args(args)\n            if (project.logger.isEnabled(LogLevel.DEBUG)) {\n                args(\"-vv\")\n            } else if (project.logger.isEnabled(LogLevel.INFO)) {\n                args(\"-v\")\n            }\n            if (release) {\n                args(\"--release\")\n            }\n            args(listOf(\"--target\", target))\n        }.assertNormalExitValue()\n    }\n}"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/buildSrc/src/main/kotlin/RustPlugin.kt",
    "content": "import com.android.build.api.dsl.ApplicationExtension\nimport org.gradle.api.DefaultTask\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.kotlin.dsl.configure\nimport org.gradle.kotlin.dsl.get\n\nconst val TASK_GROUP = \"rust\"\n\nopen class Config {\n    lateinit var rootDirRel: String\n}\n\nopen class RustPlugin : Plugin<Project> {\n    private lateinit var config: Config\n\n    override fun apply(project: Project) = with(project) {\n        config = extensions.create(\"rust\", Config::class.java)\n\n        val defaultAbiList = listOf({{quote-and-join abi-list}});\n        val abiList = (findProperty(\"abiList\") as? String)?.split(',') ?: defaultAbiList\n\n        val defaultArchList = listOf({{quote-and-join arch-list}});\n        val archList = (findProperty(\"archList\") as? String)?.split(',') ?: defaultArchList\n\n        val targetsList = (findProperty(\"targetList\") as? String)?.split(',') ?: listOf({{quote-and-join target-list}})\n\n        extensions.configure<ApplicationExtension> {\n            @Suppress(\"UnstableApiUsage\")\n            flavorDimensions.add(\"abi\")\n            productFlavors {\n                create(\"universal\") {\n                    dimension = \"abi\"\n                    ndk {\n                        abiFilters += abiList\n                    }\n                }\n                defaultArchList.forEachIndexed { index, arch ->\n                    create(arch) {\n                        dimension = \"abi\"\n                        ndk {\n                            abiFilters.add(defaultAbiList[index])\n                        }\n                    }\n                }\n            }\n        }\n\n        afterEvaluate {\n            for (profile in listOf(\"debug\", \"release\")) {\n                val profileCapitalized = profile.replaceFirstChar { it.uppercase() }\n                val buildTask = tasks.maybeCreate(\n                    \"rustBuildUniversal$profileCapitalized\",\n                    DefaultTask::class.java\n                ).apply {\n                    group = TASK_GROUP\n                    description = \"Build dynamic library in $profile mode for all targets\"\n                }\n\n                tasks[\"mergeUniversal${profileCapitalized}JniLibFolders\"].dependsOn(buildTask)\n\n                for (targetPair in targetsList.withIndex()) {\n                    val targetName = targetPair.value\n                    val targetArch = archList[targetPair.index]\n                    val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() }\n                    val targetBuildTask = project.tasks.maybeCreate(\n                        \"rustBuild$targetArchCapitalized$profileCapitalized\",\n                        BuildTask::class.java\n                    ).apply {\n                        group = TASK_GROUP\n                        description = \"Build dynamic library in $profile mode for $targetArch\"\n                        rootDirRel = config.rootDirRel\n                        target = targetName\n                        release = profile == \"release\"\n                    }\n\n                    buildTask.dependsOn(targetBuildTask)\n                    tasks[\"merge$targetArchCapitalized${profileCapitalized}JniLibFolders\"].dependsOn(\n                        targetBuildTask\n                    )\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Tue May 10 19:22:52 CST 2022\ndistributionBase=GRADLE_USER_HOME\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.3-bin.zip\ndistributionPath=wrapper/dists\nzipStorePath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app\"s APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true\nandroid.nonFinalResIds=false"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/android/settings.gradle",
    "content": "include ':app'\n{{~#each asset-packs}}\ninclude ':{{this}}'{{/each}}\n\napply from: 'tauri.settings.gradle'\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/ios/.gitignore",
    "content": "xcuserdata/\nbuild/\nExternals/\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/ios/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"AppIcon-20x20@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"AppIcon-20x20@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"AppIcon-29x29@2x-1.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"AppIcon-29x29@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"AppIcon-40x40@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"AppIcon-40x40@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"60x60\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"AppIcon-60x60@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"60x60\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"AppIcon-60x60@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"AppIcon-20x20@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"AppIcon-20x20@2x-1.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"AppIcon-29x29@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"AppIcon-29x29@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"AppIcon-40x40@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"AppIcon-40x40@2x-1.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"76x76\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"AppIcon-76x76@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"76x76\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"AppIcon-76x76@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"83.5x83.5\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"AppIcon-83.5x83.5@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"1024x1024\",\n      \"idiom\" : \"ios-marketing\",\n      \"filename\" : \"AppIcon-512@2x.png\",\n      \"scale\" : \"1x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/ios/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/ios/ExportOptions.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>method</key>\n    <string>debugging</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/ios/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"17150\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" useTraitCollections=\"YES\" useSafeAreas=\"YES\" colorMatched=\"YES\" initialViewController=\"Y6W-OH-hqX\">\n    <dependencies>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"17122\"/>\n        <capability name=\"Safe area layout guides\" minToolsVersion=\"9.0\"/>\n        <capability name=\"System colors in document resources\" minToolsVersion=\"11.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"s0d-6b-0kx\">\n            <objects>\n                <viewController id=\"Y6W-OH-hqX\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"5EZ-qb-Rvc\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"414\" height=\"896\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <viewLayoutGuide key=\"safeArea\" id=\"vDu-zF-Fre\"/>\n                        <color key=\"backgroundColor\" systemColor=\"systemBackgroundColor\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"Ief-a0-LHa\" userLabel=\"First Responder\" customClass=\"UIResponder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n        </scene>\n    </scenes>\n    <resources>\n        <systemColor name=\"systemBackgroundColor\">\n            <color white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"genericGamma22GrayColorSpace\"/>\n        </systemColor>\n    </resources>\n</document>\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/ios/Podfile",
    "content": "# Uncomment the next line to define a global platform for your project\n\ntarget '{{app.name}}_iOS' do\nplatform :ios, '{{apple.ios-version}}'\n  # Pods for {{app.name}}_iOS\n  {{~#each ios-pods}}\n  pod '{{this.name}}'{{#if this.version}}, '{{this.version}}'{{/if}}{{/each}}\nend\n\ntarget '{{app.name}}_macOS' do\nplatform :osx, '{{apple.macos-version}}'\n  # Pods for {{app.name}}_macOS\n  {{~#each macos-pods}}\n  pod '{{this.name}}'{{#if this.version}}, '{{this.version}}'{{/if}}{{/each}}\nend\n\n# Delete the deployment target for iOS and macOS, causing it to be inherited from the Podfile\npost_install do |installer|\n installer.pods_project.targets.each do |target|\n  target.build_configurations.each do |config|\n   config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET'\n   config.build_settings.delete 'MACOSX_DEPLOYMENT_TARGET'\n  end\n end\nend\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/ios/Sources/{{app.name}}/bindings/bindings.h",
    "content": "#pragma once\n\nnamespace ffi {\n    extern \"C\" {\n        void start_app();\n    }\n}\n\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/ios/Sources/{{app.name}}/main.mm",
    "content": "#include \"bindings/bindings.h\"\n\nint main(int argc, char * argv[]) {\n\tffi::start_app();\n\treturn 0;\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/ios/project.yml",
    "content": "name: {{app.name}}\noptions:\n  bundleIdPrefix: {{app.identifier}}\n  deploymentTarget:\n    iOS: {{apple.ios-version}}\nfileGroups: [{{join file-groups}}]\nconfigs:\n  debug: debug\n  release: release\nsettingGroups:\n  app:\n    base:\n      PRODUCT_NAME: {{app.stylized-name}}\n      PRODUCT_BUNDLE_IDENTIFIER: {{app.identifier}}\n      {{#if apple.development-team}}\n      DEVELOPMENT_TEAM: {{apple.development-team}}\n      {{/if}}\ntargetTemplates:\n  app:\n    type: application\n    sources:\n      - path: Sources\n    scheme:\n      environmentVariables:\n        RUST_BACKTRACE: full\n        RUST_LOG: info\n    settings:\n      groups: [app]\ntargets:\n  {{app.name}}_iOS:\n    type: application\n    platform: iOS\n    sources:\n      - path: Sources\n      - path: Assets.xcassets\n      - path: Externals\n      - path: {{app.name}}_iOS\n      - path: {{app.asset-dir}}\n        buildPhase: resources\n        type: folder\n      {{~#each asset-catalogs}}\n      - {{prefix-path this}}{{/each}}\n      {{~#each ios-additional-targets}}\n      - path: {{prefix-path this}}{{/each}}\n      - path: LaunchScreen.storyboard\n    info:\n      path: {{app.name}}_iOS/Info.plist\n      properties:\n        LSRequiresIPhoneOS: true\n        UILaunchStoryboardName: LaunchScreen\n        UIRequiredDeviceCapabilities: [arm64, metal]\n        UISupportedInterfaceOrientations:\n          - UIInterfaceOrientationPortrait\n          - UIInterfaceOrientationLandscapeLeft\n          - UIInterfaceOrientationLandscapeRight\n        UISupportedInterfaceOrientations~ipad:\n          - UIInterfaceOrientationPortrait\n          - UIInterfaceOrientationPortraitUpsideDown\n          - UIInterfaceOrientationLandscapeLeft\n          - UIInterfaceOrientationLandscapeRight\n        CFBundleShortVersionString: {{apple.bundle-version-short}}\n        CFBundleVersion: \"{{apple.bundle-version}}\"\n        {{~#each apple.plist-pairs}}\n        {{this.key}}: {{this.value}}{{/each}}\n    entitlements:\n      path: {{app.name}}_iOS/{{app.name}}_iOS.entitlements\n    scheme:\n      environmentVariables:\n        RUST_BACKTRACE: full\n        RUST_LOG: info\n      {{~#if ios-command-line-arguments}}\n      commandLineArguments:\n      {{~#each ios-command-line-arguments}}\n        \"{{this}}\": true\n      {{/each}}{{~/if}}\n    settings:\n      base:\n        ENABLE_BITCODE: false\n        ARCHS: [{{join ios-valid-archs}}]\n        VALID_ARCHS: {{~#each ios-valid-archs}} {{this}} {{/each}}\n        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)\n        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)\n        ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: true\n        EXCLUDED_ARCHS[sdk=iphoneos*]: x86_64\n      groups: [app]\n    dependencies:\n      - framework: {{ lib-output-file-name }}\n        embed: false\n      {{~#each ios-libraries}}\n      - framework: {{this}}\n        embed: false{{/each}}{{#if ios-vendor-frameworks}}{{~#each ios-vendor-frameworks}}\n      - framework: {{this}}{{/each}}{{/if}}{{#if ios-vendor-sdks}}{{~#each ios-vendor-sdks}}\n      - sdk: {{prefix-path this}}{{/each}}{{/if}}\n      - sdk: CoreGraphics.framework\n      - sdk: Metal.framework\n      - sdk: MetalKit.framework\n      - sdk: QuartzCore.framework\n      - sdk: Security.framework\n      - sdk: UIKit.framework{{#if this.ios-frameworks}}{{~#each ios-frameworks}}\n      - sdk: {{this}}.framework{{/each}}{{/if}}\n      - sdk: WebKit.framework\n    preBuildScripts:\n      {{~#each ios-pre-build-scripts}}{{#if this.path}}\n      - path {{this.path}}{{/if}}{{#if this.script}}\n      - script: {{this.script}}{{/if}}{{#if this.name}}\n        name: {{this.name}}{{/if}}{{#if this.input-files}}\n        inputFiles: {{~#each this.input-files}}\n          - {{this}}{{/each}}{{/if}}{{#if this.output-files}}\n        outputFiles: {{~#each this.output-files}}\n          - {{this}}{{/each}}{{/if}}{{#if this.input-file-lists}}\n        inputFileLists: {{~#each this.output-files}}\n          - {{this}}{{/each}}{{/if}}{{#if this.output-file-lists}}\n        outputFileLists: {{~#each this.output-files}}\n          - {{this}}{{/each}}{{/if}}{{#if this.shell}}\n        shell: {{this.shell}}{{/if}}{{#if this.show-env-vars}}\n        showEnvVars: {{this.show_env_vars}}{{/if}}{{#if this.run-only-when-installing}}\n        runOnlyWhenInstalling: {{this.run-only-when-installing}}{{/if}}{{#if this.based-on-dependency-analysis}}\n        basedOnDependencyAnalysis: {{this.based-on-dependency-analysis}}{{/if}}{{#if this.discovered-dependency-file}}\n        discoveredDependencyFile: {{this.discovered-dependency-file}}{{/if}}\n      {{~/each}}\n\n      - script: {{ tauri-binary }} {{ tauri-binary-args-str }} -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:?}\n        name: Build Rust Code\n        basedOnDependencyAnalysis: false\n        outputFiles:\n          - $(SRCROOT)/Externals/x86_64/${CONFIGURATION}/{{ lib-output-file-name }}\n          - $(SRCROOT)/Externals/arm64/${CONFIGURATION}/{{ lib-output-file-name }}\n    {{~#if ios-post-compile-scripts}}\n    postCompileScripts:\n      {{~#each ios-post-compile-scripts}}{{#if this.path}}\n      - path {{this.path}}{{/if}}{{#if this.script}}\n      - script: {{this.script}}{{/if}}{{#if this.name}}\n        name: {{this.name}}{{/if}}{{#if this.input-files}}\n        inputFiles: {{~#each this.input-files}}\n          - {{this}}{{/each}}{{/if}}{{#if this.output-files}}\n        outputFiles: {{~#each this.output-files}}\n          - {{this}}{{/each}}{{/if}}{{#if this.input-file-lists}}\n        inputFileLists: {{~#each this.output-files}}\n          - {{this}}{{/each}}{{/if}}{{#if this.output-file-lists}}\n        outputFileLists: {{~#each this.output-files}}\n          - {{this}}{{/each}}{{/if}}{{#if this.shell}}\n        shell: {{this.shell}}{{/if}}{{#if this.show-env-vars}}\n        showEnvVars: {{this.show_env_vars}}{{/if}}{{#if this.run-only-when-installing}}\n        runOnlyWhenInstalling: {{this.run-only-when-installing}}{{/if}}{{#if this.based-on-dependency-analysis}}\n        basedOnDependencyAnalysis: {{this.based-on-dependency-analysis}}{{/if}}{{#if this.discovered-dependency-file}}\n        discoveredDependencyFile: {{this.discovered-dependency-file}}{{/if}}\n      {{~/each~}}\n    {{~/if~}}\n    {{~#if ios-post-build-scripts}}\n    postBuildScripts:\n      {{~#each ios-post-build-scripts}}{{#if this.path}}\n      - path {{this.path}}{{/if}}{{#if this.script}}\n      - script: {{this.script}}{{/if}}{{#if this.name}}\n        name: {{this.name}}{{/if}}{{#if this.input-files}}\n        inputFiles: {{~#each this.input-files}}\n          - {{this}}{{/each}}{{/if}}{{#if this.output-files}}\n        outputFiles: {{~#each this.output-files}}\n          - {{this}}{{/each}}{{/if}}{{#if this.input-file-lists}}\n        inputFileLists: {{~#each this.output-files}}\n          - {{this}}{{/each}}{{/if}}{{#if this.output-file-lists}}\n        outputFileLists: {{~#each this.output-files}}\n          - {{this}}{{/each}}{{/if}}{{#if this.shell}}\n        shell: {{this.shell}}{{/if}}{{#if this.show-env-vars}}\n        showEnvVars: {{this.show_env_vars}}{{/if}}{{#if this.run-only-when-installing}}\n        runOnlyWhenInstalling: {{this.run-only-when-installing}}{{/if}}{{#if this.based-on-dependency-analysis}}\n        basedOnDependencyAnalysis: {{this.based-on-dependency-analysis}}{{/if}}{{#if this.discovered-dependency-file}}\n        discoveredDependencyFile: {{this.discovered-dependency-file}}{{/if}}\n      {{~/each~}}\n    {{~/if}}\n"
  },
  {
    "path": "crates/tauri-cli/templates/mobile/ios/{{app.name}}.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n{{#if apple.use-legacy-build-system}}\n\t<key>BuildSystemType</key>\n\t<string>Original</string>\n\t<key>DisableBuildSystemDeprecationDiagnostic</key>\n\t<true/>\n{{/if}}\n</dict>\n</plist>\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/.github/workflows/audit.yml",
    "content": "{{{{raw}}}}\nname: Audit\n\non:\n  schedule:\n    - cron: '0 0 * * *'\n  push:\n    branches:\n      - main\n    paths:\n      - \".github/workflows/audit.yml\"\n      - \"**/Cargo.lock\"\n      - \"**/Cargo.toml\"\n  pull_request:\n    branches:\n      - main\n    paths:\n      - \".github/workflows/audit.yml\"\n      - \"**/Cargo.lock\"\n      - \"**/Cargo.toml\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  audit:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: rustsec/audit-check@v1\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n{{{{/raw}}}}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/.github/workflows/clippy.yml",
    "content": "{{{{raw}}}}\nname: Check\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - \".github/workflows/check.yml\"\n      - \"**/*.rs\"\n      - \"**/Cargo.toml\"\n  pull_request:\n    branches:\n      - main\n    paths:\n      - \".github/workflows/check.yml\"\n      - \"**/*.rs\"\n      - \"**/Cargo.toml\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  fmt:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          components: rustfmt\n      - run: cargo fmt --all -- --check\n\n  clippy:\n    strategy:\n      fail-fast: false\n      matrix:\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n\n    runs-on: ${{ matrix.platform }}\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          components: clippy\n      - name: install webkit2gtk\n        if: matrix.platform == 'ubuntu-latest'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y webkit2gtk-4.1\n      - uses: Swatinem/rust-cache@v2\n      - run: cargo clippy --all-targets --all-features -- -D warnings\n{{{{/raw}}}}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/.github/workflows/test.yml",
    "content": "{{{{raw}}}}\nname: Test\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  test:\n    strategy:\n      fail-fast: false\n      matrix:\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n\n    runs-on: ${{ matrix.platform }}\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - name: install webkit2gtk\n        if: matrix.platform == 'ubuntu-latest'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y webkit2gtk-4.1\n      - uses: Swatinem/rust-cache@v2\n      - run: cargo test --all-targets --all-features -- -D warnings\n{{{{/raw}}}}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/.gitignore",
    "content": "/.vs\n.DS_Store\n.Thumbs.db\n*.sublime*\n.idea/\ndebug.log\npackage-lock.json\n.vscode/settings.json\nyarn.lock\n\n/.tauri\n/target\nCargo.lock\nnode_modules/\n\ndist-js\ndist\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/Cargo.crate-manifest",
    "content": "[package]\nname = \"tauri-plugin-{{ plugin_name }}\"\nversion = \"0.1.0\"\nauthors = [ \"{{ author }}\" ]\ndescription = \"\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\nexclude = [\"/examples\", \"/dist-js\", \"/guest-js\", \"/node_modules\"]\nlinks = \"tauri-plugin-{{ plugin_name }}\"\n\n[dependencies]\ntauri = {{  tauri_dep }}\nserde = \"1.0\"\nthiserror = \"2\"\n\n[build-dependencies]\ntauri-plugin = {{{ tauri_plugin_dep }}}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/README.md",
    "content": "# Tauri Plugin {{ plugin_name_original }}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/.vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"svelte.svelte-vscode\",\n    \"tauri-apps.tauri-vscode\",\n    \"rust-lang.rust-analyzer\"\n  ]\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/README.md",
    "content": "# Svelte + Vite\n\nThis template should help get you started developing with Tauri and Svelte in Vite.\n\n## Recommended IDE Setup\n\n[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer).\n\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Tauri + Svelte</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"moduleResolution\": \"bundler\",\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    /**\n     * svelte-preprocess cannot figure out whether you have\n     * a value or a type, so tell TypeScript to enforce using\n     * `import type` instead of `import` for Types.\n     */\n    \"verbatimModuleSyntax\": true,\n    \"isolatedModules\": true,\n    \"resolveJsonModule\": true,\n    /**\n     * To have warnings / errors of the Svelte compiler at the\n     * correct position, enable source maps by default.\n     */\n    \"sourceMap\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"baseUrl\": \".\",\n    /**\n     * Typecheck JS in `.svelte` and `.js` files by default.\n     * Disable this if you'd like to use dynamic types.\n     */\n    \"checkJs\": true\n  },\n  /**\n   * Use global.d.ts instead of compilerOptions.types\n   * to avoid limiting type declarations.\n   */\n  \"include\": [\"src/**/*.d.ts\", \"src/**/*.js\", \"src/**/*.svelte\"]\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/package.json",
    "content": "{\n  \"name\": \"tauri-app\",\n  \"private\": true,\n  \"version\": \"0.1.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"tauri\": \"tauri\"\n  },\n  \"dependencies\": {\n    \"@tauri-apps/api\": \"^2.0.0\",\n    \"tauri-plugin-{{ plugin_name }}-api\": \"file:../../\"\n  },\n  \"devDependencies\": {\n    \"@sveltejs/vite-plugin-svelte\": \"^7.0.0\",\n    \"svelte\": \"^5.0.0\",\n    \"vite\": \"^8.0.0\",\n    \"@tauri-apps/cli\": \"^2.0.0\"\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/src/App.svelte",
    "content": "<script>\n  import Greet from './lib/Greet.svelte'\n  import { ping } from 'tauri-plugin-{{ plugin_name }}-api'\n\n\tlet response = $state('')\n\n\tfunction updateResponse(returnValue) {\n\t\tresponse += `[${new Date().toLocaleTimeString()}] ` + (typeof returnValue === 'string' ? returnValue : JSON.stringify(returnValue)) + '<br>'\n\t}\n\n\tfunction _ping() {\n\t\tping(\"Pong!\").then(updateResponse).catch(updateResponse)\n\t}\n</script>\n\n<main class=\"container\">\n  <h1>Welcome to Tauri!</h1>\n\n  <div class=\"row\">\n    <a href=\"https://vite.dev\" target=\"_blank\">\n      <img src=\"/vite.svg\" class=\"logo vite\" alt=\"Vite Logo\" />\n    </a>\n    <a href=\"https://tauri.app\" target=\"_blank\">\n      <img src=\"/tauri.svg\" class=\"logo tauri\" alt=\"Tauri Logo\" />\n    </a>\n    <a href=\"https://svelte.dev\" target=\"_blank\">\n      <img src=\"/svelte.svg\" class=\"logo svelte\" alt=\"Svelte Logo\" />\n    </a>\n  </div>\n\n  <p>\n    Click on the Tauri, Vite, and Svelte logos to learn more.\n  </p>\n\n  <div class=\"row\">\n    <Greet />\n  </div>\n\n  <div>\n    <button onclick=\"{_ping}\">Ping</button>\n    <div>{@html response}</div>\n  </div>\n\n</main>\n\n<style>\n  .logo.vite:hover {\n    filter: drop-shadow(0 0 2em #747bff);\n  }\n\n  .logo.svelte:hover {\n    filter: drop-shadow(0 0 2em #ff3e00);\n  }\n</style>\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/src/lib/Greet.svelte",
    "content": "<script>\n  import { invoke } from \"@tauri-apps/api/core\"\n\n  let name = $state(\"\");\n  let greetMsg = $state(\"\")\n\n  async function greet(){\n    // Learn more about Tauri commands at https://v2.tauri.app/develop/calling-rust/#commands\n    greetMsg = await invoke(\"greet\", { name })\n  }\n</script>\n\n<div>\n  <div class=\"row\">\n    <input id=\"greet-input\" placeholder=\"Enter a name...\" bind:value={name} />\n    <button onclick={greet}>\n      Greet\n    </button>\n  </div>\n  <p>{greetMsg}</p>\n</div>\n\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/src/main.js",
    "content": "import \"./style.css\";\nimport App from \"./App.svelte\";\nimport { mount } from 'svelte';\n\nconst app = mount(App, {\n  target: document.getElementById(\"app\"),\n});\n\nexport default app;\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/src/style.css",
    "content": ":root {\n  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;\n  font-size: 16px;\n  line-height: 24px;\n  font-weight: 400;\n\n  color: #0f0f0f;\n  background-color: #f6f6f6;\n\n  font-synthesis: none;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-text-size-adjust: 100%;\n}\n\n.container {\n  margin: 0;\n  padding-top: 10vh;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  text-align: center;\n}\n\n.logo {\n  height: 6em;\n  padding: 1.5em;\n  will-change: filter;\n  transition: 0.75s;\n}\n\n.logo.tauri:hover {\n  filter: drop-shadow(0 0 2em #24c8db);\n}\n\n.row {\n  display: flex;\n  justify-content: center;\n}\n\na {\n  font-weight: 500;\n  color: #646cff;\n  text-decoration: inherit;\n}\n\na:hover {\n  color: #535bf2;\n}\n\nh1 {\n  text-align: center;\n}\n\ninput,\nbutton {\n  border-radius: 8px;\n  border: 1px solid transparent;\n  padding: 0.6em 1.2em;\n  font-size: 1em;\n  font-weight: 500;\n  font-family: inherit;\n  color: #0f0f0f;\n  background-color: #ffffff;\n  transition: border-color 0.25s;\n  box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);\n}\n\nbutton {\n  cursor: pointer;\n}\n\nbutton:hover {\n  border-color: #396cd8;\n}\n\ninput,\nbutton {\n  outline: none;\n}\n\n#greet-input {\n  margin-right: 5px;\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    color: #f6f6f6;\n    background-color: #2f2f2f;\n  }\n\n  a:hover {\n    color: #24c8db;\n  }\n\n  input,\n  button {\n    color: #ffffff;\n    background-color: #0f0f0f98;\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/src/vite-env.d.ts",
    "content": "/// <reference types=\"svelte\" />\n/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/src-tauri/.gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/src-tauri/Cargo.crate-manifest",
    "content": "[package]\nname = \"tauri-app\"\nversion = \"0.1.0\"\ndescription = \"A Tauri App\"\nauthors = [\"you\"]\nlicense = \"\"\nrepository = \"\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\n\n[lib]\nname = \"tauri_app_lib\"\ncrate-type = [\"staticlib\", \"cdylib\", \"rlib\"]\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[build-dependencies]\ntauri-build = {{ tauri_build_dep }}\n\n[dependencies]\ntauri = {{ tauri_example_dep }}\ntauri-plugin-{{ plugin_name }} = { path = \"../../../\" }\n\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/src-tauri/build.rs",
    "content": "fn main() {\n  tauri_build::build()\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/src-tauri/capabilities/default.json",
    "content": "{\n  \"$schema\": \"../gen/schemas/desktop-schema.json\",\n  \"identifier\": \"default\",\n  \"description\": \"enables the default permissions\",\n  \"windows\": [\n    \"main\"\n  ],\n  \"permissions\": [\n    \"core:default\",\n    \"{{ plugin_name }}:default\"\n  ]\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/src-tauri/src/lib.rs",
    "content": "// Learn more about Tauri commands at https://v2.tauri.app/develop/calling-rust/#commands\n#[tauri::command]\nfn greet(name: &str) -> String {\n    format!(\"Hello, {}! You've been greeted from Rust!\", name)\n}\n\n#[cfg_attr(mobile, tauri::mobile_entry_point)]\npub fn run() {\n    tauri::Builder::default()\n        .invoke_handler(tauri::generate_handler![greet])\n        .plugin(tauri_plugin_{{ plugin_name_snake_case }}::init())\n        .run(tauri::generate_context!())\n        .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/src-tauri/src/main.rs",
    "content": "// Prevents additional console window on Windows in release, DO NOT REMOVE!!\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nfn main() {\n  tauri_app_lib::run();\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/src-tauri/tauri.conf.json",
    "content": "{\n  \"productName\": \"tauri-app\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"beforeDevCommand\": \"pnpm dev\",\n    \"beforeBuildCommand\": \"pnpm build\",\n    \"devUrl\": \"http://localhost:1420\",\n    \"frontendDist\": \"../dist\"\n  },\n  \"app\": {\n    \"withGlobalTauri\": false,\n    \"security\": {\n      \"csp\": null\n    },\n    \"windows\": [\n      {\n        \"fullscreen\": false,\n        \"height\": 600,\n        \"resizable\": true,\n        \"title\": \"tauri-app\",\n        \"width\": 800\n      }\n    ]\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"icons/32x32.png\",\n      \"icons/128x128.png\",\n      \"icons/128x128@2x.png\",\n      \"icons/icon.icns\",\n      \"icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-api/tauri-app/vite.config.js",
    "content": "import { defineConfig } from \"vite\";\nimport { svelte } from \"@sveltejs/vite-plugin-svelte\";\n\nconst host = process.env.TAURI_DEV_HOST;\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [svelte()],\n\n  // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`\n  // prevent Vite from obscuring rust errors\n  clearScreen: false,\n  // tauri expects a fixed port, fail if that port is not available\n  server: {\n    host: host || false,\n    port: 1420,\n    strictPort: true,\n    hmr: host ? {\n      protocol: 'ws',\n      host,\n      port: 1421\n    } : undefined,\n  },\n})\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-basic/vanilla/.gitignore",
    "content": "node_modules/\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-basic/vanilla/package.json",
    "content": "{\n  \"name\": \"app\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@tauri-apps/cli\": \"^2.0.0-alpha.17\"\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-basic/vanilla/public/index.html",
    "content": "<html>\n  <body>\n    <div>Plugin example</div>\n  </body>\n</html>\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-basic/vanilla/src-tauri/.gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-basic/vanilla/src-tauri/Cargo.crate-manifest",
    "content": "[package]\nname = \"tauri-app\"\nversion = \"0.1.0\"\ndescription = \"A Tauri App\"\nauthors = [\"you\"]\nlicense = \"\"\nrepository = \"\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\n\n[lib]\nname = \"tauri_app_lib\"\ncrate-type = [\"staticlib\", \"cdylib\", \"rlib\"]\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[build-dependencies]\ntauri-build = {{ tauri_build_dep }}\n\n[dependencies]\ntauri = {{ tauri_example_dep }}\ntauri-plugin-{{ plugin_name }} = { path = \"../../../\" }\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-basic/vanilla/src-tauri/build.rs",
    "content": "fn main() {\n  tauri_build::build()\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-basic/vanilla/src-tauri/capabilities/default.json",
    "content": "{\n  \"$schema\": \"../gen/schemas/desktop-schema.json\",\n  \"identifier\": \"default\",\n  \"description\": \"enables the default permissions\",\n  \"windows\": [\n    \"main\"\n  ],\n  \"permissions\": [\n    \"core:default\",\n    \"{{ plugin_name }}:default\"\n  ]\n}"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-basic/vanilla/src-tauri/rustfmt.toml",
    "content": "max_width = 100\nhard_tabs = false\ntab_spaces = 2\nnewline_style = \"Auto\"\nuse_small_heuristics = \"Default\"\nreorder_imports = true\nreorder_modules = true\nremove_nested_parens = true\nedition = \"2021\"\nmerge_derives = true\nuse_try_shorthand = false\nuse_field_init_shorthand = false\nforce_explicit_abi = true\nimports_granularity = \"Crate\"\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-basic/vanilla/src-tauri/src/lib.rs",
    "content": "#[cfg_attr(mobile, tauri::mobile_entry_point)]\npub fn run() {\n  tauri::Builder::default()\n    .plugin(tauri_plugin_{{ plugin_name_snake_case }}::init())\n    .run(tauri::generate_context!())\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-basic/vanilla/src-tauri/src/main.rs",
    "content": "// Prevents additional console window on Windows in release, DO NOT REMOVE!!\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nfn main() {\n  tauri_app_lib::run();\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/__example-basic/vanilla/src-tauri/tauri.conf.json",
    "content": "{\n  \"productName\": \"app\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.{{ plugin_name }}\",\n  \"build\": {\n    \"frontendDist\": \"../public\"\n  },\n  \"app\": {\n    \"windows\": [\n      {\n        \"title\": \"app\",\n        \"width\": 800,\n        \"height\": 600,\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'; connect-src ipc: http://ipc.localhost\"\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"icons/32x32.png\",\n      \"icons/128x128.png\",\n      \"icons/128x128@2x.png\",\n      \"icons/icon.icns\",\n      \"icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/android/.gitignore",
    "content": "/build\n/.tauri\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/android/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n    id(\"org.jetbrains.kotlin.android\")\n}\n\nandroid {\n    namespace = \"{{android_package_id}}\"\n    compileSdk = 36\n\n    defaultConfig {\n        minSdk = 21\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles(\"consumer-rules.pro\")\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = false\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n}\n\ndependencies {\n\n    implementation(\"androidx.core:core-ktx:1.9.0\")\n    implementation(\"androidx.appcompat:appcompat:1.6.0\")\n    implementation(\"com.google.android.material:material:1.7.0\")\n    testImplementation(\"junit:junit:4.13.2\")\n    androidTestImplementation(\"androidx.test.ext:junit:1.1.5\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.5.1\")\n    implementation(project(\":tauri-android\"))\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/android/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/android/settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        mavenCentral()\n        gradlePluginPortal()\n        google()\n    }\n    resolutionStrategy {\n        eachPlugin {\n            switch (requested.id.id) {\n                case \"com.android.library\":\n                    useVersion(\"8.0.2\")\n                    break\n                case \"org.jetbrains.kotlin.android\":\n                    useVersion(\"1.8.20\")\n                    break\n            }\n        }\n    }\n}\n\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        mavenCentral()\n        google()\n\n    }\n}\n\ninclude ':tauri-android'\nproject(':tauri-android').projectDir = new File('./.tauri/tauri-api')\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/android/src/androidTest/java/ExampleInstrumentedTest.kt",
    "content": "package {{android_package_id}}\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.runners.AndroidJUnit4\n\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\nimport org.junit.Assert.*\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"{{android_package_id}}\", appContext.packageName)\n    }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/android/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n</manifest>\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/android/src/main/java/Example.kt",
    "content": "package {{android_package_id}}\n\nimport android.util.Log\n\nclass Example {\n    fun pong(value: String): String {\n        Log.i(\"Pong\", value)\n        return value\n    }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/android/src/main/java/ExamplePlugin.kt",
    "content": "package {{android_package_id}}\n\nimport android.app.Activity\nimport app.tauri.annotation.Command\nimport app.tauri.annotation.InvokeArg\nimport app.tauri.annotation.TauriPlugin\nimport app.tauri.plugin.JSObject\nimport app.tauri.plugin.Plugin\nimport app.tauri.plugin.Invoke\n\n@InvokeArg\nclass PingArgs {\n  var value: String? = null\n}\n\n@TauriPlugin\nclass ExamplePlugin(private val activity: Activity): Plugin(activity) {\n    private val implementation = Example()\n\n    @Command\n    fun ping(invoke: Invoke) {\n        val args = invoke.parseArgs(PingArgs::class.java)\n\n        val ret = JSObject()\n        ret.put(\"value\", implementation.pong(args.value ?: \"default value :(\"))\n        invoke.resolve(ret)\n    }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/android/src/test/java/ExampleUnitTest.kt",
    "content": "package {{android_package_id}}\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/build.rs",
    "content": "const COMMANDS: &[&str] = &[\"ping\"];\n\nfn main() {\n  tauri_plugin::Builder::new(COMMANDS)\n    .android_path(\"android\")\n    .ios_path(\"ios\")\n    .build();\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/guest-js/index.ts",
    "content": "{{#if license_header}}\n{{ license_header }}\n{{/if}}\nimport { invoke } from '@tauri-apps/api/core'\n\nexport async function ping(value: string): Promise<string | null> {\n  return await invoke<{value?: string}>('plugin:{{ plugin_name }}|ping', {\n    payload: {\n      value,\n    },\n  }).then((r) => (r.value ? r.value : null));\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/ios-spm/.gitignore",
    "content": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\nDerivedData/\n.swiftpm/config/registries.json\n.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata\n.netrc\nPackage.resolved\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/ios-spm/Package.swift",
    "content": "// swift-tools-version:5.3\n// The swift-tools-version declares the minimum version of Swift required to build this package.\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"tauri-plugin-{{ plugin_name }}\",\n    platforms: [\n        .macOS(.v10_13),\n        .iOS(.v13),\n    ],\n    products: [\n        // Products define the executables and libraries a package produces, and make them visible to other packages.\n        .library(\n            name: \"tauri-plugin-{{ plugin_name }}\",\n            type: .static,\n            targets: [\"tauri-plugin-{{ plugin_name }}\"]),\n    ],\n    dependencies: [\n        .package(name: \"Tauri\", path: \"../.tauri/tauri-api\")\n    ],\n    targets: [\n        // Targets are the basic building blocks of a package. A target can define a module or a test suite.\n        // Targets can depend on other targets in this package, and on products in packages this package depends on.\n        .target(\n            name: \"tauri-plugin-{{ plugin_name }}\",\n            dependencies: [\n                .byName(name: \"Tauri\")\n            ],\n            path: \"Sources\")\n    ]\n)\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/ios-spm/README.md",
    "content": "# Tauri Plugin {{ plugin_name_original }}\n\nA description of this package.\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/ios-spm/Sources/ExamplePlugin.swift",
    "content": "import SwiftRs\nimport Tauri\nimport UIKit\nimport WebKit\n\nclass PingArgs: Decodable {\n  let value: String?\n}\n\nclass ExamplePlugin: Plugin {\n  @objc public func ping(_ invoke: Invoke) throws {\n    let args = try invoke.parseArgs(PingArgs.self)\n    invoke.resolve([\"value\": args.value ?? \"\"])\n  }\n}\n\n@_cdecl(\"init_plugin_{{ plugin_name_snake_case }}\")\nfunc initPlugin() -> Plugin {\n  return ExamplePlugin()\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/ios-spm/Tests/PluginTests/PluginTests.swift",
    "content": "import XCTest\n@testable import ExamplePlugin\n\nfinal class ExamplePluginTests: XCTestCase {\n    func testExample() throws {\n        let plugin = ExamplePlugin()\n    }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/ios-xcode/tauri-plugin-{{ plugin_name }}/ExamplePlugin.swift",
    "content": "import SwiftRs\nimport Tauri\nimport UIKit\nimport WebKit\n\nclass PingArgs: Decodable {\n  let value: String?\n}\n\nclass ExamplePlugin: Plugin {\n  @objc public func ping(_ invoke: Invoke) throws {\n    let args = try invoke.parseArgs(PingArgs.self)\n    invoke.resolve([\"value\": args.value ?? \"\"])\n  }\n}\n\n@_cdecl(\"init_plugin_{{ plugin_name_snake_case }}\")\nfunc initPlugin() -> Plugin {\n  return ExamplePlugin()\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/ios-xcode/tauri-plugin-{{ plugin_name }}.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 60;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\tA0E2115A2BF552D2003BCF4D /* ExamplePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0E211592BF552D2003BCF4D /* ExamplePlugin.swift */; };\n\t\tA0E211622BF55305003BCF4D /* Tauri in Frameworks */ = {isa = PBXBuildFile; productRef = A0E211612BF55305003BCF4D /* Tauri */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\tA0E211542BF552D2003BCF4D /* CopyFiles */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"include/$(PRODUCT_NAME)\";\n\t\t\tdstSubfolderSpec = 16;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\tA0E211562BF552D2003BCF4D /* libtauri-plugin-{{ plugin_name }}.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = \"libtauri-plugin-{{ plugin_name }}.a\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tA0E211592BF552D2003BCF4D /* ExamplePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplePlugin.swift; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\tA0E211532BF552D2003BCF4D /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tA0E211622BF55305003BCF4D /* Tauri in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\tA0E2114D2BF552D2003BCF4D = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tA0E211582BF552D2003BCF4D /* tauri-plugin-{{ plugin_name }} */,\n\t\t\t\tA0E211572BF552D2003BCF4D /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tA0E211572BF552D2003BCF4D /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tA0E211562BF552D2003BCF4D /* libtauri-plugin-{{ plugin_name }}.a */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tA0E211582BF552D2003BCF4D /* tauri-plugin-{{ plugin_name }} */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tA0E211592BF552D2003BCF4D /* ExamplePlugin.swift */,\n\t\t\t);\n\t\t\tpath = \"tauri-plugin-{{ plugin_name }}\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\tA0E211552BF552D2003BCF4D /* tauri-plugin-{{ plugin_name }} */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = A0E2115D2BF552D2003BCF4D /* Build configuration list for PBXNativeTarget \"tauri-plugin-{{ plugin_name }}\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tA0E211522BF552D2003BCF4D /* Sources */,\n\t\t\t\tA0E211532BF552D2003BCF4D /* Frameworks */,\n\t\t\t\tA0E211542BF552D2003BCF4D /* CopyFiles */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = \"tauri-plugin-{{ plugin_name }}\";\n\t\t\tpackageProductDependencies = (\n\t\t\t\tA0E211612BF55305003BCF4D /* Tauri */,\n\t\t\t);\n\t\t\tproductName = \"tauri-plugin-{{ plugin_name }}\";\n\t\t\tproductReference = A0E211562BF552D2003BCF4D /* libtauri-plugin-{{ plugin_name }}.a */;\n\t\t\tproductType = \"com.apple.product-type.library.static\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\tA0E2114E2BF552D2003BCF4D /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1540;\n\t\t\t\tLastUpgradeCheck = 1540;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\tA0E211552BF552D2003BCF4D = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 15.4;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = A0E211512BF552D2003BCF4D /* Build configuration list for PBXProject \"tauri-plugin-{{ plugin_name }}\" */;\n\t\t\tcompatibilityVersion = \"Xcode 14.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = A0E2114D2BF552D2003BCF4D;\n\t\t\tpackageReferences = (\n\t\t\t\tA0E211602BF55305003BCF4D /* XCLocalSwiftPackageReference \"../.tauri/tauri-api\" */,\n\t\t\t);\n\t\t\tproductRefGroup = A0E211572BF552D2003BCF4D /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\tA0E211552BF552D2003BCF4D /* tauri-plugin-{{ plugin_name }} */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\tA0E211522BF552D2003BCF4D /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tA0E2115A2BF552D2003BCF4D /* ExamplePlugin.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\tA0E2115B2BF552D2003BCF4D /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"DEBUG $(inherited)\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tA0E2115C2BF552D2003BCF4D /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tA0E2115E2BF552D2003BCF4D /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tOTHER_LDFLAGS = \"-ObjC\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tA0E2115F2BF552D2003BCF4D /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tOTHER_LDFLAGS = \"-ObjC\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\tA0E211512BF552D2003BCF4D /* Build configuration list for PBXProject \"tauri-plugin-{{ plugin_name }}\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tA0E2115B2BF552D2003BCF4D /* Debug */,\n\t\t\t\tA0E2115C2BF552D2003BCF4D /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tA0E2115D2BF552D2003BCF4D /* Build configuration list for PBXNativeTarget \"tauri-plugin-{{ plugin_name }}\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tA0E2115E2BF552D2003BCF4D /* Debug */,\n\t\t\t\tA0E2115F2BF552D2003BCF4D /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCLocalSwiftPackageReference section */\n\t\tA0E211602BF55305003BCF4D /* XCLocalSwiftPackageReference \"../.tauri/tauri-api\" */ = {\n\t\t\tisa = XCLocalSwiftPackageReference;\n\t\t\trelativePath = \"../.tauri/tauri-api\";\n\t\t};\n/* End XCLocalSwiftPackageReference section */\n\n/* Begin XCSwiftPackageProductDependency section */\n\t\tA0E211612BF55305003BCF4D /* Tauri */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = Tauri;\n\t\t};\n/* End XCSwiftPackageProductDependency section */\n\t};\n\trootObject = A0E2114E2BF552D2003BCF4D /* Project object */;\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/ios-xcode/tauri-plugin-{{ plugin_name }}.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/ios-xcode/tauri-plugin-{{ plugin_name }}.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/package.json",
    "content": "{\n  \"name\": \"tauri-plugin-{{ plugin_name }}-api\",\n  \"version\": \"0.1.0\",\n  \"author\": \"{{ author }}\",\n  \"description\": \"\",\n  \"type\": \"module\",\n  \"types\": \"./dist-js/index.d.ts\",\n  \"main\": \"./dist-js/index.cjs\",\n  \"module\": \"./dist-js/index.js\",\n  \"exports\": {\n    \"types\": \"./dist-js/index.d.ts\",\n    \"import\": \"./dist-js/index.js\",\n    \"require\": \"./dist-js/index.cjs\"\n  },\n  \"files\": [\n    \"dist-js\",\n    \"README.md\"\n  ],\n  \"scripts\": {\n    \"build\": \"rollup -c\",\n    \"prepublishOnly\": \"pnpm build\",\n    \"pretest\": \"pnpm build\"\n  },\n  \"dependencies\": {\n    \"@tauri-apps/api\": \"^2.0.0\"\n  },\n  \"devDependencies\": {\n    \"@rollup/plugin-typescript\": \"^12.0.0\",\n    \"rollup\": \"^4.9.6\",\n    \"typescript\": \"^5.3.3\",\n    \"tslib\": \"^2.6.2\"\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/rollup.config.js",
    "content": "import { readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { cwd } from 'node:process'\nimport typescript from '@rollup/plugin-typescript'\n\nconst pkg = JSON.parse(readFileSync(join(cwd(), 'package.json'), 'utf8'))\n\nexport default {\n  input: 'guest-js/index.ts',\n  output: [\n    {\n      file: pkg.exports.import,\n      format: 'esm'\n    },\n    {\n      file: pkg.exports.require,\n      format: 'cjs'\n    }\n  ],\n  plugins: [\n    typescript({\n      declaration: true,\n      declarationDir: dirname(pkg.exports.import)\n    })\n  ],\n  external: [\n    /^@tauri-apps\\/api/,\n    ...Object.keys(pkg.dependencies || {}),\n    ...Object.keys(pkg.peerDependencies || {})\n  ]\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/src/commands.rs",
    "content": "{{#if license_header}}\n{{ license_header }}\n{{/if}}\nuse tauri::{AppHandle, command, Runtime};\n\nuse crate::models::*;\nuse crate::Result;\nuse crate::{{ plugin_name_pascal_case }}Ext;\n\n#[command]\npub(crate) async fn ping<R: Runtime>(\n    app: AppHandle<R>,\n    payload: PingRequest,\n) -> Result<PingResponse> {\n    app.{{ plugin_name_snake_case }}().ping(payload)\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/src/desktop.rs",
    "content": "{{#if license_header}}\n{{ license_header }}\n{{/if}}\nuse serde::de::DeserializeOwned;\nuse tauri::{plugin::PluginApi, AppHandle, Runtime};\n\nuse crate::models::*;\n\npub fn init<R: Runtime, C: DeserializeOwned>(\n  app: &AppHandle<R>,\n  _api: PluginApi<R, C>,\n) -> crate::Result<{{ plugin_name_pascal_case }}<R>> {\n  Ok({{ plugin_name_pascal_case }}(app.clone()))\n}\n\n/// Access to the {{ plugin_name }} APIs.\npub struct {{ plugin_name_pascal_case }}<R: Runtime>(AppHandle<R>);\n\nimpl<R: Runtime> {{ plugin_name_pascal_case }}<R> {\n  pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {\n    Ok(PingResponse {\n      value: payload.value,\n    })\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/src/error.rs",
    "content": "{{#if license_header}}\n{{ license_header }}\n{{/if}}\nuse serde::{ser::Serializer, Serialize};\n\npub type Result<T> = std::result::Result<T, Error>;\n\n#[derive(Debug, thiserror::Error)]\npub enum Error {\n  #[error(transparent)]\n  Io(#[from] std::io::Error),\n  #[cfg(mobile)]\n  #[error(transparent)]\n  PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),\n}\n\nimpl Serialize for Error {\n  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    serializer.serialize_str(self.to_string().as_ref())\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/src/lib.rs",
    "content": "{{#if license_header}}\n{{ license_header }}\n{{/if}}\nuse tauri::{\n  plugin::{Builder, TauriPlugin},\n  Manager, Runtime,\n};\n\npub use models::*;\n\n#[cfg(desktop)]\nmod desktop;\n#[cfg(mobile)]\nmod mobile;\n\nmod commands;\nmod error;\nmod models;\n\npub use error::{Error, Result};\n\n#[cfg(desktop)]\nuse desktop::{{ plugin_name_pascal_case }};\n#[cfg(mobile)]\nuse mobile::{{ plugin_name_pascal_case }};\n\n/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the {{ plugin_name }} APIs.\npub trait {{ plugin_name_pascal_case }}Ext<R: Runtime> {\n  fn {{ plugin_name_snake_case }}(&self) -> &{{ plugin_name_pascal_case }}<R>;\n}\n\nimpl<R: Runtime, T: Manager<R>> crate::{{ plugin_name_pascal_case }}Ext<R> for T {\n  fn {{ plugin_name_snake_case }}(&self) -> &{{ plugin_name_pascal_case }}<R> {\n    self.state::<{{ plugin_name_pascal_case }}<R>>().inner()\n  }\n}\n\n/// Initializes the plugin.\npub fn init<R: Runtime>() -> TauriPlugin<R> {\n  Builder::new(\"{{ plugin_name }}\")\n    .invoke_handler(tauri::generate_handler![commands::ping])\n    .setup(|app, api| {\n      #[cfg(mobile)]\n      let {{ plugin_name_snake_case }} = mobile::init(app, api)?;\n      #[cfg(desktop)]\n      let {{ plugin_name_snake_case }} = desktop::init(app, api)?;\n      app.manage({{ plugin_name_snake_case }});\n      Ok(())\n    })\n    .build()\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/src/mobile.rs",
    "content": "{{#if license_header}}\n{{ license_header }}\n{{/if}}\nuse serde::de::DeserializeOwned;\nuse tauri::{\n  plugin::{PluginApi, PluginHandle},\n  AppHandle, Runtime,\n};\n\nuse crate::models::*;\n\n#[cfg(target_os = \"ios\")]\ntauri::ios_plugin_binding!(init_plugin_{{ plugin_name_snake_case }});\n\n// initializes the Kotlin or Swift plugin classes\npub fn init<R: Runtime, C: DeserializeOwned>(\n  _app: &AppHandle<R>,\n  api: PluginApi<R, C>,\n) -> crate::Result<{{ plugin_name_pascal_case }}<R>> {\n  #[cfg(target_os = \"android\")]\n  let handle = api.register_android_plugin(\"{{ android_package_id }}\", \"ExamplePlugin\")?;\n  #[cfg(target_os = \"ios\")]\n  let handle = api.register_ios_plugin(init_plugin_{{ plugin_name_snake_case }})?;\n  Ok({{ plugin_name_pascal_case }}(handle))\n}\n\n/// Access to the {{ plugin_name }} APIs.\npub struct {{ plugin_name_pascal_case }}<R: Runtime>(PluginHandle<R>);\n\nimpl<R: Runtime> {{ plugin_name_pascal_case }}<R> {\n  pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {\n    self\n      .0\n      .run_mobile_plugin(\"ping\", payload)\n      .map_err(Into::into)\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/src/models.rs",
    "content": "{{#if license_header}}\n{{ license_header }}\n{{/if}}\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct PingRequest {\n  pub value: Option<String>,\n}\n\n#[derive(Debug, Clone, Default, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct PingResponse {\n  pub value: Option<String>,\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/plugin/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2021\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noImplicitAny\": true,\n    \"noEmit\": true\n  },\n  \"include\": [\"guest-js/*.ts\"],\n  \"exclude\": [\"dist-js\", \"node_modules\"]\n}\n"
  },
  {
    "path": "crates/tauri-cli/templates/tauri.conf.json",
    "content": "{\n  \"$schema\": \"https://schema.tauri.app/config/2\",\n  \"productName\": \"{{ app_name }}\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"frontendDist\": \"{{ frontend_dist }}\"{{#if dev_url}},\n    \"devUrl\": \"{{ dev_url }}\"{{/if}}{{#if before_dev_command}},\n    \"beforeDevCommand\": \"{{ before_dev_command }}\"{{/if}}{{#if before_build_command}},\n    \"beforeBuildCommand\": \"{{ before_build_command }}\"{{/if}}\n  },\n  \"app\": {\n    \"windows\": [\n      {\n        \"title\": \"{{ window_title }}\",\n        \"width\": 800,\n        \"height\": 600,\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ],\n    \"security\": {\n      \"csp\": null\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"icons/32x32.png\",\n      \"icons/128x128.png\",\n      \"icons/128x128@2x.png\",\n      \"icons/icon.icns\",\n      \"icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "crates/tauri-cli/tests/fixtures/pbxproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t2B78BA327A38C76093D36092 /* libapi_lib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A00E5F95D64FD14E47F85BD /* libapi_lib.a */; };\n\t\t3043432501C9BC2DB6B4CB95 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71EB788DE4662CFC0D97F567 /* CoreGraphics.framework */; };\n\t\t328B4ADB3700C1873BEB7B10 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 90D3B673AFAB8D8AB561F616 /* main.mm */; };\n\t\t6F379F15DA085785BA2624D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B7E79E23E646BA7968B457C /* Assets.xcassets */; };\n\t\t9AADB041D25772D04E543F15 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62601E25FA39E62BE119B74D /* Metal.framework */; };\n\t\t9DDA3BE70DD0E4013973FE38 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6082E363D51372A7658C351 /* UIKit.framework */; };\n\t\tAC8BDC2C7A63FA3FDC5967F4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B2D1B108AE002010BDEC6D2 /* LaunchScreen.storyboard */; };\n\t\tAFA0CA286325FD7A34968CA2 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384966E551417F94A02D2706 /* Security.framework */; };\n\t\tB60763BD194DFACA215EC7DA /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC377692DC31A070A0188C9D /* QuartzCore.framework */; };\n\t\tC6D80743F168BDF017B7769E /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59CFE20DCF760BE67D9CE3D6 /* WebKit.framework */; };\n\t\tDFFF888045C8D9D9FB69E8FD /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 338E66700FD330B99D434DD7 /* MetalKit.framework */; };\n\t\tF86717F05E27C72C9FA1FB27 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 74A8FDFB350B966F5AAD4A24 /* assets */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t0E96CE07CD20273DD46BF325 /* main.rs */ = {isa = PBXFileReference; path = main.rs; sourceTree = \"<group>\"; };\n\t\t1C1AB1B414CA2795AFBEDDB9 /* tray.rs */ = {isa = PBXFileReference; path = tray.rs; sourceTree = \"<group>\"; };\n\t\t2F63E2AA460089BB58D40C79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t338E66700FD330B99D434DD7 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; };\n\t\t384966E551417F94A02D2706 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };\n\t\t3CA88F22095BE63D88585625 /* menu_plugin.rs */ = {isa = PBXFileReference; path = menu_plugin.rs; sourceTree = \"<group>\"; };\n\t\t4A00E5F95D64FD14E47F85BD /* libapi_lib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapi_lib.a; sourceTree = \"<group>\"; };\n\t\t4B2D1B108AE002010BDEC6D2 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\t59CFE20DCF760BE67D9CE3D6 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };\n\t\t5AC703CEBA41A121596066F3 /* api_iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = api_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t62601E25FA39E62BE119B74D /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };\n\t\t6B7E79E23E646BA7968B457C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t71EB788DE4662CFC0D97F567 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };\n\t\t74A8FDFB350B966F5AAD4A24 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; };\n\t\t785D025E9542F7E098BF22B5 /* lib.rs */ = {isa = PBXFileReference; path = lib.rs; sourceTree = \"<group>\"; };\n\t\t879941AE3DAA14534BBC6391 /* api_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = api_iOS.entitlements; sourceTree = \"<group>\"; };\n\t\t90D3B673AFAB8D8AB561F616 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = \"<group>\"; };\n\t\tB6082E363D51372A7658C351 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };\n\t\tDC377692DC31A070A0188C9D /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };\n\t\tEC8C7948C50C3C9B5D96CB61 /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = \"<group>\"; };\n\t\tF835F52713CE8F029D5D252C /* cmd.rs */ = {isa = PBXFileReference; path = cmd.rs; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t11E18DCDB3ADFE87C18915EF /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t2B78BA327A38C76093D36092 /* libapi_lib.a in Frameworks */,\n\t\t\t\t3043432501C9BC2DB6B4CB95 /* CoreGraphics.framework in Frameworks */,\n\t\t\t\t9AADB041D25772D04E543F15 /* Metal.framework in Frameworks */,\n\t\t\t\tDFFF888045C8D9D9FB69E8FD /* MetalKit.framework in Frameworks */,\n\t\t\t\tB60763BD194DFACA215EC7DA /* QuartzCore.framework in Frameworks */,\n\t\t\t\tAFA0CA286325FD7A34968CA2 /* Security.framework in Frameworks */,\n\t\t\t\t9DDA3BE70DD0E4013973FE38 /* UIKit.framework in Frameworks */,\n\t\t\t\tC6D80743F168BDF017B7769E /* WebKit.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t0677CEAF1F282F38CBA0F140 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t74A8FDFB350B966F5AAD4A24 /* assets */,\n\t\t\t\t6B7E79E23E646BA7968B457C /* Assets.xcassets */,\n\t\t\t\t4B2D1B108AE002010BDEC6D2 /* LaunchScreen.storyboard */,\n\t\t\t\tF2116A6428EED18BE2A07E2B /* api_iOS */,\n\t\t\t\t86D903732E10FAC4D300E8DF /* Externals */,\n\t\t\t\t7A9A7AC155D9E22E54D6D847 /* Sources */,\n\t\t\t\tCF9AA87D2F6E9C389B7AB70B /* src */,\n\t\t\t\t10C9FC3FA3E12D6A4A67999D /* Frameworks */,\n\t\t\t\t4AC51E67B71E27F15B02C5CD /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t07051859D6E2D8109C8FB128 /* bindings */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tEC8C7948C50C3C9B5D96CB61 /* bindings.h */,\n\t\t\t);\n\t\t\tpath = bindings;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t10C9FC3FA3E12D6A4A67999D /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t71EB788DE4662CFC0D97F567 /* CoreGraphics.framework */,\n\t\t\t\t4A00E5F95D64FD14E47F85BD /* libapi_lib.a */,\n\t\t\t\t62601E25FA39E62BE119B74D /* Metal.framework */,\n\t\t\t\t338E66700FD330B99D434DD7 /* MetalKit.framework */,\n\t\t\t\tDC377692DC31A070A0188C9D /* QuartzCore.framework */,\n\t\t\t\t384966E551417F94A02D2706 /* Security.framework */,\n\t\t\t\tB6082E363D51372A7658C351 /* UIKit.framework */,\n\t\t\t\t59CFE20DCF760BE67D9CE3D6 /* WebKit.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t4AC51E67B71E27F15B02C5CD /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t5AC703CEBA41A121596066F3 /* api_iOS.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t7A9A7AC155D9E22E54D6D847 /* Sources */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tA3574F52DBC5463B9C3D043D /* api */,\n\t\t\t);\n\t\t\tpath = Sources;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t86D903732E10FAC4D300E8DF /* Externals */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tpath = Externals;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tA3574F52DBC5463B9C3D043D /* api */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t90D3B673AFAB8D8AB561F616 /* main.mm */,\n\t\t\t\t07051859D6E2D8109C8FB128 /* bindings */,\n\t\t\t);\n\t\t\tpath = api;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCF9AA87D2F6E9C389B7AB70B /* src */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tF835F52713CE8F029D5D252C /* cmd.rs */,\n\t\t\t\t785D025E9542F7E098BF22B5 /* lib.rs */,\n\t\t\t\t0E96CE07CD20273DD46BF325 /* main.rs */,\n\t\t\t\t3CA88F22095BE63D88585625 /* menu_plugin.rs */,\n\t\t\t\t1C1AB1B414CA2795AFBEDDB9 /* tray.rs */,\n\t\t\t);\n\t\t\tname = src;\n\t\t\tpath = ../../src;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tF2116A6428EED18BE2A07E2B /* api_iOS */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t879941AE3DAA14534BBC6391 /* api_iOS.entitlements */,\n\t\t\t\t2F63E2AA460089BB58D40C79 /* Info.plist */,\n\t\t\t);\n\t\t\tpath = api_iOS;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t54DC6E273C78071F3BA12EF3 /* api_iOS */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 01CBC40275452376830D79B1 /* Build configuration list for PBXNativeTarget \"api_iOS\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tFF948951157DE71465B5BD5F /* Build Rust Code */,\n\t\t\t\t71E73CC9AB5F1323EC1F6365 /* Sources */,\n\t\t\t\tCA2BEC44B6EDA1F21B6155CD /* Resources */,\n\t\t\t\t11E18DCDB3ADFE87C18915EF /* Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = api_iOS;\n\t\t\tproductName = api_iOS;\n\t\t\tproductReference = 5AC703CEBA41A121596066F3 /* api_iOS.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t9BC88C3717DA5D4B78A51C15 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = YES;\n\t\t\t\tLastUpgradeCheck = 1430;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t54DC6E273C78071F3BA12EF3 = {\n\t\t\t\t\t\tDevelopmentTeam = Q93MBH6S2F;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 8FA67D0F928A09CD639137D1 /* Build configuration list for PBXProject \"api\" */;\n\t\t\tcompatibilityVersion = \"Xcode 14.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\tBase,\n\t\t\t\ten,\n\t\t\t);\n\t\t\tmainGroup = 0677CEAF1F282F38CBA0F140;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t54DC6E273C78071F3BA12EF3 /* api_iOS */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\tCA2BEC44B6EDA1F21B6155CD /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6F379F15DA085785BA2624D4 /* Assets.xcassets in Resources */,\n\t\t\t\tAC8BDC2C7A63FA3FDC5967F4 /* LaunchScreen.storyboard in Resources */,\n\t\t\t\tF86717F05E27C72C9FA1FB27 /* assets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\tFF948951157DE71465B5BD5F /* Build Rust Code */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Build Rust Code\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapi_lib.a\",\n\t\t\t\t\"$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapi_lib.a\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"cargo 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:?}\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t71E73CC9AB5F1323EC1F6365 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t328B4ADB3700C1873BEB7B10 /* main.mm in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\tA83F70B4C02DD0222038C7F1 /* release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = release;\n\t\t};\n\t\tB6AD77E490F315562F75D3D7 /* debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = debug;\n\t\t};\n\t\tBF284FE6E7AE0C8DDCCE398B /* debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;\n\t\t\t\tARCHS = (\n\t\t\t\t\tarm64,\n\t\t\t\t);\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = api_iOS/api_iOS.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"iPhone Developer\";\n\t\t\t\tDEVELOPMENT_TEAM = Q93MBH6S2F;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\t\"EXCLUDED_ARCHS[sdk=iphoneos*]\" = \"x86_64\";\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"\\\".\\\"\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = api_iOS/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\t\"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)\";\n\t\t\t\t\"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)\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.tauri.api;\n\t\t\t\tPRODUCT_NAME = \"Tauri API\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALID_ARCHS = \"arm64\";\n\t\t\t};\n\t\t\tname = debug;\n\t\t};\n\t\tDB_0E254D0FD84970B57F6410 /* release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;\n\t\t\t\tARCHS = (\n\t\t\t\t\tarm64,\n\t\t\t\t);\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = api_iOS/api_iOS.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"iPhone Developer\";\n\t\t\t\tDEVELOPMENT_TEAM = Q93MBH6S2F;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\t\"EXCLUDED_ARCHS[sdk=iphoneos*]\" = \"x86_64\";\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"\\\".\\\"\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = api_iOS/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\t\"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)\";\n\t\t\t\t\"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)\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.tauri.api;\n\t\t\t\tPRODUCT_NAME = \"Tauri API\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALID_ARCHS = \"arm64\";\n\t\t\t};\n\t\t\tname = release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t01CBC40275452376830D79B1 /* Build configuration list for PBXNativeTarget \"api_iOS\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tBF284FE6E7AE0C8DDCCE398B /* debug */,\n\t\t\t\tDB_0E254D0FD84970B57F6410 /* release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = debug;\n\t\t};\n\t\t8FA67D0F928A09CD639137D1 /* Build configuration list for PBXProject \"api\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB6AD77E490F315562F75D3D7 /* debug */,\n\t\t\t\tA83F70B4C02DD0222038C7F1 /* release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = debug;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 9BC88C3717DA5D4B78A51C15 /* Project object */;\n}\n"
  },
  {
    "path": "crates/tauri-cli/tests/fixtures/pbxproj/snapshots/tauri_cli__helpers__pbxproj__tests__project-modified.pbxproj.snap",
    "content": "---\nsource: crates/tauri-cli/src/helpers/pbxproj.rs\nexpression: pbxproj.serialize()\n---\n// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t2B78BA327A38C76093D36092 /* libapi_lib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A00E5F95D64FD14E47F85BD /* libapi_lib.a */; };\n\t\t3043432501C9BC2DB6B4CB95 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71EB788DE4662CFC0D97F567 /* CoreGraphics.framework */; };\n\t\t328B4ADB3700C1873BEB7B10 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 90D3B673AFAB8D8AB561F616 /* main.mm */; };\n\t\t6F379F15DA085785BA2624D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B7E79E23E646BA7968B457C /* Assets.xcassets */; };\n\t\t9AADB041D25772D04E543F15 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62601E25FA39E62BE119B74D /* Metal.framework */; };\n\t\t9DDA3BE70DD0E4013973FE38 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6082E363D51372A7658C351 /* UIKit.framework */; };\n\t\tAC8BDC2C7A63FA3FDC5967F4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B2D1B108AE002010BDEC6D2 /* LaunchScreen.storyboard */; };\n\t\tAFA0CA286325FD7A34968CA2 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384966E551417F94A02D2706 /* Security.framework */; };\n\t\tB60763BD194DFACA215EC7DA /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC377692DC31A070A0188C9D /* QuartzCore.framework */; };\n\t\tC6D80743F168BDF017B7769E /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59CFE20DCF760BE67D9CE3D6 /* WebKit.framework */; };\n\t\tDFFF888045C8D9D9FB69E8FD /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 338E66700FD330B99D434DD7 /* MetalKit.framework */; };\n\t\tF86717F05E27C72C9FA1FB27 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 74A8FDFB350B966F5AAD4A24 /* assets */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t0E96CE07CD20273DD46BF325 /* main.rs */ = {isa = PBXFileReference; path = main.rs; sourceTree = \"<group>\"; };\n\t\t1C1AB1B414CA2795AFBEDDB9 /* tray.rs */ = {isa = PBXFileReference; path = tray.rs; sourceTree = \"<group>\"; };\n\t\t2F63E2AA460089BB58D40C79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t338E66700FD330B99D434DD7 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; };\n\t\t384966E551417F94A02D2706 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };\n\t\t3CA88F22095BE63D88585625 /* menu_plugin.rs */ = {isa = PBXFileReference; path = menu_plugin.rs; sourceTree = \"<group>\"; };\n\t\t4A00E5F95D64FD14E47F85BD /* libapi_lib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapi_lib.a; sourceTree = \"<group>\"; };\n\t\t4B2D1B108AE002010BDEC6D2 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\t59CFE20DCF760BE67D9CE3D6 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };\n\t\t5AC703CEBA41A121596066F3 /* api_iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = api_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t62601E25FA39E62BE119B74D /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };\n\t\t6B7E79E23E646BA7968B457C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t71EB788DE4662CFC0D97F567 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };\n\t\t74A8FDFB350B966F5AAD4A24 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; };\n\t\t785D025E9542F7E098BF22B5 /* lib.rs */ = {isa = PBXFileReference; path = lib.rs; sourceTree = \"<group>\"; };\n\t\t879941AE3DAA14534BBC6391 /* api_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = api_iOS.entitlements; sourceTree = \"<group>\"; };\n\t\t90D3B673AFAB8D8AB561F616 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = \"<group>\"; };\n\t\tB6082E363D51372A7658C351 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };\n\t\tDC377692DC31A070A0188C9D /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };\n\t\tEC8C7948C50C3C9B5D96CB61 /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = \"<group>\"; };\n\t\tF835F52713CE8F029D5D252C /* cmd.rs */ = {isa = PBXFileReference; path = cmd.rs; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t11E18DCDB3ADFE87C18915EF /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t2B78BA327A38C76093D36092 /* libapi_lib.a in Frameworks */,\n\t\t\t\t3043432501C9BC2DB6B4CB95 /* CoreGraphics.framework in Frameworks */,\n\t\t\t\t9AADB041D25772D04E543F15 /* Metal.framework in Frameworks */,\n\t\t\t\tDFFF888045C8D9D9FB69E8FD /* MetalKit.framework in Frameworks */,\n\t\t\t\tB60763BD194DFACA215EC7DA /* QuartzCore.framework in Frameworks */,\n\t\t\t\tAFA0CA286325FD7A34968CA2 /* Security.framework in Frameworks */,\n\t\t\t\t9DDA3BE70DD0E4013973FE38 /* UIKit.framework in Frameworks */,\n\t\t\t\tC6D80743F168BDF017B7769E /* WebKit.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t0677CEAF1F282F38CBA0F140 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t74A8FDFB350B966F5AAD4A24 /* assets */,\n\t\t\t\t6B7E79E23E646BA7968B457C /* Assets.xcassets */,\n\t\t\t\t4B2D1B108AE002010BDEC6D2 /* LaunchScreen.storyboard */,\n\t\t\t\tF2116A6428EED18BE2A07E2B /* api_iOS */,\n\t\t\t\t86D903732E10FAC4D300E8DF /* Externals */,\n\t\t\t\t7A9A7AC155D9E22E54D6D847 /* Sources */,\n\t\t\t\tCF9AA87D2F6E9C389B7AB70B /* src */,\n\t\t\t\t10C9FC3FA3E12D6A4A67999D /* Frameworks */,\n\t\t\t\t4AC51E67B71E27F15B02C5CD /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t07051859D6E2D8109C8FB128 /* bindings */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tEC8C7948C50C3C9B5D96CB61 /* bindings.h */,\n\t\t\t);\n\t\t\tpath = bindings;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t10C9FC3FA3E12D6A4A67999D /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t71EB788DE4662CFC0D97F567 /* CoreGraphics.framework */,\n\t\t\t\t4A00E5F95D64FD14E47F85BD /* libapi_lib.a */,\n\t\t\t\t62601E25FA39E62BE119B74D /* Metal.framework */,\n\t\t\t\t338E66700FD330B99D434DD7 /* MetalKit.framework */,\n\t\t\t\tDC377692DC31A070A0188C9D /* QuartzCore.framework */,\n\t\t\t\t384966E551417F94A02D2706 /* Security.framework */,\n\t\t\t\tB6082E363D51372A7658C351 /* UIKit.framework */,\n\t\t\t\t59CFE20DCF760BE67D9CE3D6 /* WebKit.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t4AC51E67B71E27F15B02C5CD /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t5AC703CEBA41A121596066F3 /* api_iOS.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t7A9A7AC155D9E22E54D6D847 /* Sources */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tA3574F52DBC5463B9C3D043D /* api */,\n\t\t\t);\n\t\t\tpath = Sources;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t86D903732E10FAC4D300E8DF /* Externals */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tpath = Externals;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tA3574F52DBC5463B9C3D043D /* api */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t90D3B673AFAB8D8AB561F616 /* main.mm */,\n\t\t\t\t07051859D6E2D8109C8FB128 /* bindings */,\n\t\t\t);\n\t\t\tpath = api;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCF9AA87D2F6E9C389B7AB70B /* src */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tF835F52713CE8F029D5D252C /* cmd.rs */,\n\t\t\t\t785D025E9542F7E098BF22B5 /* lib.rs */,\n\t\t\t\t0E96CE07CD20273DD46BF325 /* main.rs */,\n\t\t\t\t3CA88F22095BE63D88585625 /* menu_plugin.rs */,\n\t\t\t\t1C1AB1B414CA2795AFBEDDB9 /* tray.rs */,\n\t\t\t);\n\t\t\tname = src;\n\t\t\tpath = ../../src;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tF2116A6428EED18BE2A07E2B /* api_iOS */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t879941AE3DAA14534BBC6391 /* api_iOS.entitlements */,\n\t\t\t\t2F63E2AA460089BB58D40C79 /* Info.plist */,\n\t\t\t);\n\t\t\tpath = api_iOS;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t54DC6E273C78071F3BA12EF3 /* api_iOS */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 01CBC40275452376830D79B1 /* Build configuration list for PBXNativeTarget \"api_iOS\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tFF948951157DE71465B5BD5F /* Build Rust Code */,\n\t\t\t\t71E73CC9AB5F1323EC1F6365 /* Sources */,\n\t\t\t\tCA2BEC44B6EDA1F21B6155CD /* Resources */,\n\t\t\t\t11E18DCDB3ADFE87C18915EF /* Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = api_iOS;\n\t\t\tproductName = api_iOS;\n\t\t\tproductReference = 5AC703CEBA41A121596066F3 /* api_iOS.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t9BC88C3717DA5D4B78A51C15 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = YES;\n\t\t\t\tLastUpgradeCheck = 1430;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t54DC6E273C78071F3BA12EF3 = {\n\t\t\t\t\t\tDevelopmentTeam = Q93MBH6S2F;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 8FA67D0F928A09CD639137D1 /* Build configuration list for PBXProject \"api\" */;\n\t\t\tcompatibilityVersion = \"Xcode 14.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\tBase,\n\t\t\t\ten,\n\t\t\t);\n\t\t\tmainGroup = 0677CEAF1F282F38CBA0F140;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t54DC6E273C78071F3BA12EF3 /* api_iOS */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\tCA2BEC44B6EDA1F21B6155CD /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6F379F15DA085785BA2624D4 /* Assets.xcassets in Resources */,\n\t\t\t\tAC8BDC2C7A63FA3FDC5967F4 /* LaunchScreen.storyboard in Resources */,\n\t\t\t\tF86717F05E27C72C9FA1FB27 /* assets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\tFF948951157DE71465B5BD5F /* Build Rust Code */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Build Rust Code\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapi_lib.a\",\n\t\t\t\t\"$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapi_lib.a\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"cargo 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:?}\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t71E73CC9AB5F1323EC1F6365 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t328B4ADB3700C1873BEB7B10 /* main.mm in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\tA83F70B4C02DD0222038C7F1 /* release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = release;\n\t\t};\n\t\tB6AD77E490F315562F75D3D7 /* debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = debug;\n\t\t};\n\t\tBF284FE6E7AE0C8DDCCE398B /* debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;\n\t\t\t\tARCHS = (\n\t\t\t\t\tarm64,\n\t\t\t\t);\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = api_iOS/api_iOS.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"iPhone Developer\";\n\t\t\t\tDEVELOPMENT_TEAM = Q93MBH6S2F;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\t\"EXCLUDED_ARCHS[sdk=iphoneos*]\" = \"x86_64\";\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"\\\".\\\"\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = api_iOS/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\t\"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)\";\n\t\t\t\t\"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)\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.tauri.api;\n\t\t\t\tPRODUCT_NAME = \"Tauri API\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALID_ARCHS = \"arm64\";\n\t\t\t};\n\t\t\tname = debug;\n\t\t};\n\t\tDB_0E254D0FD84970B57F6410 /* release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;\n\t\t\t\tARCHS = (\n\t\t\t\t\tarm64,\n\t\t\t\t);\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = api_iOS/api_iOS.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"iPhone Developer\";\n\t\t\t\tDEVELOPMENT_TEAM = Q93MBH6S2F;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\t\"EXCLUDED_ARCHS[sdk=iphoneos*]\" = \"x86_64\";\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"\\\".\\\"\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = api_iOS/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\t\"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)\";\n\t\t\t\t\"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)\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.tauri.api;\n\t\t\t\tPRODUCT_NAME = \"Tauri Test\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALID_ARCHS = \"arm64\";\n\t\t\t\tUNKNOWN = 9283j49238h;\n\t\t\t};\n\t\t\tname = release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t01CBC40275452376830D79B1 /* Build configuration list for PBXNativeTarget \"api_iOS\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tBF284FE6E7AE0C8DDCCE398B /* debug */,\n\t\t\t\tDB_0E254D0FD84970B57F6410 /* release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = debug;\n\t\t};\n\t\t8FA67D0F928A09CD639137D1 /* Build configuration list for PBXProject \"api\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB6AD77E490F315562F75D3D7 /* debug */,\n\t\t\t\tA83F70B4C02DD0222038C7F1 /* release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = debug;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 9BC88C3717DA5D4B78A51C15 /* Project object */;\n}\n"
  },
  {
    "path": "crates/tauri-cli/tests/fixtures/pbxproj/snapshots/tauri_cli__helpers__pbxproj__tests__project.pbxproj.snap",
    "content": "---\nsource: crates/tauri-cli/src/helpers/pbxproj.rs\nexpression: \"super::parse(fixtures_path.join(\\\"project.pbxproj\\\")).expect(\\\"failed to parse pbxproj\\\")\"\n---\nPbxproj {\n    xc_build_configuration: {\n        \"A83F70B4C02DD0222038C7F1\": XCBuildConfiguration {\n            build_settings: [\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 262,\n                    key: \"ALWAYS_SEARCH_USER_PATHS\",\n                    value: \"NO\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 263,\n                    key: \"CLANG_ANALYZER_NONNULL\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 264,\n                    key: \"CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION\",\n                    value: \"YES_AGGRESSIVE\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 265,\n                    key: \"CLANG_CXX_LANGUAGE_STANDARD\",\n                    value: \"\\\"gnu++14\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 266,\n                    key: \"CLANG_CXX_LIBRARY\",\n                    value: \"\\\"libc++\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 267,\n                    key: \"CLANG_ENABLE_MODULES\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 268,\n                    key: \"CLANG_ENABLE_OBJC_ARC\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 269,\n                    key: \"CLANG_ENABLE_OBJC_WEAK\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 270,\n                    key: \"CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 271,\n                    key: \"CLANG_WARN_BOOL_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 272,\n                    key: \"CLANG_WARN_COMMA\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 273,\n                    key: \"CLANG_WARN_CONSTANT_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 274,\n                    key: \"CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 275,\n                    key: \"CLANG_WARN_DIRECT_OBJC_ISA_USAGE\",\n                    value: \"YES_ERROR\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 276,\n                    key: \"CLANG_WARN_DOCUMENTATION_COMMENTS\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 277,\n                    key: \"CLANG_WARN_EMPTY_BODY\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 278,\n                    key: \"CLANG_WARN_ENUM_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 279,\n                    key: \"CLANG_WARN_INFINITE_RECURSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 280,\n                    key: \"CLANG_WARN_INT_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 281,\n                    key: \"CLANG_WARN_NON_LITERAL_NULL_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 282,\n                    key: \"CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 283,\n                    key: \"CLANG_WARN_OBJC_LITERAL_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 284,\n                    key: \"CLANG_WARN_OBJC_ROOT_CLASS\",\n                    value: \"YES_ERROR\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 285,\n                    key: \"CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 286,\n                    key: \"CLANG_WARN_RANGE_LOOP_ANALYSIS\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 287,\n                    key: \"CLANG_WARN_STRICT_PROTOTYPES\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 288,\n                    key: \"CLANG_WARN_SUSPICIOUS_MOVE\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 289,\n                    key: \"CLANG_WARN_UNGUARDED_AVAILABILITY\",\n                    value: \"YES_AGGRESSIVE\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 290,\n                    key: \"CLANG_WARN_UNREACHABLE_CODE\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 291,\n                    key: \"CLANG_WARN__DUPLICATE_METHOD_MATCH\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 292,\n                    key: \"COPY_PHASE_STRIP\",\n                    value: \"NO\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 293,\n                    key: \"DEBUG_INFORMATION_FORMAT\",\n                    value: \"\\\"dwarf-with-dsym\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 294,\n                    key: \"ENABLE_NS_ASSERTIONS\",\n                    value: \"NO\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 295,\n                    key: \"ENABLE_STRICT_OBJC_MSGSEND\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 296,\n                    key: \"GCC_C_LANGUAGE_STANDARD\",\n                    value: \"gnu11\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 297,\n                    key: \"GCC_NO_COMMON_BLOCKS\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 298,\n                    key: \"GCC_WARN_64_TO_32_BIT_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 299,\n                    key: \"GCC_WARN_ABOUT_RETURN_TYPE\",\n                    value: \"YES_ERROR\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 300,\n                    key: \"GCC_WARN_UNDECLARED_SELECTOR\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 301,\n                    key: \"GCC_WARN_UNINITIALIZED_AUTOS\",\n                    value: \"YES_AGGRESSIVE\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 302,\n                    key: \"GCC_WARN_UNUSED_FUNCTION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 303,\n                    key: \"GCC_WARN_UNUSED_VARIABLE\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 304,\n                    key: \"IPHONEOS_DEPLOYMENT_TARGET\",\n                    value: \"14.0\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 305,\n                    key: \"MTL_ENABLE_DEBUG_INFO\",\n                    value: \"NO\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 306,\n                    key: \"MTL_FAST_MATH\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 307,\n                    key: \"PRODUCT_NAME\",\n                    value: \"\\\"$(TARGET_NAME)\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 308,\n                    key: \"SDKROOT\",\n                    value: \"iphoneos\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 309,\n                    key: \"SWIFT_COMPILATION_MODE\",\n                    value: \"wholemodule\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 310,\n                    key: \"SWIFT_OPTIMIZATION_LEVEL\",\n                    value: \"\\\"-O\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 311,\n                    key: \"SWIFT_VERSION\",\n                    value: \"5.0\",\n                },\n            ],\n        },\n        \"B6AD77E490F315562F75D3D7\": XCBuildConfiguration {\n            build_settings: [\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 318,\n                    key: \"ALWAYS_SEARCH_USER_PATHS\",\n                    value: \"NO\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 319,\n                    key: \"CLANG_ANALYZER_NONNULL\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 320,\n                    key: \"CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION\",\n                    value: \"YES_AGGRESSIVE\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 321,\n                    key: \"CLANG_CXX_LANGUAGE_STANDARD\",\n                    value: \"\\\"gnu++14\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 322,\n                    key: \"CLANG_CXX_LIBRARY\",\n                    value: \"\\\"libc++\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 323,\n                    key: \"CLANG_ENABLE_MODULES\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 324,\n                    key: \"CLANG_ENABLE_OBJC_ARC\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 325,\n                    key: \"CLANG_ENABLE_OBJC_WEAK\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 326,\n                    key: \"CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 327,\n                    key: \"CLANG_WARN_BOOL_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 328,\n                    key: \"CLANG_WARN_COMMA\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 329,\n                    key: \"CLANG_WARN_CONSTANT_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 330,\n                    key: \"CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 331,\n                    key: \"CLANG_WARN_DIRECT_OBJC_ISA_USAGE\",\n                    value: \"YES_ERROR\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 332,\n                    key: \"CLANG_WARN_DOCUMENTATION_COMMENTS\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 333,\n                    key: \"CLANG_WARN_EMPTY_BODY\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 334,\n                    key: \"CLANG_WARN_ENUM_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 335,\n                    key: \"CLANG_WARN_INFINITE_RECURSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 336,\n                    key: \"CLANG_WARN_INT_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 337,\n                    key: \"CLANG_WARN_NON_LITERAL_NULL_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 338,\n                    key: \"CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 339,\n                    key: \"CLANG_WARN_OBJC_LITERAL_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 340,\n                    key: \"CLANG_WARN_OBJC_ROOT_CLASS\",\n                    value: \"YES_ERROR\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 341,\n                    key: \"CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 342,\n                    key: \"CLANG_WARN_RANGE_LOOP_ANALYSIS\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 343,\n                    key: \"CLANG_WARN_STRICT_PROTOTYPES\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 344,\n                    key: \"CLANG_WARN_SUSPICIOUS_MOVE\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 345,\n                    key: \"CLANG_WARN_UNGUARDED_AVAILABILITY\",\n                    value: \"YES_AGGRESSIVE\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 346,\n                    key: \"CLANG_WARN_UNREACHABLE_CODE\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 347,\n                    key: \"CLANG_WARN__DUPLICATE_METHOD_MATCH\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 348,\n                    key: \"COPY_PHASE_STRIP\",\n                    value: \"NO\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 349,\n                    key: \"DEBUG_INFORMATION_FORMAT\",\n                    value: \"dwarf\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 350,\n                    key: \"ENABLE_STRICT_OBJC_MSGSEND\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 351,\n                    key: \"ENABLE_TESTABILITY\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 352,\n                    key: \"GCC_C_LANGUAGE_STANDARD\",\n                    value: \"gnu11\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 353,\n                    key: \"GCC_DYNAMIC_NO_PIC\",\n                    value: \"NO\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 354,\n                    key: \"GCC_NO_COMMON_BLOCKS\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 355,\n                    key: \"GCC_OPTIMIZATION_LEVEL\",\n                    value: \"0\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 356,\n                    key: \"GCC_PREPROCESSOR_DEFINITIONS\",\n                    value: \"(\\t\\t\\t\\t\\t\\\"$(inherited)\\\",\\n\\t\\t\\t\\t\\t\\\"DEBUG=1\\\",\\n\\t\\t\\t\\t);\\n\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 360,\n                    key: \"GCC_WARN_64_TO_32_BIT_CONVERSION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 361,\n                    key: \"GCC_WARN_ABOUT_RETURN_TYPE\",\n                    value: \"YES_ERROR\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 362,\n                    key: \"GCC_WARN_UNDECLARED_SELECTOR\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 363,\n                    key: \"GCC_WARN_UNINITIALIZED_AUTOS\",\n                    value: \"YES_AGGRESSIVE\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 364,\n                    key: \"GCC_WARN_UNUSED_FUNCTION\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 365,\n                    key: \"GCC_WARN_UNUSED_VARIABLE\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 366,\n                    key: \"IPHONEOS_DEPLOYMENT_TARGET\",\n                    value: \"14.0\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 367,\n                    key: \"MTL_ENABLE_DEBUG_INFO\",\n                    value: \"INCLUDE_SOURCE\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 368,\n                    key: \"MTL_FAST_MATH\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 369,\n                    key: \"ONLY_ACTIVE_ARCH\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 370,\n                    key: \"PRODUCT_NAME\",\n                    value: \"\\\"$(TARGET_NAME)\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 371,\n                    key: \"SDKROOT\",\n                    value: \"iphoneos\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 372,\n                    key: \"SWIFT_ACTIVE_COMPILATION_CONDITIONS\",\n                    value: \"DEBUG\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 373,\n                    key: \"SWIFT_OPTIMIZATION_LEVEL\",\n                    value: \"\\\"-Onone\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 374,\n                    key: \"SWIFT_VERSION\",\n                    value: \"5.0\",\n                },\n            ],\n        },\n        \"BF284FE6E7AE0C8DDCCE398B\": XCBuildConfiguration {\n            build_settings: [\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 381,\n                    key: \"ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 382,\n                    key: \"ARCHS\",\n                    value: \"(\\t\\t\\t\\t\\tarm64,\\n\\t\\t\\t\\t);\\n\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 385,\n                    key: \"ASSETCATALOG_COMPILER_APPICON_NAME\",\n                    value: \"AppIcon\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 386,\n                    key: \"CODE_SIGN_ENTITLEMENTS\",\n                    value: \"api_iOS/api_iOS.entitlements\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 387,\n                    key: \"CODE_SIGN_IDENTITY\",\n                    value: \"\\\"iPhone Developer\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 388,\n                    key: \"DEVELOPMENT_TEAM\",\n                    value: \"Q93MBH6S2F\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 389,\n                    key: \"ENABLE_BITCODE\",\n                    value: \"NO\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 390,\n                    key: \"\\\"EXCLUDED_ARCHS[sdk=iphoneos*]\\\"\",\n                    value: \"\\\"x86_64\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 391,\n                    key: \"FRAMEWORK_SEARCH_PATHS\",\n                    value: \"(\\t\\t\\t\\t\\t\\\"$(inherited)\\\",\\n\\t\\t\\t\\t\\t\\\"\\\\\\\".\\\\\\\"\\\",\\n\\t\\t\\t\\t);\\n\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 395,\n                    key: \"INFOPLIST_FILE\",\n                    value: \"api_iOS/Info.plist\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 396,\n                    key: \"LD_RUNPATH_SEARCH_PATHS\",\n                    value: \"(\\t\\t\\t\\t\\t\\\"$(inherited)\\\",\\n\\t\\t\\t\\t\\t\\\"@executable_path/Frameworks\\\",\\n\\t\\t\\t\\t);\\n\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 400,\n                    key: \"\\\"LIBRARY_SEARCH_PATHS[arch=arm64]\\\"\",\n                    value: \"\\\"$(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)\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 401,\n                    key: \"\\\"LIBRARY_SEARCH_PATHS[arch=x86_64]\\\"\",\n                    value: \"\\\"$(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)\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 402,\n                    key: \"PRODUCT_BUNDLE_IDENTIFIER\",\n                    value: \"com.tauri.api\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 403,\n                    key: \"PRODUCT_NAME\",\n                    value: \"\\\"Tauri API\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 404,\n                    key: \"SDKROOT\",\n                    value: \"iphoneos\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 405,\n                    key: \"TARGETED_DEVICE_FAMILY\",\n                    value: \"\\\"1,2\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 406,\n                    key: \"VALID_ARCHS\",\n                    value: \"\\\"arm64\\\"\",\n                },\n            ],\n        },\n        \"DB_0E254D0FD84970B57F6410\": XCBuildConfiguration {\n            build_settings: [\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 413,\n                    key: \"ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES\",\n                    value: \"YES\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 414,\n                    key: \"ARCHS\",\n                    value: \"(\\t\\t\\t\\t\\tarm64,\\n\\t\\t\\t\\t);\\n\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 417,\n                    key: \"ASSETCATALOG_COMPILER_APPICON_NAME\",\n                    value: \"AppIcon\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 418,\n                    key: \"CODE_SIGN_ENTITLEMENTS\",\n                    value: \"api_iOS/api_iOS.entitlements\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 419,\n                    key: \"CODE_SIGN_IDENTITY\",\n                    value: \"\\\"iPhone Developer\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 420,\n                    key: \"DEVELOPMENT_TEAM\",\n                    value: \"Q93MBH6S2F\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 421,\n                    key: \"ENABLE_BITCODE\",\n                    value: \"NO\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 422,\n                    key: \"\\\"EXCLUDED_ARCHS[sdk=iphoneos*]\\\"\",\n                    value: \"\\\"x86_64\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 423,\n                    key: \"FRAMEWORK_SEARCH_PATHS\",\n                    value: \"(\\t\\t\\t\\t\\t\\\"$(inherited)\\\",\\n\\t\\t\\t\\t\\t\\\"\\\\\\\".\\\\\\\"\\\",\\n\\t\\t\\t\\t);\\n\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 427,\n                    key: \"INFOPLIST_FILE\",\n                    value: \"api_iOS/Info.plist\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 428,\n                    key: \"LD_RUNPATH_SEARCH_PATHS\",\n                    value: \"(\\t\\t\\t\\t\\t\\\"$(inherited)\\\",\\n\\t\\t\\t\\t\\t\\\"@executable_path/Frameworks\\\",\\n\\t\\t\\t\\t);\\n\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 432,\n                    key: \"\\\"LIBRARY_SEARCH_PATHS[arch=arm64]\\\"\",\n                    value: \"\\\"$(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)\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 433,\n                    key: \"\\\"LIBRARY_SEARCH_PATHS[arch=x86_64]\\\"\",\n                    value: \"\\\"$(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)\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 434,\n                    key: \"PRODUCT_BUNDLE_IDENTIFIER\",\n                    value: \"com.tauri.api\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 435,\n                    key: \"PRODUCT_NAME\",\n                    value: \"\\\"Tauri API\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 436,\n                    key: \"SDKROOT\",\n                    value: \"iphoneos\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 437,\n                    key: \"TARGETED_DEVICE_FAMILY\",\n                    value: \"\\\"1,2\\\"\",\n                },\n                BuildSettings {\n                    identation: \"\\t\\t\\t\\t\",\n                    line_number: 438,\n                    key: \"VALID_ARCHS\",\n                    value: \"\\\"arm64\\\"\",\n                },\n            ],\n        },\n    },\n    xc_configuration_list: {\n        \"01CBC40275452376830D79B1\": XCConfigurationList {\n            comment: \"/* Build configuration list for PBXNativeTarget \\\"api_iOS\\\" */\",\n            build_configurations: [\n                BuildConfigurationRef {\n                    id: \"BF284FE6E7AE0C8DDCCE398B\",\n                    comments: \"/* debug */\",\n                },\n                BuildConfigurationRef {\n                    id: \"DB_0E254D0FD84970B57F6410\",\n                    comments: \"/* release */\",\n                },\n            ],\n        },\n        \"8FA67D0F928A09CD639137D1\": XCConfigurationList {\n            comment: \"/* Build configuration list for PBXProject \\\"api\\\" */\",\n            build_configurations: [\n                BuildConfigurationRef {\n                    id: \"B6AD77E490F315562F75D3D7\",\n                    comments: \"/* debug */\",\n                },\n                BuildConfigurationRef {\n                    id: \"A83F70B4C02DD0222038C7F1\",\n                    comments: \"/* release */\",\n                },\n            ],\n        },\n    },\n}\n"
  },
  {
    "path": "crates/tauri-codegen/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.5.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.3`\n\n## \\[2.5.4]\n\n### Bug Fixes\n\n- [`eb5d88427`](https://www.github.com/tauri-apps/tauri/commit/eb5d88427a7dcb347fb0feae9e816db05b101844) ([#14883](https://www.github.com/tauri-apps/tauri/pull/14883) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `tauri::Context` code generation failing with `can't capture dynamic environment in a fn item` when custom assets are provided.\n\n## \\[2.5.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.2`\n\n## \\[2.5.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.1`\n\n## \\[2.5.1]\n\n### Performance Improvements\n\n- [`8e3bd63db`](https://www.github.com/tauri-apps/tauri/commit/8e3bd63db919a4cf72bb3d28028033d8654deb34) ([#14457](https://www.github.com/tauri-apps/tauri/pull/14457) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Wrap the generated context code in a function to make rust analyzer faster\n\n## \\[2.5.0]\n\n### Bug Fixes\n\n- [`c5008b829`](https://www.github.com/tauri-apps/tauri/commit/c5008b829dc779f0768089bff9b891fc76b11355) ([#14274](https://www.github.com/tauri-apps/tauri/pull/14274)) Do not hash empty scripts when generating the Content-Security-Policy SHA-256 hashes.\n- [`7b0d4e732`](https://www.github.com/tauri-apps/tauri/commit/7b0d4e73227e42d88732b6d9fe643499dd78ec4e) ([#14265](https://www.github.com/tauri-apps/tauri/pull/14265)) Fix JavaScript SHA256 hash generation on Windows not ignoring carriage return characters.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.0`\n- [`6aa7f2d85`](https://www.github.com/tauri-apps/tauri/commit/6aa7f2d852870aeba1d4dd0e07f8be2bc9b66286) Upgraded to `tauri-utils@2.8.0`\n\n## \\[2.4.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.7.0`.\n\n## \\[2.3.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.6.0`\n\n## \\[2.3.0]\n\n### New Features\n\n- [`414619c36`](https://www.github.com/tauri-apps/tauri/commit/414619c36e94e21939534dd72c0438b93da75546) ([#13536](https://www.github.com/tauri-apps/tauri/pull/13536) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) Added support for the `bundleName` property in the macOS bundler configuration. This allows specifying the `CFBundleName` value for generated macOS bundles.\n\n### What's Changed\n\n- [`168629646`](https://www.github.com/tauri-apps/tauri/commit/168629646335f24cc7f1c4a61df22688b2198f98) ([#13418](https://www.github.com/tauri-apps/tauri/pull/13418) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Put dynamic ACL into a feature `dynamic-acl`, this is currently enabled by default to align with the previous behaviors, you can disable it through `default-features = false` to reduce the final binary size by not including the ACL references\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.5.0`\n\n## \\[2.2.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.4.0`\n- [`48b12b440`](https://www.github.com/tauri-apps/tauri/commit/48b12b440478937c46fdfef9f9d95194be117020) Update to `tauri-utils@2.4.0`\n\n## \\[2.1.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.1`\n\n## \\[2.1.0]\n\n### New Features\n\n- [`013f8f652`](https://www.github.com/tauri-apps/tauri/commit/013f8f652302f2d49c5ec0a075582033d8b074fb) ([#12890](https://www.github.com/tauri-apps/tauri/pull/12890) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Added `build > removeUnusedCommands` to trigger the build scripts and macros to remove unused commands based on the capabilities you defined. Note this won't be accounting for dynamically added ACLs so make sure to check it when using this.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.0`\n\n## \\[2.0.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.2.0`\n\n## \\[2.0.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.1`\n\n## \\[2.0.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.0`\n\n## \\[2.0.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.2`\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.1`\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78.\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0`\n\n## \\[2.0.0-rc.13]\n\n### Bug Fixes\n\n- [`1efa5e718`](https://www.github.com/tauri-apps/tauri/commit/1efa5e7184009537b688a395596c696173ae0231) ([#11099](https://www.github.com/tauri-apps/tauri/pull/11099) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Rerun build script if the platform-specific configuration file changes.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.13`\n\n## \\[2.0.0-rc.12]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.12`\n\n## \\[2.0.0-rc.11]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.11`\n\n## \\[2.0.0-rc.10]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.10`\n\n## \\[2.0.0-rc.9]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.9`\n\n## \\[2.0.0-rc.8]\n\n### What's Changed\n\n- [`27d018343`](https://www.github.com/tauri-apps/tauri/commit/27d01834312ee7953b6ccd5b0a368e7a69b225e9) ([#10844](https://www.github.com/tauri-apps/tauri/pull/10844) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Changes how the Info.plist is embedded on macOS development to avoid a clippy warning.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.8`\n\n## \\[2.0.0-rc.7]\n\n### Bug Fixes\n\n- [`88bc35732`](https://www.github.com/tauri-apps/tauri/commit/88bc357325ba278527d8cba956e828f5744c8a34) ([#10734](https://www.github.com/tauri-apps/tauri/pull/10734) by [@chippers](https://www.github.com/tauri-apps/tauri/../../chippers)) Generate context in a separate thread to prevent a stack overflow.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.7`\n\n## \\[2.0.0-rc.6]\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.6`\n\n## \\[2.0.0-rc.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.5`\n\n## \\[2.0.0-rc.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.4`\n\n## \\[2.0.0-rc.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.3`\n- [`0afee5ed8`](https://www.github.com/tauri-apps/tauri/commit/0afee5ed80265c95c4581e173c4886677cfed593) ([#10436](https://www.github.com/tauri-apps/tauri/pull/10436) by [@nyurik](https://www.github.com/tauri-apps/tauri/../../nyurik)) Updated brotli to v6.\n\n## \\[2.0.0-rc.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.2`\n\n## \\[2.0.0-rc.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.1`\n\n## \\[2.0.0-rc.0]\n\n### Enhancements\n\n- [`1e0793b68`](https://www.github.com/tauri-apps/tauri/commit/1e0793b6821799829e380c88066b3415cc9006df) ([#10357](https://www.github.com/tauri-apps/tauri/pull/10357)) Enhance `AssetResolver::get` in development mode by reading distDir directly as a fallback to the embedded assets.\n\n### Bug Fixes\n\n- [`24445d71d`](https://www.github.com/tauri-apps/tauri/commit/24445d71de92d526d0ccaecb54f13003ddc6f6b4)([#10432](https://www.github.com/tauri-apps/tauri/pull/10432)) Fixes asset resolving when not using the `compression` feature.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.0`\n\n## \\[2.0.0-beta.19]\n\n### Bug Fixes\n\n- [`5d2922985`](https://www.github.com/tauri-apps/tauri/commit/5d2922985801908e4b929a7a0e387806ff02ab89) ([#10268](https://www.github.com/tauri-apps/tauri/pull/10268) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix icon rewriting always triggering build to rerun due to conflicts between file names.\n\n### What's Changed\n\n- [`4c239729c`](https://www.github.com/tauri-apps/tauri/commit/4c239729c3e1b899ecbc6793c3682848e8de1729) ([#10167](https://www.github.com/tauri-apps/tauri/pull/10167) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add support for `test = true` in `generate_context!` macro to skip some code generation that could affect some tests, for now it only skips empedding a plist on macOS.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.19`\n\n## \\[2.0.0-beta.18]\n\n### New Features\n\n- [`5b769948a`](https://www.github.com/tauri-apps/tauri/commit/5b769948a81cac333f64c870a470ba6525bd5cd3) ([#9959](https://www.github.com/tauri-apps/tauri/pull/9959)) Add `include_image_codegen` function to help embedding instances of `Image` struct at compile-time in rust to be used with window, menu or tray icons.\n\n### Bug Fixes\n\n- [`1f6e478c8`](https://www.github.com/tauri-apps/tauri/commit/1f6e478c842a16219798b9962718e9ddb969c041) ([#9878](https://www.github.com/tauri-apps/tauri/pull/9878)) Fixes Info.plist rewriting always triggering build to rerun.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.18`\n\n## \\[2.0.0-beta.17]\n\n### What's Changed\n\n- [`ccc3ea729`](https://www.github.com/tauri-apps/tauri/commit/ccc3ea729de205ef467f737f1feeb5bf02d9cd72)([#9646](https://www.github.com/tauri-apps/tauri/pull/9646)) Use `TAURI_ENV_TARGET_TRIPLE` (which is set by `tauri-build`) to determine the target when reading the config file.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.17`\n\n## \\[2.0.0-beta.16]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.16`\n\n## \\[2.0.0-beta.15]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.15`\n\n## \\[2.0.0-beta.14]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.14`\n\n## \\[2.0.0-beta.13]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.13`\n\n## \\[2.0.0-beta.12]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.12`\n\n## \\[2.0.0-beta.11]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.11`\n\n## \\[2.0.0-beta.10]\n\n### New Features\n\n- [`e227fe02f`](https://www.github.com/tauri-apps/tauri/commit/e227fe02f986e145c0731a64693e1c830a9eb5b0)([#9156](https://www.github.com/tauri-apps/tauri/pull/9156)) Allow plugins to define (at compile time) JavaScript that are initialized when `withGlobalTauri` is true.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.10`\n\n## \\[2.0.0-beta.9]\n\n### New Features\n\n- [`ba0206d8a`](https://www.github.com/tauri-apps/tauri/commit/ba0206d8a30a9b43ec5090dcaabd1a23baa1420c)([#9141](https://www.github.com/tauri-apps/tauri/pull/9141)) The `Context` codegen now accepts a `assets` input to define a custom `tauri::Assets` implementation.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.9`\n\n## \\[2.0.0-beta.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.8`\n\n### Breaking Changes\n\n- [`ed48e2b3c`](https://www.github.com/tauri-apps/tauri/commit/ed48e2b3c7fa914e4c9af432c02b8154f872c68a)([#9122](https://www.github.com/tauri-apps/tauri/pull/9122)) Expose `tauri::image` module to export the `JsImage` type and removed the `Image` root re-export.\n\n## \\[2.0.0-beta.7]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.7`\n\n### Breaking Changes\n\n- [`d1e77acd8`](https://www.github.com/tauri-apps/tauri/commit/d1e77acd8dfdf554b90b542513a58a2de1ef2360)([#9011](https://www.github.com/tauri-apps/tauri/pull/9011)) Change the generated context code to use the new `Image` type in tauri.\n\n## \\[2.0.0-beta.6]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.6`\n\n### Breaking Changes\n\n- [`3657ad82`](https://www.github.com/tauri-apps/tauri/commit/3657ad82f88ce528551d032d521c52eed3f396b4)([#9008](https://www.github.com/tauri-apps/tauri/pull/9008)) Allow defining permissions for the application commands via `tauri_build::Attributes::app_manifest`.\n\n## \\[2.0.0-beta.5]\n\n### Enhancements\n\n- [`bc5b5e67`](https://www.github.com/tauri-apps/tauri/commit/bc5b5e671a546512f823f1c157421b4c3311dfc0)([#8984](https://www.github.com/tauri-apps/tauri/pull/8984)) Do not include a CSP tag in the application HTML and rely on the custom protocol response header instead.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.5`\n\n## \\[2.0.0-beta.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.4`\n\n## \\[2.0.0-beta.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.3`\n\n## \\[2.0.0-beta.2]\n\n### Enhancements\n\n- [`83a68deb`](https://www.github.com/tauri-apps/tauri/commit/83a68deb5676d39cd4728d2e140f6b46d5f787ed)([#8797](https://www.github.com/tauri-apps/tauri/pull/8797)) Added a new configuration option `tauri.conf.json > app > security > capabilities` to reference existing capabilities and inline new ones. If it is empty, all capabilities are still included preserving the current behavior.\n- [`8d16a80d`](https://www.github.com/tauri-apps/tauri/commit/8d16a80d2fb2468667e7987d0cc99dbc7e3b9d0a)([#8802](https://www.github.com/tauri-apps/tauri/pull/8802)) The `generate_context` proc macro now accepts a `capabilities` attribute where the value is an array of file paths that can be [conditionally compiled](https://doc.rust-lang.org/reference/conditional-compilation.html). These capabilities are added to the application along the capabilities defined in the Tauri configuration file.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.2`\n\n## \\[2.0.0-beta.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.1`\n\n## \\[2.0.0-beta.0]\n\n### New Features\n\n- [`74a2a603`](https://www.github.com/tauri-apps/tauri/commit/74a2a6036a5e57462f161d728cbd8a6f121028ca)([#8661](https://www.github.com/tauri-apps/tauri/pull/8661)) Implement access control list for IPC usage.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.0`\n\n### Breaking Changes\n\n- [`8de308d1`](https://www.github.com/tauri-apps/tauri/commit/8de308d1bf6a855d7a26af58bd0e744938ba47d8)([#8723](https://www.github.com/tauri-apps/tauri/pull/8723)) Restructured Tauri config per [RFC#5](https://github.com/tauri-apps/rfcs/blob/f3e82a6b0c5390401e855850d47dc7b7d9afd684/texts/0005-tauri-config-restructure.md):\n\n  - Moved `package.productName`, `package.version` and `tauri.bundle.identifier` fields to the top-level.\n  - Removed `package` object.\n  - Renamed `tauri` object to `app`.\n  - Moved `tauri.bundle` object to the top-level.\n  - Renamed `build.distDir` field to `frontendDist`.\n  - Renamed `build.devPath` field to `devUrl` and will no longer accepts paths, it will only accept URLs.\n  - Moved `tauri.pattern` to `app.security.pattern`.\n  - Removed `tauri.bundle.updater` object, and its fields have been moved to the updater plugin under `plugins.updater` object.\n  - Moved `build.withGlobalTauri` to `app.withGlobalTauri`.\n  - Moved `tauri.bundle.dmg` object to `bundle.macOS.dmg`.\n  - Moved `tauri.bundle.deb` object to `bundle.linux.deb`.\n  - Moved `tauri.bundle.appimage` object to `bundle.linux.appimage`.\n  - Removed all license fields from each bundle configuration object and instead added `bundle.license` and `bundle.licenseFile`.\n  - Renamed `AppUrl` to `FrontendDist` and refactored its variants to be more explicit.\n\n## \\[2.0.0-alpha.13]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.13`\n\n## \\[2.0.0-alpha.12]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.12`\n\n## \\[2.0.0-alpha.11]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.11`\n\n## \\[2.0.0-alpha.10]\n\n### Enhancements\n\n- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.10`\n\n## \\[2.0.0-alpha.9]\n\n### New Features\n\n- [`880266a7`](https://www.github.com/tauri-apps/tauri/commit/880266a7f697e1fe58d685de3bb6836ce5251e92)([#8031](https://www.github.com/tauri-apps/tauri/pull/8031)) Bump the MSRV to 1.70.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.9`\n\n### Breaking Changes\n\n- [`ebcc21e4`](https://www.github.com/tauri-apps/tauri/commit/ebcc21e4b95f4e8c27639fb1bca545b432f52d5e)([#8057](https://www.github.com/tauri-apps/tauri/pull/8057)) Renamed the beforeDevCommand, beforeBuildCommand and beforeBundleCommand hooks environment variables from `TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG` to `TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG` to differentiate the prefix with other CLI environment variables.\n\n## \\[2.0.0-alpha.8]\n\n### Enhancements\n\n- [`100d9ede`](https://www.github.com/tauri-apps/tauri/commit/100d9ede35995d9db21d2087dd5606adfafb89a5)([#7802](https://www.github.com/tauri-apps/tauri/pull/7802)) Use `Target` enum from `tauri_utils::platform`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.8`\n\n## \\[2.0.0-alpha.7]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.7`\n\n## \\[2.0.0-alpha.6]\n\n### Dependencies\n\n- Updated to latest `tauri-utils`\n\n## \\[2.0.0-alpha.5]\n\n- [`96639ca2`](https://www.github.com/tauri-apps/tauri/commit/96639ca239c9e4f75142fc07868ac46822111cff)([#6749](https://www.github.com/tauri-apps/tauri/pull/6749)) Moved the `shell` functionality to its own plugin in the plugins-workspace repository.\n- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65.\n- [`ae102980`](https://www.github.com/tauri-apps/tauri/commit/ae102980fcdde3f55effdc0623ea425b48d07dd1)([#6719](https://www.github.com/tauri-apps/tauri/pull/6719)) Refactor the `Context` conditional fields and only parse the tray icon on desktop.\n\n## \\[2.0.0-alpha.4]\n\n- Added `android` configuration object under `tauri > bundle`.\n  - Bumped due to a bump in tauri-utils.\n  - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07\n\n## \\[2.0.0-alpha.3]\n\n- Pull changes from Tauri 1.3 release.\n  - [](https://www.github.com/tauri-apps/tauri/commit/undefined)  on undefined\n\n## \\[2.0.0-alpha.2]\n\n- Return `bool` in the invoke handler.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n\n## \\[2.0.0-alpha.1]\n\n- Bump the MSRV to 1.64.\n  - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30\n- Added `crate_name` field on `PackageInfo`.\n  - [630a7f4b](https://www.github.com/tauri-apps/tauri/commit/630a7f4b18cef169bfd48673609306fec434e397) refactor: remove mobile log initialization, ref [#6049](https://www.github.com/tauri-apps/tauri/pull/6049) ([#6081](https://www.github.com/tauri-apps/tauri/pull/6081)) on 2023-01-17\n\n## \\[2.0.0-alpha.0]\n\n- Change `devPath` URL to use the local IP address on iOS and Android.\n  - [6f061504](https://www.github.com/tauri-apps/tauri/commit/6f0615044d09ec58393a7ebca5e45bb175e20db3) feat(cli): add `android dev` and `ios dev` commands ([#4982](https://www.github.com/tauri-apps/tauri/pull/4982)) on 2022-08-20\n- First mobile alpha release!\n  - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08\n\n## \\[1.4.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.2`\n\n## \\[1.4.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.0`\n\n## \\[1.4.0]\n\n### Enhancements\n\n- [`17d5a4f5`](https://www.github.com/tauri-apps/tauri/commit/17d5a4f51f244d3ff42014b5d1b075fad7c636a5)([#6706](https://www.github.com/tauri-apps/tauri/pull/6706)) Early panic if the PNG icon is not RGBA.\n- [`d2710e9d`](https://www.github.com/tauri-apps/tauri/commit/d2710e9d2e8fd93975ef6494512370faa8cb3b7e)([#6944](https://www.github.com/tauri-apps/tauri/pull/6944)) Unpin `time`, `ignore`, and `winnow` crate versions. Developers now have to pin crates if needed themselves. A list of crates that need pinning to adhere to Tauri's MSRV will be visible in Tauri's GitHub workflow: https://github.com/tauri-apps/tauri/blob/dev/.github/workflows/test-core.yml#L85.\n\n## \\[1.3.0]\n\n- Bump minimum supported Rust version to 1.60.\n  - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31\n- Pin `time` to `0.3.15`.\n  - [3d16461b](https://www.github.com/tauri-apps/tauri/commit/3d16461b68583ba7db037fbc217786e79b46ddf2) fix(core): pin time to 0.3.15 ([#6312](https://www.github.com/tauri-apps/tauri/pull/6312)) on 2023-02-19\n\n## \\[1.2.1]\n\n- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`.\n  - Bumped due to a bump in tauri-utils.\n  - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18\n\n## \\[1.2.0]\n\n- Properly serialize HTML template tags.\n  - [aec5537d](https://www.github.com/tauri-apps/tauri/commit/aec5537de0205f62b2ae5c89da04d21930a6fc2e) fix(codegen): serialize template tags, closes [#4410](https://www.github.com/tauri-apps/tauri/pull/4410) ([#5247](https://www.github.com/tauri-apps/tauri/pull/5247)) on 2022-09-28\n- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28\n\n## \\[1.1.1]\n\n- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs.\n  - Bumped due to a bump in tauri-utils.\n  - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16\n\n## \\[1.1.0]\n\n- Use `TARGET` environment variable for code generation inside build scripts.\n  - [6ba99689](https://www.github.com/tauri-apps/tauri/commit/6ba99689aa7ed0ffa9072a1c8ab5db12c9bf95af) feat(codegen): use TARGET environment variable if set ([#4921](https://www.github.com/tauri-apps/tauri/pull/4921)) on 2022-08-12\n- Added support to configuration files in TOML format (Tauri.toml file).\n  - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02\n- Improve tray icon read error message.\n  - [52f0c8bb](https://www.github.com/tauri-apps/tauri/commit/52f0c8bb836c6d50b7ce2393161394f4ce78f5ae) feat(core): improve tray icon read error messages ([#4850](https://www.github.com/tauri-apps/tauri/pull/4850)) on 2022-08-03\n- Fix relative paths in `version` field of `tauri.config.json` not being correctly parsed by `generate_context!()`.\n  - [accbc5e8](https://www.github.com/tauri-apps/tauri/commit/accbc5e8806a32efc555d019634fc2fa14d17f0a) fix(codegen): fix relative paths in `version` field of `tauri.config.json`, closes [#4723](https://www.github.com/tauri-apps/tauri/pull/4723) ([#4725](https://www.github.com/tauri-apps/tauri/pull/4725)) on 2022-07-24\n- Only rewrite temporary icon files when the content change, avoid needless rebuilds.\n  - [f957cbb5](https://www.github.com/tauri-apps/tauri/commit/f957cbb56ccbd8d1c047a978b4579946252a6fd2) fix(codegen): write output file when contents change ([#4889](https://www.github.com/tauri-apps/tauri/pull/4889)) on 2022-08-09\n\n## \\[1.0.4]\n\n- Validate `__TAURI_ISOLATION_HOOK__` being set by a file in the isolation application.\n  - [3b4ed970](https://www.github.com/tauri-apps/tauri/commit/3b4ed970e663f5bffbfe0358610f9c3f157c513f) feat(codegen): validate `__TAURI_ISOLATION_HOOK__` is referenced ([#4631](https://www.github.com/tauri-apps/tauri/pull/4631)) on 2022-07-11\n\n## \\[1.0.3]\n\n- The `TAURI_CONFIG` environment variable now represents the configuration to be merged instead of the entire JSON.\n  - [fa028ebf](https://www.github.com/tauri-apps/tauri/commit/fa028ebf3c8ca7b43a70d283a01dbea86217594f) refactor: do not pass entire config from CLI to core, send patch instead ([#4598](https://www.github.com/tauri-apps/tauri/pull/4598)) on 2022-07-06\n\n## \\[1.0.2]\n\n- Expose `platform::windows_version` function.\n  - Bumped due to a bump in tauri-utils.\n  - [bf764e83](https://www.github.com/tauri-apps/tauri/commit/bf764e83e01e7443e6cc54572001e1c98c357465) feat(utils): expose `windows_version` function ([#4534](https://www.github.com/tauri-apps/tauri/pull/4534)) on 2022-06-30\n\n## \\[1.0.1]\n\n- Set the bundle name and app metadata in the Info.plist file in development mode.\n  - [38f5db6e](https://www.github.com/tauri-apps/tauri/commit/38f5db6e6a8b496b50d486db6fd8204266de3a69) feat(codegen): fill app metadata in development Info.plist on 2022-06-21\n- Set the application icon in development mode on macOS.\n  - [307c2ebf](https://www.github.com/tauri-apps/tauri/commit/307c2ebfb68238dacab6088f9c6ba310c727c68c) feat(core): set macOS app icon in development ([#4385](https://www.github.com/tauri-apps/tauri/pull/4385)) on 2022-06-19\n\n## \\[1.0.0]\n\n- Upgrade to `stable`!\n  - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15\n\n## \\[1.0.0-rc.11]\n\n- Read the tray icon path relatively to the config directory.\n  - [562e8ca2](https://www.github.com/tauri-apps/tauri/commit/562e8ca23facf1a8e5fa6c8cdf872357d3523a78) fix(codegen): tray icon path is relative to the config directory on 2022-06-15\n\n## \\[1.0.0-rc.10]\n\n- **Breaking change:** The `TrayIcon` enum has been removed and now `Icon` is used instead.\n  This allows you to use more image formats and use embedded icons on Linux.\n  - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14\n\n## \\[1.0.0-rc.9]\n\n- Added a config flag to bundle the media framework used by webkit2gtk `tauri.conf.json > tauri > bundle > appimage > bundleMediaFramework`.\n  - Bumped due to a bump in tauri-utils.\n  - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10\n\n## \\[1.0.0-rc.8]\n\n- **Breaking change:** `PackageInfo::version` is now a `semver::Version` instead of a `String`.\n  - [2badbd2d](https://www.github.com/tauri-apps/tauri/commit/2badbd2d7ed51bf33c1b547b4c837b600574bd4a) refactor: force semver versions, change updater `should_install` sig ([#4215](https://www.github.com/tauri-apps/tauri/pull/4215)) on 2022-05-25\n  - [a7388e23](https://www.github.com/tauri-apps/tauri/commit/a7388e23c3b9019d48b078cae00a75c74d74d11b) fix(ci): adjust change file to include tauri-utils and tauri-codegen on 2022-05-27\n\n## \\[1.0.0-rc.7]\n\n- Allow configuring the display options for the MSI execution allowing quieter updates.\n  - Bumped due to a bump in tauri-utils.\n  - [9f2c3413](https://www.github.com/tauri-apps/tauri/commit/9f2c34131952ea83c3f8e383bc3cec7e1450429f) feat(core): configure msiexec display options, closes [#3951](https://www.github.com/tauri-apps/tauri/pull/3951) ([#4061](https://www.github.com/tauri-apps/tauri/pull/4061)) on 2022-05-15\n\n## \\[1.0.0-rc.6]\n\n- The `dangerous_allow_asset_csp_modification` configuration value has been changed to allow a list of CSP directives to disable.\n  - [164078c0](https://www.github.com/tauri-apps/tauri/commit/164078c0b719ccbc12e956fecf8a7d4a3c5044e1) feat: allow limiting dangerousDisableAssetCspModification, closes [#3831](https://www.github.com/tauri-apps/tauri/pull/3831) ([#4021](https://www.github.com/tauri-apps/tauri/pull/4021)) on 2022-05-02\n\n## \\[1.0.0-rc.5]\n\n- Read platform-specific configuration files when generating code without the `TAURI_CONFIG` env var.\n  - [edf85bc1](https://www.github.com/tauri-apps/tauri/commit/edf85bc1d18450c92aee17f7f99c163abe432ebd) fix(codegen): read platform-specific config file ([#3966](https://www.github.com/tauri-apps/tauri/pull/3966)) on 2022-04-25\n\n## \\[1.0.0-rc.4]\n\n- Added an option to disable the CSP injection of distributable assets nonces and hashes.\n  - [f6e32ee1](https://www.github.com/tauri-apps/tauri/commit/f6e32ee1880eb364ed76beb937c9d12e14d54910) feat(core): add dangerous option to disable compile time CSP injection ([#3775](https://www.github.com/tauri-apps/tauri/pull/3775)) on 2022-03-28\n\n- Replace multiple dependencies who's C code compiled concurrently and caused\n  the other ones to bloat compile time significantly.\n\n- `zstd` -> `brotli`\n\n- `blake3` -> a vendored version of the blake3 reference\n\n- `ring` -> `getrandom`\n\nSee https://github.com/tauri-apps/tauri/pull/3773 for more information about\nthese specific choices.\n\n- [8661e3e2](https://www.github.com/tauri-apps/tauri/commit/8661e3e24d96c399bfbcdee5d8e9d6beba2265a7) replace dependencies with long build times when used together (closes [#3571](https://www.github.com/tauri-apps/tauri/pull/3571)) ([#3773](https://www.github.com/tauri-apps/tauri/pull/3773)) on 2022-03-27\n\n## \\[1.0.0-rc.3]\n\n- Parse window icons at compile time.\n  - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05\n\n## \\[1.0.0-rc.2]\n\n- Changed the default value for `tauri > bundle > macOS > minimumSystemVersion` to `10.13`.\n  - Bumped due to a bump in tauri-utils.\n  - [fce344b9](https://www.github.com/tauri-apps/tauri/commit/fce344b90b7227f8f5514853c2f885fb24d3648e) feat(core): set default value for `minimum_system_version` to 10.13 ([#3497](https://www.github.com/tauri-apps/tauri/pull/3497)) on 2022-02-17\n\n## \\[1.0.0-rc.1]\n\n- Change default value for the `freezePrototype` configuration to `false`.\n  - Bumped due to a bump in tauri-utils.\n  - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12\n\n## \\[1.0.0-rc.0]\n\n- Apply `nonce` to `script` and `style` tags and set them on the `CSP` (`script-src` and `style-src` fetch directives).\n  - [cf54dcf9](https://www.github.com/tauri-apps/tauri/commit/cf54dcf9c81730e42c9171daa9c8aa474c95b522) feat: improve `CSP` security with nonces and hashes, add `devCsp` \\[TRI-004] ([#8](https://www.github.com/tauri-apps/tauri/pull/8)) on 2022-01-09\n- Added the `isolation` pattern.\n  - [d5d6d2ab](https://www.github.com/tauri-apps/tauri/commit/d5d6d2abc17cd89c3a079d2ce01581193469dbc0) Isolation Pattern ([#43](https://www.github.com/tauri-apps/tauri/pull/43)) Co-authored-by: Ngo Iok Ui (Wu Yu Wei) <wusyong9104@gmail.com> Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app> on 2022-01-17\n- Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension.\n\nHere is the logic flow that determines if JSON or JSON5 will be used to parse the config:\n\n1. Check if `tauri.conf.json` exists\n   a. Parse it with `serde_json`\n   b. Parse it with `json5` if `serde_json` fails\n   c. Return original `serde_json` error if all above steps failed\n2. Check if `tauri.conf.json5` exists\n   a. Parse it with `json5`\n   b. Return error if all above steps failed\n3. Return error if all above steps failed\n\n- [995de57a](https://www.github.com/tauri-apps/tauri/commit/995de57a76cf51215277673e526d7ec32b86b564) Add seamless support for using JSON5 in the config file ([#47](https://www.github.com/tauri-apps/tauri/pull/47)) on 2022-02-03\n- The minimum Rust version is now `1.56`.\n  - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22\n\n## \\[1.0.0-beta.4]\n\n- Embed Info.plist file contents on binary on dev.\n  - [537ab1b6](https://www.github.com/tauri-apps/tauri/commit/537ab1b6d5a792c550a535619965c9e4126292e6) feat(core): inject src-tauri/Info.plist file on dev and merge on bundle, closes [#1570](https://www.github.com/tauri-apps/tauri/pull/1570) [#2338](https://www.github.com/tauri-apps/tauri/pull/2338) ([#2444](https://www.github.com/tauri-apps/tauri/pull/2444)) on 2021-08-15\n- Fix ES Module detection for default imports with relative paths or scoped packages and exporting of async functions.\n  - [b2b36cfe](https://www.github.com/tauri-apps/tauri/commit/b2b36cfe8dfcccb341638a4cb6dc23a514c54148) fix(core): fixes ES Module detection for default imports with relative paths or scoped packages ([#2380](https://www.github.com/tauri-apps/tauri/pull/2380)) on 2021-08-10\n  - [fbf8caf5](https://www.github.com/tauri-apps/tauri/commit/fbf8caf5c419cb4fc3d123be910e094a8e8c4bef) fix(core): ESM detection when using `export async function` ([#2425](https://www.github.com/tauri-apps/tauri/pull/2425)) on 2021-08-14\n\n## \\[1.0.0-beta.3]\n\n- Improve ESM detection with regexes.\n  - [4b0ec018](https://www.github.com/tauri-apps/tauri/commit/4b0ec0188078a8fefd4119fe5e19ebc30191f802) fix(core): improve JS ESM detection ([#2139](https://www.github.com/tauri-apps/tauri/pull/2139)) on 2021-07-02\n- Inject invoke key on `script` tags with `type=\"module\"`.\n  - [f03eea9c](https://www.github.com/tauri-apps/tauri/commit/f03eea9c9b964709532afbc4d1dd343b3fd96081) feat(core): inject invoke key on `<script type=\"module\">` ([#2120](https://www.github.com/tauri-apps/tauri/pull/2120)) on 2021-06-29\n\n## \\[1.0.0-beta.2]\n\n- Detect ESM scripts and inject the invoke key directly instead of using an IIFE.\n  - [7765c7fa](https://www.github.com/tauri-apps/tauri/commit/7765c7fa281853ddfb26b6b17534df95eaede804) fix(core): invoke key injection on ES module, improve performance ([#2094](https://www.github.com/tauri-apps/tauri/pull/2094)) on 2021-06-27\n- Improve invoke key code injection performance time rewriting code at compile time.\n  - [7765c7fa](https://www.github.com/tauri-apps/tauri/commit/7765c7fa281853ddfb26b6b17534df95eaede804) fix(core): invoke key injection on ES module, improve performance ([#2094](https://www.github.com/tauri-apps/tauri/pull/2094)) on 2021-06-27\n\n## \\[1.0.0-beta.1]\n\n- Allow `dev_path` and `dist_dir` to be an array of root files and directories to embed.\n  - [6ec54c53](https://www.github.com/tauri-apps/tauri/commit/6ec54c53b504eec3873d326b1a45e450227d46ed) feat(core): allow `dev_path`, `dist_dir` as array of paths, fixes [#1897](https://www.github.com/tauri-apps/tauri/pull/1897) ([#1926](https://www.github.com/tauri-apps/tauri/pull/1926)) on 2021-05-31\n- Validate `tauri.conf.json > build > devPath` and `tauri.conf.json > build > distDir` values.\n  - [e97846aa](https://www.github.com/tauri-apps/tauri/commit/e97846aae933cad5cba284a2a133ae7aaee1107c) feat(core): validate `devPath` and `distDir` values ([#1848](https://www.github.com/tauri-apps/tauri/pull/1848)) on 2021-05-17\n- Read `tauri.conf.json > tauri > bundle > icons` and use the first `.png` icon as window icon on Linux. Defaults to `icon/icon.png` if a PNG icon is not configured.\n  - [40b717ed](https://www.github.com/tauri-apps/tauri/commit/40b717edc57288a1393fad0529390e101ab903c1) feat(core): set window icon on Linux, closes [#1922](https://www.github.com/tauri-apps/tauri/pull/1922) ([#1937](https://www.github.com/tauri-apps/tauri/pull/1937)) on 2021-06-01\n\n## \\[1.0.0-beta.0]\n\n- **Breaking:** The `assets` field on the `tauri::Context` struct is now a `Arc<impl Assets>`.\n  - [5110c70](https://www.github.com/tauri-apps/tauri/commit/5110c704be67e51d49fb83f3710afb593973dcf9) feat(core): allow users to access the Assets instance ([#1691](https://www.github.com/tauri-apps/tauri/pull/1691)) on 2021-05-03\n- Reintroduce `csp` injection, configured on `tauri.conf.json > tauri > security > csp`.\n  - [6132f3f](https://www.github.com/tauri-apps/tauri/commit/6132f3f4feb64488ef618f690a4f06adce864d91) feat(core): reintroduce CSP injection ([#1704](https://www.github.com/tauri-apps/tauri/pull/1704)) on 2021-05-04\n- Added the \\`#\\[non_exhaustive] attribute where appropriate.\n  - [e087f0f](https://www.github.com/tauri-apps/tauri/commit/e087f0f9374355ac4b4a48f94727ef8b26b1c4cf) feat: add `#[non_exhaustive]` attribute ([#1725](https://www.github.com/tauri-apps/tauri/pull/1725)) on 2021-05-05\n\n## \\[1.0.0-beta-rc.1]\n\n- The package info APIs now checks the `package` object on `tauri.conf.json`.\n  - [8fd1baf](https://www.github.com/tauri-apps/tauri/commit/8fd1baf69b14bb81d7be9d31605ed7f02058b392) fix(core): pull package info from tauri.conf.json if set ([#1581](https://www.github.com/tauri-apps/tauri/pull/1581)) on 2021-04-22\n  - [f575aaa](https://www.github.com/tauri-apps/tauri/commit/f575aaad71f23d44b2f89cf9ee5d84817dc3bb7a) fix: change files not referencing core packages ([#1619](https://www.github.com/tauri-apps/tauri/pull/1619)) on 2021-04-25\n\n## \\[1.0.0-beta-rc.0]\n\n- Update all code files to have our license header.\n  - [bf82136](https://www.github.com/tauri-apps/tauri/commit/bf8213646689175f8a158b956911f3a43e360690) feat(license): SPDX Headers ([#1449](https://www.github.com/tauri-apps/tauri/pull/1449)) on 2021-04-11\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n"
  },
  {
    "path": "crates/tauri-codegen/Cargo.toml",
    "content": "[package]\nname = \"tauri-codegen\"\nversion = \"2.5.5\"\ndescription = \"code generation meant to be consumed inside of `tauri` through `tauri-build` or `tauri-macros`\"\nexclude = [\"CHANGELOG.md\", \"/target\"]\nreadme = \"README.md\"\nauthors.workspace = true\nhomepage.workspace = true\nrepository.workspace = true\ncategories.workspace = true\nlicense.workspace = true\nedition.workspace = true\nrust-version.workspace = true\n\n[dependencies]\nsha2 = \"0.10\"\nbase64 = \"0.22\"\nproc-macro2 = \"1\"\nquote = \"1\"\nsyn = \"2\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\ntauri-utils = { version = \"2.8.3\", path = \"../tauri-utils\", features = [\n  \"build\",\n] }\nthiserror = \"2\"\nwalkdir = \"2\"\nbrotli = { version = \"8\", optional = true, default-features = false, features = [\n  \"std\",\n] }\nuuid = { version = \"1\", features = [\"v4\"] }\nsemver = \"1\"\nico = \"0.5\"\npng = \"0.17\"\njson-patch = \"3\"\nurl = \"2\"\n\n[target.\"cfg(target_os = \\\"macos\\\")\".dependencies]\nplist = \"1\"\ntime = { version = \"0.3\", features = [\"parsing\", \"formatting\"] }\n\n[features]\ncompression = [\"brotli\", \"tauri-utils/compression\"]\nisolation = [\"tauri-utils/isolation\"]\nconfig-json5 = [\"tauri-utils/config-json5\"]\nconfig-toml = [\"tauri-utils/config-toml\"]\n"
  },
  {
    "path": "crates/tauri-codegen/LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/tauri-codegen/LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2017 - Present Tauri Apps Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "crates/tauri-codegen/README.md",
    "content": "# tauri-codegen\n\n <img align=\"right\" src=\"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\" height=\"128\" width=\"128\">\n\n[![status](https://img.shields.io/badge/Status-Stable-green.svg)](https://github.com/tauri-apps/tauri)\n[![Chat Server](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/SpmNs4S)\n\n[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml)\n[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app)\n\n[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)\n[![support](https://img.shields.io/badge/sponsor-Opencollective-blue.svg)](https://opencollective.com/tauri)\n\n| Component     | Version                                                                                                        |\n| ------------- | -------------------------------------------------------------------------------------------------------------- |\n| tauri-codegen | [![](https://img.shields.io/crates/v/tauri-codegen?style=flat-square)](https://crates.io/crates/tauri-codegen) |\n\n## About Tauri\n\nTauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.\n\nTauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.\n\n## This module\n\n- Embed, hash, and compress assets, including icons for the app as well as the tray icon.\n- Parse `tauri.conf.json` at compile time and generate the Config struct.\n\nTo learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.\n\n## Semver\n\n**tauri** is following [Semantic Versioning 2.0](https://semver.org/).\n\n## Licenses\n\nCode: (c) 2021 - The Tauri Programme within The Commons Conservancy.\n\nMIT or MIT/Apache 2.0 where applicable.\n\nLogo: CC-BY-NC-ND\n\n- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)\n"
  },
  {
    "path": "crates/tauri-codegen/src/context.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::collections::BTreeMap;\nuse std::convert::identity;\nuse std::path::{Path, PathBuf};\nuse std::{ffi::OsStr, str::FromStr};\n\nuse crate::{\n  embedded_assets::{\n    ensure_out_dir, AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsResult,\n  },\n  image::CachedIcon,\n};\nuse base64::Engine;\nuse proc_macro2::TokenStream;\nuse quote::quote;\nuse sha2::{Digest, Sha256};\nuse syn::Expr;\nuse tauri_utils::{\n  acl::{\n    get_capabilities, manifest::Manifest, resolved::Resolved, ACL_MANIFESTS_FILE_NAME,\n    CAPABILITIES_FILE_NAME,\n  },\n  assets::AssetKey,\n  config::{Config, FrontendDist, PatternKind},\n  html::{inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node, NodeRef},\n  platform::Target,\n  tokens::{map_lit, str_lit},\n};\n\n/// Necessary data needed by [`context_codegen`] to generate code for a Tauri application context.\npub struct ContextData {\n  pub dev: bool,\n  pub config: Config,\n  pub config_parent: PathBuf,\n  pub root: TokenStream,\n  /// Additional capabilities to include.\n  pub capabilities: Option<Vec<PathBuf>>,\n  /// The custom assets implementation\n  pub assets: Option<Expr>,\n  /// Skip runtime-only types generation for tests (e.g. embed-plist usage).\n  pub test: bool,\n}\n\nfn inject_script_hashes(document: &NodeRef, key: &AssetKey, csp_hashes: &mut CspHashes) {\n  if let Ok(inline_script_elements) = document.select(\"script:not(:empty)\") {\n    let mut scripts = Vec::new();\n    for inline_script_el in inline_script_elements {\n      let script = inline_script_el.as_node().text_contents();\n      let mut hasher = Sha256::new();\n      hasher.update(tauri_utils::html::normalize_script_for_csp(\n        script.as_bytes(),\n      ));\n      let hash = hasher.finalize();\n      scripts.push(format!(\n        \"'sha256-{}'\",\n        base64::engine::general_purpose::STANDARD.encode(hash)\n      ));\n    }\n    csp_hashes\n      .inline_scripts\n      .entry(key.clone().into())\n      .or_default()\n      .append(&mut scripts);\n  }\n}\n\nfn map_core_assets(\n  options: &AssetOptions,\n) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> EmbeddedAssetsResult<()> {\n  let csp = options.csp;\n  let dangerous_disable_asset_csp_modification =\n    options.dangerous_disable_asset_csp_modification.clone();\n  move |key, path, input, csp_hashes| {\n    if path.extension() == Some(OsStr::new(\"html\")) {\n      #[allow(clippy::collapsible_if)]\n      if csp {\n        let document = parse_html(String::from_utf8_lossy(input).into_owned());\n\n        inject_nonce_token(&document, &dangerous_disable_asset_csp_modification);\n\n        if dangerous_disable_asset_csp_modification.can_modify(\"script-src\") {\n          inject_script_hashes(&document, key, csp_hashes);\n        }\n\n        *input = serialize_html_node(&document);\n      }\n    }\n    Ok(())\n  }\n}\n\n#[cfg(feature = \"isolation\")]\nfn map_isolation(\n  _options: &AssetOptions,\n  dir: PathBuf,\n) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> EmbeddedAssetsResult<()> {\n  // create the csp for the isolation iframe styling now, to make the runtime less complex\n  let mut hasher = Sha256::new();\n  hasher.update(tauri_utils::pattern::isolation::IFRAME_STYLE);\n  let hash = hasher.finalize();\n  let iframe_style_csp_hash = format!(\n    \"'sha256-{}'\",\n    base64::engine::general_purpose::STANDARD.encode(hash)\n  );\n\n  move |key, path, input, csp_hashes| {\n    if path.extension() == Some(OsStr::new(\"html\")) {\n      let isolation_html = parse_html(String::from_utf8_lossy(input).into_owned());\n\n      // this is appended, so no need to reverse order it\n      tauri_utils::html::inject_codegen_isolation_script(&isolation_html);\n\n      // temporary workaround for windows not loading assets\n      tauri_utils::html::inline_isolation(&isolation_html, &dir);\n\n      inject_nonce_token(\n        &isolation_html,\n        &tauri_utils::config::DisabledCspModificationKind::Flag(false),\n      );\n\n      inject_script_hashes(&isolation_html, key, csp_hashes);\n\n      csp_hashes.styles.push(iframe_style_csp_hash.clone());\n\n      *input = isolation_html.to_string().as_bytes().to_vec()\n    }\n\n    Ok(())\n  }\n}\n\n/// Build a `tauri::Context` for including in application code.\npub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {\n  let ContextData {\n    dev,\n    config,\n    config_parent,\n    root,\n    capabilities: additional_capabilities,\n    assets,\n    test,\n  } = data;\n\n  #[allow(unused_variables)]\n  let running_tests = test;\n\n  let target = std::env::var(\"TAURI_ENV_TARGET_TRIPLE\")\n    .as_deref()\n    .map(Target::from_triple)\n    .unwrap_or_else(|_| Target::current());\n\n  let mut options = AssetOptions::new(config.app.security.pattern.clone())\n    .freeze_prototype(config.app.security.freeze_prototype)\n    .dangerous_disable_asset_csp_modification(\n      config\n        .app\n        .security\n        .dangerous_disable_asset_csp_modification\n        .clone(),\n    );\n  let csp = if dev {\n    config\n      .app\n      .security\n      .dev_csp\n      .as_ref()\n      .or(config.app.security.csp.as_ref())\n  } else {\n    config.app.security.csp.as_ref()\n  };\n  if csp.is_some() {\n    options = options.with_csp();\n  }\n\n  let assets = if let Some(assets) = assets {\n    quote!(#assets)\n  } else if dev && config.build.dev_url.is_some() {\n    let assets = EmbeddedAssets::default();\n    quote!(#assets)\n  } else {\n    let assets = match &config.build.frontend_dist {\n      Some(url) => match url {\n        FrontendDist::Url(_url) => Default::default(),\n        FrontendDist::Directory(path) => {\n          let assets_path = config_parent.join(path);\n          if !assets_path.exists() {\n            panic!(\n              \"The `frontendDist` configuration is set to `{path:?}` but this path doesn't exist\"\n            )\n          }\n          EmbeddedAssets::new(assets_path, &options, map_core_assets(&options))?\n        }\n        FrontendDist::Files(files) => EmbeddedAssets::new(\n          files\n            .iter()\n            .map(|p| config_parent.join(p))\n            .collect::<Vec<_>>(),\n          &options,\n          map_core_assets(&options),\n        )?,\n        _ => unimplemented!(),\n      },\n      None => Default::default(),\n    };\n    quote!(#assets)\n  };\n\n  let out_dir = ensure_out_dir()?;\n\n  let default_window_icon = {\n    if target == Target::Windows {\n      // handle default window icons for Windows targets\n      let icon_path = find_icon(\n        &config,\n        &config_parent,\n        |i| i.ends_with(\".ico\"),\n        \"icons/icon.ico\",\n      );\n      if icon_path.exists() {\n        let icon = CachedIcon::new(&root, &icon_path)?;\n        quote!(::std::option::Option::Some(#icon))\n      } else {\n        let icon_path = find_icon(\n          &config,\n          &config_parent,\n          |i| i.ends_with(\".png\"),\n          \"icons/icon.png\",\n        );\n        let icon = CachedIcon::new(&root, &icon_path)?;\n        quote!(::std::option::Option::Some(#icon))\n      }\n    } else {\n      // handle default window icons for Unix targets\n      let icon_path = find_icon(\n        &config,\n        &config_parent,\n        |i| i.ends_with(\".png\"),\n        \"icons/icon.png\",\n      );\n      let icon = CachedIcon::new(&root, &icon_path)?;\n      quote!(::std::option::Option::Some(#icon))\n    }\n  };\n\n  let app_icon = if target == Target::MacOS && dev {\n    let mut icon_path = find_icon(\n      &config,\n      &config_parent,\n      |i| i.ends_with(\".icns\"),\n      \"icons/icon.png\",\n    );\n    if !icon_path.exists() {\n      icon_path = find_icon(\n        &config,\n        &config_parent,\n        |i| i.ends_with(\".png\"),\n        \"icons/icon.png\",\n      );\n    }\n\n    let icon = CachedIcon::new_raw(&root, &icon_path)?;\n    quote!(::std::option::Option::Some(#icon.to_vec()))\n  } else {\n    quote!(::std::option::Option::None)\n  };\n\n  let package_name = if let Some(product_name) = &config.product_name {\n    quote!(#product_name.to_string())\n  } else {\n    quote!(env!(\"CARGO_PKG_NAME\").to_string())\n  };\n  let package_version = if let Some(version) = &config.version {\n    semver::Version::from_str(version)?;\n    quote!(#version.to_string())\n  } else {\n    quote!(env!(\"CARGO_PKG_VERSION\").to_string())\n  };\n  let package_info = quote!(\n    #root::PackageInfo {\n      name: #package_name,\n      version: #package_version.parse().unwrap(),\n      authors: env!(\"CARGO_PKG_AUTHORS\"),\n      description: env!(\"CARGO_PKG_DESCRIPTION\"),\n      crate_name: env!(\"CARGO_PKG_NAME\"),\n    }\n  );\n\n  let with_tray_icon_code = if target.is_desktop() {\n    if let Some(tray) = &config.app.tray_icon {\n      let tray_icon_icon_path = config_parent.join(&tray.icon_path);\n      let icon = CachedIcon::new(&root, &tray_icon_icon_path)?;\n      quote!(context.set_tray_icon(::std::option::Option::Some(#icon));)\n    } else {\n      quote!()\n    }\n  } else {\n    quote!()\n  };\n\n  #[cfg(target_os = \"macos\")]\n  let maybe_embed_plist_block = if target == Target::MacOS && dev && !running_tests {\n    let info_plist_path = config_parent.join(\"Info.plist\");\n    let mut info_plist = if info_plist_path.exists() {\n      plist::Value::from_file(&info_plist_path)\n        .unwrap_or_else(|e| panic!(\"failed to read plist {}: {}\", info_plist_path.display(), e))\n    } else {\n      plist::Value::Dictionary(Default::default())\n    };\n\n    if let Some(plist) = info_plist.as_dictionary_mut() {\n      if let Some(bundle_name) = config\n        .bundle\n        .macos\n        .bundle_name\n        .as_ref()\n        .or(config.product_name.as_ref())\n      {\n        plist.insert(\"CFBundleName\".into(), bundle_name.as_str().into());\n      }\n\n      if let Some(version) = &config.version {\n        let bundle_version = &config.bundle.macos.bundle_version;\n        plist.insert(\"CFBundleShortVersionString\".into(), version.clone().into());\n        plist.insert(\n          \"CFBundleVersion\".into(),\n          bundle_version\n            .clone()\n            .unwrap_or_else(|| version.clone())\n            .into(),\n        );\n      }\n    }\n\n    let mut plist_contents = std::io::BufWriter::new(Vec::new());\n    info_plist\n      .to_writer_xml(&mut plist_contents)\n      .expect(\"failed to serialize plist\");\n    let plist_contents =\n      String::from_utf8_lossy(&plist_contents.into_inner().unwrap()).into_owned();\n\n    let plist = crate::Cached::try_from(plist_contents)?;\n    quote!({\n      tauri::embed_plist::embed_info_plist!(#plist);\n    })\n  } else {\n    quote!()\n  };\n  #[cfg(not(target_os = \"macos\"))]\n  let maybe_embed_plist_block = quote!();\n\n  let pattern = match &options.pattern {\n    PatternKind::Brownfield => quote!(#root::Pattern::Brownfield),\n    #[cfg(not(feature = \"isolation\"))]\n    PatternKind::Isolation { dir: _ } => {\n      quote!(#root::Pattern::Brownfield)\n    }\n    #[cfg(feature = \"isolation\")]\n    PatternKind::Isolation { dir } => {\n      let dir = config_parent.join(dir);\n      if !dir.exists() {\n        panic!(\"The isolation application path is set to `{dir:?}` but it does not exist\")\n      }\n\n      let mut sets_isolation_hook = false;\n\n      let key = uuid::Uuid::new_v4().to_string();\n      let map_isolation = map_isolation(&options, dir.clone());\n      let assets = EmbeddedAssets::new(dir, &options, |key, path, input, csp_hashes| {\n        // we check if `__TAURI_ISOLATION_HOOK__` exists in the isolation code\n        // before modifying the files since we inject our own `__TAURI_ISOLATION_HOOK__` reference in HTML files\n        if String::from_utf8_lossy(input).contains(\"__TAURI_ISOLATION_HOOK__\") {\n          sets_isolation_hook = true;\n        }\n        map_isolation(key, path, input, csp_hashes)\n      })?;\n\n      if !sets_isolation_hook {\n        panic!(\"The isolation application does not contain a file setting the `window.__TAURI_ISOLATION_HOOK__` value.\");\n      }\n\n      let schema = options.isolation_schema;\n\n      quote!(#root::Pattern::Isolation {\n        assets: ::std::sync::Arc::new(#assets),\n        schema: #schema.into(),\n        key: #key.into(),\n        crypto_keys: std::boxed::Box::new(::tauri::utils::pattern::isolation::Keys::new().expect(\"unable to generate cryptographically secure keys for Tauri \\\"Isolation\\\" Pattern\")),\n      })\n    }\n  };\n\n  let acl_file_path = out_dir.join(ACL_MANIFESTS_FILE_NAME);\n  let acl: BTreeMap<String, Manifest> = if acl_file_path.exists() {\n    let acl_file =\n      std::fs::read_to_string(acl_file_path).expect(\"failed to read plugin manifest map\");\n    serde_json::from_str(&acl_file).expect(\"failed to parse plugin manifest map\")\n  } else {\n    Default::default()\n  };\n\n  let capabilities_file_path = out_dir.join(CAPABILITIES_FILE_NAME);\n  let capabilities_from_files = if capabilities_file_path.exists() {\n    let capabilities_json =\n      std::fs::read_to_string(&capabilities_file_path).expect(\"failed to read capabilities\");\n    serde_json::from_str(&capabilities_json).expect(\"failed to parse capabilities\")\n  } else {\n    Default::default()\n  };\n  let capabilities = get_capabilities(\n    &config,\n    capabilities_from_files,\n    additional_capabilities.as_deref(),\n  )\n  .unwrap();\n\n  let resolved = Resolved::resolve(&acl, capabilities, target).expect(\"failed to resolve ACL\");\n\n  let acl_tokens = map_lit(\n    quote! { ::std::collections::BTreeMap },\n    &acl,\n    str_lit,\n    identity,\n  );\n\n  let runtime_authority = quote!(#root::runtime_authority!(#acl_tokens, #resolved));\n\n  let plugin_global_api_scripts = if config.app.with_global_tauri {\n    if let Some(scripts) = tauri_utils::plugin::read_global_api_scripts(&out_dir) {\n      let scripts = scripts.into_iter().map(|s| quote!(#s));\n      quote!(::std::option::Option::Some(&[#(#scripts),*]))\n    } else {\n      quote!(::std::option::Option::None)\n    }\n  } else {\n    quote!(::std::option::Option::None)\n  };\n\n  let maybe_config_parent_setter = if dev {\n    let config_parent = config_parent.to_string_lossy();\n    quote!({\n      context.with_config_parent(#config_parent);\n    })\n  } else {\n    quote!()\n  };\n\n  let context = quote!({\n    #maybe_embed_plist_block\n\n    #[allow(unused_mut, clippy::let_and_return)]\n    let mut context = #root::Context::new(\n      #config,\n      ::std::boxed::Box::new(assets),\n      #default_window_icon,\n      #app_icon,\n      #package_info,\n      #pattern,\n      #runtime_authority,\n      #plugin_global_api_scripts\n    );\n\n    #with_tray_icon_code\n    #maybe_config_parent_setter\n\n    context\n  });\n\n  // Wrapping in a function to make rust analyzer faster,\n  // see https://github.com/tauri-apps/tauri/pull/14457\n  // We take the assets as an argument so when the caller provides custom `assets` the closure\n  // does not capture from the caller's scope (\"can't capture dynamic environment in a fn item\").\n  let output = quote!({\n    fn inner<R: #root::Runtime, A: #root::Assets<R> + 'static>(assets: A) -> #root::Context<R> {\n      let thread = ::std::thread::Builder::new()\n        .name(String::from(\"generated tauri context creation\"))\n        .stack_size(8 * 1024 * 1024)\n        .spawn(move || #context)\n        .expect(\"unable to create thread with 8MiB stack\");\n\n      match thread.join() {\n        Ok(context) => context,\n        Err(_) => {\n          eprintln!(\"the generated Tauri `Context` panicked during creation\");\n          ::std::process::exit(101);\n        }\n      }\n    }\n    inner(#assets)\n  });\n\n  Ok(output)\n}\n\nfn find_icon(\n  config: &Config,\n  config_parent: &Path,\n  predicate: impl Fn(&&String) -> bool,\n  default: &str,\n) -> PathBuf {\n  let icon_path = config\n    .bundle\n    .icon\n    .iter()\n    .find(predicate)\n    .map(AsRef::as_ref)\n    .unwrap_or(default);\n  config_parent.join(icon_path)\n}\n"
  },
  {
    "path": "crates/tauri-codegen/src/embedded_assets.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse base64::Engine;\nuse proc_macro2::TokenStream;\nuse quote::{quote, ToTokens, TokenStreamExt};\nuse sha2::{Digest, Sha256};\nuse std::{\n  collections::HashMap,\n  fs::File,\n  path::{Path, PathBuf},\n};\nuse tauri_utils::config::PatternKind;\nuse tauri_utils::{assets::AssetKey, config::DisabledCspModificationKind};\nuse thiserror::Error;\nuse walkdir::{DirEntry, WalkDir};\n\n#[cfg(feature = \"compression\")]\nuse brotli::enc::backward_references::BrotliEncoderParams;\n\n/// The subdirectory inside the target directory we want to place assets.\nconst TARGET_PATH: &str = \"tauri-codegen-assets\";\n\n/// (key, (original filepath, compressed bytes))\ntype Asset = (AssetKey, (PathBuf, PathBuf));\n\n/// All possible errors while reading and compressing an [`EmbeddedAssets`] directory\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum EmbeddedAssetsError {\n  #[error(\"failed to read asset at {path} because {error}\")]\n  AssetRead {\n    path: PathBuf,\n    error: std::io::Error,\n  },\n\n  #[error(\"failed to write asset from {path} to Vec<u8> because {error}\")]\n  AssetWrite {\n    path: PathBuf,\n    error: std::io::Error,\n  },\n\n  #[error(\"failed to create hex from bytes because {0}\")]\n  Hex(std::fmt::Error),\n\n  #[error(\"invalid prefix {prefix} used while including path {path}\")]\n  PrefixInvalid { prefix: PathBuf, path: PathBuf },\n\n  #[error(\"invalid extension `{extension}` used for image {path}, must be `ico` or `png`\")]\n  InvalidImageExtension { extension: PathBuf, path: PathBuf },\n\n  #[error(\"failed to walk directory {path} because {error}\")]\n  Walkdir {\n    path: PathBuf,\n    error: walkdir::Error,\n  },\n\n  #[error(\"OUT_DIR env var is not set, do you have a build script?\")]\n  OutDir,\n\n  #[error(\"version error: {0}\")]\n  Version(#[from] semver::Error),\n}\n\npub type EmbeddedAssetsResult<T> = Result<T, EmbeddedAssetsError>;\n\n/// Represent a directory of assets that are compressed and embedded.\n///\n/// This is the compile time generation of [`tauri_utils::assets::Assets`] from a directory. Assets\n/// from the directory are added as compiler dependencies by dummy including the original,\n/// uncompressed assets.\n///\n/// The assets are compressed during this runtime, and can only be represented as a [`TokenStream`]\n/// through [`ToTokens`]. The generated code is meant to be injected into an application to include\n/// the compressed assets in that application's binary.\n#[derive(Default)]\npub struct EmbeddedAssets {\n  assets: HashMap<AssetKey, (PathBuf, PathBuf)>,\n  csp_hashes: CspHashes,\n}\n\npub struct EmbeddedAssetsInput(Vec<PathBuf>);\n\nimpl From<PathBuf> for EmbeddedAssetsInput {\n  fn from(path: PathBuf) -> Self {\n    Self(vec![path])\n  }\n}\n\nimpl From<Vec<PathBuf>> for EmbeddedAssetsInput {\n  fn from(paths: Vec<PathBuf>) -> Self {\n    Self(paths)\n  }\n}\n\n/// Holds a list of (prefix, entry)\nstruct RawEmbeddedAssets {\n  paths: Vec<(PathBuf, DirEntry)>,\n  csp_hashes: CspHashes,\n}\n\nimpl RawEmbeddedAssets {\n  /// Creates a new list of (prefix, entry) from a collection of inputs.\n  fn new(input: EmbeddedAssetsInput, options: &AssetOptions) -> Result<Self, EmbeddedAssetsError> {\n    let mut csp_hashes = CspHashes::default();\n\n    input\n      .0\n      .into_iter()\n      .flat_map(|path| {\n        let prefix = if path.is_dir() {\n          path.clone()\n        } else {\n          path\n            .parent()\n            .expect(\"embedded file asset has no parent\")\n            .to_path_buf()\n        };\n\n        WalkDir::new(&path)\n          .follow_links(true)\n          .contents_first(true)\n          .into_iter()\n          .map(move |entry| (prefix.clone(), entry))\n      })\n      .filter_map(|(prefix, entry)| {\n        match entry {\n          // we only serve files, not directory listings\n          Ok(entry) if entry.file_type().is_dir() => None,\n\n          // compress all files encountered\n          Ok(entry) => {\n            if let Err(error) = csp_hashes\n              .add_if_applicable(&entry, &options.dangerous_disable_asset_csp_modification)\n            {\n              Some(Err(error))\n            } else {\n              Some(Ok((prefix, entry)))\n            }\n          }\n\n          // pass down error through filter to fail when encountering any error\n          Err(error) => Some(Err(EmbeddedAssetsError::Walkdir {\n            path: prefix,\n            error,\n          })),\n        }\n      })\n      .collect::<Result<Vec<(PathBuf, DirEntry)>, _>>()\n      .map(|paths| Self { paths, csp_hashes })\n  }\n}\n\n/// Holds all hashes that we will apply on the CSP tag/header.\n#[derive(Debug, Default)]\npub struct CspHashes {\n  /// Scripts that are part of the asset collection (JS or MJS files).\n  pub(crate) scripts: Vec<String>,\n  /// Inline scripts (`<script>code</script>`). Maps a HTML path to a list of hashes.\n  pub(crate) inline_scripts: HashMap<String, Vec<String>>,\n  /// A list of hashes of the contents of all `style` elements.\n  pub(crate) styles: Vec<String>,\n}\n\nimpl CspHashes {\n  /// Only add a CSP hash to the appropriate category if we think the file matches\n  ///\n  /// Note: this only checks the file extension, much like how a browser will assume a .js file is\n  /// a JavaScript file unless HTTP headers tell it otherwise.\n  pub fn add_if_applicable(\n    &mut self,\n    entry: &DirEntry,\n    dangerous_disable_asset_csp_modification: &DisabledCspModificationKind,\n  ) -> Result<(), EmbeddedAssetsError> {\n    let path = entry.path();\n\n    // we only hash JavaScript files for now, may expand to other CSP hashable types in the future\n    if let Some(\"js\") | Some(\"mjs\") = path.extension().and_then(|os| os.to_str()) {\n      if dangerous_disable_asset_csp_modification.can_modify(\"script-src\") {\n        let mut hasher = Sha256::new();\n        hasher.update(\n          &std::fs::read(path)\n            .map(|b| tauri_utils::html::normalize_script_for_csp(&b))\n            .map_err(|error| EmbeddedAssetsError::AssetRead {\n              path: path.to_path_buf(),\n              error,\n            })?,\n        );\n        let hash = hasher.finalize();\n        self.scripts.push(format!(\n          \"'sha256-{}'\",\n          base64::engine::general_purpose::STANDARD.encode(hash)\n        ));\n      }\n    }\n\n    Ok(())\n  }\n}\n\n/// Options used to embed assets.\n#[derive(Default)]\npub struct AssetOptions {\n  pub(crate) csp: bool,\n  pub(crate) pattern: PatternKind,\n  pub(crate) freeze_prototype: bool,\n  pub(crate) dangerous_disable_asset_csp_modification: DisabledCspModificationKind,\n  #[cfg(feature = \"isolation\")]\n  pub(crate) isolation_schema: String,\n}\n\nimpl AssetOptions {\n  /// Creates the default asset options.\n  pub fn new(pattern: PatternKind) -> Self {\n    Self {\n      csp: false,\n      pattern,\n      freeze_prototype: false,\n      dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),\n      #[cfg(feature = \"isolation\")]\n      isolation_schema: format!(\"isolation-{}\", uuid::Uuid::new_v4()),\n    }\n  }\n\n  /// Instruct the asset handler to inject the CSP token to HTML files (Linux only) and add asset nonces and hashes to the policy.\n  #[must_use]\n  pub fn with_csp(mut self) -> Self {\n    self.csp = true;\n    self\n  }\n\n  /// Instruct the asset handler to include a script to freeze the `Object.prototype` on all HTML files.\n  #[must_use]\n  pub fn freeze_prototype(mut self, freeze: bool) -> Self {\n    self.freeze_prototype = freeze;\n    self\n  }\n\n  /// Instruct the asset handler to **NOT** modify the CSP. This is **NOT** recommended.\n  pub fn dangerous_disable_asset_csp_modification(\n    mut self,\n    dangerous_disable_asset_csp_modification: DisabledCspModificationKind,\n  ) -> Self {\n    self.dangerous_disable_asset_csp_modification = dangerous_disable_asset_csp_modification;\n    self\n  }\n}\n\nimpl EmbeddedAssets {\n  /// Compress a collection of files and directories, ready to be generated into [`Assets`].\n  ///\n  /// [`Assets`]: tauri_utils::assets::Assets\n  pub fn new(\n    input: impl Into<EmbeddedAssetsInput>,\n    options: &AssetOptions,\n    mut map: impl FnMut(\n      &AssetKey,\n      &Path,\n      &mut Vec<u8>,\n      &mut CspHashes,\n    ) -> Result<(), EmbeddedAssetsError>,\n  ) -> Result<Self, EmbeddedAssetsError> {\n    // we need to pre-compute all files now, so that we can inject data from all files into a few\n    let RawEmbeddedAssets { paths, csp_hashes } = RawEmbeddedAssets::new(input.into(), options)?;\n\n    struct CompressState {\n      csp_hashes: CspHashes,\n      assets: HashMap<AssetKey, (PathBuf, PathBuf)>,\n    }\n\n    let CompressState { assets, csp_hashes } = paths.into_iter().try_fold(\n      CompressState {\n        csp_hashes,\n        assets: HashMap::new(),\n      },\n      move |mut state, (prefix, entry)| {\n        let (key, asset) =\n          Self::compress_file(&prefix, entry.path(), &mut map, &mut state.csp_hashes)?;\n        state.assets.insert(key, asset);\n        Result::<_, EmbeddedAssetsError>::Ok(state)\n      },\n    )?;\n\n    Ok(Self { assets, csp_hashes })\n  }\n\n  /// Use highest compression level for release, the fastest one for everything else\n  #[cfg(feature = \"compression\")]\n  fn compression_settings() -> BrotliEncoderParams {\n    let mut settings = BrotliEncoderParams::default();\n\n    // the following compression levels are hand-picked and are not min-maxed.\n    // they have a good balance of runtime vs size for the respective profile goals.\n    // see the \"brotli\" section of this comment https://github.com/tauri-apps/tauri/issues/3571#issuecomment-1054847558\n    if cfg!(debug_assertions) {\n      settings.quality = 2\n    } else {\n      settings.quality = 9\n    }\n\n    settings\n  }\n\n  /// Compress a file and spit out the information in a [`HashMap`] friendly form.\n  fn compress_file(\n    prefix: &Path,\n    path: &Path,\n    map: &mut impl FnMut(\n      &AssetKey,\n      &Path,\n      &mut Vec<u8>,\n      &mut CspHashes,\n    ) -> Result<(), EmbeddedAssetsError>,\n    csp_hashes: &mut CspHashes,\n  ) -> Result<Asset, EmbeddedAssetsError> {\n    let mut input = std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead {\n      path: path.to_owned(),\n      error,\n    })?;\n\n    // get a key to the asset path without the asset directory prefix\n    let key = path\n      .strip_prefix(prefix)\n      .map(AssetKey::from) // format the path for use in assets\n      .map_err(|_| EmbeddedAssetsError::PrefixInvalid {\n        prefix: prefix.to_owned(),\n        path: path.to_owned(),\n      })?;\n\n    // perform any caller-requested input manipulation\n    map(&key, path, &mut input, csp_hashes)?;\n\n    // we must canonicalize the base of our paths to allow long paths on windows\n    let out_dir = std::env::var(\"OUT_DIR\")\n      .map_err(|_| EmbeddedAssetsError::OutDir)\n      .map(PathBuf::from)\n      .and_then(|p| p.canonicalize().map_err(|_| EmbeddedAssetsError::OutDir))\n      .map(|p| p.join(TARGET_PATH))?;\n\n    // make sure that our output directory is created\n    std::fs::create_dir_all(&out_dir).map_err(|_| EmbeddedAssetsError::OutDir)?;\n\n    // get a hash of the input - allows for caching existing files\n    let hash = crate::checksum(&input).map_err(EmbeddedAssetsError::Hex)?;\n\n    // use the content hash to determine filename, keep extensions that exist\n    let out_path = if let Some(ext) = path.extension().and_then(|e| e.to_str()) {\n      out_dir.join(format!(\"{hash}.{ext}\"))\n    } else {\n      out_dir.join(hash)\n    };\n\n    // only compress and write to the file if it doesn't already exist.\n    if !out_path.exists() {\n      #[allow(unused_mut)]\n      let mut out_file =\n        File::create(&out_path).map_err(|error| EmbeddedAssetsError::AssetWrite {\n          path: out_path.clone(),\n          error,\n        })?;\n\n      #[cfg(not(feature = \"compression\"))]\n      {\n        use std::io::Write;\n        out_file\n          .write_all(&input)\n          .map_err(|error| EmbeddedAssetsError::AssetWrite {\n            path: path.to_owned(),\n            error,\n          })?;\n      }\n\n      #[cfg(feature = \"compression\")]\n      {\n        let mut input = std::io::Cursor::new(input);\n        // entirely write input to the output file path with compression\n        brotli::BrotliCompress(&mut input, &mut out_file, &Self::compression_settings()).map_err(\n          |error| EmbeddedAssetsError::AssetWrite {\n            path: path.to_owned(),\n            error,\n          },\n        )?;\n      }\n    }\n\n    Ok((key, (path.into(), out_path)))\n  }\n}\n\nimpl ToTokens for EmbeddedAssets {\n  fn to_tokens(&self, tokens: &mut TokenStream) {\n    let mut assets = TokenStream::new();\n    for (key, (input, output)) in &self.assets {\n      let key: &str = key.as_ref();\n      let input = input.display().to_string();\n      let output = output.display().to_string();\n\n      // add original asset as a compiler dependency, rely on dead code elimination to clean it up\n      assets.append_all(quote!(#key => {\n        const _: &[u8] = include_bytes!(#input);\n        include_bytes!(#output)\n      },));\n    }\n\n    let mut global_hashes = TokenStream::new();\n    for script_hash in &self.csp_hashes.scripts {\n      let hash = script_hash.as_str();\n      global_hashes.append_all(quote!(CspHash::Script(#hash),));\n    }\n\n    for style_hash in &self.csp_hashes.styles {\n      let hash = style_hash.as_str();\n      global_hashes.append_all(quote!(CspHash::Style(#hash),));\n    }\n\n    let mut html_hashes = TokenStream::new();\n    for (path, hashes) in &self.csp_hashes.inline_scripts {\n      let key = path.as_str();\n      let mut value = TokenStream::new();\n      for script_hash in hashes {\n        let hash = script_hash.as_str();\n        value.append_all(quote!(CspHash::Script(#hash),));\n      }\n      html_hashes.append_all(quote!(#key => &[#value],));\n    }\n\n    // we expect phf related items to be in path when generating the path code\n    tokens.append_all(quote! {{\n        #[allow(unused_imports)]\n        use ::tauri::utils::assets::{CspHash, EmbeddedAssets, phf, phf::phf_map};\n        EmbeddedAssets::new(phf_map! { #assets }, &[#global_hashes], phf_map! { #html_hashes })\n    }});\n  }\n}\n\npub(crate) fn ensure_out_dir() -> EmbeddedAssetsResult<PathBuf> {\n  let out_dir = std::env::var(\"OUT_DIR\")\n    .map_err(|_| EmbeddedAssetsError::OutDir)\n    .map(PathBuf::from)\n    .and_then(|p| p.canonicalize().map_err(|_| EmbeddedAssetsError::OutDir))?;\n\n  // make sure that our output directory is created\n  std::fs::create_dir_all(&out_dir).map_err(|_| EmbeddedAssetsError::OutDir)?;\n  Ok(out_dir)\n}\n"
  },
  {
    "path": "crates/tauri-codegen/src/image.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::{\n  embedded_assets::{EmbeddedAssetsError, EmbeddedAssetsResult},\n  Cached,\n};\nuse proc_macro2::TokenStream;\nuse quote::{quote, ToTokens, TokenStreamExt};\nuse std::{ffi::OsStr, io::Cursor, path::Path};\n\n/// The format the Icon is consumed as.\npub(crate) enum IconFormat {\n  /// The image, completely unmodified.\n  Raw,\n\n  /// RGBA raw data, meant to be consumed by [`tauri::image::Image`].\n  Image { width: u32, height: u32 },\n}\n\npub struct CachedIcon {\n  cache: Cached,\n  format: IconFormat,\n  root: TokenStream,\n}\n\nimpl CachedIcon {\n  pub fn new(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {\n    match icon.extension().map(OsStr::to_string_lossy).as_deref() {\n      Some(\"png\") => Self::new_png(root, icon),\n      Some(\"ico\") => Self::new_ico(root, icon),\n      unknown => Err(EmbeddedAssetsError::InvalidImageExtension {\n        extension: unknown.unwrap_or_default().into(),\n        path: icon.to_path_buf(),\n      }),\n    }\n  }\n\n  /// Cache the icon without any manipulation.\n  pub fn new_raw(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {\n    let buf = Self::open(icon);\n    Cached::try_from(buf).map(|cache| Self {\n      cache,\n      root: root.clone(),\n      format: IconFormat::Raw,\n    })\n  }\n\n  /// Cache an ICO icon as RGBA data, see [`ImageFormat::Image`].\n  pub fn new_ico(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {\n    let buf = Self::open(icon);\n\n    let icon_dir = ico::IconDir::read(Cursor::new(&buf))\n      .unwrap_or_else(|e| panic!(\"failed to parse icon {}: {}\", icon.display(), e));\n\n    let entry = &icon_dir.entries()[0];\n    let rgba = entry\n      .decode()\n      .unwrap_or_else(|e| panic!(\"failed to decode icon {}: {}\", icon.display(), e))\n      .rgba_data()\n      .to_vec();\n\n    Cached::try_from(rgba).map(|cache| Self {\n      cache,\n      root: root.clone(),\n      format: IconFormat::Image {\n        width: entry.width(),\n        height: entry.height(),\n      },\n    })\n  }\n\n  /// Cache a PNG icon as RGBA data, see [`ImageFormat::Image`].\n  pub fn new_png(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {\n    let buf = Self::open(icon);\n    let decoder = png::Decoder::new(Cursor::new(&buf));\n    let mut reader = decoder\n      .read_info()\n      .unwrap_or_else(|e| panic!(\"failed to read icon {}: {}\", icon.display(), e));\n\n    if reader.output_color_type().0 != png::ColorType::Rgba {\n      panic!(\"icon {} is not RGBA\", icon.display());\n    }\n\n    let mut rgba = Vec::with_capacity(reader.output_buffer_size());\n    while let Ok(Some(row)) = reader.next_row() {\n      rgba.extend(row.data());\n    }\n\n    Cached::try_from(rgba).map(|cache| Self {\n      cache,\n      root: root.clone(),\n      format: IconFormat::Image {\n        width: reader.info().width,\n        height: reader.info().height,\n      },\n    })\n  }\n\n  fn open(path: &Path) -> Vec<u8> {\n    std::fs::read(path).unwrap_or_else(|e| panic!(\"failed to open icon {}: {}\", path.display(), e))\n  }\n}\n\nimpl ToTokens for CachedIcon {\n  fn to_tokens(&self, tokens: &mut TokenStream) {\n    let root = &self.root;\n    let cache = &self.cache;\n    let raw = quote!(::std::include_bytes!(#cache));\n    tokens.append_all(match self.format {\n      IconFormat::Raw => raw,\n      IconFormat::Image { width, height } => {\n        quote!(#root::image::Image::new(#raw, #width, #height))\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "crates/tauri-codegen/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! - Embed, hash, and compress assets, including icons for the app as well as the tray icon.\n//! - Parse `tauri.conf.json` at compile time and generate the Config struct.\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n\npub use self::context::{context_codegen, ContextData};\nuse crate::embedded_assets::{ensure_out_dir, EmbeddedAssetsError};\nuse proc_macro2::TokenStream;\nuse quote::{quote, ToTokens, TokenStreamExt};\nuse std::{\n  borrow::Cow,\n  fmt::{self, Write},\n  path::{Path, PathBuf},\n};\npub use tauri_utils::config::{parse::ConfigError, Config};\nuse tauri_utils::platform::Target;\nuse tauri_utils::write_if_changed;\n\nmod context;\npub mod embedded_assets;\npub mod image;\n#[doc(hidden)]\npub mod vendor;\n\n/// Represents all the errors that can happen while reading the config during codegen.\n#[derive(Debug, thiserror::Error)]\n#[non_exhaustive]\npub enum CodegenConfigError {\n  #[error(\"unable to access current working directory: {0}\")]\n  CurrentDir(std::io::Error),\n\n  // this error should be \"impossible\" because we use std::env::current_dir() - cover it anyways\n  #[error(\"Tauri config file has no parent, this shouldn't be possible. file an issue on https://github.com/tauri-apps/tauri - target {0}\")]\n  Parent(PathBuf),\n\n  #[error(\"unable to parse inline JSON TAURI_CONFIG env var: {0}\")]\n  FormatInline(serde_json::Error),\n\n  #[error(transparent)]\n  Json(#[from] serde_json::Error),\n\n  #[error(\"{0}\")]\n  ConfigError(#[from] ConfigError),\n}\n\n/// Get the [`Config`] from the `TAURI_CONFIG` environmental variable, or read from the passed path.\n///\n/// If the passed path is relative, it should be relative to the current working directory of the\n/// compiling crate.\npub fn get_config(path: &Path) -> Result<(Config, PathBuf), CodegenConfigError> {\n  let path = if path.is_relative() {\n    let cwd = std::env::current_dir().map_err(CodegenConfigError::CurrentDir)?;\n    Cow::Owned(cwd.join(path))\n  } else {\n    Cow::Borrowed(path)\n  };\n\n  // this should be impossible because of the use of `current_dir()` above, but handle it anyways\n  let parent = path\n    .parent()\n    .map(ToOwned::to_owned)\n    .ok_or_else(|| CodegenConfigError::Parent(path.into_owned()))?;\n\n  let target = std::env::var(\"TAURI_ENV_TARGET_TRIPLE\")\n    .as_deref()\n    .map(Target::from_triple)\n    .unwrap_or_else(|_| Target::current());\n\n  // in the future we may want to find a way to not need the TAURI_CONFIG env var so that\n  // it is impossible for the content of two separate configs to get mixed up. The chances are\n  // already unlikely unless the developer goes out of their way to run the cli on a different\n  // project than the target crate.\n  let mut config =\n    serde_json::from_value(tauri_utils::config::parse::read_from(target, &parent)?.0)?;\n\n  if let Ok(env) = std::env::var(\"TAURI_CONFIG\") {\n    let merge_config: serde_json::Value =\n      serde_json::from_str(&env).map_err(CodegenConfigError::FormatInline)?;\n    json_patch::merge(&mut config, &merge_config);\n  }\n\n  // Set working directory to where `tauri.config.json` is, so that relative paths in it are parsed correctly.\n  let old_cwd = std::env::current_dir().map_err(CodegenConfigError::CurrentDir)?;\n  std::env::set_current_dir(parent.clone()).map_err(CodegenConfigError::CurrentDir)?;\n\n  let config = serde_json::from_value(config)?;\n\n  // Reset working directory.\n  std::env::set_current_dir(old_cwd).map_err(CodegenConfigError::CurrentDir)?;\n\n  Ok((config, parent))\n}\n\n/// Create a blake3 checksum of the passed bytes.\nfn checksum(bytes: &[u8]) -> Result<String, fmt::Error> {\n  let mut hasher = vendor::blake3_reference::Hasher::default();\n  hasher.update(bytes);\n\n  let mut bytes = [0u8; 32];\n  hasher.finalize(&mut bytes);\n\n  let mut hex = String::with_capacity(2 * bytes.len());\n  for b in bytes {\n    write!(hex, \"{b:02x}\")?;\n  }\n  Ok(hex)\n}\n\n/// Cache the data to `$OUT_DIR`, only if it does not already exist.\n///\n/// Due to using a checksum as the filename, an existing file should be the exact same content\n/// as the data being checked.\nstruct Cached {\n  checksum: String,\n}\n\nimpl TryFrom<String> for Cached {\n  type Error = EmbeddedAssetsError;\n\n  fn try_from(value: String) -> Result<Self, Self::Error> {\n    Self::try_from(Vec::from(value))\n  }\n}\n\nimpl TryFrom<Vec<u8>> for Cached {\n  type Error = EmbeddedAssetsError;\n\n  fn try_from(content: Vec<u8>) -> Result<Self, Self::Error> {\n    let checksum = checksum(content.as_ref()).map_err(EmbeddedAssetsError::Hex)?;\n    let path = ensure_out_dir()?.join(&checksum);\n\n    write_if_changed(&path, &content)\n      .map(|_| Self { checksum })\n      .map_err(|error| EmbeddedAssetsError::AssetWrite { path, error })\n  }\n}\n\nimpl ToTokens for Cached {\n  fn to_tokens(&self, tokens: &mut TokenStream) {\n    let path = &self.checksum;\n    tokens.append_all(quote!(::std::concat!(::std::env!(\"OUT_DIR\"), \"/\", #path)))\n  }\n}\n"
  },
  {
    "path": "crates/tauri-codegen/src/vendor/blake3_reference.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! This is a lightly modified version of the BLAKE3 reference implementation.\n//!\n//! The changes applied are to remove unused item warnings due to using it\n//! vendored along with some minor clippy suggestions. No logic changes. I\n//! suggest diffing against the original to find all the changes.\n//!\n//! ## Original Header\n//! This is the reference implementation of BLAKE3. It is used for testing and\n//! as a readable example of the algorithms involved. Section 5.1 of [the BLAKE3\n//! spec](https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf)\n//! discusses this implementation. You can render docs for this implementation\n//! by running `cargo doc --open` in this directory.\n//!\n//! # Example\n//!\n//! ```\n//! let mut hasher = tauri_codegen::vendor::blake3_reference::Hasher::new();\n//! hasher.update(b\"abc\");\n//! hasher.update(b\"def\");\n//! let mut hash = [0; 32];\n//! hasher.finalize(&mut hash);\n//! let mut extended_hash = [0; 500];\n//! hasher.finalize(&mut extended_hash);\n//! assert_eq!(hash, extended_hash[..32]);\n//! ```\n//!\n//! CC0-1.0 OR Apache-2.0\n\nuse core::cmp::min;\nuse core::convert::TryInto;\n\nconst OUT_LEN: usize = 32;\nconst BLOCK_LEN: usize = 64;\nconst CHUNK_LEN: usize = 1024;\n\nconst CHUNK_START: u32 = 1 << 0;\nconst CHUNK_END: u32 = 1 << 1;\nconst PARENT: u32 = 1 << 2;\nconst ROOT: u32 = 1 << 3;\n\nconst IV: [u32; 8] = [\n  0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,\n];\n\nconst MSG_PERMUTATION: [usize; 16] = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8];\n\n// The mixing function, G, which mixes either a column or a diagonal.\nfn g(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize, mx: u32, my: u32) {\n  state[a] = state[a].wrapping_add(state[b]).wrapping_add(mx);\n  state[d] = (state[d] ^ state[a]).rotate_right(16);\n  state[c] = state[c].wrapping_add(state[d]);\n  state[b] = (state[b] ^ state[c]).rotate_right(12);\n  state[a] = state[a].wrapping_add(state[b]).wrapping_add(my);\n  state[d] = (state[d] ^ state[a]).rotate_right(8);\n  state[c] = state[c].wrapping_add(state[d]);\n  state[b] = (state[b] ^ state[c]).rotate_right(7);\n}\n\nfn round(state: &mut [u32; 16], m: &[u32; 16]) {\n  // Mix the columns.\n  g(state, 0, 4, 8, 12, m[0], m[1]);\n  g(state, 1, 5, 9, 13, m[2], m[3]);\n  g(state, 2, 6, 10, 14, m[4], m[5]);\n  g(state, 3, 7, 11, 15, m[6], m[7]);\n  // Mix the diagonals.\n  g(state, 0, 5, 10, 15, m[8], m[9]);\n  g(state, 1, 6, 11, 12, m[10], m[11]);\n  g(state, 2, 7, 8, 13, m[12], m[13]);\n  g(state, 3, 4, 9, 14, m[14], m[15]);\n}\n\nfn permute(m: &mut [u32; 16]) {\n  let mut permuted = [0; 16];\n  for i in 0..16 {\n    permuted[i] = m[MSG_PERMUTATION[i]];\n  }\n  *m = permuted;\n}\n\nfn compress(\n  chaining_value: &[u32; 8],\n  block_words: &[u32; 16],\n  counter: u64,\n  block_len: u32,\n  flags: u32,\n) -> [u32; 16] {\n  let mut state = [\n    chaining_value[0],\n    chaining_value[1],\n    chaining_value[2],\n    chaining_value[3],\n    chaining_value[4],\n    chaining_value[5],\n    chaining_value[6],\n    chaining_value[7],\n    IV[0],\n    IV[1],\n    IV[2],\n    IV[3],\n    counter as u32,\n    (counter >> 32) as u32,\n    block_len,\n    flags,\n  ];\n  let mut block = *block_words;\n\n  round(&mut state, &block); // round 1\n  permute(&mut block);\n  round(&mut state, &block); // round 2\n  permute(&mut block);\n  round(&mut state, &block); // round 3\n  permute(&mut block);\n  round(&mut state, &block); // round 4\n  permute(&mut block);\n  round(&mut state, &block); // round 5\n  permute(&mut block);\n  round(&mut state, &block); // round 6\n  permute(&mut block);\n  round(&mut state, &block); // round 7\n\n  for i in 0..8 {\n    state[i] ^= state[i + 8];\n    state[i + 8] ^= chaining_value[i];\n  }\n  state\n}\n\nfn first_8_words(compression_output: [u32; 16]) -> [u32; 8] {\n  compression_output[0..8].try_into().unwrap()\n}\n\nfn words_from_little_endian_bytes(bytes: &[u8], words: &mut [u32]) {\n  debug_assert_eq!(bytes.len(), 4 * words.len());\n  for (four_bytes, word) in bytes.chunks_exact(4).zip(words) {\n    *word = u32::from_le_bytes(four_bytes.try_into().unwrap());\n  }\n}\n\n// Each chunk or parent node can produce either an 8-word chaining value or, by\n// setting the ROOT flag, any number of final output bytes. The Output struct\n// captures the state just prior to choosing between those two possibilities.\nstruct Output {\n  input_chaining_value: [u32; 8],\n  block_words: [u32; 16],\n  counter: u64,\n  block_len: u32,\n  flags: u32,\n}\n\nimpl Output {\n  fn chaining_value(&self) -> [u32; 8] {\n    first_8_words(compress(\n      &self.input_chaining_value,\n      &self.block_words,\n      self.counter,\n      self.block_len,\n      self.flags,\n    ))\n  }\n\n  fn root_output_bytes(&self, out_slice: &mut [u8]) {\n    for (output_block_counter, out_block) in (0u64..).zip(out_slice.chunks_mut(2 * OUT_LEN)) {\n      let words = compress(\n        &self.input_chaining_value,\n        &self.block_words,\n        output_block_counter,\n        self.block_len,\n        self.flags | ROOT,\n      );\n      // The output length might not be a multiple of 4.\n      for (word, out_word) in words.iter().zip(out_block.chunks_mut(4)) {\n        out_word.copy_from_slice(&word.to_le_bytes()[..out_word.len()]);\n      }\n    }\n  }\n}\n\nstruct ChunkState {\n  chaining_value: [u32; 8],\n  chunk_counter: u64,\n  block: [u8; BLOCK_LEN],\n  block_len: u8,\n  blocks_compressed: u8,\n  flags: u32,\n}\n\nimpl ChunkState {\n  fn new(key_words: [u32; 8], chunk_counter: u64, flags: u32) -> Self {\n    Self {\n      chaining_value: key_words,\n      chunk_counter,\n      block: [0; BLOCK_LEN],\n      block_len: 0,\n      blocks_compressed: 0,\n      flags,\n    }\n  }\n\n  fn len(&self) -> usize {\n    BLOCK_LEN * self.blocks_compressed as usize + self.block_len as usize\n  }\n\n  fn start_flag(&self) -> u32 {\n    if self.blocks_compressed == 0 {\n      CHUNK_START\n    } else {\n      0\n    }\n  }\n\n  fn update(&mut self, mut input: &[u8]) {\n    while !input.is_empty() {\n      // If the block buffer is full, compress it and clear it. More\n      // input is coming, so this compression is not CHUNK_END.\n      if self.block_len as usize == BLOCK_LEN {\n        let mut block_words = [0; 16];\n        words_from_little_endian_bytes(&self.block, &mut block_words);\n        self.chaining_value = first_8_words(compress(\n          &self.chaining_value,\n          &block_words,\n          self.chunk_counter,\n          BLOCK_LEN as u32,\n          self.flags | self.start_flag(),\n        ));\n        self.blocks_compressed += 1;\n        self.block = [0; BLOCK_LEN];\n        self.block_len = 0;\n      }\n\n      // Copy input bytes into the block buffer.\n      let want = BLOCK_LEN - self.block_len as usize;\n      let take = min(want, input.len());\n      self.block[self.block_len as usize..][..take].copy_from_slice(&input[..take]);\n      self.block_len += take as u8;\n      input = &input[take..];\n    }\n  }\n\n  fn output(&self) -> Output {\n    let mut block_words = [0; 16];\n    words_from_little_endian_bytes(&self.block, &mut block_words);\n    Output {\n      input_chaining_value: self.chaining_value,\n      block_words,\n      counter: self.chunk_counter,\n      block_len: self.block_len as u32,\n      flags: self.flags | self.start_flag() | CHUNK_END,\n    }\n  }\n}\n\nfn parent_output(\n  left_child_cv: [u32; 8],\n  right_child_cv: [u32; 8],\n  key_words: [u32; 8],\n  flags: u32,\n) -> Output {\n  let mut block_words = [0; 16];\n  block_words[..8].copy_from_slice(&left_child_cv);\n  block_words[8..].copy_from_slice(&right_child_cv);\n  Output {\n    input_chaining_value: key_words,\n    block_words,\n    counter: 0,                  // Always 0 for parent nodes.\n    block_len: BLOCK_LEN as u32, // Always BLOCK_LEN (64) for parent nodes.\n    flags: PARENT | flags,\n  }\n}\n\nfn parent_cv(\n  left_child_cv: [u32; 8],\n  right_child_cv: [u32; 8],\n  key_words: [u32; 8],\n  flags: u32,\n) -> [u32; 8] {\n  parent_output(left_child_cv, right_child_cv, key_words, flags).chaining_value()\n}\n\n/// An incremental hasher that can accept any number of writes.\npub struct Hasher {\n  chunk_state: ChunkState,\n  key_words: [u32; 8],\n  cv_stack: [[u32; 8]; 54], // Space for 54 subtree chaining values:\n  cv_stack_len: u8,         // 2^54 * CHUNK_LEN = 2^64\n  flags: u32,\n}\n\nimpl Hasher {\n  fn new_internal(key_words: [u32; 8], flags: u32) -> Self {\n    Self {\n      chunk_state: ChunkState::new(key_words, 0, flags),\n      key_words,\n      cv_stack: [[0; 8]; 54],\n      cv_stack_len: 0,\n      flags,\n    }\n  }\n\n  /// Construct a new `Hasher` for the regular hash function.\n  pub fn new() -> Self {\n    Self::new_internal(IV, 0)\n  }\n\n  fn push_stack(&mut self, cv: [u32; 8]) {\n    self.cv_stack[self.cv_stack_len as usize] = cv;\n    self.cv_stack_len += 1;\n  }\n\n  fn pop_stack(&mut self) -> [u32; 8] {\n    self.cv_stack_len -= 1;\n    self.cv_stack[self.cv_stack_len as usize]\n  }\n\n  // Section 5.1.2 of the BLAKE3 spec explains this algorithm in more detail.\n  fn add_chunk_chaining_value(&mut self, mut new_cv: [u32; 8], mut total_chunks: u64) {\n    // This chunk might complete some subtrees. For each completed subtree,\n    // its left child will be the current top entry in the CV stack, and\n    // its right child will be the current value of `new_cv`. Pop each left\n    // child off the stack, merge it with `new_cv`, and overwrite `new_cv`\n    // with the result. After all these merges, push the final value of\n    // `new_cv` onto the stack. The number of completed subtrees is given\n    // by the number of trailing 0-bits in the new total number of chunks.\n    while total_chunks & 1 == 0 {\n      new_cv = parent_cv(self.pop_stack(), new_cv, self.key_words, self.flags);\n      total_chunks >>= 1;\n    }\n    self.push_stack(new_cv);\n  }\n\n  /// Add input to the hash state. This can be called any number of times.\n  pub fn update(&mut self, mut input: &[u8]) {\n    while !input.is_empty() {\n      // If the current chunk is complete, finalize it and reset the\n      // chunk state. More input is coming, so this chunk is not ROOT.\n      if self.chunk_state.len() == CHUNK_LEN {\n        let chunk_cv = self.chunk_state.output().chaining_value();\n        let total_chunks = self.chunk_state.chunk_counter + 1;\n        self.add_chunk_chaining_value(chunk_cv, total_chunks);\n        self.chunk_state = ChunkState::new(self.key_words, total_chunks, self.flags);\n      }\n\n      // Compress input bytes into the current chunk state.\n      let want = CHUNK_LEN - self.chunk_state.len();\n      let take = min(want, input.len());\n      self.chunk_state.update(&input[..take]);\n      input = &input[take..];\n    }\n  }\n\n  /// Finalize the hash and write any number of output bytes.\n  pub fn finalize(&self, out_slice: &mut [u8]) {\n    // Starting with the Output from the current chunk, compute all the\n    // parent chaining values along the right edge of the tree, until we\n    // have the root Output.\n    let mut output = self.chunk_state.output();\n    let mut parent_nodes_remaining = self.cv_stack_len as usize;\n    while parent_nodes_remaining > 0 {\n      parent_nodes_remaining -= 1;\n      output = parent_output(\n        self.cv_stack[parent_nodes_remaining],\n        output.chaining_value(),\n        self.key_words,\n        self.flags,\n      );\n    }\n    output.root_output_bytes(out_slice);\n  }\n}\n\nimpl Default for Hasher {\n  fn default() -> Self {\n    Self::new()\n  }\n}\n"
  },
  {
    "path": "crates/tauri-codegen/src/vendor/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Manual vendored dependencies - NOT STABLE.\n//!\n//! This module and all submodules are not considered part of the public\n//! api. They can and will change at any time for any reason in any\n//! version.\n\npub mod blake3_reference;\n"
  },
  {
    "path": "crates/tauri-driver/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.0.5]\n\n### Bug Fixes\n\n- [`06f911aaf`](https://www.github.com/tauri-apps/tauri/commit/06f911aaff495121f08ebc77d9d1b41382298a1f) ([#14871](https://www.github.com/tauri-apps/tauri/pull/14871) by [@goosewobbler](https://www.github.com/tauri-apps/tauri/../../goosewobbler)) Prevent native WebDriver stdout from polluting tauri-driver's stdout used by test frameworks.\n\n## \\[2.0.4]\n\n### Enhancements\n\n- [`0cf2d9933`](https://www.github.com/tauri-apps/tauri/commit/0cf2d9933f20375349f9f307b4dd7049690030d9) ([#13238](https://www.github.com/tauri-apps/tauri/pull/13238)) Automatically append the `.exe` extension on the application path in `tauri:options`.\n\n### Bug Fixes\n\n- [`577c7ffc4`](https://www.github.com/tauri-apps/tauri/commit/577c7ffc45ef005403cdf698e595614038c7d1e1) ([#10108](https://www.github.com/tauri-apps/tauri/pull/10108)) Ensure the webdriver process is closed when the tauri-driver process finishes.\n\n## \\[2.0.3]\n\n### Bug Fixes\n\n- [`fb294af8e`](https://www.github.com/tauri-apps/tauri/commit/fb294af8e3717d547029f3bbf9323318e0d9861a) ([#12383](https://www.github.com/tauri-apps/tauri/pull/12383) by [@bicarlsen](https://www.github.com/tauri-apps/tauri/../../bicarlsen)) Parse ms:edgeOptions separately to prevent `invalid argument` errors.\n\n## \\[2.0.2]\n\n### Enhancements\n\n- [`70f96e322`](https://www.github.com/tauri-apps/tauri/commit/70f96e3222871a1931a18d6093d7efcbc59e7fee) ([#12240](https://www.github.com/tauri-apps/tauri/pull/12240) by [@getong](https://www.github.com/tauri-apps/tauri/../../getong)) Updated `hyper` to version 1. This won't affect the user facing API.\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78.\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n## \\[0.1.5]\n\n### Bug Fixes\n\n- [`5f64ed2b7`](https://www.github.com/tauri-apps/tauri/commit/5f64ed2b78201b7e379b6234f7a799d9695b11d7) ([#10738](https://www.github.com/tauri-apps/tauri/pull/10738) by [@chippers](https://www.github.com/tauri-apps/tauri/../../chippers)) support both 1.x and 2.x automation env vars in `tauri-driver`\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n## \\[0.1.4]\n\n### New Features\n\n- [`435d7513`](https://www.github.com/tauri-apps/tauri/commit/435d7513e45eab8b512e9a7e695a1adef8a98a46)([#8609](https://www.github.com/tauri-apps/tauri/pull/8609)) Added `webviewOptions` object to the `tauri:options` capability to configure the [Edge webview options](https://learn.microsoft.com/en-us/microsoft-edge/webdriver-chromium/capabilities-edge-options#webviewoptions-object) on Windows.\n\n## \\[0.1.3]\n\n### What's Changed\n\n- [`9edebbba`](https://www.github.com/tauri-apps/tauri/commit/9edebbba4ec472772b2f6307232e8d256f62c8ba)([#7475](https://www.github.com/tauri-apps/tauri/pull/7475)) Update locked dependencies to fix a Windows build issue when using them with a recent Rust compiler.\n- [`9edebbba`](https://www.github.com/tauri-apps/tauri/commit/9edebbba4ec472772b2f6307232e8d256f62c8ba)([#7475](https://www.github.com/tauri-apps/tauri/pull/7475)) Bump minimum Rust version to `1.60` to be in line with the rest of the Tauri project.\n\n## \\[0.1.2]\n\n- Expose `native-host` option in tauri-driver and set default to `127.0.0.1`.\n  - [cd9dfc7b](https://www.github.com/tauri-apps/tauri/commit/cd9dfc7b9a3fe0e04e40d9b0f9be674aefd0d725) fix(driver): expose native-host option and set default to 127.0.0.1 ([#3816](https://www.github.com/tauri-apps/tauri/pull/3816)) on 2022-03-31\n\n## \\[0.1.1]\n\n- The minimum Rust version is now `1.56`.\n  - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22\n- Add `args` field (array of application CLI arguments) to the `tauri:options` capabilities.\n  - [d0970e34](https://www.github.com/tauri-apps/tauri/commit/d0970e3499297a6c102a36f2dc479d3d657bfaf3) feat(driver): add `args` to `tauri:options` ([#3154](https://www.github.com/tauri-apps/tauri/pull/3154)) on 2022-01-03\n\n## \\[0.1.0]\n\n- Initial release including Linux and Windows support.\n  - [be76fb1d](https://www.github.com/tauri-apps/tauri/commit/be76fb1dfe73a1605cc2ad246418579f4c2e1999) WebDriver support ([#1972](https://www.github.com/tauri-apps/tauri/pull/1972)) on 2021-06-23\n  - [c22e5a7c](https://www.github.com/tauri-apps/tauri/commit/c22e5a7c2ebede41657973b80eff6b68106817fc) fix(covector): keep `tauri-driver` version as alpha on 2021-06-23\n  - [b4426eda](https://www.github.com/tauri-apps/tauri/commit/b4426eda9e64fcdd25a2d72e548b8b0fbfa09619) Revert \"WebDriver support ([#1972](https://www.github.com/tauri-apps/tauri/pull/1972))\" on 2021-06-23\n  - [4b2aa356](https://www.github.com/tauri-apps/tauri/commit/4b2aa35684632ed2afd7dec4ad848df5704868e4) Add back WebDriver support ([#2324](https://www.github.com/tauri-apps/tauri/pull/2324)) on 2021-08-01\n"
  },
  {
    "path": "crates/tauri-driver/Cargo.toml",
    "content": "[package]\nname = \"tauri-driver\"\nversion = \"2.0.5\"\nauthors = [\"Tauri Programme within The Commons Conservancy\"]\ncategories = [\"gui\", \"web-programming\"]\nlicense = \"Apache-2.0 OR MIT\"\nhomepage = \"https://tauri.app\"\nrepository = \"https://github.com/tauri-apps/tauri\"\ndescription = \"Webdriver server for Tauri applications\"\nreadme = \"README.md\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\n\n[dependencies]\nanyhow = \"1\"\nfutures = \"0.3\"\nfutures-util = \"0.3\"\nhttp-body-util = \"0.1\"\nhyper = { version = \"1\", features = [\"client\", \"http1\", \"server\"] }\nhyper-util = { version = \"0.1\", features = [\n  \"client\",\n  \"client-legacy\",\n  \"http1\",\n  \"server\",\n  \"tokio\",\n] }\npico-args = \"0.5\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\ntokio = { version = \"1\", features = [\"macros\"] }\nwhich = \"8\"\n\n[target.\"cfg(unix)\".dependencies]\nsignal-hook = \"0.4\"\nsignal-hook-tokio = { version = \"0.4\", features = [\"futures-v0_3\"] }\n\n[target.\"cfg(windows)\".dependencies]\nwin32job = \"2\"\n"
  },
  {
    "path": "crates/tauri-driver/LICENSE.spdx",
    "content": "../../LICENSE.spdx"
  },
  {
    "path": "crates/tauri-driver/LICENSE_APACHE-2.0",
    "content": "../../LICENSE_APACHE-2.0"
  },
  {
    "path": "crates/tauri-driver/LICENSE_MIT",
    "content": "../../LICENSE_MIT"
  },
  {
    "path": "crates/tauri-driver/README.md",
    "content": "# `tauri-driver` _(pre-alpha)_\n\nCross-platform WebDriver server for Tauri applications.\n\nThis is a [WebDriver Intermediary Node] that wraps the native WebDriver server\nfor platforms that [Tauri] supports. Your WebDriver client will connect to the\nrunning `tauri-driver` server, and `tauri-driver` will handle starting the\nnative WebDriver server for you behind the scenes. It requires two separate\nports to be used since two distinct [WebDriver Remote Ends] run.\n\nYou can configure the ports used with arguments when starting the binary:\n\n- `--port` (default: `4444`)\n- `--native-port` (default: `4445`)\n\nSupported platforms:\n\n- Linux via `WebKitWebDriver`\n- Windows via [Microsoft Edge Driver]\n- **[Todo]** macOS via [Appium Mac2 Driver] (probably)\n\n_note: the (probably) items haven't been proof-of-concept'd yet, and if it is\nnot possible to use the listed native webdriver, then a custom implementation\nwill be used that wraps around [wry]._\n\n## Installation\n\nYou can install tauri-driver using Cargo:\n\n```sh\ncargo install tauri-driver --locked\n```\n\n## Trying it out\n\nCheck out the documentation at https://tauri.app/develop/tests/webdriver/,\nincluding a small example application with WebDriver tests.\n\n[WebDriver Intermediary Node]: https://www.w3.org/TR/webdriver/#dfn-intermediary-nodes\n[WebDriver Remote Ends]: https://www.w3.org/TR/webdriver/#dfn-remote-ends\n[Microsoft Edge Driver]: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/\n[Appium Mac2 Driver]: https://github.com/appium/appium-mac2-driver\n[wry]: https://github.com/tauri-apps/wry\n[Tauri]: https://github.com/tauri-apps/tauri\n"
  },
  {
    "path": "crates/tauri-driver/src/cli.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::PathBuf;\n\nconst HELP: &str = \"\\\nUSAGE: tauri-driver [FLAGS] [OPTIONS]\n\nFLAGS:\n  -h, --help              Prints help information\n\nOPTIONS:\n  --port NUMBER           Sets the tauri-driver intermediary port\n  --native-port NUMBER    Sets the port of the underlying WebDriver\n  --native-host HOST      Sets the host of the underlying WebDriver (Linux only)\n  --native-driver PATH    Sets the path to the native WebDriver binary\n\";\n\n#[derive(Debug, Clone)]\npub struct Args {\n  pub port: u16,\n  pub native_port: u16,\n  pub native_host: String,\n  pub native_driver: Option<PathBuf>,\n}\n\nimpl From<pico_args::Arguments> for Args {\n  fn from(mut args: pico_args::Arguments) -> Self {\n    // if the user wanted help, we don't care about parsing the rest of the args\n    if args.contains([\"-h\", \"--help\"]) {\n      println!(\"{HELP}\");\n      std::process::exit(0);\n    }\n\n    let native_driver = match args.opt_value_from_str(\"--native-driver\") {\n      Ok(native_driver) => native_driver,\n      Err(e) => {\n        eprintln!(\"Error while parsing option --native-driver: {e}\");\n        std::process::exit(1);\n      }\n    };\n\n    let parsed = Args {\n      port: args.value_from_str(\"--port\").unwrap_or(4444),\n      native_port: args.value_from_str(\"--native-port\").unwrap_or(4445),\n      native_host: args\n        .value_from_str(\"--native-host\")\n        .unwrap_or(String::from(\"127.0.0.1\")),\n      native_driver,\n    };\n\n    // be strict about accepting args, error for anything extraneous\n    let rest = args.finish();\n    if !rest.is_empty() {\n      eprintln!(\"Error: unused arguments left: {rest:?}\");\n      eprintln!(\"{HELP}\");\n      std::process::exit(1);\n    }\n\n    parsed\n  }\n}\n"
  },
  {
    "path": "crates/tauri-driver/src/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Cross-platform WebDriver server for Tauri applications.\n//!\n//! This is a [WebDriver Intermediary Node](https://www.w3.org/TR/webdriver/#dfn-intermediary-nodes) that wraps the native WebDriver server for platforms that [Tauri](https://github.com/tauri-apps/tauri) supports. Your WebDriver client will connect to the running `tauri-driver` server, and `tauri-driver` will handle starting the native WebDriver server for you behind the scenes. It requires two separate ports to be used since two distinct [WebDriver Remote Ends](https://www.w3.org/TR/webdriver/#dfn-remote-ends) run.\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n\n#[cfg(any(target_os = \"linux\", windows))]\nmod cli;\n#[cfg(any(target_os = \"linux\", windows))]\nmod server;\n#[cfg(any(target_os = \"linux\", windows))]\nmod webdriver;\n\n#[cfg(not(any(target_os = \"linux\", windows)))]\nfn main() {\n  println!(\"tauri-driver is not supported on this platform\");\n  std::process::exit(1);\n}\n\n#[cfg(any(target_os = \"linux\", windows))]\nfn main() {\n  let args = pico_args::Arguments::from_env().into();\n\n  #[cfg(windows)]\n  let _job_handle = {\n    let job = win32job::Job::create().unwrap();\n    let mut info = job.query_extended_limit_info().unwrap();\n    info.limit_kill_on_job_close();\n    job.set_extended_limit_info(&info).unwrap();\n    job.assign_current_process().unwrap();\n    job\n  };\n\n  // start the native webdriver on the port specified in args\n  let mut driver = webdriver::native(&args);\n  let driver = driver\n    .spawn()\n    .expect(\"error while running native webdriver\");\n\n  // start our webdriver intermediary node\n  if let Err(e) = server::run(args, driver) {\n    eprintln!(\"error while running server: {e}\");\n    std::process::exit(1);\n  }\n}\n"
  },
  {
    "path": "crates/tauri-driver/src/server.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::cli::Args;\nuse anyhow::Error;\nuse futures_util::TryFutureExt;\nuse http_body_util::{BodyExt, Full};\nuse hyper::{\n  body::{Bytes, Incoming},\n  header::CONTENT_LENGTH,\n  http::uri::Authority,\n  service::service_fn,\n  Method, Request, Response,\n};\nuse hyper_util::{\n  client::legacy::{connect::HttpConnector, Client},\n  rt::{TokioExecutor, TokioIo},\n  server::conn::auto,\n};\nuse serde::Deserialize;\nuse serde_json::{json, Map, Value};\nuse std::path::PathBuf;\nuse std::process::Child;\nuse tokio::net::TcpListener;\n\nconst TAURI_OPTIONS: &str = \"tauri:options\";\n\n#[derive(Debug, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct TauriOptions {\n  application: PathBuf,\n  #[serde(default)]\n  args: Vec<String>,\n  #[cfg(target_os = \"windows\")]\n  #[serde(default)]\n  webview_options: Option<Value>,\n}\n\nimpl TauriOptions {\n  #[cfg(target_os = \"linux\")]\n  fn into_native_object(self) -> Map<String, Value> {\n    let mut map = Map::new();\n    map.insert(\n      \"webkitgtk:browserOptions\".into(),\n      json!({\"binary\": self.application, \"args\": self.args}),\n    );\n    map\n  }\n\n  #[cfg(target_os = \"windows\")]\n  fn into_native_object(self) -> Map<String, Value> {\n    let mut ms_edge_options = Map::new();\n    ms_edge_options.insert(\n      \"binary\".into(),\n      json!(self.application.with_extension(\"exe\")),\n    );\n    ms_edge_options.insert(\"args\".into(), self.args.into());\n\n    if let Some(webview_options) = self.webview_options {\n      ms_edge_options.insert(\"webviewOptions\".into(), webview_options);\n    }\n\n    let mut map = Map::new();\n    map.insert(\"ms:edgeChromium\".into(), json!(true));\n    map.insert(\"browserName\".into(), json!(\"webview2\"));\n    map.insert(\"ms:edgeOptions\".into(), ms_edge_options.into());\n    map\n  }\n}\n\nasync fn handle(\n  client: Client<HttpConnector, Full<Bytes>>,\n  req: Request<Incoming>,\n  args: Args,\n) -> Result<Response<Incoming>, Error> {\n  // manipulate a new session to convert options to the native driver format\n  let new_req: Request<Full<Bytes>> =\n    if let (&Method::POST, \"/session\") = (req.method(), req.uri().path()) {\n      let (mut parts, body) = req.into_parts();\n\n      // get the body from the future stream and parse it as json\n      let body = body.collect().await?.to_bytes().to_vec();\n      let json: Value = serde_json::from_slice(&body)?;\n\n      // manipulate the json to convert from tauri option to native driver options\n      let json = map_capabilities(json);\n\n      // serialize json and update the content-length header to be accurate\n      let bytes = serde_json::to_vec(&json)?;\n      parts.headers.insert(CONTENT_LENGTH, bytes.len().into());\n\n      Request::from_parts(parts, Full::new(bytes.into()))\n    } else {\n      let (parts, body) = req.into_parts();\n\n      let body = body.collect().await?.to_bytes().to_vec();\n\n      Request::from_parts(parts, Full::new(body.into()))\n    };\n\n  client\n    .request(forward_to_native_driver(new_req, args)?)\n    .err_into()\n    .await\n}\n\n/// Transform the request to a request for the native webdriver server.\nfn forward_to_native_driver(\n  mut req: Request<Full<Bytes>>,\n  args: Args,\n) -> Result<Request<Full<Bytes>>, Error> {\n  let host: Authority = {\n    let headers = req.headers_mut();\n    headers.remove(\"host\").expect(\"hyper request has host\")\n  }\n  .to_str()?\n  .parse()?;\n\n  let path = req\n    .uri()\n    .path_and_query()\n    .expect(\"hyper request has uri\")\n    .clone();\n\n  let uri = format!(\n    \"http://{}:{}{}\",\n    host.host(),\n    args.native_port,\n    path.as_str()\n  );\n\n  let (mut parts, body) = req.into_parts();\n  parts.uri = uri.parse()?;\n  Ok(Request::from_parts(parts, body))\n}\n\n/// only happy path for now, no errors\nfn map_capabilities(mut json: Value) -> Value {\n  let mut native = None;\n  if let Some(capabilities) = json.get_mut(\"capabilities\") {\n    if let Some(always_match) = capabilities.get_mut(\"alwaysMatch\") {\n      if let Some(always_match) = always_match.as_object_mut() {\n        if let Some(tauri_options) = always_match.remove(TAURI_OPTIONS) {\n          if let Ok(options) = serde_json::from_value::<TauriOptions>(tauri_options) {\n            native = Some(options.into_native_object());\n          }\n        }\n\n        if let Some(native) = native.clone() {\n          always_match.extend(native);\n        }\n      }\n    }\n  }\n\n  if let Some(native) = native {\n    if let Some(desired) = json.get_mut(\"desiredCapabilities\") {\n      if let Some(desired) = desired.as_object_mut() {\n        desired.remove(TAURI_OPTIONS);\n        desired.extend(native);\n      }\n    }\n  }\n\n  json\n}\n\n#[tokio::main(flavor = \"current_thread\")]\npub async fn run(args: Args, mut _driver: Child) -> Result<(), Error> {\n  #[cfg(unix)]\n  let (signals_handle, signals_task) = {\n    use futures_util::StreamExt;\n    use signal_hook::consts::signal::*;\n\n    let signals = signal_hook_tokio::Signals::new([SIGTERM, SIGINT, SIGQUIT])?;\n    let signals_handle = signals.handle();\n    let signals_task = tokio::spawn(async move {\n      let mut signals = signals.fuse();\n      #[allow(clippy::never_loop)]\n      while let Some(signal) = signals.next().await {\n        match signal {\n          SIGTERM | SIGINT | SIGQUIT => {\n            _driver\n              .kill()\n              .expect(\"unable to kill native webdriver server\");\n            std::process::exit(0);\n          }\n          _ => unreachable!(),\n        }\n      }\n    });\n    (signals_handle, signals_task)\n  };\n\n  let address = std::net::SocketAddr::from(([127, 0, 0, 1], args.port));\n\n  // the client we use to proxy requests to the native webdriver\n  let client = Client::builder(TokioExecutor::new())\n    .http1_preserve_header_case(true)\n    .http1_title_case_headers(true)\n    .retry_canceled_requests(false)\n    .build_http();\n\n  // set up a http1 server that uses the service we just created\n  let srv = async move {\n    if let Ok(listener) = TcpListener::bind(address).await {\n      loop {\n        let client = client.clone();\n        let args = args.clone();\n        if let Ok((stream, _)) = listener.accept().await {\n          let io = TokioIo::new(stream);\n\n          tokio::task::spawn(async move {\n            if let Err(err) = auto::Builder::new(TokioExecutor::new())\n              .http1()\n              .title_case_headers(true)\n              .preserve_header_case(true)\n              .serve_connection(\n                io,\n                service_fn(|request| handle(client.clone(), request, args.clone())),\n              )\n              .await\n            {\n              println!(\"Error serving connection: {err:?}\");\n            }\n          });\n        } else {\n          println!(\"accept new stream fail, ignore here\");\n        }\n      }\n    } else {\n      println!(\"can not listen to address: {address:?}\");\n    }\n  };\n  srv.await;\n\n  #[cfg(unix)]\n  {\n    signals_handle.close();\n    signals_task.await?;\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-driver/src/webdriver.rs",
    "content": "// Copyright 2019-2026 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::cli::Args;\nuse std::{\n  env::current_dir,\n  process::{Command, Stdio},\n};\n\n// the name of the binary to find in $PATH\n#[cfg(target_os = \"linux\")]\nconst DRIVER_BINARY: &str = \"WebKitWebDriver\";\n\n#[cfg(target_os = \"windows\")]\nconst DRIVER_BINARY: &str = \"msedgedriver.exe\";\n\n/// Find the native driver binary in the PATH, or exits the process with an error.\npub fn native(args: &Args) -> Command {\n  let native_binary = match args.native_driver.as_deref() {\n    Some(custom) => {\n      if custom.exists() {\n        custom.to_owned()\n      } else {\n        eprintln!(\n          \"can not find the supplied binary path {}. This is currently required.\",\n          custom.display()\n        );\n        match current_dir() {\n          Ok(cwd) => eprintln!(\"current working directory: {}\", cwd.display()),\n          Err(error) => eprintln!(\"can not find current working directory: {error}\"),\n        }\n        std::process::exit(1);\n      }\n    }\n    None => match which::which(DRIVER_BINARY) {\n      Ok(binary) => binary,\n      Err(error) => {\n        eprintln!(\n          \"can not find binary {DRIVER_BINARY} in the PATH. This is currently required.\\\n          You can also pass a custom path with --native-driver\"\n        );\n        eprintln!(\"{error:?}\");\n        std::process::exit(1);\n      }\n    },\n  };\n\n  let mut cmd = Command::new(native_binary);\n  cmd.env(\"TAURI_AUTOMATION\", \"true\"); // 1.x\n  cmd.env(\"TAURI_WEBVIEW_AUTOMATION\", \"true\"); // 2.x\n  cmd.arg(format!(\"--port={}\", args.native_port));\n  cmd.arg(format!(\"--host={}\", args.native_host));\n\n  // Don't inherit stdout from parent to prevent native WebDriver binary/HTTP protocol data\n  // from corrupting tauri-driver's stdout (which gets captured by the test framework).\n  // Keep stderr inherited so WebDriver logs/errors are still visible.\n  cmd.stdout(Stdio::null());\n\n  cmd\n}\n"
  },
  {
    "path": "crates/tauri-macos-sign/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.3.3]\n\n### Dependencies\n\n- [`268bb339f`](https://www.github.com/tauri-apps/tauri/commit/268bb339f0c512f021cc94e102573432cf2696d0) ([#14766](https://www.github.com/tauri-apps/tauri/pull/14766) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Remove once-cell-regex from direct dependencies.\n\n## \\[2.3.2]\n\n### Dependencies\n\n- [`514cf21e1`](https://www.github.com/tauri-apps/tauri/commit/514cf21e1417c7a78a0db494f891ba79d948b73d) ([#14591](https://www.github.com/tauri-apps/tauri/pull/14591) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) Update num-bigint-dig from 0.8.4 to 0.8.6\n\n## \\[2.3.1]\n\n### Performance Improvements\n\n- [`ee3cc4a91`](https://www.github.com/tauri-apps/tauri/commit/ee3cc4a91bf1315ecaefe90f423ffd55ef6c40db) ([#14475](https://www.github.com/tauri-apps/tauri/pull/14475) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) perf: remove needless clones in various files for improved performance. No user facing changes.\n\n## \\[2.3.0]\n\n### Enhancements\n\n- [`f59bf9d53`](https://www.github.com/tauri-apps/tauri/commit/f59bf9d5392ffd209e26ce5259c26d1acc31c4ba) ([#14337](https://www.github.com/tauri-apps/tauri/pull/14337) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) **Potentially breaking change:** Export custom Error enum instead of using anyhow. The changes happened in https://github.com/tauri-apps/tauri/pull/14126.\n\n## \\[2.2.0]\n\n### New Features\n\n- [`a9ec12843`](https://www.github.com/tauri-apps/tauri/commit/a9ec12843aa7d0eb774bd3a53e2e63da12cfa77b) ([#13521](https://www.github.com/tauri-apps/tauri/pull/13521) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added a `--skip-stapling` option to make `tauri build|bundle` *not* wait for notarization to finish on macOS.\n\n## \\[2.1.0]\n\n### Enhancements\n\n- [`d6520a21c`](https://www.github.com/tauri-apps/tauri/commit/d6520a21ce02c3e2be2955999946c2cb7bdb07aa) ([#12541](https://www.github.com/tauri-apps/tauri/pull/12541) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Updated `wry` to 0.50, `windows` to 0.60, `webview2-com` to 0.36, and `objc2` to 0.6. This can be a **breaking change** if you use the `with_webview` API!\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78.\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n## \\[0.1.1-rc.1]\n\n### Enhancements\n\n- [`f5d61822b`](https://www.github.com/tauri-apps/tauri/commit/f5d61822bf5988827776dd58bed75c19364e86bd) ([#11184](https://www.github.com/tauri-apps/tauri/pull/11184) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `Keychain::with_certificate_file` and `certificate::generate_self_signed`.\n\n## \\[0.1.1-rc.0]\n\n### Bug Fixes\n\n- [`1b0c447fc`](https://www.github.com/tauri-apps/tauri/commit/1b0c447fcbc424e08e4260277ec178df86f45d1d) ([#10654](https://www.github.com/tauri-apps/tauri/pull/10654) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes output not visible when running on Node.js via NAPI.\n\n## \\[0.1.0-beta.0]\n\n### New Features\n\n- [`7c7fa0964`](https://www.github.com/tauri-apps/tauri/commit/7c7fa0964db3403037fdb9a34de2b877ddb8df1c) ([#9963](https://www.github.com/tauri-apps/tauri/pull/9963) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Initial release.\n"
  },
  {
    "path": "crates/tauri-macos-sign/Cargo.toml",
    "content": "[package]\nname = \"tauri-macos-sign\"\nversion = \"2.3.3\"\nauthors = [\"Tauri Programme within The Commons Conservancy\"]\nlicense = \"Apache-2.0 OR MIT\"\nkeywords = [\"codesign\", \"signing\", \"macos\", \"ios\", \"tauri\"]\nrepository = \"https://github.com/tauri-apps/tauri\"\ndescription = \"Code signing utilities for macOS and iOS apps\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\n\n[dependencies]\nthiserror = \"2\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\ntempfile = \"3\"\nx509-certificate = \"0.23\"\nonce_cell = \"1\"\nregex = \"1\"\nos_pipe = \"1\"\nplist = \"1\"\nrand = \"0.9\"\ndirs = \"6\"\nlog = { version = \"0.4.21\", features = [\"kv\"] }\napple-codesign = { version = \"0.27\", default-features = false }\nchrono = \"0.4\"\np12 = \"0.6\"\nbase64 = \"0.22\"\n"
  },
  {
    "path": "crates/tauri-macos-sign/README.md",
    "content": "# Tauri MacOS Sign\n\nUtilities for setting up macOS certificates, code signing and notarization for macOS and iOS apps.\n"
  },
  {
    "path": "crates/tauri-macos-sign/src/certificate.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse apple_codesign::create_self_signed_code_signing_certificate;\nuse x509_certificate::{EcdsaCurve, KeyAlgorithm};\n\npub use apple_codesign::CertificateProfile;\n\nuse crate::{Error, Result};\n\n/// Self signed certificate options.\npub struct SelfSignedCertificateRequest {\n  /// Which key type to use\n  pub algorithm: String,\n\n  /// Profile\n  pub profile: CertificateProfile,\n\n  /// Team ID (this is a short string attached to your Apple Developer account)\n  pub team_id: String,\n\n  /// The name of the person this certificate is for\n  pub person_name: String,\n\n  /// Country Name (C) value for certificate identifier\n  pub country_name: String,\n\n  /// How many days the certificate should be valid for\n  pub validity_days: i64,\n\n  /// Certificate password.\n  pub password: String,\n}\n\npub fn generate_self_signed(request: SelfSignedCertificateRequest) -> Result<Vec<u8>> {\n  let algorithm = match request.algorithm.as_str() {\n    \"ecdsa\" => KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1),\n    \"ed25519\" => KeyAlgorithm::Ed25519,\n    \"rsa\" => KeyAlgorithm::Rsa,\n    value => panic!(\"algorithm values should have been validated by arg parser: {value}\"),\n  };\n\n  let validity_duration = chrono::Duration::days(request.validity_days);\n\n  let (cert, key_pair) = create_self_signed_code_signing_certificate(\n    algorithm,\n    request.profile,\n    &request.team_id,\n    &request.person_name,\n    &request.country_name,\n    validity_duration,\n  )\n  .map_err(|error| Error::FailedToCreateSelfSignedCertificate {\n    error: Box::new(error),\n  })?;\n\n  let pfx = p12::PFX::new(\n    &cert\n      .encode_der()\n      .map_err(|error| Error::FailedToEncodeDER { error })?,\n    &key_pair.to_pkcs8_one_asymmetric_key_der(),\n    None,\n    &request.password,\n    \"code-signing\",\n  )\n  .ok_or(Error::FailedToCreatePFX)?;\n  let der = pfx.to_der();\n\n  Ok(der)\n}\n"
  },
  {
    "path": "crates/tauri-macos-sign/src/keychain/identity.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse once_cell::sync::OnceCell;\nuse regex::Regex;\nuse std::{collections::BTreeSet, path::Path, process::Command};\nuse x509_certificate::certificate::X509Certificate;\n\nuse crate::{Error, Result};\n\nfn get_pem_list(keychain_path: &Path, name_substr: &str) -> Result<std::process::Output> {\n  Command::new(\"security\")\n    .arg(\"find-certificate\")\n    .args([\"-p\", \"-a\"])\n    .arg(\"-c\")\n    .arg(name_substr)\n    .arg(keychain_path)\n    .stdin(os_pipe::dup_stdin().unwrap())\n    .stderr(os_pipe::dup_stderr().unwrap())\n    .output()\n    .map_err(|error| Error::CommandFailed {\n      command: \"security find-certificate\".to_string(),\n      error,\n    })\n}\n\n#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]\npub struct Team {\n  pub name: String,\n  pub certificate_name: String,\n  pub id: String,\n  pub cert_prefix: &'static str,\n}\n\nimpl Team {\n  fn from_x509(cert_prefix: &'static str, cert: X509Certificate) -> Result<Self> {\n    let common_name = cert\n      .subject_common_name()\n      .ok_or(Error::CertificateMissingCommonName)?;\n\n    let organization = cert\n      .subject_name()\n      .iter_organization()\n      .next()\n      .and_then(|v| v.to_string().ok());\n\n    let name = if let Some(organization) = organization {\n      println!(\"found cert {common_name:?} with organization {organization:?}\");\n      organization\n    } else {\n      println!(\n                \"found cert {common_name:?} but failed to get organization; falling back to displaying common name\"\n            );\n      static APPLE_DEV: OnceCell<Regex> = OnceCell::new();\n      APPLE_DEV.get_or_init(|| Regex::new(r\"Apple Develop\\w+: (.*) \\(.+\\)\").unwrap())\n                .captures(&common_name)\n                .map(|caps| caps[1].to_owned())\n                .unwrap_or_else(|| {\n                    println!(\"regex failed to capture nice part of name in cert {common_name:?}; falling back to displaying full name\");\n                    common_name.clone()\n                })\n    };\n\n    let id = cert\n      .subject_name()\n      .iter_organizational_unit()\n      .next()\n      .and_then(|v| v.to_string().ok())\n      .ok_or_else(|| Error::CertificateMissingOrganizationUnit {\n        common_name: common_name.clone(),\n      })?;\n\n    Ok(Self {\n      name,\n      certificate_name: common_name,\n      id,\n      cert_prefix,\n    })\n  }\n\n  pub fn certificate_name(&self) -> String {\n    self.certificate_name.clone()\n  }\n}\n\npub fn list(keychain_path: &Path) -> Result<Vec<Team>> {\n  let certs = {\n    let mut certs = Vec::new();\n    for cert_prefix in [\n      \"iOS Distribution:\",\n      \"Apple Distribution:\",\n      \"Developer ID Application:\",\n      \"Mac App Distribution:\",\n      \"Apple Development:\",\n      \"iOS App Development:\",\n      \"Mac Development:\",\n    ] {\n      let pem_list_out = get_pem_list(keychain_path, cert_prefix)?;\n      let cert_list = X509Certificate::from_pem_multiple(pem_list_out.stdout)\n        .map_err(|error| Error::X509Certificate { error })?;\n      certs.extend(cert_list.into_iter().map(|cert| (cert_prefix, cert)));\n    }\n    certs\n  };\n  Ok(\n    certs\n      .into_iter()\n      .flat_map(|(cert_prefix, cert)| {\n        Team::from_x509(cert_prefix, cert).map_err(|err| {\n          log::error!(\"{err}\");\n          err\n        })\n      })\n      // Silly way to sort this and ensure no dupes\n      .collect::<BTreeSet<_>>()\n      .into_iter()\n      .collect(),\n  )\n}\n"
  },
  {
    "path": "crates/tauri-macos-sign/src/keychain.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  ffi::OsString,\n  path::{Path, PathBuf},\n  process::Command,\n};\n\nuse crate::{assert_command, CommandExt, Error, Result};\nuse rand::distr::{Alphanumeric, SampleString};\n\nmod identity;\n\npub use identity::Team;\n\npub enum SigningIdentity {\n  Team(Team),\n  Identifier(String),\n}\n\npub struct Keychain {\n  // none means the default keychain must be used\n  path: Option<PathBuf>,\n  signing_identity: SigningIdentity,\n}\n\nimpl Drop for Keychain {\n  fn drop(&mut self) {\n    if let Some(path) = &self.path {\n      let _ = Command::new(\"security\")\n        .arg(\"delete-keychain\")\n        .arg(path)\n        .piped();\n    }\n  }\n}\n\nimpl Keychain {\n  /// Use a certificate in the default keychain.\n  pub fn with_signing_identity(identity: impl Into<String>) -> Self {\n    Self {\n      path: None,\n      signing_identity: SigningIdentity::Identifier(identity.into()),\n    }\n  }\n\n  /// Import certificate from base64 string.\n  /// certificate_encoded is the p12 certificate base64 encoded.\n  /// By example you can use; openssl base64 -in MyCertificate.p12 -out MyCertificate-base64.txt\n  /// Then use the value of the base64 as `certificate_encoded`.\n  /// You need to set certificate_password to the password you set when you exported your certificate.\n  /// <https://help.apple.com/xcode/mac/current/#/dev154b28f09> see: `Export a signing certificate`\n  pub fn with_certificate(\n    certificate_encoded: &OsString,\n    certificate_password: &OsString,\n  ) -> Result<Self> {\n    let tmp_dir = tempfile::tempdir().map_err(Error::TempDir)?;\n    let cert_path = tmp_dir.path().join(\"cert.p12\");\n    super::decode_base64(certificate_encoded, &cert_path)?;\n    Self::with_certificate_file(&cert_path, certificate_password)\n  }\n\n  pub fn with_certificate_file(cert_path: &Path, certificate_password: &OsString) -> Result<Self> {\n    let home_dir = dirs::home_dir().ok_or(Error::ResolveHomeDir)?;\n    let keychain_path = home_dir.join(\"Library\").join(\"Keychains\").join(format!(\n      \"{}.keychain-db\",\n      Alphanumeric.sample_string(&mut rand::rng(), 16)\n    ));\n    let keychain_password = Alphanumeric.sample_string(&mut rand::rng(), 16);\n\n    let keychain_list_output = Command::new(\"security\")\n      .args([\"list-keychain\", \"-d\", \"user\"])\n      .output()\n      .map_err(|e| Error::CommandFailed {\n        command: \"security list-keychain -d user\".to_string(),\n        error: e,\n      })?;\n\n    assert_command(\n      Command::new(\"security\")\n        .args([\"create-keychain\", \"-p\", &keychain_password])\n        .arg(&keychain_path)\n        .piped(),\n      \"failed to create keychain\",\n    )\n    .map_err(|error| Error::CommandFailed {\n      command: \"security create-Keychain\".to_string(),\n      error,\n    })?;\n\n    assert_command(\n      Command::new(\"security\")\n        .args([\"unlock-keychain\", \"-p\", &keychain_password])\n        .arg(&keychain_path)\n        .piped(),\n      \"failed to set unlock keychain\",\n    )\n    .map_err(|error| Error::CommandFailed {\n      command: \"security unlock-keychain\".to_string(),\n      error,\n    })?;\n\n    assert_command(\n      Command::new(\"security\")\n        .arg(\"import\")\n        .arg(cert_path)\n        .arg(\"-P\")\n        .arg(certificate_password)\n        .args([\n          \"-T\",\n          \"/usr/bin/codesign\",\n          \"-T\",\n          \"/usr/bin/pkgbuild\",\n          \"-T\",\n          \"/usr/bin/productbuild\",\n        ])\n        .arg(\"-k\")\n        .arg(&keychain_path)\n        .piped(),\n      \"failed to import keychain certificate\",\n    )\n    .map_err(|error| Error::CommandFailed {\n      command: \"security import\".to_string(),\n      error,\n    })?;\n\n    assert_command(\n      Command::new(\"security\")\n        .args([\"set-keychain-settings\", \"-t\", \"3600\", \"-u\"])\n        .arg(&keychain_path)\n        .piped(),\n      \"failed to set keychain settings\",\n    )\n    .map_err(|error| Error::CommandFailed {\n      command: \"security set-keychain-settings\".to_string(),\n      error,\n    })?;\n\n    assert_command(\n      Command::new(\"security\")\n        .args([\n          \"set-key-partition-list\",\n          \"-S\",\n          \"apple-tool:,apple:,codesign:\",\n          \"-s\",\n          \"-k\",\n          &keychain_password,\n        ])\n        .arg(&keychain_path)\n        .piped(),\n      \"failed to set keychain settings\",\n    )\n    .map_err(|error| Error::CommandFailed {\n      command: \"security set-key-partition-list\".to_string(),\n      error,\n    })?;\n\n    let current_keychains = String::from_utf8_lossy(&keychain_list_output.stdout)\n      .split('\\n')\n      .map(|line| {\n        line\n          .trim_matches(|c: char| c.is_whitespace() || c == '\"')\n          .to_string()\n      })\n      .filter(|l| !l.is_empty())\n      .collect::<Vec<String>>();\n\n    assert_command(\n      Command::new(\"security\")\n        .args([\"list-keychain\", \"-d\", \"user\", \"-s\"])\n        .args(current_keychains)\n        .arg(&keychain_path)\n        .piped(),\n      \"failed to list keychain\",\n    )\n    .map_err(|error| Error::CommandFailed {\n      command: \"security list-keychain\".to_string(),\n      error,\n    })?;\n\n    let signing_identity = identity::list(&keychain_path)\n      .map(|l| l.first().cloned())?\n      .ok_or(Error::ResolveSigningIdentity)?;\n\n    Ok(Self {\n      path: Some(keychain_path),\n      signing_identity: SigningIdentity::Team(signing_identity),\n    })\n  }\n\n  pub fn signing_identity(&self) -> String {\n    match &self.signing_identity {\n      SigningIdentity::Team(t) => t.certificate_name(),\n      SigningIdentity::Identifier(i) => i.to_string(),\n    }\n  }\n\n  pub fn team_id(&self) -> Option<&str> {\n    match &self.signing_identity {\n      SigningIdentity::Team(t) => Some(&t.id),\n      SigningIdentity::Identifier(_) => None,\n    }\n  }\n\n  pub fn sign(\n    &self,\n    path: &Path,\n    entitlements_path: Option<&Path>,\n    hardened_runtime: bool,\n  ) -> Result<()> {\n    let identity = match &self.signing_identity {\n      SigningIdentity::Team(t) => t.certificate_name(),\n      SigningIdentity::Identifier(i) => i.clone(),\n    };\n    println!(\"Signing with identity \\\"{identity}\\\"\");\n\n    println!(\"Signing {}\", path.display());\n\n    let mut args = vec![\"--force\", \"-s\", &identity];\n\n    if hardened_runtime {\n      args.push(\"--options\");\n      args.push(\"runtime\");\n    }\n\n    let mut codesign = Command::new(\"codesign\");\n    codesign.args(args);\n    if let Some(p) = &self.path {\n      codesign.arg(\"--keychain\").arg(p);\n    }\n\n    if let Some(entitlements_path) = entitlements_path {\n      codesign.arg(\"--entitlements\");\n      codesign.arg(entitlements_path);\n    }\n\n    codesign.arg(path);\n\n    assert_command(codesign.piped(), \"failed to sign app\").map_err(|error| {\n      Error::CommandFailed {\n        command: \"codesign\".to_string(),\n        error,\n      }\n    })?;\n\n    Ok(())\n  }\n\n  pub fn path(&self) -> Option<&Path> {\n    self.path.as_deref()\n  }\n}\n"
  },
  {
    "path": "crates/tauri-macos-sign/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  ffi::{OsStr, OsString},\n  path::{Path, PathBuf},\n  process::{Command, ExitStatus},\n};\n\nuse serde::Deserialize;\n\npub mod certificate;\nmod keychain;\nmod provisioning_profile;\n\npub use keychain::{Keychain, Team};\npub use provisioning_profile::ProvisioningProfile;\n\n#[derive(Debug, thiserror::Error)]\npub enum Error {\n  #[error(\"failed to create temp directory: {0}\")]\n  TempDir(std::io::Error),\n  #[error(\"failed to resolve home dir\")]\n  ResolveHomeDir,\n  #[error(\"failed to resolve signing identity\")]\n  ResolveSigningIdentity,\n  #[error(\"failed to decode provisioning profile\")]\n  FailedToDecodeProvisioningProfile,\n  #[error(\"could not find provisioning profile UUID\")]\n  FailedToFindProvisioningProfileUuid,\n  #[error(\"{context} {path}: {error}\")]\n  Plist {\n    context: &'static str,\n    path: PathBuf,\n    error: plist::Error,\n  },\n  #[error(\"failed to upload app to Apple's notarization servers: {error}\")]\n  FailedToUploadApp { error: std::io::Error },\n  #[error(\"failed to notarize app: {0}\")]\n  Notarize(String),\n  #[error(\"failed to parse notarytool output as JSON: {output}\")]\n  ParseNotarytoolOutput { output: String },\n  #[error(\"failed to run command {command}: {error}\")]\n  CommandFailed {\n    command: String,\n    error: std::io::Error,\n  },\n  #[error(\"{context} {path}: {error}\")]\n  Fs {\n    context: &'static str,\n    path: PathBuf,\n    error: std::io::Error,\n  },\n  #[error(\"failed to parse X509 certificate: {error}\")]\n  X509Certificate {\n    error: x509_certificate::X509CertificateError,\n  },\n  #[error(\"failed to create PFX from self signed certificate\")]\n  FailedToCreatePFX,\n  #[error(\"failed to create self signed certificate: {error}\")]\n  FailedToCreateSelfSignedCertificate {\n    error: Box<apple_codesign::AppleCodesignError>,\n  },\n  #[error(\"failed to encode DER: {error}\")]\n  FailedToEncodeDER { error: std::io::Error },\n  #[error(\"failed to decode base64 certificate: {0}\")]\n  Base64Decode(base64::DecodeError),\n  #[error(\"certificate missing common name\")]\n  CertificateMissingCommonName,\n  #[error(\"certificate missing organization unit for common name {common_name}\")]\n  CertificateMissingOrganizationUnit { common_name: String },\n}\n\npub type Result<T> = std::result::Result<T, Error>;\n\ntrait CommandExt {\n  // The `pipe` function sets the stdout and stderr to properly\n  // show the command output in the Node.js wrapper.\n  fn piped(&mut self) -> std::io::Result<ExitStatus>;\n}\n\nimpl CommandExt for Command {\n  fn piped(&mut self) -> std::io::Result<ExitStatus> {\n    self.stdin(os_pipe::dup_stdin()?);\n    self.stdout(os_pipe::dup_stdout()?);\n    self.stderr(os_pipe::dup_stderr()?);\n    let program = self.get_program().to_string_lossy().into_owned();\n    log::debug!(action = \"Running\"; \"Command `{} {}`\", program, self.get_args().map(|arg| arg.to_string_lossy()).fold(String::new(), |acc, arg| format!(\"{acc} {arg}\")));\n\n    self.status()\n  }\n}\n\npub enum ApiKey {\n  Path(PathBuf),\n  Raw(Vec<u8>),\n}\n\npub enum AppleNotarizationCredentials {\n  AppleId {\n    apple_id: OsString,\n    password: OsString,\n    team_id: OsString,\n  },\n  ApiKey {\n    issuer: OsString,\n    key_id: OsString,\n    key: ApiKey,\n  },\n}\n\n#[derive(Deserialize)]\nstruct NotarytoolSubmitOutput {\n  id: String,\n  #[serde(default)]\n  status: Option<String>,\n  message: String,\n}\n\npub fn notarize(\n  keychain: &Keychain,\n  app_bundle_path: &Path,\n  auth: &AppleNotarizationCredentials,\n) -> Result<()> {\n  notarize_inner(keychain, app_bundle_path, auth, true)\n}\n\npub fn notarize_without_stapling(\n  keychain: &Keychain,\n  app_bundle_path: &Path,\n  auth: &AppleNotarizationCredentials,\n) -> Result<()> {\n  notarize_inner(keychain, app_bundle_path, auth, false)\n}\n\nfn notarize_inner(\n  keychain: &Keychain,\n  app_bundle_path: &Path,\n  auth: &AppleNotarizationCredentials,\n  wait: bool,\n) -> Result<()> {\n  let bundle_stem = app_bundle_path\n    .file_stem()\n    .expect(\"failed to get bundle filename\");\n\n  let tmp_dir = tempfile::tempdir().map_err(Error::TempDir)?;\n  let zip_path = tmp_dir\n    .path()\n    .join(format!(\"{}.zip\", bundle_stem.to_string_lossy()));\n  let zip_args = vec![\n    \"-c\",\n    \"-k\",\n    \"--keepParent\",\n    \"--sequesterRsrc\",\n    app_bundle_path\n      .to_str()\n      .expect(\"failed to convert bundle_path to string\"),\n    zip_path\n      .to_str()\n      .expect(\"failed to convert zip_path to string\"),\n  ];\n\n  // use ditto to create a PKZip almost identical to Finder\n  // this remove almost 99% of false alarm in notarization\n  assert_command(\n    Command::new(\"ditto\").args(zip_args).piped(),\n    \"failed to zip app with ditto\",\n  )\n  .map_err(|error| Error::CommandFailed {\n    command: \"ditto\".to_string(),\n    error,\n  })?;\n\n  // sign the zip file\n  keychain.sign(&zip_path, None, false)?;\n\n  let mut notarize_args = vec![\n    \"notarytool\",\n    \"submit\",\n    zip_path\n      .to_str()\n      .expect(\"failed to convert zip_path to string\"),\n    \"--output-format\",\n    \"json\",\n  ];\n  if wait {\n    notarize_args.push(\"--wait\");\n  }\n  let notarize_args = notarize_args;\n\n  println!(\"Notarizing {}\", app_bundle_path.display());\n\n  let output = Command::new(\"xcrun\")\n    .args(notarize_args)\n    .notarytool_args(auth, tmp_dir.path())?\n    .output()\n    .map_err(|error| Error::FailedToUploadApp { error })?;\n\n  if !output.status.success() {\n    return Err(Error::Notarize(\n      String::from_utf8_lossy(&output.stderr).into_owned(),\n    ));\n  }\n\n  let output_str = String::from_utf8_lossy(&output.stdout);\n  if let Ok(submit_output) = serde_json::from_str::<NotarytoolSubmitOutput>(&output_str) {\n    let log_message = format!(\n      \"{} with status {} for id {} ({})\",\n      if wait { \"Finished\" } else { \"Submitted\" },\n      submit_output.status.as_deref().unwrap_or(\"Pending\"),\n      submit_output.id,\n      submit_output.message\n    );\n    // status is empty when not waiting for the notarization to finish\n    if submit_output.status.map_or(!wait, |s| s == \"Accepted\") {\n      println!(\"Notarizing {log_message}\");\n\n      if wait {\n        println!(\"Stapling app...\");\n        staple_app(app_bundle_path.to_path_buf())?;\n      } else {\n        println!(\"Not waiting for notarization to finish.\");\n        println!(\"You can use `xcrun notarytool log` to check the notarization progress.\");\n        println!(\n          \"When it's done you can optionally staple your app via `xcrun stapler staple {}`\",\n          app_bundle_path.display()\n        );\n      }\n\n      Ok(())\n    } else if let Ok(output) = Command::new(\"xcrun\")\n      .args([\"notarytool\", \"log\"])\n      .arg(&submit_output.id)\n      .notarytool_args(auth, tmp_dir.path())?\n      .output()\n    {\n      Err(Error::Notarize(format!(\n        \"{log_message}\\nLog:\\n{}\",\n        String::from_utf8_lossy(&output.stdout)\n      )))\n    } else {\n      Err(Error::Notarize(log_message))\n    }\n  } else {\n    Err(Error::ParseNotarytoolOutput {\n      output: output_str.into_owned(),\n    })\n  }\n}\n\nfn staple_app(mut app_bundle_path: PathBuf) -> Result<()> {\n  let app_bundle_path_clone = app_bundle_path.clone();\n  let filename = app_bundle_path_clone\n    .file_name()\n    .expect(\"failed to get bundle filename\")\n    .to_str()\n    .expect(\"failed to convert bundle filename to string\");\n\n  app_bundle_path.pop();\n\n  Command::new(\"xcrun\")\n    .args(vec![\"stapler\", \"staple\", \"-v\", filename])\n    .current_dir(app_bundle_path)\n    .output()\n    .map_err(|error| Error::CommandFailed {\n      command: \"xcrun stapler staple\".to_string(),\n      error,\n    })?;\n\n  Ok(())\n}\n\npub trait NotarytoolCmdExt {\n  fn notarytool_args(\n    &mut self,\n    auth: &AppleNotarizationCredentials,\n    temp_dir: &Path,\n  ) -> Result<&mut Self>;\n}\n\nimpl NotarytoolCmdExt for Command {\n  fn notarytool_args(\n    &mut self,\n    auth: &AppleNotarizationCredentials,\n    temp_dir: &Path,\n  ) -> Result<&mut Self> {\n    match auth {\n      AppleNotarizationCredentials::AppleId {\n        apple_id,\n        password,\n        team_id,\n      } => Ok(\n        self\n          .arg(\"--apple-id\")\n          .arg(apple_id)\n          .arg(\"--password\")\n          .arg(password)\n          .arg(\"--team-id\")\n          .arg(team_id),\n      ),\n      AppleNotarizationCredentials::ApiKey {\n        key,\n        key_id,\n        issuer,\n      } => {\n        let key_path = match key {\n          ApiKey::Raw(k) => {\n            let key_path = temp_dir.join(\"AuthKey.p8\");\n            std::fs::write(&key_path, k).map_err(|error| Error::Fs {\n              context: \"failed to write notarization API key to temp file\",\n              path: key_path.clone(),\n              error,\n            })?;\n            key_path\n          }\n          ApiKey::Path(p) => p.to_owned(),\n        };\n\n        Ok(\n          self\n            .arg(\"--key-id\")\n            .arg(key_id)\n            .arg(\"--key\")\n            .arg(key_path)\n            .arg(\"--issuer\")\n            .arg(issuer),\n        )\n      }\n    }\n  }\n}\n\nfn decode_base64(base64_input: &OsStr, out_path: &Path) -> Result<()> {\n  use base64::Engine;\n\n  let input = base64_input\n    .to_str()\n    .expect(\"failed to convert base64 to string\");\n\n  // strip whitespace before decoding\n  let cleaned: String = input.chars().filter(|c| !c.is_ascii_whitespace()).collect();\n\n  let decoded = base64::engine::general_purpose::STANDARD\n    .decode(&cleaned)\n    .map_err(Error::Base64Decode)?;\n\n  std::fs::write(out_path, &decoded).map_err(|error| Error::Fs {\n    context: \"failed to write decoded certificate\",\n    path: out_path.to_path_buf(),\n    error,\n  })?;\n\n  Ok(())\n}\n\nfn assert_command(\n  response: std::result::Result<std::process::ExitStatus, std::io::Error>,\n  error_message: &str,\n) -> std::io::Result<()> {\n  let status =\n    response.map_err(|e| std::io::Error::new(e.kind(), format!(\"{error_message}: {e}\")))?;\n  if !status.success() {\n    Err(std::io::Error::other(error_message))\n  } else {\n    Ok(())\n  }\n}\n"
  },
  {
    "path": "crates/tauri-macos-sign/src/provisioning_profile.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{ffi::OsStr, path::PathBuf, process::Command};\n\nuse crate::{Error, Result};\nuse rand::distr::{Alphanumeric, SampleString};\n\npub struct ProvisioningProfile {\n  path: PathBuf,\n}\n\nimpl ProvisioningProfile {\n  pub fn from_base64(base64: &OsStr) -> Result<Self> {\n    let home_dir = dirs::home_dir().ok_or(Error::ResolveHomeDir)?;\n    let provisioning_profiles_folder = home_dir\n      .join(\"Library\")\n      .join(\"MobileDevice\")\n      .join(\"Provisioning Profiles\");\n    std::fs::create_dir_all(&provisioning_profiles_folder).map_err(|error| Error::Fs {\n      context: \"failed to create provisioning profiles folder\",\n      path: provisioning_profiles_folder.clone(),\n      error,\n    })?;\n\n    let provisioning_profile_path = provisioning_profiles_folder.join(format!(\n      \"{}.mobileprovision\",\n      Alphanumeric.sample_string(&mut rand::rng(), 16)\n    ));\n    super::decode_base64(base64, &provisioning_profile_path)?;\n\n    Ok(Self {\n      path: provisioning_profile_path,\n    })\n  }\n\n  pub fn uuid(&self) -> Result<String> {\n    let output = Command::new(\"security\")\n      .args([\"cms\", \"-D\", \"-i\"])\n      .arg(&self.path)\n      .output()\n      .map_err(|error| Error::CommandFailed {\n        command: \"security cms -D -i\".to_string(),\n        error,\n      })?;\n\n    if !output.status.success() {\n      return Err(Error::FailedToDecodeProvisioningProfile);\n    }\n\n    let plist =\n      plist::from_bytes::<plist::Dictionary>(&output.stdout).map_err(|error| Error::Plist {\n        context: \"failed to parse provisioning profile as plist\",\n        path: self.path.clone(),\n        error,\n      })?;\n\n    plist\n      .get(\"UUID\")\n      .and_then(|v| v.as_string().map(ToString::to_string))\n      .ok_or(Error::FailedToFindProvisioningProfileUuid)\n  }\n}\n"
  },
  {
    "path": "crates/tauri-macros/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.5.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.3`\n- Upgraded to `tauri-codegen@2.5.5`\n\n## \\[2.5.4]\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.5.4`\n\n## \\[2.5.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.2`\n- Upgraded to `tauri-codegen@2.5.3`\n\n## \\[2.5.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.1`\n- Upgraded to `tauri-codegen@2.5.2`\n\n## \\[2.5.1]\n\n### Bug Fixes\n\n- [`4b00130b8`](https://www.github.com/tauri-apps/tauri/commit/4b00130b86a27b6f121bf57897b5e92d83bcc0fc) ([#14385](https://www.github.com/tauri-apps/tauri/pull/14385) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS deadlock when running on the simulator from Xcode by properly piping stdout/stderr messages through the Xcode console and OSLog.\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.5.1`\n\n## \\[2.5.0]\n\n### Bug Fixes\n\n- [`69476d8e2`](https://www.github.com/tauri-apps/tauri/commit/69476d8e2314b85bf46046140bc5495fe29b7d29) ([#14170](https://www.github.com/tauri-apps/tauri/pull/14170)) Fix the stack overflow when having too many commands in a single invoke handler in release build\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.0`\n- Upgraded to `tauri-codegen@2.5.0`\n- [`6aa7f2d85`](https://www.github.com/tauri-apps/tauri/commit/6aa7f2d852870aeba1d4dd0e07f8be2bc9b66286) Upgraded to `tauri-utils@2.8.0`\n\n## \\[2.4.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.7.0`.\n- Upgraded to `tauri-codegen@2.4.0`\n\n## \\[2.3.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.6.0`\n- Upgraded to `tauri-codegen@2.3.1`\n\n## \\[2.3.1]\n\n### Performance Improvements\n\n- [`0a552a868`](https://www.github.com/tauri-apps/tauri/commit/0a552a868c559d367d85c264bf052009b048bd8e) ([#13690](https://www.github.com/tauri-apps/tauri/pull/13690) by [@montyc1999](https://www.github.com/tauri-apps/tauri/../../montyc1999)) Cache `rustc -V` output in `#[tauri::command]` macros\n\n## \\[2.2.1]\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.3.0`\n- Upgraded to `tauri-utils@2.5.0`\n\n## \\[2.2.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.4.0`\n- Upgraded to `tauri-codegen@2.2.0`\n- [`48b12b440`](https://www.github.com/tauri-apps/tauri/commit/48b12b440478937c46fdfef9f9d95194be117020) Update to `tauri-utils@2.4.0`\n\n## \\[2.1.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.1`\n- Upgraded to `tauri-codegen@2.1.1`\n\n## \\[2.1.0]\n\n### New Features\n\n- [`013f8f652`](https://www.github.com/tauri-apps/tauri/commit/013f8f652302f2d49c5ec0a075582033d8b074fb) ([#12890](https://www.github.com/tauri-apps/tauri/pull/12890) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Added `build > removeUnusedCommands` to trigger the build scripts and macros to remove unused commands based on the capabilities you defined. Note this won't be accounting for dynamically added ACLs so make sure to check it when using this.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.0`\n- Upgraded to `tauri-codegen@2.1.0`\n\n## \\[2.0.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.2.0`\n- Upgraded to `tauri-codegen@2.0.5`\n\n## \\[2.0.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.1`\n- Upgraded to `tauri-codegen@2.0.4`\n\n## \\[2.0.3]\n\n### Enhancements\n\n- [`17c6952ae`](https://www.github.com/tauri-apps/tauri/commit/17c6952aec965fa41e6695ad68461a218afc20f1) ([#11522](https://www.github.com/tauri-apps/tauri/pull/11522) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Enhance the error message when using `async` commands with a reference.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.0`\n- Upgraded to `tauri-codegen@2.0.3`\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.1`\n- Upgraded to `tauri-codegen@2.0.1`\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78.\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0`\n- Upgraded to `tauri-codegen@2.0.0`\n\n## \\[2.0.0-rc.12]\n\n### New Features\n\n- [`1d8b67b29`](https://www.github.com/tauri-apps/tauri/commit/1d8b67b2970a09ec478093e127612fac823de805) ([#11162](https://www.github.com/tauri-apps/tauri/pull/11162) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Support async functions for `mobile_entry_point` macro\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.13`\n- Upgraded to `tauri-codegen@2.0.0-rc.13`\n\n## \\[2.0.0-rc.11]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.12`\n- Upgraded to `tauri-codegen@2.0.0-rc.12`\n\n## \\[2.0.0-rc.10]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.11`\n- Upgraded to `tauri-codegen@2.0.0-rc.11`\n\n## \\[2.0.0-rc.9]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.10`\n- Upgraded to `tauri-codegen@2.0.0-rc.10`\n\n## \\[2.0.0-rc.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.9`\n- Upgraded to `tauri-codegen@2.0.0-rc.9`\n\n## \\[2.0.0-rc.7]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.8`\n- Upgraded to `tauri-codegen@2.0.0-rc.8`\n\n## \\[2.0.0-rc.6]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.7`\n- Upgraded to `tauri-codegen@2.0.0-rc.7`\n\n## \\[2.0.0-rc.6]\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.6`\n- Upgraded to `tauri-codegen@2.0.0-rc.6`\n\n## \\[2.0.0-rc.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.5`\n- Upgraded to `tauri-codegen@2.0.0-rc.5`\n\n## \\[2.0.0-rc.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.4`\n- Upgraded to `tauri-codegen@2.0.0-rc.4`\n\n## \\[2.0.0-rc.3]\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.0.0-rc.3`\n- Upgraded to `tauri-utils@2.0.0-rc.3`\n\n## \\[2.0.0-rc.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.2`\n- Upgraded to `tauri-codegen@2.0.0-rc.2`\n\n## \\[2.0.0-rc.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.1`\n- Upgraded to `tauri-codegen@2.0.0-rc.1`\n\n## \\[2.0.0-rc.0]\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.0.0-rc.0`\n- Upgraded to `tauri-utils@2.0.0-rc.0`\n\n## \\[2.0.0-beta.19]\n\n### What's Changed\n\n- [`4c239729c`](https://www.github.com/tauri-apps/tauri/commit/4c239729c3e1b899ecbc6793c3682848e8de1729) ([#10167](https://www.github.com/tauri-apps/tauri/pull/10167) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add support for `test = true` in `generate_context!` macro to skip some code generation that could affect some tests, for now it only skips empedding a plist on macOS.\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.0.0-beta.19`\n- Upgraded to `tauri-utils@2.0.0-beta.19`\n\n## \\[2.0.0-beta.18]\n\n### New Features\n\n- [`5b769948a`](https://www.github.com/tauri-apps/tauri/commit/5b769948a81cac333f64c870a470ba6525bd5cd3) ([#9959](https://www.github.com/tauri-apps/tauri/pull/9959)) Add `include_image` macro to help embedding instances of `Image` struct at compile-time in rust to be used with window, menu or tray icons.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.18`\n- Upgraded to `tauri-codegen@2.0.0-beta.18`\n\n## \\[2.0.0-beta.17]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.17`\n- Upgraded to `tauri-codegen@2.0.0-beta.17`\n\n### Breaking Changes\n\n- [`1df5cdeb0`](https://www.github.com/tauri-apps/tauri/commit/1df5cdeb06f5464e0eec4055e21b7b7bc8739eed)([#9858](https://www.github.com/tauri-apps/tauri/pull/9858)) Use `tauri.conf.json > identifier` to set the `PackageName` in Android and `BundleId` in iOS.\n\n## \\[2.0.0-beta.16]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.16`\n- Upgraded to `tauri-codegen@2.0.0-beta.16`\n\n## \\[2.0.0-beta.15]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.15`\n- Upgraded to `tauri-codegen@2.0.0-beta.15`\n\n## \\[2.0.0-beta.14]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.14`\n- Upgraded to `tauri-codegen@2.0.0-beta.14`\n\n## \\[2.0.0-beta.13]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.13`\n- Upgraded to `tauri-codegen@2.0.0-beta.13`\n\n## \\[2.0.0-beta.12]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.12`\n- Upgraded to `tauri-codegen@2.0.0-beta.12`\n\n## \\[2.0.0-beta.11]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.11`\n- Upgraded to `tauri-codegen@2.0.0-beta.11`\n\n## \\[2.0.0-beta.10]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.10`\n- Upgraded to `tauri-codegen@2.0.0-beta.10`\n\n## \\[2.0.0-beta.9]\n\n### New Features\n\n- [`ba0206d8a`](https://www.github.com/tauri-apps/tauri/commit/ba0206d8a30a9b43ec5090dcaabd1a23baa1420c)([#9141](https://www.github.com/tauri-apps/tauri/pull/9141)) The `Context` codegen now accepts a `assets` input to define a custom `tauri::Assets` implementation.\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.0.0-beta.9`\n- Upgraded to `tauri-utils@2.0.0-beta.9`\n\n## \\[2.0.0-beta.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.8`\n- Upgraded to `tauri-codegen@2.0.0-beta.8`\n\n## \\[2.0.0-beta.7]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.7`\n- Upgraded to `tauri-codegen@2.0.0-beta.7`\n\n## \\[2.0.0-beta.6]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.6`\n- Upgraded to `tauri-codegen@2.0.0-beta.6`\n\n## \\[2.0.0-beta.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.5`\n- Upgraded to `tauri-codegen@2.0.0-beta.5`\n\n## \\[2.0.0-beta.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.4`\n- Upgraded to `tauri-codegen@2.0.0-beta.4`\n\n## \\[2.0.0-beta.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.3`\n- Upgraded to `tauri-codegen@2.0.0-beta.3`\n\n## \\[2.0.0-beta.2]\n\n### Enhancements\n\n- [`8d16a80d`](https://www.github.com/tauri-apps/tauri/commit/8d16a80d2fb2468667e7987d0cc99dbc7e3b9d0a)([#8802](https://www.github.com/tauri-apps/tauri/pull/8802)) The `generate_context` proc macro now accepts a `capabilities` attribute where the value is an array of file paths that can be [conditionally compiled](https://doc.rust-lang.org/reference/conditional-compilation.html). These capabilities are added to the application along the capabilities defined in the Tauri configuration file.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.2`\n- Upgraded to `tauri-codegen@2.0.0-beta.2`\n\n## \\[2.0.0-beta.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.1`\n- Upgraded to `tauri-codegen@2.0.0-beta.1`\n\n## \\[2.0.0-beta.0]\n\n### New Features\n\n- [`74a2a603`](https://www.github.com/tauri-apps/tauri/commit/74a2a6036a5e57462f161d728cbd8a6f121028ca)([#8661](https://www.github.com/tauri-apps/tauri/pull/8661)) Implement access control list for IPC usage.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.0`\n- Upgraded to `tauri-codegen@2.0.0-beta.0`\n\n## \\[2.0.0-alpha.13]\n\n### Enhancements\n\n- [`d621d343`](https://www.github.com/tauri-apps/tauri/commit/d621d3437ce3947175eecf345b2c6d1c4c7ce020)([#8607](https://www.github.com/tauri-apps/tauri/pull/8607)) Added tracing for window startup, plugins, `Window::eval`, events, IPC, updater and custom protocol request handlers behind the `tracing` feature flag.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.13`\n- Upgraded to `tauri-codegen@2.0.0-alpha.13`\n\n## \\[2.0.0-alpha.12]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.12`\n- Upgraded to `tauri-codegen@2.0.0-alpha.12`\n\n## \\[2.0.0-alpha.11]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.11`\n- Upgraded to `tauri-codegen@2.0.0-alpha.11`\n- [`b6ed4ecb`](https://www.github.com/tauri-apps/tauri/commit/b6ed4ecb3730c346c973f1b34aa0de1b7d43ac44)([#7634](https://www.github.com/tauri-apps/tauri/pull/7634)) Update to syn v2.\n\n## \\[2.0.0-alpha.10]\n\n### Enhancements\n\n- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release.\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.0.0-alpha.10`\n- Upgraded to `tauri-utils@2.0.0-alpha.10`\n\n## \\[2.0.0-alpha.9]\n\n### New Features\n\n- [`880266a7`](https://www.github.com/tauri-apps/tauri/commit/880266a7f697e1fe58d685de3bb6836ce5251e92)([#8031](https://www.github.com/tauri-apps/tauri/pull/8031)) Bump the MSRV to 1.70.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.9`\n- Upgraded to `tauri-codegen@2.0.0-alpha.9`\n\n### Breaking Changes\n\n- [`ebcc21e4`](https://www.github.com/tauri-apps/tauri/commit/ebcc21e4b95f4e8c27639fb1bca545b432f52d5e)([#8057](https://www.github.com/tauri-apps/tauri/pull/8057)) Renamed the beforeDevCommand, beforeBuildCommand and beforeBundleCommand hooks environment variables from `TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG` to `TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG` to differentiate the prefix with other CLI environment variables.\n\n## \\[2.0.0-alpha.8]\n\n### Enhancements\n\n- [`100d9ede`](https://www.github.com/tauri-apps/tauri/commit/100d9ede35995d9db21d2087dd5606adfafb89a5)([#7802](https://www.github.com/tauri-apps/tauri/pull/7802)) Use `Target` enum from `tauri_utils::platform`.\n\n### Dependencies\n\n- Upgraded to `tauri-codegen@2.0.0-alpha.8`\n- Upgraded to `tauri-utils@2.0.0-alpha.8`\n\n## \\[2.0.0-alpha.7]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.7`\n- Upgraded to `tauri-codegen@2.0.0-alpha.7`\n\n### Breaking Changes\n\n- [`fbeb5b91`](https://www.github.com/tauri-apps/tauri/commit/fbeb5b9185baeda19e865228179e3e44c165f1d9)([#7170](https://www.github.com/tauri-apps/tauri/pull/7170)) Moved `tauri::api::ipc` to `tauri::ipc` and refactored all types.\n\n## \\[2.0.0-alpha.6]\n\n### Dependencies\n\n- Updated to latest `tauri-utils`\n\n## \\[2.0.0-alpha.5]\n\n- [`7a4b1fb9`](https://www.github.com/tauri-apps/tauri/commit/7a4b1fb96da475053c61960f362bbecf18cd00d4)([#6839](https://www.github.com/tauri-apps/tauri/pull/6839)) Added support to attibutes for each command path in the `generate_handler` macro.\n- [`96639ca2`](https://www.github.com/tauri-apps/tauri/commit/96639ca239c9e4f75142fc07868ac46822111cff)([#6749](https://www.github.com/tauri-apps/tauri/pull/6749)) Moved the `shell` functionality to its own plugin in the plugins-workspace repository.\n- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65.\n- [`9a79dc08`](https://www.github.com/tauri-apps/tauri/commit/9a79dc085870e0c1a5df13481ff271b8c6cc3b78)([#6947](https://www.github.com/tauri-apps/tauri/pull/6947)) Removed the module command macros.\n\n## \\[2.0.0-alpha.4]\n\n- Added `android` configuration object under `tauri > bundle`.\n  - Bumped due to a bump in tauri-utils.\n  - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07\n\n## \\[2.0.0-alpha.3]\n\n- Pull changes from Tauri 1.3 release.\n  - [](https://www.github.com/tauri-apps/tauri/commit/undefined)  on undefined\n\n## \\[2.0.0-alpha.2]\n\n- Resolve Android package name from single word bundle identifiers.\n  - [60a8b07d](https://www.github.com/tauri-apps/tauri/commit/60a8b07dc7c56c9c45331cb57d9afb410e7eadf3) fix: handle single word bundle identifier when resolving Android domain ([#6313](https://www.github.com/tauri-apps/tauri/pull/6313)) on 2023-02-19\n- Return `bool` in the invoke handler.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n- Refactored the implementation of the `mobile_entry_point` macro.\n  - [9feab904](https://www.github.com/tauri-apps/tauri/commit/9feab904bf08b5c168d4779c21d0419409a68d30) feat(core): add API to call Android plugin ([#6239](https://www.github.com/tauri-apps/tauri/pull/6239)) on 2023-02-10\n\n## \\[2.0.0-alpha.1]\n\n- Refactor mobile environment variables.\n  - [dee9460f](https://www.github.com/tauri-apps/tauri/commit/dee9460f9c9bc92e9c638e7691e616849ac2085b) feat: keep CLI alive when iOS app exits, show logs, closes [#5855](https://www.github.com/tauri-apps/tauri/pull/5855) ([#5902](https://www.github.com/tauri-apps/tauri/pull/5902)) on 2022-12-27\n- Bump the MSRV to 1.64.\n  - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30\n- Removed mobile logging initialization, which will be handled by `tauri-plugin-log`.\n  - [](https://www.github.com/tauri-apps/tauri/commit/undefined)  on undefined\n\n## \\[2.0.0-alpha.0]\n\n- Added the `mobile_entry_point` macro.\n  - [98904863](https://www.github.com/tauri-apps/tauri/commit/9890486321c9c79ccfb7c547fafee85b5c3ffa71) feat(core): add `mobile_entry_point` macro ([#4983](https://www.github.com/tauri-apps/tauri/pull/4983)) on 2022-08-21\n- First mobile alpha release!\n  - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08\n\n## \\[1.4.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.2`\n- Upgraded to `tauri-codegen@1.4.2`\n\n## \\[1.4.2]\n\n### Enhancements\n\n- [`5e05236b`](https://www.github.com/tauri-apps/tauri/commit/5e05236b4987346697c7caae0567d3c50714c198)([#8289](https://www.github.com/tauri-apps/tauri/pull/8289)) Added tracing for window startup, plugins, `Window::eval`, events, IPC, updater and custom protocol request handlers behind the `tracing` feature flag.\n\n## \\[1.4.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.0`\n- Upgraded to `tauri-codegen@1.4.1`\n\n## \\[1.4.0]\n\n### Enhancements\n\n- [`d68a25e3`](https://www.github.com/tauri-apps/tauri/commit/d68a25e32e012e57a9e5225b589b9ecbea70a887)([#6124](https://www.github.com/tauri-apps/tauri/pull/6124)) Improve compiler error message when generating an async command that has a reference input and don't return a Result.\n\n## \\[1.3.0]\n\n- Bump minimum supported Rust version to 1.60.\n  - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31\n\n## \\[1.2.1]\n\n- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`.\n  - Bumped due to a bump in tauri-utils.\n  - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18\n\n## \\[1.2.0]\n\n- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28\n\n## \\[1.1.1]\n\n- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs.\n  - Bumped due to a bump in tauri-utils.\n  - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16\n\n## \\[1.1.0]\n\n- Added support to configuration files in TOML format (Tauri.toml file).\n  - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02\n\n## \\[1.0.4]\n\n- Adjust command imports to fix `items_after_statements` Clippy warning.\n  - [d3e19e34](https://www.github.com/tauri-apps/tauri/commit/d3e19e3420a023cddc46173a2d1f1e6c5a390a1b) fix(macros): `items_after_statements` Clippy warning, closes [#4639](https://www.github.com/tauri-apps/tauri/pull/4639) on 2022-07-11\n- Remove raw identifier (`r#`) prefix from command arguments.\n  - [ac72800f](https://www.github.com/tauri-apps/tauri/commit/ac72800fb630738e2502569935ecdc83e3e78055) fix(macros): strip `r#` from command arguments, closes [#4654](https://www.github.com/tauri-apps/tauri/pull/4654) ([#4657](https://www.github.com/tauri-apps/tauri/pull/4657)) on 2022-07-12\n\n## \\[1.0.3]\n\n- Add `#[doc(hidden)]` attribute to the `#[command]` generated macro.\n  - [d4cdf807](https://www.github.com/tauri-apps/tauri/commit/d4cdf80781a7955ac620fe6d394e82d067178c4d) feat(macros): hide command macro from docs, closes [#4550](https://www.github.com/tauri-apps/tauri/pull/4550) ([#4556](https://www.github.com/tauri-apps/tauri/pull/4556)) on 2022-07-01\n\n## \\[1.0.2]\n\n- Expose `platform::windows_version` function.\n  - Bumped due to a bump in tauri-utils.\n  - [bf764e83](https://www.github.com/tauri-apps/tauri/commit/bf764e83e01e7443e6cc54572001e1c98c357465) feat(utils): expose `windows_version` function ([#4534](https://www.github.com/tauri-apps/tauri/pull/4534)) on 2022-06-30\n\n## \\[1.0.1]\n\n- Set the bundle name and app metadata in the Info.plist file in development mode.\n  - [38f5db6e](https://www.github.com/tauri-apps/tauri/commit/38f5db6e6a8b496b50d486db6fd8204266de3a69) feat(codegen): fill app metadata in development Info.plist on 2022-06-21\n- Set the application icon in development mode on macOS.\n  - [307c2ebf](https://www.github.com/tauri-apps/tauri/commit/307c2ebfb68238dacab6088f9c6ba310c727c68c) feat(core): set macOS app icon in development ([#4385](https://www.github.com/tauri-apps/tauri/pull/4385)) on 2022-06-19\n\n## \\[1.0.0]\n\n- Upgrade to `stable`!\n  - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15\n\n## \\[1.0.0-rc.11]\n\n- Read the tray icon path relatively to the config directory.\n  - Bumped due to a bump in tauri-codegen.\n  - [562e8ca2](https://www.github.com/tauri-apps/tauri/commit/562e8ca23facf1a8e5fa6c8cdf872357d3523a78) fix(codegen): tray icon path is relative to the config directory on 2022-06-15\n\n## \\[1.0.0-rc.10]\n\n- **Breaking change:** The `TrayIcon` enum has been removed and now `Icon` is used instead.\n  This allows you to use more image formats and use embedded icons on Linux.\n  - Bumped due to a bump in tauri-codegen.\n  - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14\n\n## \\[1.0.0-rc.9]\n\n- Added a config flag to bundle the media framework used by webkit2gtk `tauri.conf.json > tauri > bundle > appimage > bundleMediaFramework`.\n  - Bumped due to a bump in tauri-utils.\n  - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10\n\n## \\[1.0.0-rc.8]\n\n- **Breaking change:** `PackageInfo::version` is now a `semver::Version` instead of a `String`.\n  - Bumped due to a bump in tauri-utils.\n  - [2badbd2d](https://www.github.com/tauri-apps/tauri/commit/2badbd2d7ed51bf33c1b547b4c837b600574bd4a) refactor: force semver versions, change updater `should_install` sig ([#4215](https://www.github.com/tauri-apps/tauri/pull/4215)) on 2022-05-25\n  - [a7388e23](https://www.github.com/tauri-apps/tauri/commit/a7388e23c3b9019d48b078cae00a75c74d74d11b) fix(ci): adjust change file to include tauri-utils and tauri-codegen on 2022-05-27\n\n## \\[1.0.0-rc.7]\n\n- Allow configuring the display options for the MSI execution allowing quieter updates.\n  - Bumped due to a bump in tauri-utils.\n  - [9f2c3413](https://www.github.com/tauri-apps/tauri/commit/9f2c34131952ea83c3f8e383bc3cec7e1450429f) feat(core): configure msiexec display options, closes [#3951](https://www.github.com/tauri-apps/tauri/pull/3951) ([#4061](https://www.github.com/tauri-apps/tauri/pull/4061)) on 2022-05-15\n\n## \\[1.0.0-rc.6]\n\n- Added `$schema` support to `tauri.conf.json`.\n  - Bumped due to a bump in tauri-utils.\n  - [715cbde3](https://www.github.com/tauri-apps/tauri/commit/715cbde3842a916c4ebeab2cab348e1774b5c192) feat(config): add `$schema` to `tauri.conf.json`, closes [#3464](https://www.github.com/tauri-apps/tauri/pull/3464) ([#4031](https://www.github.com/tauri-apps/tauri/pull/4031)) on 2022-05-03\n- The `dangerous_allow_asset_csp_modification` configuration value has been changed to allow a list of CSP directives to disable.\n  - Bumped due to a bump in tauri-utils.\n  - [164078c0](https://www.github.com/tauri-apps/tauri/commit/164078c0b719ccbc12e956fecf8a7d4a3c5044e1) feat: allow limiting dangerousDisableAssetCspModification, closes [#3831](https://www.github.com/tauri-apps/tauri/pull/3831) ([#4021](https://www.github.com/tauri-apps/tauri/pull/4021)) on 2022-05-02\n\n## \\[1.0.0-rc.5]\n\n- Read platform-specific configuration files when generating code without the `TAURI_CONFIG` env var.\n  - Bumped due to a bump in tauri-codegen.\n  - [edf85bc1](https://www.github.com/tauri-apps/tauri/commit/edf85bc1d18450c92aee17f7f99c163abe432ebd) fix(codegen): read platform-specific config file ([#3966](https://www.github.com/tauri-apps/tauri/pull/3966)) on 2022-04-25\n\n## \\[1.0.0-rc.4]\n\n- Replace multiple dependencies who's C code compiled concurrently and caused\n  the other ones to bloat compile time significantly.\n\n- `zstd` -> `brotli`\n\n- `blake3` -> a vendored version of the blake3 reference\n\n- `ring` -> `getrandom`\n\nSee <https://github.com/tauri-apps/tauri/pull/3773> for more information about\nthese specific choices.\n\n- [8661e3e2](https://www.github.com/tauri-apps/tauri/commit/8661e3e24d96c399bfbcdee5d8e9d6beba2265a7) replace dependencies with long build times when used together (closes [#3571](https://www.github.com/tauri-apps/tauri/pull/3571)) ([#3773](https://www.github.com/tauri-apps/tauri/pull/3773)) on 2022-03-27\n\n## \\[1.0.0-rc.3]\n\n- Parse window icons at compile time.\n  - Bumped due to a bump in tauri-codegen.\n  - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05\n\n## \\[1.0.0-rc.2]\n\n- Changed the default value for `tauri > bundle > macOS > minimumSystemVersion` to `10.13`.\n  - Bumped due to a bump in tauri-utils.\n  - [fce344b9](https://www.github.com/tauri-apps/tauri/commit/fce344b90b7227f8f5514853c2f885fb24d3648e) feat(core): set default value for `minimum_system_version` to 10.13 ([#3497](https://www.github.com/tauri-apps/tauri/pull/3497)) on 2022-02-17\n\n## \\[1.0.0-rc.1]\n\n- Change default value for the `freezePrototype` configuration to `false`.\n  - Bumped due to a bump in tauri-utils.\n  - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12\n\n## \\[1.0.0-rc.0]\n\n- Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension.\n\nHere is the logic flow that determines if JSON or JSON5 will be used to parse the config:\n\n1. Check if `tauri.conf.json` exists\n   a. Parse it with `serde_json`\n   b. Parse it with `json5` if `serde_json` fails\n   c. Return original `serde_json` error if all above steps failed\n2. Check if `tauri.conf.json5` exists\n   a. Parse it with `json5`\n   b. Return error if all above steps failed\n3. Return error if all above steps failed\n\n- [995de57a](https://www.github.com/tauri-apps/tauri/commit/995de57a76cf51215277673e526d7ec32b86b564) Add seamless support for using JSON5 in the config file ([#47](https://www.github.com/tauri-apps/tauri/pull/47)) on 2022-02-03\n- The minimum Rust version is now `1.56`.\n  - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22\n\n## \\[1.0.0-beta.5]\n\n- Embed Info.plist file contents on binary on dev.\n  - Bumped due to a bump in tauri-codegen.\n  - [537ab1b6](https://www.github.com/tauri-apps/tauri/commit/537ab1b6d5a792c550a535619965c9e4126292e6) feat(core): inject src-tauri/Info.plist file on dev and merge on bundle, closes [#1570](https://www.github.com/tauri-apps/tauri/pull/1570) [#2338](https://www.github.com/tauri-apps/tauri/pull/2338) ([#2444](https://www.github.com/tauri-apps/tauri/pull/2444)) on 2021-08-15\n- Fix ES Module detection for default imports with relative paths or scoped packages and exporting of async functions.\n  - Bumped due to a bump in tauri-codegen.\n  - [b2b36cfe](https://www.github.com/tauri-apps/tauri/commit/b2b36cfe8dfcccb341638a4cb6dc23a514c54148) fix(core): fixes ES Module detection for default imports with relative paths or scoped packages ([#2380](https://www.github.com/tauri-apps/tauri/pull/2380)) on 2021-08-10\n  - [fbf8caf5](https://www.github.com/tauri-apps/tauri/commit/fbf8caf5c419cb4fc3d123be910e094a8e8c4bef) fix(core): ESM detection when using `export async function` ([#2425](https://www.github.com/tauri-apps/tauri/pull/2425)) on 2021-08-14\n\n## \\[1.0.0-beta.4]\n\n- `Params` has been removed, along with all the associated types on it. Functions that previously accepted those\n  associated types now accept strings instead. Type that used a generic parameter `Params` now use `Runtime` instead. If\n  you use the `wry` feature, then types with a `Runtime` generic parameter should default to `Wry`, letting you omit the\n  explicit type and let the compiler infer it instead.\n\n`tauri`:\n\n- See `Params` note\n- If you were using `Params` inside a function parameter or definition, all references to it have been replaced with a\n  simple runtime that defaults to `Wry`. If you are not using a custom runtime, just remove `Params` from the definition\n  of functions/items that previously took it. If you are using a custom runtime, you *may* need to pass the runtime type\n  to these functions.\n- If you were using custom types for `Params` (uncommon and if you don't understand you probably were not using it), all\n  methods that were previously taking the custom type now takes an `Into<String>` or a `&str`. The types were already\n  required to be string-able, so just make sure to convert it into a string before passing it in if this breaking change\n  affects you.\n\n`tauri-macros`:\n\n- (internal) Added private `default_runtime` proc macro to allow us to give item definitions a custom runtime only when\n  the specified feature is enabled.\n\n`tauri-runtime`:\n\n- See `Params` note\n- Removed `Params`, `MenuId`, `Tag`, `TagRef`.\n- Added `menu::{MenuHash, MenuId, MenuIdRef}` as type aliases for the internal type that menu types now use.\n  - All previous menu items that had a `MenuId` generic now use the underlying `MenuId` type without a generic.\n- `Runtime`, `RuntimeHandle`, and `Dispatch` have no more generic parameter on `create_window(...)` and instead use the\n  `Runtime` type directly\n- `Runtime::system_tray` has no more `MenuId` generic and uses the string based `SystemTray` type directly.\n- (internal) `CustomMenuItem::id_value()` is now hashed on creation and exposed as the `id` field with type `MenuHash`.\n\n`tauri-runtime-wry`:\n\n- See `Params` note\n- update menu and runtime related types to the ones changed in `tauri-runtime`.\n\n`tauri-utils`:\n\n- `Assets::get` signature has changed to take a `&AssetKey` instead of `impl Into<AssetKey>` to become trait object\n  safe.\n- [fd8fab50](https://www.github.com/tauri-apps/tauri/commit/fd8fab507c8fa1b113b841af14c6693eb3955f6b) refactor(core): remove `Params` and replace with strings ([#2191](https://www.github.com/tauri-apps/tauri/pull/2191)) on 2021-07-15\n\n## \\[1.0.0-beta.3]\n\n- Detect ESM scripts and inject the invoke key directly instead of using an IIFE.\n  - Bumped due to a bump in tauri-codegen.\n  - [7765c7fa](https://www.github.com/tauri-apps/tauri/commit/7765c7fa281853ddfb26b6b17534df95eaede804) fix(core): invoke key injection on ES module, improve performance ([#2094](https://www.github.com/tauri-apps/tauri/pull/2094)) on 2021-06-27\n- Improve invoke key code injection performance time rewriting code at compile time.\n  - Bumped due to a bump in tauri-codegen.\n  - [7765c7fa](https://www.github.com/tauri-apps/tauri/commit/7765c7fa281853ddfb26b6b17534df95eaede804) fix(core): invoke key injection on ES module, improve performance ([#2094](https://www.github.com/tauri-apps/tauri/pull/2094)) on 2021-06-27\n\n## \\[1.0.0-beta.2]\n\n- internal: Refactor all macro code that expects specific bindings to be passed Idents\n  - [39f8f269](https://www.github.com/tauri-apps/tauri/commit/39f8f269164d2fda3d5b614a193b12bb266e4b4b) refactor(macros): explicitly pass idents ([#1812](https://www.github.com/tauri-apps/tauri/pull/1812)) on 2021-05-13\n\n## \\[1.0.0-beta.1]\n\n- Fixes a name collision when the command function is named `invoke`.\n  - [7862ec5](https://www.github.com/tauri-apps/tauri/commit/7862ec562fa70e3733263ce1f690d6cd2943c0b4) fix(macros): change invoke binding in generate handler ([#1804](https://www.github.com/tauri-apps/tauri/pull/1804)) on 2021-05-12\n- Fixes a name collision when the command function is named `message` or `resolver`.\n  - [0b87532](https://www.github.com/tauri-apps/tauri/commit/0b875327067ca825ff6f6f26c9b2ce6fcb001e79) fix(macros): fix rest of command collisions ([#1805](https://www.github.com/tauri-apps/tauri/pull/1805)) on 2021-05-12\n- Fixes a name collision when the command function is named `cmd`.\n  - [d36b726](https://www.github.com/tauri-apps/tauri/commit/d36b7269261d329dd7d7fcd4d5098f3fca167364) fix(macros): collision when command is named `cmd` ([#1802](https://www.github.com/tauri-apps/tauri/pull/1802)) on 2021-05-12\n\n## \\[1.0.0-beta.0]\n\n- Only commands with a `async fn` are executed on a separate task. `#[command] fn command_name` runs on the main thread.\n  - [bb8dafb](https://www.github.com/tauri-apps/tauri/commit/bb8dafbe1ea6edde7385631d41ac05e96a3309ef) feat(core): #\\[command] return with autoref specialization workaround fix [#1672](https://www.github.com/tauri-apps/tauri/pull/1672) ([#1734](https://www.github.com/tauri-apps/tauri/pull/1734)) on 2021-05-09\n- `#[command]` now generates a macro instead of a function to allow passing through `Params` and other generics.\n  `generate_handler!` has been changed to consume the generated `#[command]` macro\n  - [1453d4b](https://www.github.com/tauri-apps/tauri/commit/1453d4bf842ed6891ec604e0635344c930282189) feat(core): support generics (especially Param) in #\\[command] ([#1622](https://www.github.com/tauri-apps/tauri/pull/1622)) on 2021-05-05\n- Improves support for commands returning `Result`.\n  - [bb8dafb](https://www.github.com/tauri-apps/tauri/commit/bb8dafbe1ea6edde7385631d41ac05e96a3309ef) feat(core): #\\[command] return with autoref specialization workaround fix [#1672](https://www.github.com/tauri-apps/tauri/pull/1672) ([#1734](https://www.github.com/tauri-apps/tauri/pull/1734)) on 2021-05-09\n- Adds support to command state, triggered when a command argument is `arg: State<'_, StateType>`.\n  - [8b6f3de](https://www.github.com/tauri-apps/tauri/commit/8b6f3de0ad47684e72a2ae5f884d8675acfaeeac) feat(core): add state management, closes [#1655](https://www.github.com/tauri-apps/tauri/pull/1655) ([#1665](https://www.github.com/tauri-apps/tauri/pull/1665)) on 2021-05-02\n\n## \\[1.0.0-beta-rc.1]\n\n- Fixes the Message `command` name value on plugin invoke handler.\n  - [422dd5e](https://www.github.com/tauri-apps/tauri/commit/422dd5e2a0a03bb1556915c78f110bfab092c874) fix(core): command name on plugin invoke handler ([#1577](https://www.github.com/tauri-apps/tauri/pull/1577)) on 2021-04-21\n  - [f575aaa](https://www.github.com/tauri-apps/tauri/commit/f575aaad71f23d44b2f89cf9ee5d84817dc3bb7a) fix: change files not referencing core packages ([#1619](https://www.github.com/tauri-apps/tauri/pull/1619)) on 2021-04-25\n\n## \\[1.0.0-beta-rc.0]\n\n- Update all code files to have our license header.\n  - [bf82136](https://www.github.com/tauri-apps/tauri/commit/bf8213646689175f8a158b956911f3a43e360690) feat(license): SPDX Headers ([#1449](https://www.github.com/tauri-apps/tauri/pull/1449)) on 2021-04-11\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Added new macros to simplify the creation of commands that can be called by the webview.\n  - [1f2e7a3](https://www.github.com/tauri-apps/tauri/commit/1f2e7a3226ccf0ee3e30ae0cba3c67f7e219d1f2) feat(core): improved command matching with macros, fixes [#1157](https://www.github.com/tauri-apps/tauri/pull/1157) ([#1301](https://www.github.com/tauri-apps/tauri/pull/1301)) on 2021-02-28\n"
  },
  {
    "path": "crates/tauri-macros/Cargo.toml",
    "content": "[package]\nname = \"tauri-macros\"\nversion = \"2.5.5\"\ndescription = \"Macros for the tauri crate.\"\nexclude = [\"CHANGELOG.md\", \"/target\"]\nreadme = \"README.md\"\nauthors.workspace = true\nhomepage.workspace = true\nrepository.workspace = true\ncategories.workspace = true\nlicense.workspace = true\nedition.workspace = true\nrust-version.workspace = true\n\n[lib]\nproc-macro = true\n\n[dependencies]\nproc-macro2 = { version = \"1\", features = [\"span-locations\"] }\nquote = \"1\"\nsyn = { version = \"2\", features = [\"full\"] }\nheck = \"0.5\"\ntauri-codegen = { version = \"2.5.5\", default-features = false, path = \"../tauri-codegen\" }\ntauri-utils = { version = \"2.8.3\", path = \"../tauri-utils\" }\n\n[features]\ncustom-protocol = []\ncompression = [\"tauri-codegen/compression\"]\nisolation = [\"tauri-codegen/isolation\"]\nconfig-json5 = [\"tauri-codegen/config-json5\", \"tauri-utils/config-json5\"]\nconfig-toml = [\"tauri-codegen/config-toml\", \"tauri-utils/config-toml\"]\ntracing = []\n"
  },
  {
    "path": "crates/tauri-macros/LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/tauri-macros/LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2017 - Present Tauri Apps Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "crates/tauri-macros/README.md",
    "content": "# tauri-macros\n\n <img align=\"right\" src=\"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\" height=\"128\" width=\"128\">\n\n[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev)\n[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri)\n[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield)\n[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S)\n[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app)\n[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)\n[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri)\n\n| Component    | Version                                                                                                      |\n| ------------ | ------------------------------------------------------------------------------------------------------------ |\n| tauri-macros | [![](https://img.shields.io/crates/v/tauri-macros?style=flat-square)](https://crates.io/crates/tauri-macros) |\n\n## About Tauri\n\nTauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.\n\nTauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.\n\n## This module\n\nCreate macros for the context, handler, and commands by leveraging the `tauri-codegen` crate.\n\nTo learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.\n\n## Semver\n\n**tauri** is following [Semantic Versioning 2.0](https://semver.org/).\n\n## Licenses\n\nCode: (c) 2021 - The Tauri Programme within The Commons Conservancy.\n\nMIT or MIT/Apache 2.0 where applicable.\n\nLogo: CC-BY-NC-ND\n\n- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)\n"
  },
  {
    "path": "crates/tauri-macros/src/command/handler.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse quote::format_ident;\nuse syn::{\n  parse::{Parse, ParseBuffer, ParseStream},\n  Attribute, Ident, Path, Token,\n};\n\nstruct CommandDef {\n  path: Path,\n  attrs: Vec<Attribute>,\n}\n\nimpl Parse for CommandDef {\n  fn parse(input: ParseStream) -> syn::Result<Self> {\n    let attrs = input.call(Attribute::parse_outer)?;\n    let path = input.parse()?;\n\n    Ok(CommandDef { path, attrs })\n  }\n}\n\n/// The items parsed from [`generate_handle!`](crate::generate_handle).\npub struct Handler {\n  command_defs: Vec<CommandDef>,\n  commands: Vec<Ident>,\n  wrappers: Vec<Path>,\n}\n\nimpl Parse for Handler {\n  fn parse(input: &ParseBuffer<'_>) -> syn::Result<Self> {\n    let plugin_name = try_get_plugin_name(input)?;\n\n    let mut command_defs = input\n      .parse_terminated(CommandDef::parse, Token![,])?\n      .into_iter()\n      .collect();\n\n    filter_unused_commands(plugin_name, &mut command_defs);\n    let mut commands = Vec::new();\n    let mut wrappers = Vec::new();\n\n    // parse the command names and wrappers from the passed paths\n    for command_def in &command_defs {\n      let mut wrapper = command_def.path.clone();\n      let last = super::path_to_command(&mut wrapper);\n\n      // the name of the actual command function\n      let command = last.ident.clone();\n\n      // set the path to the command function wrapper\n      last.ident = super::format_command_wrapper(&command);\n\n      commands.push(command);\n      wrappers.push(wrapper);\n    }\n\n    Ok(Self {\n      command_defs,\n      commands,\n      wrappers,\n    })\n  }\n}\n\n/// Try to get the plugin name by parsing the input for a `#![plugin(...)]` attribute,\n/// if it's not present, try getting it from `CARGO_PKG_NAME` environment variable\nfn try_get_plugin_name(input: &ParseBuffer<'_>) -> Result<Option<String>, syn::Error> {\n  if let Ok(attrs) = input.call(Attribute::parse_inner) {\n    for attr in attrs {\n      if attr.path().is_ident(\"plugin\") {\n        // Parse the content inside #![plugin(...)]\n        let plugin_name = attr.parse_args::<Ident>()?.to_string();\n        return Ok(Some(if plugin_name == \"__TAURI_CHANNEL__\" {\n          // TODO: Remove this in v3\n          plugin_name\n        } else {\n          plugin_name.replace(\"_\", \"-\")\n        }));\n      }\n    }\n  }\n  Ok(\n    std::env::var(\"CARGO_PKG_NAME\")\n      .ok()\n      .and_then(|var| var.strip_prefix(\"tauri-plugin-\").map(String::from)),\n  )\n}\n\nfn filter_unused_commands(plugin_name: Option<String>, command_defs: &mut Vec<CommandDef>) {\n  let allowed_commands = tauri_utils::acl::read_allowed_commands();\n  let Some(allowed_commands) = allowed_commands else {\n    return;\n  };\n\n  // TODO: Remove this in v3\n  if plugin_name.as_deref() == Some(\"__TAURI_CHANNEL__\") {\n    // Always allowed\n    return;\n  }\n\n  if plugin_name.is_none() && !allowed_commands.has_app_acl {\n    // All application commands are allowed if we don't have an application ACL\n    //\n    // note that inline plugins without the #![plugin()] attribute would also get to this check\n    // which means inline plugins must have an app manifest to get proper unused command removal\n    return;\n  }\n\n  let mut unused_commands = Vec::new();\n\n  let command_prefix = if let Some(plugin_name) = &plugin_name {\n    format!(\"plugin:{plugin_name}|\")\n  } else {\n    \"\".into()\n  };\n\n  command_defs.retain(|command_def| {\n    let mut wrapper = command_def.path.clone();\n    let last = super::path_to_command(&mut wrapper);\n\n    // the name of the actual command function\n    let command_name = &last.ident;\n\n    let command = format!(\"{command_prefix}{command_name}\");\n    let is_allowed = allowed_commands.commands.contains(&command);\n\n    if !is_allowed {\n      unused_commands.push(command_name.to_string());\n    }\n\n    is_allowed\n  });\n\n  if !unused_commands.is_empty() {\n    let plugin_display_name = plugin_name.as_deref().unwrap_or(\"application\");\n    let unused_commands_display = unused_commands.join(\", \");\n    println!(\"Removed unused commands from {plugin_display_name}: {unused_commands_display}\",);\n  }\n}\n\nimpl From<Handler> for proc_macro::TokenStream {\n  fn from(\n    Handler {\n      command_defs,\n      commands,\n      wrappers,\n    }: Handler,\n  ) -> Self {\n    let cmd = format_ident!(\"__tauri_cmd__\");\n    let invoke = format_ident!(\"__tauri_invoke__\");\n    let (paths, attrs): (Vec<Path>, Vec<Vec<Attribute>>) = command_defs\n      .into_iter()\n      .map(|def| (def.path, def.attrs))\n      .unzip();\n    quote::quote!(move |#invoke| {\n      let #cmd = #invoke.message.command();\n      match #cmd {\n        #(#(#attrs)* stringify!(#commands) => #wrappers!(#paths, #invoke),)*\n        _ => {\n          return false;\n        },\n      }\n    })\n    .into()\n  }\n}\n"
  },
  {
    "path": "crates/tauri-macros/src/command/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse proc_macro2::Ident;\nuse syn::{Path, PathSegment};\n\npub use self::{handler::Handler, wrapper::wrapper};\n\nmod handler;\nmod wrapper;\n\n/// The autogenerated wrapper ident.\nfn format_command_wrapper(function: &Ident) -> Ident {\n  quote::format_ident!(\"__cmd__{}\", function)\n}\n\n/// This function will panic if the passed [`syn::Path`] does not have any segments.\nfn path_to_command(path: &mut Path) -> &mut PathSegment {\n  path\n    .segments\n    .last_mut()\n    .expect(\"parsed syn::Path has no segment\")\n}\n"
  },
  {
    "path": "crates/tauri-macros/src/command/wrapper.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{env::var, sync::OnceLock};\n\nuse heck::{ToLowerCamelCase, ToSnakeCase};\nuse proc_macro::TokenStream;\nuse proc_macro2::{Ident, Span, TokenStream as TokenStream2};\nuse quote::{format_ident, quote, quote_spanned};\nuse syn::{\n  ext::IdentExt,\n  parse::{Parse, ParseStream},\n  parse_macro_input,\n  punctuated::Punctuated,\n  spanned::Spanned,\n  Expr, ExprLit, FnArg, ItemFn, Lit, Meta, Pat, Token, Visibility,\n};\nuse tauri_utils::acl::REMOVE_UNUSED_COMMANDS_ENV_VAR;\n\n#[allow(clippy::large_enum_variant)]\nenum WrapperAttributeKind {\n  Meta(Meta),\n  Async,\n}\n\nimpl Parse for WrapperAttributeKind {\n  fn parse(input: ParseStream) -> syn::Result<Self> {\n    match input.parse::<Meta>() {\n      Ok(m) => Ok(Self::Meta(m)),\n      Err(e) => match input.parse::<Token![async]>() {\n        Ok(_) => Ok(Self::Async),\n        Err(_) => Err(e),\n      },\n    }\n  }\n}\n\nstruct WrapperAttributes {\n  root: TokenStream2,\n  execution_context: ExecutionContext,\n  argument_case: ArgumentCase,\n}\n\nimpl Parse for WrapperAttributes {\n  fn parse(input: ParseStream) -> syn::Result<Self> {\n    let mut wrapper_attributes = WrapperAttributes {\n      root: quote!(::tauri),\n      execution_context: ExecutionContext::Blocking,\n      argument_case: ArgumentCase::Camel,\n    };\n\n    let attrs = Punctuated::<WrapperAttributeKind, Token![,]>::parse_terminated(input)?;\n    for attr in attrs {\n      match attr {\n        WrapperAttributeKind::Meta(Meta::List(_)) => {\n          return Err(syn::Error::new(input.span(), \"unexpected list input\"));\n        }\n        WrapperAttributeKind::Meta(Meta::NameValue(v)) => {\n          if v.path.is_ident(\"rename_all\") {\n            if let Expr::Lit(ExprLit {\n              lit: Lit::Str(s),\n              attrs: _,\n            }) = v.value\n            {\n              wrapper_attributes.argument_case = match s.value().as_str() {\n                \"snake_case\" => ArgumentCase::Snake,\n                \"camelCase\" => ArgumentCase::Camel,\n                _ => {\n                  return Err(syn::Error::new(\n                    s.span(),\n                    \"expected \\\"camelCase\\\" or \\\"snake_case\\\"\",\n                  ))\n                }\n              };\n            }\n          } else if v.path.is_ident(\"root\") {\n            if let Expr::Lit(ExprLit {\n              lit: Lit::Str(s),\n              attrs: _,\n            }) = v.value\n            {\n              let lit = s.value();\n\n              wrapper_attributes.root = if lit == \"crate\" {\n                quote!($crate)\n              } else {\n                let ident = Ident::new(&lit, Span::call_site());\n                quote!(#ident)\n              };\n            }\n          }\n        }\n        WrapperAttributeKind::Meta(Meta::Path(_)) => {\n          return Err(syn::Error::new(\n            input.span(),\n            \"unexpected input, expected one of `rename_all`, `root`, `async`\",\n          ));\n        }\n        WrapperAttributeKind::Async => {\n          wrapper_attributes.execution_context = ExecutionContext::Async;\n        }\n      }\n    }\n\n    Ok(wrapper_attributes)\n  }\n}\n\n/// The execution context of the command.\nenum ExecutionContext {\n  Async,\n  Blocking,\n}\n\n/// The case of each argument name.\n#[derive(Copy, Clone)]\nenum ArgumentCase {\n  Snake,\n  Camel,\n}\n\n/// The bindings we attach to `tauri::Invoke`.\nstruct Invoke {\n  message: Ident,\n  resolver: Ident,\n  acl: Ident,\n}\n\n/// Create a new [`Wrapper`] from the function and the generated code parsed from the function.\npub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {\n  let mut attrs = parse_macro_input!(attributes as WrapperAttributes);\n  let function = parse_macro_input!(item as ItemFn);\n  let wrapper = super::format_command_wrapper(&function.sig.ident);\n  let visibility = &function.vis;\n\n  if function.sig.asyncness.is_some() {\n    attrs.execution_context = ExecutionContext::Async;\n  }\n\n  // macros used with `pub use my_macro;` need to be exported with `#[macro_export]`\n  let maybe_macro_export = match &function.vis {\n    Visibility::Public(_) | Visibility::Restricted(_) => quote!(#[macro_export]),\n    _ => TokenStream2::default(),\n  };\n\n  let invoke = Invoke {\n    message: format_ident!(\"__tauri_message__\"),\n    resolver: format_ident!(\"__tauri_resolver__\"),\n    acl: format_ident!(\"__tauri_acl__\"),\n  };\n\n  // Tauri currently doesn't support async commands that take a reference as input and don't return\n  // a result. See: https://github.com/tauri-apps/tauri/issues/2533\n  //\n  // For now, we provide an informative error message to the user in that case. Once #2533 is\n  // resolved, this check can be removed.\n  let mut async_command_check = TokenStream2::new();\n  if function.sig.asyncness.is_some() {\n    // This check won't catch all possible problems but it should catch the most common ones.\n    let mut ref_argument_span = None;\n\n    for arg in &function.sig.inputs {\n      if let syn::FnArg::Typed(pat) = arg {\n        match &*pat.ty {\n          syn::Type::Reference(_) => {\n            ref_argument_span = Some(pat.span());\n          }\n          syn::Type::Path(path) => {\n            // Check if the type contains a lifetime argument\n            let last = path.path.segments.last().unwrap();\n            if let syn::PathArguments::AngleBracketed(args) = &last.arguments {\n              if args\n                .args\n                .iter()\n                .any(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))\n              {\n                ref_argument_span = Some(pat.span());\n              }\n            }\n          }\n          _ => {}\n        }\n\n        if let Some(span) = ref_argument_span {\n          if let syn::ReturnType::Type(_, return_type) = &function.sig.output {\n            // To check if the return type is `Result` we require it to check a trait that is\n            // only implemented by `Result`. That way we don't exclude renamed result types\n            // which we wouldn't otherwise be able to detect purely from the token stream.\n            // The \"error message\" displayed to the user is simply the trait name.\n            //\n            // TODO: remove this check once our MSRV is high enough\n            let diagnostic = if is_rustc_at_least(1, 78) {\n              quote!(#[diagnostic::on_unimplemented(message = \"async commands that contain references as inputs must return a `Result`\")])\n            } else {\n              quote!()\n            };\n\n            async_command_check = quote_spanned! {return_type.span() =>\n              #[allow(unreachable_code, clippy::diverging_sub_expression, clippy::used_underscore_binding)]\n              const _: () = if false {\n                #diagnostic\n                trait AsyncCommandMustReturnResult {}\n                impl<A, B> AsyncCommandMustReturnResult for ::std::result::Result<A, B> {}\n                let _check: #return_type = unreachable!();\n                let _: &dyn AsyncCommandMustReturnResult = &_check;\n              };\n            };\n          } else {\n            return quote_spanned! {\n              span => compile_error!(\"async commands that contain references as inputs must return a `Result`\");\n            }.into();\n          }\n        }\n      }\n    }\n  }\n\n  let plugin_name = var(\"CARGO_PKG_NAME\")\n    .expect(\"missing `CARGO_PKG_NAME` environment variable\")\n    .strip_prefix(\"tauri-plugin-\")\n    .map(|name| quote!(::core::option::Option::Some(#name)))\n    .unwrap_or_else(|| quote!(::core::option::Option::None));\n\n  let body = match attrs.execution_context {\n    ExecutionContext::Async => body_async(&plugin_name, &function, &invoke, &attrs)\n      .unwrap_or_else(syn::Error::into_compile_error),\n    ExecutionContext::Blocking => body_blocking(&plugin_name, &function, &invoke, &attrs)\n      .unwrap_or_else(syn::Error::into_compile_error),\n  };\n\n  let Invoke {\n    message,\n    resolver,\n    acl,\n  } = invoke;\n\n  let root = attrs.root;\n\n  let kind = match attrs.execution_context {\n    ExecutionContext::Async if function.sig.asyncness.is_none() => \"sync_threadpool\",\n    ExecutionContext::Async => \"async\",\n    ExecutionContext::Blocking => \"sync\",\n  };\n\n  let loc = function.span().start();\n  let line = loc.line;\n  let col = loc.column;\n\n  let maybe_span = if cfg!(feature = \"tracing\") {\n    quote!({\n      let _span = tracing::debug_span!(\n        \"ipc::request::handler\",\n        cmd = #message.command(),\n        kind = #kind,\n        loc.line = #line,\n        loc.col = #col,\n        is_internal = false,\n      )\n      .entered();\n    })\n  } else {\n    quote!()\n  };\n\n  // Allow this to be unused when we're building with `build > removeUnusedCommands` for dead code elimination\n  let maybe_allow_unused = if var(REMOVE_UNUSED_COMMANDS_ENV_VAR).is_ok() {\n    quote!(#[allow(unused)])\n  } else {\n    TokenStream2::default()\n  };\n\n  // Rely on rust 2018 edition to allow importing a macro from a path.\n  quote!(\n    #async_command_check\n\n    #maybe_allow_unused\n    #function\n\n    #maybe_allow_unused\n    #maybe_macro_export\n    #[doc(hidden)]\n    macro_rules! #wrapper {\n      // double braces because the item is expected to be a block expression\n      ($path:path, $invoke:ident) => {\n        // The IIFE here is for preventing stack overflow on Windows,\n        // see https://github.com/tauri-apps/tauri/issues/12488\n        {\n          move || {\n            #[allow(unused_imports)]\n            use #root::ipc::private::*;\n            // prevent warnings when the body is a `compile_error!` or if the command has no arguments\n            #[allow(unused_variables)]\n            let #root::ipc::Invoke { message: #message, resolver: #resolver, acl: #acl } = $invoke;\n\n            #maybe_span\n\n            #body\n          }\n        }()\n      };\n    }\n\n    // allow the macro to be resolved with the same path as the command function\n    #[allow(unused_imports)]\n    #visibility use #wrapper;\n  )\n  .into()\n}\n\n/// Generates an asynchronous command response from the arguments and return value of a function.\n///\n/// See the [`tauri::command`] module for all the items and traits that make this possible.\n///\n/// [`tauri::command`]: https://docs.rs/tauri/*/tauri/runtime/index.html\nfn body_async(\n  plugin_name: &TokenStream2,\n  function: &ItemFn,\n  invoke: &Invoke,\n  attributes: &WrapperAttributes,\n) -> syn::Result<TokenStream2> {\n  let Invoke {\n    message,\n    resolver,\n    acl,\n  } = invoke;\n  parse_args(plugin_name, function, message, acl, attributes).map(|args| {\n    #[cfg(feature = \"tracing\")]\n    quote! {\n      use tracing::Instrument;\n\n      let span = tracing::debug_span!(\"ipc::request::run\");\n      #resolver.respond_async_serialized(async move {\n        let result = $path(#(#args?),*);\n        let kind = (&result).async_kind();\n        kind.future(result).await\n      }\n      .instrument(span));\n      return true;\n    }\n\n    #[cfg(not(feature = \"tracing\"))]\n    quote! {\n      #resolver.respond_async_serialized(async move {\n        let result = $path(#(#args?),*);\n        let kind = (&result).async_kind();\n        kind.future(result).await\n      });\n      return true;\n    }\n  })\n}\n\n/// Generates a blocking command response from the arguments and return value of a function.\n///\n/// See the [`tauri::command`] module for all the items and traits that make this possible.\n///\n/// [`tauri::command`]: https://docs.rs/tauri/*/tauri/runtime/index.html\nfn body_blocking(\n  plugin_name: &TokenStream2,\n  function: &ItemFn,\n  invoke: &Invoke,\n  attributes: &WrapperAttributes,\n) -> syn::Result<TokenStream2> {\n  let Invoke {\n    message,\n    resolver,\n    acl,\n  } = invoke;\n  let args = parse_args(plugin_name, function, message, acl, attributes)?;\n\n  // the body of a `match` to early return any argument that wasn't successful in parsing.\n  let match_body = quote!({\n    Ok(arg) => arg,\n    Err(err) => { #resolver.invoke_error(err); return true },\n  });\n\n  let maybe_span = if cfg!(feature = \"tracing\") {\n    quote!(let _span = tracing::debug_span!(\"ipc::request::run\").entered();)\n  } else {\n    quote!()\n  };\n\n  Ok(quote! {\n    #maybe_span\n    let result = $path(#(match #args #match_body),*);\n    let kind = (&result).blocking_kind();\n    kind.block(result, #resolver);\n    return true;\n  })\n}\n\n/// Parse all arguments for the command wrapper to use from the signature of the command function.\nfn parse_args(\n  plugin_name: &TokenStream2,\n  function: &ItemFn,\n  message: &Ident,\n  acl: &Ident,\n  attributes: &WrapperAttributes,\n) -> syn::Result<Vec<TokenStream2>> {\n  function\n    .sig\n    .inputs\n    .iter()\n    .map(|arg| {\n      parse_arg(\n        plugin_name,\n        &function.sig.ident,\n        arg,\n        message,\n        acl,\n        attributes,\n      )\n    })\n    .collect()\n}\n\n/// Transform a [`FnArg`] into a command argument.\nfn parse_arg(\n  plugin_name: &TokenStream2,\n  command: &Ident,\n  arg: &FnArg,\n  message: &Ident,\n  acl: &Ident,\n  attributes: &WrapperAttributes,\n) -> syn::Result<TokenStream2> {\n  // we have no use for self arguments\n  let mut arg = match arg {\n    FnArg::Typed(arg) => arg.pat.as_ref().clone(),\n    FnArg::Receiver(arg) => {\n      return Err(syn::Error::new(\n        arg.span(),\n        \"unable to use self as a command function parameter\",\n      ))\n    }\n  };\n\n  // we only support patterns that allow us to extract some sort of keyed identifier\n  let mut key = match &mut arg {\n    Pat::Ident(arg) => arg.ident.unraw().to_string(),\n    Pat::Wild(_) => \"\".into(), // we always convert to camelCase, so \"_\" will end up empty anyways\n    Pat::Struct(s) => super::path_to_command(&mut s.path).ident.to_string(),\n    Pat::TupleStruct(s) => super::path_to_command(&mut s.path).ident.to_string(),\n    err => {\n      return Err(syn::Error::new(\n        err.span(),\n        \"only named, wildcard, struct, and tuple struct arguments allowed\",\n      ))\n    }\n  };\n\n  // also catch self arguments that use FnArg::Typed syntax\n  if key == \"self\" {\n    return Err(syn::Error::new(\n      key.span(),\n      \"unable to use self as a command function parameter\",\n    ));\n  }\n\n  match attributes.argument_case {\n    ArgumentCase::Camel => {\n      key = key.to_lower_camel_case();\n    }\n    ArgumentCase::Snake => {\n      key = key.to_snake_case();\n    }\n  }\n\n  let root = &attributes.root;\n\n  Ok(quote!(#root::ipc::CommandArg::from_command(\n    #root::ipc::CommandItem {\n      plugin: #plugin_name,\n      name: stringify!(#command),\n      key: #key,\n      message: &#message,\n      acl: &#acl,\n    }\n  )))\n}\n\nfn is_rustc_at_least(major: u32, minor: u32) -> bool {\n  let version = rustc_version();\n  version.0 >= major && version.1 >= minor\n}\n\nfn rustc_version() -> &'static (u32, u32) {\n  static RUSTC_VERSION: OnceLock<(u32, u32)> = OnceLock::new();\n  RUSTC_VERSION.get_or_init(|| {\n    cross_command(\"rustc\")\n      .arg(\"-V\")\n      .output()\n      .ok()\n      .and_then(|o| {\n        let version = String::from_utf8_lossy(&o.stdout)\n          .trim()\n          .split(' ')\n          .nth(1)\n          .unwrap_or_default()\n          .split('.')\n          .take(2)\n          .flat_map(|p| p.parse::<u32>().ok())\n          .collect::<Vec<_>>();\n        version\n          .first()\n          .and_then(|major| version.get(1).map(|minor| (*major, *minor)))\n      })\n      .unwrap_or((1, 0))\n  })\n}\n\nfn cross_command(bin: &str) -> std::process::Command {\n  #[cfg(target_os = \"windows\")]\n  let cmd = {\n    let mut cmd = std::process::Command::new(\"cmd\");\n    cmd.arg(\"/c\").arg(bin);\n    cmd\n  };\n  #[cfg(not(target_os = \"windows\"))]\n  let cmd = std::process::Command::new(bin);\n  cmd\n}\n"
  },
  {
    "path": "crates/tauri-macros/src/context.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse proc_macro2::{Ident, Span, TokenStream};\nuse quote::{quote, ToTokens};\nuse std::path::PathBuf;\nuse syn::{\n  parse::{Parse, ParseBuffer},\n  punctuated::Punctuated,\n  Expr, ExprLit, Lit, LitBool, LitStr, Meta, PathArguments, PathSegment, Token,\n};\nuse tauri_codegen::{context_codegen, get_config, ContextData};\nuse tauri_utils::{config::parse::does_supported_file_name_exist, platform::Target};\n\npub(crate) struct ContextItems {\n  config_file: PathBuf,\n  root: syn::Path,\n  capabilities: Option<Vec<PathBuf>>,\n  assets: Option<Expr>,\n  test: bool,\n}\n\nimpl Parse for ContextItems {\n  fn parse(input: &ParseBuffer<'_>) -> syn::parse::Result<Self> {\n    let target = std::env::var(\"TARGET\")\n      .or_else(|_| std::env::var(\"TAURI_ENV_TARGET_TRIPLE\"))\n      .as_deref()\n      .map(Target::from_triple)\n      .unwrap_or_else(|_| Target::current());\n\n    let mut root = None;\n    let mut capabilities = None;\n    let mut assets = None;\n    let mut test = false;\n    let config_file = input.parse::<LitStr>().ok().map(|raw| {\n      let _ = input.parse::<Token![,]>();\n      let path = PathBuf::from(raw.value());\n      if path.is_relative() {\n        std::env::var(\"CARGO_MANIFEST_DIR\")\n          .map(|m| PathBuf::from(m).join(path))\n          .map_err(|e| e.to_string())\n      } else {\n        Ok(path)\n      }\n      .and_then(|path| {\n        if does_supported_file_name_exist(target, &path) {\n          Ok(path)\n        } else {\n          Err(format!(\n            \"no file at path {} exists, expected tauri config file\",\n            path.display()\n          ))\n        }\n      })\n    });\n\n    while let Ok(meta) = input.parse::<Meta>() {\n      match meta {\n        Meta::Path(p) => {\n          root.replace(p);\n        }\n        Meta::NameValue(v) => {\n          let ident = v.path.require_ident()?;\n          match ident.to_string().as_str() {\n            \"capabilities\" => {\n              if let Expr::Array(array) = v.value {\n                capabilities.replace(\n                  array\n                    .elems\n                    .into_iter()\n                    .map(|e| {\n                      if let Expr::Lit(ExprLit {\n                        attrs: _,\n                        lit: Lit::Str(s),\n                      }) = e\n                      {\n                        Ok(s.value().into())\n                      } else {\n                        Err(syn::Error::new(\n                          input.span(),\n                          \"unexpected expression for capability\",\n                        ))\n                      }\n                    })\n                    .collect::<Result<Vec<_>, syn::Error>>()?,\n                );\n              } else {\n                return Err(syn::Error::new(\n                  input.span(),\n                  \"unexpected value for capabilities\",\n                ));\n              }\n            }\n            \"assets\" => {\n              assets.replace(v.value);\n            }\n            \"test\" => {\n              if let Expr::Lit(ExprLit {\n                lit: Lit::Bool(LitBool { value, .. }),\n                ..\n              }) = v.value\n              {\n                test = value;\n              } else {\n                return Err(syn::Error::new(input.span(), \"unexpected value for test\"));\n              }\n            }\n            name => {\n              return Err(syn::Error::new(\n                input.span(),\n                format!(\"unknown attribute {name}\"),\n              ));\n            }\n          }\n        }\n        Meta::List(_) => {\n          return Err(syn::Error::new(input.span(), \"unexpected list input\"));\n        }\n      }\n\n      let _ = input.parse::<Token![,]>();\n    }\n\n    Ok(Self {\n      config_file: config_file\n        .unwrap_or_else(|| {\n          std::env::var(\"CARGO_MANIFEST_DIR\")\n            .map(|m| PathBuf::from(m).join(\"tauri.conf.json\"))\n            .map_err(|e| e.to_string())\n        })\n        .map_err(|e| input.error(e))?,\n      root: root.unwrap_or_else(|| {\n        let mut segments = Punctuated::new();\n        segments.push(PathSegment {\n          ident: Ident::new(\"tauri\", Span::call_site()),\n          arguments: PathArguments::None,\n        });\n        syn::Path {\n          leading_colon: Some(Token![::](Span::call_site())),\n          segments,\n        }\n      }),\n      capabilities,\n      assets,\n      test,\n    })\n  }\n}\n\npub(crate) fn generate_context(context: ContextItems) -> TokenStream {\n  let context = get_config(&context.config_file)\n    .map_err(|e| e.to_string())\n    .map(|(config, config_parent)| ContextData {\n      dev: cfg!(not(feature = \"custom-protocol\")),\n      config,\n      config_parent,\n      root: context.root.to_token_stream(),\n      capabilities: context.capabilities,\n      assets: context.assets,\n      test: context.test,\n    })\n    .and_then(|data| context_codegen(data).map_err(|e| e.to_string()));\n\n  match context {\n    Ok(code) => code,\n    Err(error) => quote!(compile_error!(#error)),\n  }\n}\n"
  },
  {
    "path": "crates/tauri-macros/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Create macros for `tauri::Context`, invoke handler and commands leveraging the `tauri-codegen` crate.\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n\nuse std::path::PathBuf;\n\nuse crate::context::ContextItems;\nuse proc_macro::TokenStream;\nuse quote::{quote, ToTokens};\nuse syn::{parse2, parse_macro_input, LitStr};\nuse tauri_codegen::image::CachedIcon;\n\nmod command;\nmod menu;\nmod mobile;\nmod runtime;\n\n#[macro_use]\nmod context;\n\n/// Mark a function as a command handler. It creates a wrapper function with the necessary glue code.\n///\n/// # Stability\n/// The output of this macro is managed internally by Tauri,\n/// and should not be accessed directly on normal applications.\n/// It may have breaking changes in the future.\n#[proc_macro_attribute]\npub fn command(attributes: TokenStream, item: TokenStream) -> TokenStream {\n  command::wrapper(attributes, item)\n}\n\n#[proc_macro_attribute]\npub fn mobile_entry_point(attributes: TokenStream, item: TokenStream) -> TokenStream {\n  mobile::entry_point(attributes, item)\n}\n\n/// Accepts a list of command functions. Creates a handler that allows commands to be called from JS with invoke().\n///\n/// You can optionally annotate the commands with a inner attribute tag `#![plugin(your_plugin_name)]`\n/// for `build > removeUnusedCommands` to work for plugins not defined in a standalone crate like `tauri-plugin-fs`\n///\n/// # Examples\n///\n/// ```rust,ignore\n/// use tauri_macros::{command, generate_handler};\n/// #[command]\n/// fn command_one() {\n///   println!(\"command one called\");\n/// }\n/// #[command]\n/// fn command_two() {\n///   println!(\"command two called\");\n/// }\n/// fn main() {\n///   let _handler = generate_handler![command_one, command_two];\n/// }\n/// ```\n///\n/// # Stability\n///\n/// The output of this macro is managed internally by Tauri,\n/// and should not be accessed directly on normal applications.\n/// It may have breaking changes in the future.\n#[proc_macro]\npub fn generate_handler(item: TokenStream) -> TokenStream {\n  parse_macro_input!(item as command::Handler).into()\n}\n\n/// Reads a Tauri config file and generates a `::tauri::Context` based on the content.\n///\n/// # Stability\n/// The output of this macro is managed internally by Tauri,\n/// and should not be accessed directly on normal applications.\n/// It may have breaking changes in the future.\n#[proc_macro]\npub fn generate_context(items: TokenStream) -> TokenStream {\n  // this macro is exported from the context module\n  let path = parse_macro_input!(items as ContextItems);\n  context::generate_context(path).into()\n}\n\n/// Adds the default type for the last parameter (assumed to be runtime) for a specific feature.\n///\n/// e.g. To default the runtime generic to type `crate::Wry` when the `wry` feature is enabled, the\n/// syntax would look like `#[default_runtime(crate::Wry, wry)`. This is **always** set for the last\n/// generic, so make sure the last generic is the runtime when using this macro.\n#[doc(hidden)]\n#[proc_macro_attribute]\npub fn default_runtime(attributes: TokenStream, input: TokenStream) -> TokenStream {\n  let attributes = parse_macro_input!(attributes as runtime::Attributes);\n  let input = parse_macro_input!(input as runtime::Input);\n  runtime::default_runtime(attributes, input).into()\n}\n\n/// Accepts a closure-like syntax to call arbitrary code on a menu item\n/// after matching against `kind` and retrieving it from `resources_table` using `rid`.\n///\n/// You can optionally pass a 5th parameter to select which item kinds\n/// to match against, by providing a `|` separated list of item kinds\n/// ```ignore\n/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text), Check | Submenu);\n/// ```\n/// You could also provide a negated list\n/// ```ignore\n/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text), !Check);\n/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text), !Check | !Submenu);\n/// ```\n/// but you can't have mixed negations and positive kinds.\n/// ```ignore\n/// do_menu_item!(resources_table, rid, kind, |i| i.set_text(text), !Check | Submenu);\n/// ```\n///\n/// #### Example\n///\n/// ```ignore\n///  let rid = 23;\n///  let kind = ItemKind::Check;\n///  let resources_table = app.resources_table();\n///  do_menu_item!(resources_table, rid, kind, |i| i.set_text(text))\n/// ```\n/// which will expand into:\n/// ```ignore\n///  let rid = 23;\n///  let kind = ItemKind::Check;\n///  let resources_table = app.resources_table();\n///  match kind {\n///  ItemKind::Submenu => {\n///    let i = resources_table.get::<Submenu<R>>(rid)?;\n///    i.set_text(text)\n///  }\n///  ItemKind::MenuItem => {\n///    let i = resources_table.get::<MenuItem<R>>(rid)?;\n///    i.set_text(text)\n///  }\n///  ItemKind::Predefined => {\n///    let i = resources_table.get::<PredefinedMenuItem<R>>(rid)?;\n///    i.set_text(text)\n///  }\n///  ItemKind::Check => {\n///    let i = resources_table.get::<CheckMenuItem<R>>(rid)?;\n///    i.set_text(text)\n///  }\n///  ItemKind::Icon => {\n///    let i = resources_table.get::<IconMenuItem<R>>(rid)?;\n///    i.set_text(text)\n///  }\n///  _ => unreachable!(),\n///  }\n/// ```\n#[proc_macro]\npub fn do_menu_item(input: TokenStream) -> TokenStream {\n  let tokens = parse_macro_input!(input as menu::DoMenuItemInput);\n  menu::do_menu_item(tokens).into()\n}\n\n/// Convert a .png or .ico icon to an Image\n/// for things like `tauri::tray::TrayIconBuilder` to consume,\n/// relative paths are resolved from `CARGO_MANIFEST_DIR`, not current file\n///\n/// ### Examples\n///\n/// ```ignore\n/// const APP_ICON: Image<'_> = include_image!(\"./icons/32x32.png\");\n///\n/// // then use it with tray\n/// TrayIconBuilder::new().icon(APP_ICON).build().unwrap();\n///\n/// // or with window\n/// WebviewWindowBuilder::new(app, \"main\", WebviewUrl::default())\n///     .icon(APP_ICON)\n///     .unwrap()\n///     .build()\n///     .unwrap();\n///\n/// // or with any other functions that takes `Image` struct\n/// ```\n///\n/// Note: this stores the image in raw pixels to the final binary,\n/// so keep the icon size (width and height) small\n/// or else it's going to bloat your final executable\n#[proc_macro]\npub fn include_image(tokens: TokenStream) -> TokenStream {\n  let path = match parse2::<LitStr>(tokens.into()) {\n    Ok(path) => path,\n    Err(err) => return err.into_compile_error().into(),\n  };\n  let path = PathBuf::from(path.value());\n  let resolved_path = if path.is_relative() {\n    if let Ok(base_dir) = std::env::var(\"CARGO_MANIFEST_DIR\").map(PathBuf::from) {\n      base_dir.join(path)\n    } else {\n      return quote!(compile_error!(\"$CARGO_MANIFEST_DIR is not defined\")).into();\n    }\n  } else {\n    path\n  };\n  if !resolved_path.exists() {\n    let error_string = format!(\n      \"Provided Image path \\\"{}\\\" doesn't exists\",\n      resolved_path.display()\n    );\n    return quote!(compile_error!(#error_string)).into();\n  }\n\n  match CachedIcon::new(&quote!(::tauri), &resolved_path).map_err(|error| error.to_string()) {\n    Ok(icon) => icon.into_token_stream(),\n    Err(error) => quote!(compile_error!(#error)),\n  }\n  .into()\n}\n"
  },
  {
    "path": "crates/tauri-macros/src/menu.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse proc_macro2::{Ident, Span, TokenStream};\nuse quote::quote;\nuse syn::{\n  parse::{Parse, ParseStream},\n  punctuated::Punctuated,\n  Expr, Token,\n};\n\npub struct DoMenuItemInput {\n  resources_table: Ident,\n  rid: Ident,\n  kind: Ident,\n  var: Ident,\n  expr: Expr,\n  kinds: Vec<NegatedIdent>,\n}\n\n#[derive(Clone)]\nstruct NegatedIdent {\n  negated: bool,\n  ident: Ident,\n}\n\nimpl NegatedIdent {\n  fn new(ident: &str) -> Self {\n    Self {\n      negated: false,\n      ident: Ident::new(ident, Span::call_site()),\n    }\n  }\n\n  fn is_negated(&self) -> bool {\n    self.negated\n  }\n}\n\nimpl Parse for NegatedIdent {\n  fn parse(input: ParseStream) -> syn::Result<Self> {\n    let negated_token = input.parse::<Token![!]>();\n    let ident: Ident = input.parse()?;\n    Ok(NegatedIdent {\n      negated: negated_token.is_ok(),\n      ident,\n    })\n  }\n}\n\nimpl Parse for DoMenuItemInput {\n  fn parse(input: ParseStream) -> syn::Result<Self> {\n    let resources_table: Ident = input.parse()?;\n    let _: Token![,] = input.parse()?;\n    let rid: Ident = input.parse()?;\n    let _: Token![,] = input.parse()?;\n    let kind: Ident = input.parse()?;\n    let _: Token![,] = input.parse()?;\n    let _: Token![|] = input.parse()?;\n    let var: Ident = input.parse()?;\n    let _: Token![|] = input.parse()?;\n    let expr: Expr = input.parse()?;\n    let _: syn::Result<Token![,]> = input.parse();\n    let kinds = Punctuated::<NegatedIdent, Token![|]>::parse_terminated(input)?;\n\n    Ok(Self {\n      resources_table,\n      rid,\n      kind,\n      var,\n      expr,\n      kinds: kinds.into_iter().collect(),\n    })\n  }\n}\n\npub fn do_menu_item(input: DoMenuItemInput) -> TokenStream {\n  let DoMenuItemInput {\n    rid,\n    resources_table,\n    kind,\n    expr,\n    var,\n    mut kinds,\n  } = input;\n\n  let defaults = vec![\n    NegatedIdent::new(\"Submenu\"),\n    NegatedIdent::new(\"MenuItem\"),\n    NegatedIdent::new(\"Predefined\"),\n    NegatedIdent::new(\"Check\"),\n    NegatedIdent::new(\"Icon\"),\n  ];\n\n  if kinds.is_empty() {\n    kinds.extend(defaults.clone());\n  }\n\n  let has_negated = kinds.iter().any(|n| n.is_negated());\n  if has_negated {\n    kinds.extend(defaults);\n    kinds.sort_by(|a, b| a.ident.cmp(&b.ident));\n    kinds.dedup_by(|a, b| a.ident == b.ident);\n  }\n\n  let (kinds, types): (Vec<Ident>, Vec<Ident>) = kinds\n    .into_iter()\n    .filter_map(|nident| {\n      if nident.is_negated() {\n        None\n      } else {\n        match nident.ident {\n          i if i == \"MenuItem\" => Some((i, Ident::new(\"MenuItem\", Span::call_site()))),\n          i if i == \"Submenu\" => Some((i, Ident::new(\"Submenu\", Span::call_site()))),\n          i if i == \"Predefined\" => Some((i, Ident::new(\"PredefinedMenuItem\", Span::call_site()))),\n          i if i == \"Check\" => Some((i, Ident::new(\"CheckMenuItem\", Span::call_site()))),\n          i if i == \"Icon\" => Some((i, Ident::new(\"IconMenuItem\", Span::call_site()))),\n          _ => None,\n        }\n      }\n    })\n    .unzip();\n\n  quote! {\n    match #kind {\n      #(\n        ItemKind::#kinds => {\n        let #var = #resources_table.get::<#types<R>>(#rid)?;\n        #expr\n      }\n      )*\n      _ => unreachable!(),\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-macros/src/mobile.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse proc_macro::TokenStream;\nuse proc_macro2::TokenStream as TokenStream2;\nuse quote::{format_ident, quote};\nuse std::env::var;\nuse syn::{parse_macro_input, spanned::Spanned, ItemFn};\n\nfn get_env_var(name: &str, error: &mut Option<TokenStream2>, function: &ItemFn) -> TokenStream2 {\n  match var(name) {\n    Ok(value) => {\n      let ident = format_ident!(\"{value}\");\n      quote!(#ident)\n    }\n    Err(_) => {\n      error.replace(\n        syn::Error::new(\n          function.span(),\n          format!(\"`{name}` env var not set, do you have a build script with tauri-build?\",),\n        )\n        .into_compile_error(),\n      );\n      quote!()\n    }\n  }\n}\n\npub fn entry_point(_attributes: TokenStream, item: TokenStream) -> TokenStream {\n  let function = parse_macro_input!(item as ItemFn);\n  let function_name = function.sig.ident.clone();\n\n  let mut error = None;\n  let domain = get_env_var(\"TAURI_ANDROID_PACKAGE_NAME_PREFIX\", &mut error, &function);\n  let app_name = get_env_var(\"TAURI_ANDROID_PACKAGE_NAME_APP_NAME\", &mut error, &function);\n\n  let (wrapper, wrapper_name) = if function.sig.asyncness.is_some() {\n    let wrapper_name = syn::Ident::new(&format!(\"{function_name}_wrapper\"), function_name.span());\n    (\n      quote! {\n        #function\n\n        fn #wrapper_name() {\n          ::tauri::async_runtime::block_on(#function_name());\n        }\n      },\n      wrapper_name,\n    )\n  } else {\n    (\n      quote! {\n        #function\n      },\n      function_name,\n    )\n  };\n\n  if let Some(e) = error {\n    quote!(#e).into()\n  } else {\n    quote!(\n      fn stop_unwind<F: FnOnce() -> T, T>(f: F) -> T {\n        match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {\n          Ok(t) => t,\n          Err(err) => {\n            eprintln!(\"attempt to unwind out of `rust` with err: {:?}\", err);\n            std::process::abort()\n          }\n        }\n      }\n\n      #wrapper\n\n      fn _start_app() {\n        #[cfg(target_os = \"ios\")]\n        ::tauri::log_stdout();\n        #[cfg(target_os = \"android\")]\n        {\n          ::tauri::android_binding!(#domain, #app_name, _start_app, ::tauri::wry);\n        }\n        stop_unwind(#wrapper_name);\n      }\n\n      // be careful when renaming this, the `start_app` symbol is checked by the CLI\n      #[cfg(not(target_os = \"android\"))]\n      #[no_mangle]\n      #[inline(never)]\n      pub extern \"C\" fn start_app() {\n        _start_app()\n      }\n    )\n    .into()\n  }\n}\n"
  },
  {
    "path": "crates/tauri-macros/src/runtime.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse proc_macro2::TokenStream;\nuse quote::{quote, ToTokens};\nuse syn::parse::{Parse, ParseStream};\nuse syn::{\n  parse_quote, DeriveInput, Error, GenericParam, Ident, ItemTrait, ItemType, Token, Type, TypeParam,\n};\n\n#[derive(Clone)]\npub(crate) enum Input {\n  Derive(DeriveInput),\n  Trait(ItemTrait),\n  Type(ItemType),\n}\n\nimpl Parse for Input {\n  fn parse(input: ParseStream) -> syn::Result<Self> {\n    input\n      .parse::<DeriveInput>()\n      .map(Self::Derive)\n      .or_else(|_| input.parse().map(Self::Trait))\n      .or_else(|_| input.parse().map(Self::Type))\n      .map_err(|_| {\n        Error::new(\n          input.span(),\n          \"default_runtime only supports `struct`, `enum`, `type`, or `trait` definitions\",\n        )\n      })\n  }\n}\n\nimpl Input {\n  fn last_param_mut(&mut self) -> Option<&mut GenericParam> {\n    match self {\n      Input::Derive(d) => d.generics.params.last_mut(),\n      Input::Trait(t) => t.generics.params.last_mut(),\n      Input::Type(t) => t.generics.params.last_mut(),\n    }\n  }\n}\n\nimpl ToTokens for Input {\n  fn to_tokens(&self, tokens: &mut TokenStream) {\n    match self {\n      Input::Derive(d) => d.to_tokens(tokens),\n      Input::Trait(t) => t.to_tokens(tokens),\n      Input::Type(t) => t.to_tokens(tokens),\n    }\n  }\n}\n\n/// The default runtime type to enable when the provided feature is enabled.\npub(crate) struct Attributes {\n  default_type: Type,\n  feature: Ident,\n}\n\nimpl Parse for Attributes {\n  fn parse(input: ParseStream<'_>) -> syn::Result<Self> {\n    let default_type = input.parse()?;\n    input.parse::<Token![,]>()?;\n    Ok(Attributes {\n      default_type,\n      feature: input.parse()?,\n    })\n  }\n}\n\npub(crate) fn default_runtime(attributes: Attributes, input: Input) -> TokenStream {\n  // create a new copy to manipulate for the wry feature flag\n  let mut wry = input.clone();\n  let wry_runtime = wry\n    .last_param_mut()\n    .expect(\"default_runtime requires the item to have at least 1 generic parameter\");\n\n  // set the default value of the last generic parameter to the provided runtime type\n  match wry_runtime {\n    GenericParam::Type(\n      param @ TypeParam {\n        eq_token: None,\n        default: None,\n        ..\n      },\n    ) => {\n      param.eq_token = Some(parse_quote!(=));\n      param.default = Some(attributes.default_type);\n    }\n    _ => {\n      panic!(\"DefaultRuntime requires the last parameter to not have a default value\")\n    }\n  };\n\n  let feature = attributes.feature.to_string();\n\n  quote!(\n    #[cfg(feature = #feature)]\n    #wry\n\n    #[cfg(not(feature = #feature))]\n    #input\n  )\n}\n"
  },
  {
    "path": "crates/tauri-plugin/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.5.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.3`\n\n## \\[2.5.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.2`\n\n## \\[2.5.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.1`\n\n## \\[2.5.1]\n\n### Bug Fixes\n\n- [`4b6b8690a`](https://www.github.com/tauri-apps/tauri/commit/4b6b8690ab886ebdf1307951cffbe03e31280baa) ([#14347](https://www.github.com/tauri-apps/tauri/pull/14347) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused docs.rs builds to fail. No user facing changes.\n\n## \\[2.5.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.0`\n- [`6aa7f2d85`](https://www.github.com/tauri-apps/tauri/commit/6aa7f2d852870aeba1d4dd0e07f8be2bc9b66286) Upgraded to `tauri-utils@2.8.0`\n\n## \\[2.4.0]\n\n### New Features\n\n- [`a0113a8c6`](https://www.github.com/tauri-apps/tauri/commit/a0113a8c6471eccc43b3202cc009901a04e41baa) ([#13888](https://www.github.com/tauri-apps/tauri/pull/13888) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `build::mobile::update_info_plist` to allow a plugin to update the iOS project Info.plist file.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.7.0`\n\n## \\[2.3.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.6.0`\n\n## \\[2.2.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.5.0`\n\n## \\[2.2.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.4.0`\n- [`48b12b440`](https://www.github.com/tauri-apps/tauri/commit/48b12b440478937c46fdfef9f9d95194be117020) Update to `tauri-utils@2.4.0`\n\n## \\[2.1.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.1`\n\n## \\[2.1.0]\n\n### New Features\n\n- [`013f8f652`](https://www.github.com/tauri-apps/tauri/commit/013f8f652302f2d49c5ec0a075582033d8b074fb) ([#12890](https://www.github.com/tauri-apps/tauri/pull/12890) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Added `build > removeUnusedCommands` to trigger the build scripts and macros to remove unused commands based on the capabilities you defined. Note this won't be accounting for dynamically added ACLs so make sure to check it when using this.\n\n### Performance Improvements\n\n- [`1cd8f55ee`](https://www.github.com/tauri-apps/tauri/commit/1cd8f55eed326d61860fee62ba2d2f4464bdcfcc) ([#13033](https://www.github.com/tauri-apps/tauri/pull/13033) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Don't ship global `bundle.global.js` if `app > withGlobalTauri` is set to false\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.0`\n\n## \\[2.0.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.2.0`\n\n## \\[2.0.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.1`\n\n## \\[2.0.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.0`\n\n## \\[2.0.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.2`\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.1`\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78.\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0`\n\n## \\[2.0.0-rc.13]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.13`\n\n## \\[2.0.0-rc.12]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.12`\n\n## \\[2.0.0-rc.11]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.11`\n\n## \\[2.0.0-rc.10]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.10`\n\n## \\[2.0.0-rc.9]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.9`\n\n## \\[2.0.0-rc.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.8`\n\n## \\[2.0.0-rc.7]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.7`\n\n## \\[2.0.0-rc.6]\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.6`\n\n## \\[2.0.0-rc.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.5`\n\n## \\[2.0.0-rc.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.4`\n\n## \\[2.0.0-rc.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.3`\n\n## \\[2.0.0-rc.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.2`\n\n## \\[2.0.0-rc.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.1`\n\n## \\[2.0.0-rc.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.0`\n\n### Breaking Changes\n\n- [`758d28c8a`](https://www.github.com/tauri-apps/tauri/commit/758d28c8a2d5c9567158e339326b765f72da983e) ([#10390](https://www.github.com/tauri-apps/tauri/pull/10390)) Core plugin permissions are now prefixed with `core:`, the `core:default` permission set can now be used and the `core` plugin name is reserved.\n  The `tauri migrate` tool will automate the migration process, which involves prefixing all `app`, `event`, `image`, `menu`, `path`, `resources`, `tray`, `webview` and `window` permissions with `core:`.\n\n## \\[2.0.0-beta.19]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.19`\n\n## \\[2.0.0-beta.18]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.18`\n\n## \\[2.0.0-beta.17]\n\n### New Features\n\n- [`8a1ae2dea`](https://www.github.com/tauri-apps/tauri/commit/8a1ae2deaf3086e531ada25b1627f900e2e421fb)([#9843](https://www.github.com/tauri-apps/tauri/pull/9843)) Added an option to use a Xcode project for the iOS plugin instead of a plain SwiftPM project.\n\n### What's Changed\n\n- [`9ac930380`](https://www.github.com/tauri-apps/tauri/commit/9ac930380a5df3fe700e68e75df8684d261ca292)([#9850](https://www.github.com/tauri-apps/tauri/pull/9850)) Emit `cargo:rustc-check-cfg` instruction so Cargo validates custom cfg attributes on Rust 1.80 (or nightly-2024-05-05).\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.17`\n\n## \\[2.0.0-beta.16]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.16`\n\n## \\[2.0.0-beta.15]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.15`\n\n## \\[2.0.0-beta.14]\n\n### Enhancements\n\n- [`bf2635ab6`](https://www.github.com/tauri-apps/tauri/commit/bf2635ab6241a5b82569eafc939046d6e245f3ad)([#9632](https://www.github.com/tauri-apps/tauri/pull/9632)) Improve the error message that is shown when the `links` property is missing from a Tauri Plugin.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.14`\n\n## \\[2.0.0-beta.13]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.13`\n\n## \\[2.0.0-beta.12]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.12`\n\n## \\[2.0.0-beta.11]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.11`\n\n## \\[2.0.0-beta.10]\n\n### New Features\n\n- [`e227fe02f`](https://www.github.com/tauri-apps/tauri/commit/e227fe02f986e145c0731a64693e1c830a9eb5b0)([#9156](https://www.github.com/tauri-apps/tauri/pull/9156)) Allow plugins to define (at compile time) JavaScript that are initialized when `withGlobalTauri` is true.\n- [`e227fe02f`](https://www.github.com/tauri-apps/tauri/commit/e227fe02f986e145c0731a64693e1c830a9eb5b0)([#9156](https://www.github.com/tauri-apps/tauri/pull/9156)) Added `Builder::global_api_script_path` to define a JavaScript file containing the initialization script for the plugin API bindings when `withGlobalTauri` is used.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.10`\n\n## \\[2.0.0-beta.9]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.9`\n\n## \\[2.0.0-beta.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.8`\n\n## \\[2.0.0-beta.7]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.7`\n\n## \\[2.0.0-beta.6]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.6`\n\n### Breaking Changes\n\n- [`3657ad82`](https://www.github.com/tauri-apps/tauri/commit/3657ad82f88ce528551d032d521c52eed3f396b4)([#9008](https://www.github.com/tauri-apps/tauri/pull/9008)) Allow defining permissions for the application commands via `tauri_build::Attributes::app_manifest`.\n\n## \\[2.0.0-beta.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.5`\n\n## \\[2.0.0-beta.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.4`\n\n## \\[2.0.0-beta.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.3`\n\n## \\[2.0.0-beta.2]\n\n### Enhancements\n\n- [`dd7571a7`](https://www.github.com/tauri-apps/tauri/commit/dd7571a7808676c8063a4983b9c6687dfaf03a09)([#8815](https://www.github.com/tauri-apps/tauri/pull/8815)) Do not generate JSON schema and markdown reference file if the plugin does not define any permissions and delete those files if they exist.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.2`\n\n## \\[2.0.0-beta.1]\n\n### Bug Fixes\n\n- [`4e101f80`](https://www.github.com/tauri-apps/tauri/commit/4e101f801657e7d01ce8c22f9c6468067d0caab2)([#8756](https://www.github.com/tauri-apps/tauri/pull/8756)) Rerun build script when a new permission is added.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.1`\n\n## \\[2.0.0-beta.0]\n\n### New Features\n\n- [`74a2a603`](https://www.github.com/tauri-apps/tauri/commit/74a2a6036a5e57462f161d728cbd8a6f121028ca)([#8661](https://www.github.com/tauri-apps/tauri/pull/8661)) Implement access control list for IPC usage.\n"
  },
  {
    "path": "crates/tauri-plugin/Cargo.toml",
    "content": "[package]\nname = \"tauri-plugin\"\nversion = \"2.5.4\"\ndescription = \"Build script and runtime Tauri plugin definitions\"\nauthors.workspace = true\nhomepage.workspace = true\nrepository.workspace = true\ncategories.workspace = true\nlicense.workspace = true\nedition.workspace = true\nrust-version.workspace = true\n\n[package.metadata.docs.rs]\nfeatures = [\"build\", \"runtime\"]\n\n[features]\nbuild = [\n  \"dep:anyhow\",\n  \"dep:serde\",\n  \"dep:serde_json\",\n  \"dep:glob\",\n  \"dep:plist\",\n  \"dep:walkdir\",\n]\nruntime = []\n\n[dependencies]\nanyhow = { version = \"1\", optional = true }\nserde = { version = \"1\", optional = true }\ntauri-utils = { version = \"2.8.3\", default-features = false, features = [\n  \"build\",\n], path = \"../tauri-utils\" }\nserde_json = { version = \"1\", optional = true }\nglob = { version = \"0.3\", optional = true }\n# Our code requires at least 0.8.21 so don't simplify this to 0.8\nschemars = { version = \"0.8.21\", features = [\"preserve_order\"] }\nwalkdir = { version = \"2\", optional = true }\n\n[target.\"cfg(target_os = \\\"macos\\\")\".dependencies]\nplist = { version = \"1\", optional = true }\n"
  },
  {
    "path": "crates/tauri-plugin/src/build/mobile.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Mobile-specific build utilities.\n\nuse std::{\n  env::var_os,\n  fs::{copy, create_dir, create_dir_all, read_to_string, remove_dir_all, write},\n  path::{Path, PathBuf},\n};\n\nuse anyhow::{Context, Result};\n\nuse super::{build_var, cfg_alias};\n\n#[cfg(target_os = \"macos\")]\npub fn update_entitlements<F: FnOnce(&mut plist::Dictionary)>(f: F) -> Result<()> {\n  if let (Some(project_path), Ok(app_name)) = (\n    var_os(\"TAURI_IOS_PROJECT_PATH\").map(PathBuf::from),\n    std::env::var(\"TAURI_IOS_APP_NAME\"),\n  ) {\n    update_plist_file(\n      project_path\n        .join(format!(\"{app_name}_iOS\"))\n        .join(format!(\"{app_name}_iOS.entitlements\")),\n      f,\n    )?;\n  }\n\n  Ok(())\n}\n\n#[cfg(target_os = \"macos\")]\npub fn update_info_plist<F: FnOnce(&mut plist::Dictionary)>(f: F) -> Result<()> {\n  if let (Some(project_path), Ok(app_name)) = (\n    var_os(\"TAURI_IOS_PROJECT_PATH\").map(PathBuf::from),\n    std::env::var(\"TAURI_IOS_APP_NAME\"),\n  ) {\n    update_plist_file(\n      project_path\n        .join(format!(\"{app_name}_iOS\"))\n        .join(\"Info.plist\"),\n      f,\n    )?;\n  }\n\n  Ok(())\n}\n\npub fn update_android_manifest(block_identifier: &str, parent: &str, insert: String) -> Result<()> {\n  if let Some(project_path) = var_os(\"TAURI_ANDROID_PROJECT_PATH\").map(PathBuf::from) {\n    let manifest_path = project_path.join(\"app/src/main/AndroidManifest.xml\");\n    let manifest = read_to_string(&manifest_path)?;\n    let rewritten = insert_into_xml(&manifest, block_identifier, parent, &insert);\n    if rewritten != manifest {\n      write(manifest_path, rewritten)?;\n    }\n  }\n  Ok(())\n}\n\npub(crate) fn setup(\n  android_path: Option<PathBuf>,\n  #[allow(unused_variables)] ios_path: Option<PathBuf>,\n) -> Result<()> {\n  let target_os = build_var(\"CARGO_CFG_TARGET_OS\")?;\n  let mobile = target_os == \"android\" || target_os == \"ios\";\n  cfg_alias(\"mobile\", mobile);\n  cfg_alias(\"desktop\", !mobile);\n\n  match target_os.as_str() {\n    \"android\" => {\n      if let Some(path) = android_path {\n        let manifest_dir = build_var(\"CARGO_MANIFEST_DIR\").map(PathBuf::from)?;\n        let source = manifest_dir.join(path);\n\n        let tauri_library_path = std::env::var(\"DEP_TAURI_ANDROID_LIBRARY_PATH\")\n            .expect(\"missing `DEP_TAURI_ANDROID_LIBRARY_PATH` environment variable. Make sure `tauri` is a dependency of the plugin.\");\n        println!(\"cargo:rerun-if-env-changed=DEP_TAURI_ANDROID_LIBRARY_PATH\");\n\n        create_dir_all(source.join(\".tauri\")).context(\"failed to create .tauri directory\")?;\n        copy_folder(\n          Path::new(&tauri_library_path),\n          &source.join(\".tauri\").join(\"tauri-api\"),\n          &[],\n        )\n        .context(\"failed to copy tauri-api to the plugin project\")?;\n\n        println!(\"cargo:android_library_path={}\", source.display());\n      }\n    }\n    #[cfg(target_os = \"macos\")]\n    \"ios\" => {\n      if let Some(path) = ios_path {\n        let manifest_dir = std::env::var(\"CARGO_MANIFEST_DIR\")\n          .map(PathBuf::from)\n          .unwrap();\n        let tauri_library_path = std::env::var(\"DEP_TAURI_IOS_LIBRARY_PATH\")\n            .expect(\"missing `DEP_TAURI_IOS_LIBRARY_PATH` environment variable. Make sure `tauri` is a dependency of the plugin.\");\n\n        let tauri_dep_path = path.parent().unwrap().join(\".tauri\");\n        create_dir_all(&tauri_dep_path).context(\"failed to create .tauri directory\")?;\n        copy_folder(\n          Path::new(&tauri_library_path),\n          &tauri_dep_path.join(\"tauri-api\"),\n          &[\".build\", \"Package.resolved\", \"Tests\"],\n        )\n        .context(\"failed to copy tauri-api to the plugin project\")?;\n        tauri_utils::build::link_apple_library(\n          &std::env::var(\"CARGO_PKG_NAME\").unwrap(),\n          manifest_dir.join(path),\n        );\n      }\n    }\n    _ => (),\n  }\n\n  Ok(())\n}\n\nfn copy_folder(source: &Path, target: &Path, ignore_paths: &[&str]) -> Result<()> {\n  let _ = remove_dir_all(target);\n\n  for entry in walkdir::WalkDir::new(source) {\n    let entry = entry?;\n    let rel_path = entry.path().strip_prefix(source)?;\n    let rel_path_str = rel_path.to_string_lossy();\n    if ignore_paths\n      .iter()\n      .any(|path| rel_path_str.starts_with(path))\n    {\n      continue;\n    }\n    let dest_path = target.join(rel_path);\n\n    if entry.file_type().is_dir() {\n      create_dir(&dest_path)\n        .with_context(|| format!(\"failed to create directory {}\", dest_path.display()))?;\n    } else {\n      copy(entry.path(), &dest_path).with_context(|| {\n        format!(\n          \"failed to copy {} to {}\",\n          entry.path().display(),\n          dest_path.display()\n        )\n      })?;\n      println!(\"cargo:rerun-if-changed={}\", entry.path().display());\n    }\n  }\n\n  Ok(())\n}\n\n#[cfg(target_os = \"macos\")]\nfn update_plist_file<P: AsRef<Path>, F: FnOnce(&mut plist::Dictionary)>(\n  path: P,\n  f: F,\n) -> Result<()> {\n  use std::io::Cursor;\n\n  let path = path.as_ref();\n  if path.exists() {\n    let plist_str = read_to_string(path)?;\n    let mut plist = plist::Value::from_reader(Cursor::new(&plist_str))?;\n    if let Some(dict) = plist.as_dictionary_mut() {\n      f(dict);\n      let mut plist_buf = Vec::new();\n      let writer = Cursor::new(&mut plist_buf);\n      plist::to_writer_xml(writer, &plist)?;\n      let new_plist_str = String::from_utf8(plist_buf)?;\n      if new_plist_str != plist_str {\n        write(path, new_plist_str)?;\n      }\n    }\n  }\n\n  Ok(())\n}\n\nfn xml_block_comment(id: &str) -> String {\n  format!(\"<!-- {id}. AUTO-GENERATED. DO NOT REMOVE. -->\")\n}\n\nfn insert_into_xml(xml: &str, block_identifier: &str, parent_tag: &str, contents: &str) -> String {\n  let block_comment = xml_block_comment(block_identifier);\n\n  let mut rewritten = Vec::new();\n  let mut found_block = false;\n  let parent_closing_tag = format!(\"</{parent_tag}>\");\n  for line in xml.split('\\n') {\n    if line.contains(&block_comment) {\n      found_block = !found_block;\n      continue;\n    }\n\n    // found previous block which should be removed\n    if found_block {\n      continue;\n    }\n\n    if let Some(index) = line.find(&parent_closing_tag) {\n      let indentation = \" \".repeat(index + 4);\n      rewritten.push(format!(\"{indentation}{block_comment}\"));\n      for l in contents.split('\\n') {\n        rewritten.push(format!(\"{indentation}{l}\"));\n      }\n      rewritten.push(format!(\"{indentation}{block_comment}\"));\n    }\n\n    rewritten.push(line.to_string());\n  }\n\n  rewritten.join(\"\\n\")\n}\n\n#[cfg(test)]\nmod tests {\n  #[test]\n  fn insert_into_xml() {\n    let manifest = r#\"<manifest>\n    <application>\n        <intent-filter>\n        </intent-filter>\n    </application>\n</manifest>\"#;\n    let id = \"tauritest\";\n    let new = super::insert_into_xml(manifest, id, \"application\", \"<something></something>\");\n\n    let block_id_comment = super::xml_block_comment(id);\n    let expected = format!(\n      r#\"<manifest>\n    <application>\n        <intent-filter>\n        </intent-filter>\n        {block_id_comment}\n        <something></something>\n        {block_id_comment}\n    </application>\n</manifest>\"#\n    );\n\n    assert_eq!(new, expected);\n\n    // assert it's still the same after an empty update\n    let new = super::insert_into_xml(&expected, id, \"application\", \"<something></something>\");\n    assert_eq!(new, expected);\n  }\n}\n"
  },
  {
    "path": "crates/tauri-plugin/src/build/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  collections::BTreeMap,\n  path::{Path, PathBuf},\n};\n\nuse anyhow::Result;\nuse tauri_utils::acl::{self, Error};\n\npub mod mobile;\n\nuse serde::de::DeserializeOwned;\n\nuse std::{env, io::Cursor};\n\nconst RESERVED_PLUGIN_NAMES: &[&str] = &[\"core\", \"tauri\"];\n\npub fn plugin_config<T: DeserializeOwned>(name: &str) -> Option<T> {\n  let config_env_var_name = format!(\n    \"TAURI_{}_PLUGIN_CONFIG\",\n    name.to_uppercase().replace('-', \"_\")\n  );\n  if let Ok(config_str) = env::var(&config_env_var_name) {\n    println!(\"cargo:rerun-if-env-changed={config_env_var_name}\");\n    serde_json::from_reader(Cursor::new(config_str))\n      .map(Some)\n      .expect(\"failed to parse configuration\")\n  } else {\n    None\n  }\n}\n\npub struct Builder<'a> {\n  commands: &'a [&'static str],\n  global_scope_schema: Option<schemars::schema::RootSchema>,\n  global_api_script_path: Option<PathBuf>,\n  android_path: Option<PathBuf>,\n  ios_path: Option<PathBuf>,\n}\n\nimpl<'a> Builder<'a> {\n  pub fn new(commands: &'a [&'static str]) -> Self {\n    Self {\n      commands,\n      global_scope_schema: None,\n      global_api_script_path: None,\n      android_path: None,\n      ios_path: None,\n    }\n  }\n\n  /// Sets the global scope JSON schema.\n  pub fn global_scope_schema(mut self, schema: schemars::schema::RootSchema) -> Self {\n    self.global_scope_schema.replace(schema);\n    self\n  }\n\n  /// Sets the path to the script that is injected in the webview when the `withGlobalTauri` configuration is set to true.\n  ///\n  /// This is usually an IIFE that injects the plugin API JavaScript bindings to `window.__TAURI__`.\n  pub fn global_api_script_path<P: Into<PathBuf>>(mut self, path: P) -> Self {\n    self.global_api_script_path.replace(path.into());\n    self\n  }\n\n  /// Sets the Android project path.\n  pub fn android_path<P: Into<PathBuf>>(mut self, android_path: P) -> Self {\n    self.android_path.replace(android_path.into());\n    self\n  }\n\n  /// Sets the iOS project path.\n  pub fn ios_path<P: Into<PathBuf>>(mut self, ios_path: P) -> Self {\n    self.ios_path.replace(ios_path.into());\n    self\n  }\n\n  /// [`Self::try_build`] but will exit automatically if an error is found.\n  pub fn build(self) {\n    if let Err(error) = self.try_build() {\n      println!(\"{}: {error:#}\", env!(\"CARGO_PKG_NAME\"));\n      std::process::exit(1);\n    }\n  }\n\n  /// Ensure this crate is properly configured to be a Tauri plugin.\n  ///\n  /// # Errors\n  ///\n  /// Errors will occur if environmental variables expected to be set inside of [build scripts]\n  /// are not found, or if the crate violates Tauri plugin conventions.\n  pub fn try_build(self) -> Result<()> {\n    // convention: plugin names should not use underscores\n    let name = build_var(\"CARGO_PKG_NAME\")?;\n    if name.contains('_') {\n      anyhow::bail!(\"plugin names cannot contain underscores\");\n    }\n    if RESERVED_PLUGIN_NAMES.contains(&name.as_str()) {\n      anyhow::bail!(\"plugin name `{name}` is reserved\");\n    }\n\n    let out_dir = PathBuf::from(build_var(\"OUT_DIR\")?);\n\n    // requirement: links MUST be set and MUST match the name\n    let _links = std::env::var(\"CARGO_MANIFEST_LINKS\").map_err(|_| Error::LinksMissing)?;\n\n    let autogenerated = Path::new(\"permissions\").join(acl::build::AUTOGENERATED_FOLDER_NAME);\n    std::fs::create_dir_all(&autogenerated).expect(\"unable to create permissions dir\");\n\n    let commands_dir = autogenerated.join(\"commands\");\n    if !self.commands.is_empty() {\n      acl::build::autogenerate_command_permissions(&commands_dir, self.commands, \"\", true);\n    }\n\n    println!(\"cargo:rerun-if-changed=permissions\");\n    let permissions =\n      acl::build::define_permissions(\"./permissions/**/*.*\", &name, &out_dir, |_| true)?;\n\n    if permissions.is_empty() {\n      let _ = std::fs::remove_file(format!(\n        \"./permissions/{}/{}\",\n        acl::PERMISSION_SCHEMAS_FOLDER_NAME,\n        acl::PERMISSION_SCHEMA_FILE_NAME\n      ));\n      let _ = std::fs::remove_file(autogenerated.join(acl::build::PERMISSION_DOCS_FILE_NAME));\n    } else {\n      acl::schema::generate_permissions_schema(&permissions, \"./permissions\")?;\n      acl::build::generate_docs(\n        &permissions,\n        &autogenerated,\n        name.strip_prefix(\"tauri-plugin-\").unwrap_or(&name),\n      )?;\n    }\n\n    let mut permissions_map = BTreeMap::new();\n    permissions_map.insert(name.clone(), permissions);\n    tauri_utils::acl::build::generate_allowed_commands(&out_dir, None, permissions_map)?;\n\n    if let Some(global_scope_schema) = self.global_scope_schema {\n      acl::build::define_global_scope_schema(global_scope_schema, &name, &out_dir)?;\n    }\n\n    if let Some(path) = self.global_api_script_path {\n      tauri_utils::plugin::define_global_api_script_path(&path);\n    }\n\n    mobile::setup(self.android_path, self.ios_path)?;\n\n    Ok(())\n  }\n}\n\nfn cfg_alias(alias: &str, has_feature: bool) {\n  println!(\"cargo:rustc-check-cfg=cfg({alias})\");\n  if has_feature {\n    println!(\"cargo:rustc-cfg={alias}\");\n  }\n}\n\n/// Grab an env var that is expected to be set inside of build scripts.\nfn build_var(key: &'static str) -> Result<String, Error> {\n  std::env::var(key).map_err(|_| Error::BuildVar(key))\n}\n"
  },
  {
    "path": "crates/tauri-plugin/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Interface for building Tauri plugins.\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n#![cfg_attr(docsrs, feature(doc_cfg))]\n\n#[cfg(feature = \"build\")]\nmod build;\n#[cfg(feature = \"runtime\")]\nmod runtime;\n\n#[cfg(feature = \"build\")]\n#[cfg_attr(docsrs, doc(feature = \"build\"))]\npub use build::*;\n#[cfg(feature = \"runtime\")]\n#[cfg_attr(docsrs, doc(feature = \"runtime\"))]\n#[allow(unused)]\npub use runtime::*;\n"
  },
  {
    "path": "crates/tauri-plugin/src/runtime.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n"
  },
  {
    "path": "crates/tauri-runtime/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.10.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.3`\n\n## \\[2.10.0]\n\n### Bug Fixes\n\n- [`9b242e40c`](https://www.github.com/tauri-apps/tauri/commit/9b242e40c844189c877a91e513ae6196202d5ae9) ([#14700](https://www.github.com/tauri-apps/tauri/pull/14700) by [@mewi99](https://www.github.com/tauri-apps/tauri/../../mewi99)) Fix compilation errors when targeting BSD.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.2`\n- [`75057c7c0`](https://www.github.com/tauri-apps/tauri/commit/75057c7c08f0d4d3dd8d10cea4e2217e5d61fe1a) ([#14778](https://www.github.com/tauri-apps/tauri/pull/14778) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) **Breaking Change** for `with_webview` users: Updated webkit2gtk-rs crates to `v2.0.2`.\n\n## \\[2.9.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.1`\n\n## \\[2.9.1]\n\n### Bug Fixes\n\n- [`4b6b8690a`](https://www.github.com/tauri-apps/tauri/commit/4b6b8690ab886ebdf1307951cffbe03e31280baa) ([#14347](https://www.github.com/tauri-apps/tauri/pull/14347) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused docs.rs builds to fail. No user facing changes.\n\n## \\[2.9.0]\n\n### New Features\n\n- [`f5851ee00`](https://www.github.com/tauri-apps/tauri/commit/f5851ee00d6d1f4d560a220ca5a728fedd525092) ([#14089](https://www.github.com/tauri-apps/tauri/pull/14089)) Adds the `scroll_bar_style` option to the Webview and WebviewWindow builders.\n  The possible values for this option are gated behind conditional compilation\n  flags, and will need to be applied using conditional compilation if customised.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.0`\n\n## \\[2.8.0]\n\n### New Features\n\n- [`68874c68c`](https://www.github.com/tauri-apps/tauri/commit/68874c68c566638b4c21a3aa67844d1bdaeb6dab) ([#13564](https://www.github.com/tauri-apps/tauri/pull/13564) by [@robertrpf](https://www.github.com/tauri-apps/tauri/../../robertrpf)) Add window focusable attribute and set_focusable API.\n- [`22d6bcacb`](https://www.github.com/tauri-apps/tauri/commit/22d6bcacbb2001eb292ebd8c5d021447700f9512) ([#14008](https://www.github.com/tauri-apps/tauri/pull/14008) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Implement `App::set_device_event_filter` for `AppHandle` also.\n- [`33d0b3f0c`](https://www.github.com/tauri-apps/tauri/commit/33d0b3f0c133edebb1c716e2f5942d70509ae347) ([#13876](https://www.github.com/tauri-apps/tauri/pull/13876) by [@thlstsul](https://www.github.com/tauri-apps/tauri/../../thlstsul)) Added `WebviewBuilder::on_document_title_changed` and `WebviewWindowBuilder::on_document_title_changed`.\n- [`33d0b3f0c`](https://www.github.com/tauri-apps/tauri/commit/33d0b3f0c133edebb1c716e2f5942d70509ae347) ([#13876](https://www.github.com/tauri-apps/tauri/pull/13876) by [@thlstsul](https://www.github.com/tauri-apps/tauri/../../thlstsul)) Added `WebviewBuilder::on_new_window` and `WebviewWindowBuilder::on_new_window`.\n- [`dfadcb764`](https://www.github.com/tauri-apps/tauri/commit/dfadcb764bdf84089a5487005a7b4f3b7cf09494) ([#13661](https://www.github.com/tauri-apps/tauri/pull/13661) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Added `WebviewDispatch::set_cookie()` and `WebviewDispatch::delete_cookie()`.\n- [`5110a762e`](https://www.github.com/tauri-apps/tauri/commit/5110a762e9db978a28a15400bf76e3c864da2a86) ([#13830](https://www.github.com/tauri-apps/tauri/pull/13830) by [@Sky-walkerX](https://www.github.com/tauri-apps/tauri/../../Sky-walkerX)) Added `Window::set_simple_fullscreen`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.7.0`\n\n## \\[2.7.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.6.0`\n\n## \\[2.7.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.5.0`\n- [`9c16eefa3`](https://www.github.com/tauri-apps/tauri/commit/9c16eefa319b4697bac1d1019bbb5f93eca63173) ([#13629](https://www.github.com/tauri-apps/tauri/pull/13629)) Update html5ever to 0.29 and kuchikiki to version 0.8.8-speedreader.\n\n## \\[2.6.0]\n\n### New Features\n\n- [`dd4f13ce4`](https://www.github.com/tauri-apps/tauri/commit/dd4f13ce4b3cd89cde2fa3f18a063c272f215621) ([#13185](https://www.github.com/tauri-apps/tauri/pull/13185)) MacOS: Add `set_dock_visibility` method to support setting the visibility of the application in the dock.\n- [`8cf662e34`](https://www.github.com/tauri-apps/tauri/commit/8cf662e34bf738a0d16bb7b9aeb35667e2e4984b) ([#13076](https://www.github.com/tauri-apps/tauri/pull/13076)) -   add API to run initialization scripts on all frames\n  \\-   `WebviewBuilder::initialization_script_on_all_frames`\n  \\-   `WebviewWindowBuilder::initialization_script_on_all_frames`\n  \\-   `WebviewAttributes::initialization_script_on_all_frames`\n- [`ea36294cb`](https://www.github.com/tauri-apps/tauri/commit/ea36294cbca98f7725c91d1464fd92e77c89698a) ([#13208](https://www.github.com/tauri-apps/tauri/pull/13208)) Added `WebviewAttributes::input_accessory_view_builder` on iOS.\n- [`c1cd0a2dd`](https://www.github.com/tauri-apps/tauri/commit/c1cd0a2ddb5bc3e99451cbe399b5fc9f0035f571) ([#13090](https://www.github.com/tauri-apps/tauri/pull/13090)) macOS/iOS: add option to disable or enable link previews when building a webview (the webkit api has it enabled by default)\n\n  - `WebViewBuilder.allow_link_preview(allow_link_preview: bool)`\n  - `WebviewWindowBuilder.allow_link_preview(allow_link_preview: bool)`\n- [`b072e2b29`](https://www.github.com/tauri-apps/tauri/commit/b072e2b2967640ae4fa1af466ae878c156551edd) ([#9687](https://www.github.com/tauri-apps/tauri/pull/9687)) Add `preventOverflow` config option to prevent the window from overflowing the monitor size on creation\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.4.0`\n- [`bb5faa21f`](https://www.github.com/tauri-apps/tauri/commit/bb5faa21f418dd765ce81b495b56e9c519251b6d) ([#13163](https://www.github.com/tauri-apps/tauri/pull/13163)) Update windows to 0.61.\n\n## \\[2.5.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.1`\n\n## \\[2.5.0]\n\n### New Features\n\n- [`be2e6b85f`](https://www.github.com/tauri-apps/tauri/commit/be2e6b85fed226732b4a98f68cc5d72b4f8f5a13) ([#12944](https://www.github.com/tauri-apps/tauri/pull/12944) by [@Simon-Laux](https://www.github.com/tauri-apps/tauri/../../Simon-Laux)) add `Window.is_always_on_top()` and `WebviewWindow.is_always_on_top()`\n- [`658e5f5d1`](https://www.github.com/tauri-apps/tauri/commit/658e5f5d1dc1bd970ae572a42447448d064a7fee) ([#12668](https://www.github.com/tauri-apps/tauri/pull/12668) by [@thomaseizinger](https://www.github.com/tauri-apps/tauri/../../thomaseizinger)) Add `App::run_return` function. Contrary to `App::run`, this will **not** exit the process but instead return the requested exit-code. This allows the host app to perform further cleanup after Tauri has exited. `App::run_return` is not available on iOS and fallbacks to the regular `App::run` functionality.\n\n  The `App::run_iteration` function is deprecated as part of this because calling it in a loop - as suggested by the name - will cause a busy-loop.\n- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `trafficLightPosition` window configuration to set the traffic light buttons position on macOS.\n- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `traffic_light_position` window builder method to set the traffic light buttons position on macOS.\n- [`cedb24d49`](https://www.github.com/tauri-apps/tauri/commit/cedb24d494b84111daa3206c05196c8b89f1e994) ([#12665](https://www.github.com/tauri-apps/tauri/pull/12665) by [@charrondev](https://www.github.com/tauri-apps/tauri/../../charrondev)) Added `WebviewDispatch::cookies()` and `WebviewDispatch::cookies_for_url()`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.0`\n\n## \\[2.4.0]\n\n### Enhancements\n\n- [`a2d36b8c3`](https://www.github.com/tauri-apps/tauri/commit/a2d36b8c34a8dcfc6736797ca5cd4665faf75e7e) ([#12181](https://www.github.com/tauri-apps/tauri/pull/12181) by [@bastiankistner](https://www.github.com/tauri-apps/tauri/../../bastiankistner)) Add an option to change the default background throttling policy (currently for WebKit only).\n- [`d6520a21c`](https://www.github.com/tauri-apps/tauri/commit/d6520a21ce02c3e2be2955999946c2cb7bdb07aa) ([#12541](https://www.github.com/tauri-apps/tauri/pull/12541) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Updated `wry` to 0.50, `windows` to 0.60, `webview2-com` to 0.36, and `objc2` to 0.6. This can be a **breaking change** if you use the `with_webview` API!\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.2.0`\n\n## \\[2.3.0]\n\n### New Features\n\n- [`18bd639f6`](https://www.github.com/tauri-apps/tauri/commit/18bd639f6e22c0188aa219739f367b5bf5ab0398) ([#11798](https://www.github.com/tauri-apps/tauri/pull/11798) by [@lars-berger](https://www.github.com/tauri-apps/tauri/../../lars-berger)) Add `WebviewWindowBuilder/WebviewBuilder::data_store_identifier` on macOS.\n- [`dc4d79477`](https://www.github.com/tauri-apps/tauri/commit/dc4d79477665bc3bfefb4048772414cf5d78e3df) ([#11628](https://www.github.com/tauri-apps/tauri/pull/11628) by [@SpikeHD](https://www.github.com/tauri-apps/tauri/../../SpikeHD)) Add `WebviewWindowBuilder/WebviewBuilder::extensions_path` on Linux and Windows.\n- [`020ea0556`](https://www.github.com/tauri-apps/tauri/commit/020ea05561348dcd6d2a7df358f8a5190f661ba2) ([#11661](https://www.github.com/tauri-apps/tauri/pull/11661) by [@ahqsoftwares](https://www.github.com/tauri-apps/tauri/../../ahqsoftwares)) Add badging APIs:\n\n  - `Window/WebviewWindow::set_badge_count` for Linux, macOS and IOS.\n  - `Window/WebviewWindow::set_overlay_icon` for Windows Only.\n  - `Window/WebviewWindow::set_badge_label`for macOS Only.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.1`\n\n## \\[2.2.0]\n\n### New Features\n\n- [`4d545ab3c`](https://www.github.com/tauri-apps/tauri/commit/4d545ab3ca228c8a21b966b709f84a0da2864479) ([#11486](https://www.github.com/tauri-apps/tauri/pull/11486) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Window::set_background_color` and `WindowBuilder::background_color`.\n- [`f37e97d41`](https://www.github.com/tauri-apps/tauri/commit/f37e97d410c4a219e99f97692da05ca9d8e0ba3a) ([#11477](https://www.github.com/tauri-apps/tauri/pull/11477) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder/WebviewBuilder::use_https_scheme` to choose whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android\n- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder::devtools` and `WebviewBuilder::devtools` to enable or disable devtools for a specific webview.\n- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `WindowBuilder/WebviewWindowBuilder::window_classname` method to specify the name of the window class on Windows.\n\n### Bug Fixes\n\n- [`129414faa`](https://www.github.com/tauri-apps/tauri/commit/129414faa4e027c9035d56614682cacc0335a6a0) ([#11569](https://www.github.com/tauri-apps/tauri/pull/11569) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix webview not focused by default.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.0`\n\n## \\[2.1.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.2`\n\n## \\[2.1.0]\n\n### Bug Fixes\n\n- [`2d087ee4b`](https://www.github.com/tauri-apps/tauri/commit/2d087ee4b7d3e8849933f81284e4f5ed1aaa6455) ([#11268](https://www.github.com/tauri-apps/tauri/pull/11268) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Linux, fix commands, that use `Webview` or `WebviewWindow` as an argument, receiving an incorrect webview when using multi webviews.\n- [`2d087ee4b`](https://www.github.com/tauri-apps/tauri/commit/2d087ee4b7d3e8849933f81284e4f5ed1aaa6455) ([#11268](https://www.github.com/tauri-apps/tauri/pull/11268) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Linux, fix events only emitted to first webview only when using multi webviews.\n- [`2d087ee4b`](https://www.github.com/tauri-apps/tauri/commit/2d087ee4b7d3e8849933f81284e4f5ed1aaa6455) ([#11268](https://www.github.com/tauri-apps/tauri/pull/11268) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Linux, fix custom protocols receiving an incorrect webview label when using multi webviews\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.1`\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78.\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0`\n\n## \\[2.0.0-rc.13]\n\n### New Features\n\n- [`a247170e1`](https://www.github.com/tauri-apps/tauri/commit/a247170e1f620a9b012274b11cfe51e90327d6e9) ([#11056](https://www.github.com/tauri-apps/tauri/pull/11056) by [@SpikeHD](https://www.github.com/tauri-apps/tauri/../../SpikeHD)) Expose the ability to enabled browser extensions in WebView2 on Windows.\n- [`9014a3f17`](https://www.github.com/tauri-apps/tauri/commit/9014a3f1765ca406ea5c3e5224267a79c52cd53d) ([#11066](https://www.github.com/tauri-apps/tauri/pull/11066) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindow::clear_all_browsing_data` and `Webview::clear_all_browsing_data` to clear the webview browsing data.\n- [`95df53a2e`](https://www.github.com/tauri-apps/tauri/commit/95df53a2ed96873cd35a4b14a5e312d07e4e3004) ([#11143](https://www.github.com/tauri-apps/tauri/pull/11143) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Add the ability to set theme dynamically using `Window::set_theme`, `App::set_theme`\n- [`d9d2502b4`](https://www.github.com/tauri-apps/tauri/commit/d9d2502b41e39efde679e30c8955006e2ba9ea64) ([#11140](https://www.github.com/tauri-apps/tauri/pull/11140) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewDispatch::hide` and `WebviewDispatch::show` methods.\n- [`de7414aab`](https://www.github.com/tauri-apps/tauri/commit/de7414aab935e45540594ea930eb60bae4dbc979) ([#11154](https://www.github.com/tauri-apps/tauri/pull/11154) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `Window::set_enabled` and `Window::is_enabled` methods\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.13`\n\n## \\[2.0.0-rc.12]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.12`\n\n## \\[2.0.0-rc.11]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.11`\n\n## \\[2.0.0-rc.10]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.10`\n\n## \\[2.0.0-rc.9]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.9`\n\n## \\[2.0.0-rc.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.8`\n\n## \\[2.0.0-rc.7]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.7`\n\n## \\[2.0.0-rc.6]\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.6`\n\n## \\[2.0.0-rc.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.5`\n\n## \\[2.0.0-rc.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.4`\n\n## \\[2.0.0-rc.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.3`\n- [`d39c392b7`](https://www.github.com/tauri-apps/tauri/commit/d39c392b7cec746da423211f9c74632abe4b6af5) ([#10655](https://www.github.com/tauri-apps/tauri/pull/10655) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Update `tao` to 0.29 and `wry` to 0.42.\n\n## \\[2.0.0-rc.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.2`\n\n## \\[2.0.0-rc.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.1`\n\n## \\[2.0.0-rc.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.0`\n\n## \\[2.0.0-beta.21]\n\n### What's Changed\n\n- [`da25f7353`](https://www.github.com/tauri-apps/tauri/commit/da25f7353070477ba969851e974379d7666d6806) ([#10242](https://www.github.com/tauri-apps/tauri/pull/10242) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `inner_size_constraints` method on `WindowBuilder` trait and `set_size_constraints` method on `WindowDispatch` trait.\n\n## \\[2.0.0-beta.20]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.19`\n\n## \\[2.0.0-beta.19]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.18`\n- [`d4c908cfb`](https://www.github.com/tauri-apps/tauri/commit/d4c908cfb8c567abdaf99b85f65f482ea81967e5) ([#10048](https://www.github.com/tauri-apps/tauri/pull/10048)) Update `windows` crate to version `0.57` and `webview2-com` crate to version `0.31`\n\n## \\[2.0.0-beta.18]\n\n### Enhancements\n\n- [`276c4b143`](https://www.github.com/tauri-apps/tauri/commit/276c4b14385e17cff15a2e5b57fd2a7cddef9f08)([#9832](https://www.github.com/tauri-apps/tauri/pull/9832)) Added `WindowBuilder::get_theme`.\n\n### What's Changed\n\n- [`9ac930380`](https://www.github.com/tauri-apps/tauri/commit/9ac930380a5df3fe700e68e75df8684d261ca292)([#9850](https://www.github.com/tauri-apps/tauri/pull/9850)) Emit `cargo:rustc-check-cfg` instruction so Cargo validates custom cfg attributes on Rust 1.80 (or nightly-2024-05-05).\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.17`\n\n## \\[2.0.0-beta.17]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.16`\n\n## \\[2.0.0-beta.16]\n\n### New Features\n\n- [`78839b6d2`](https://www.github.com/tauri-apps/tauri/commit/78839b6d2f1005a5e6e1a54b0305136bae0c3a7c)([#4865](https://www.github.com/tauri-apps/tauri/pull/4865)) Add `RunEvent::Reopen` for handle click on dock icon on macOS.\n\n### What's Changed\n\n- [`783ef0f2d`](https://www.github.com/tauri-apps/tauri/commit/783ef0f2d331f520fa827c3112f36c0b519b9292)([#9647](https://www.github.com/tauri-apps/tauri/pull/9647)) Changed `WebviewDispatch::url` getter to return a result.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.15`\n\n## \\[2.0.0-beta.15]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.14`\n\n## \\[2.0.0-beta.14]\n\n### New Features\n\n- [`477bb8cd4`](https://www.github.com/tauri-apps/tauri/commit/477bb8cd4ea88ade3f6c1f268ad1701a68150161)([#9297](https://www.github.com/tauri-apps/tauri/pull/9297)) Add `App/AppHandle/Window/Webview/WebviewWindow::cursor_position` getter to get the current cursor position.\n\n## \\[2.0.0-beta.13]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.13`\n\n## \\[2.0.0-beta.12]\n\n### New Features\n\n- [`58a7a552d`](https://www.github.com/tauri-apps/tauri/commit/58a7a552d739b77b71d61af11c53f7f2dc7a6e7e)([#9378](https://www.github.com/tauri-apps/tauri/pull/9378)) Added the `set_zoom` function to the webview API.\n- [`58a7a552d`](https://www.github.com/tauri-apps/tauri/commit/58a7a552d739b77b71d61af11c53f7f2dc7a6e7e)([#9378](https://www.github.com/tauri-apps/tauri/pull/9378)) Add `zoom_hotkeys_enabled` to enable browser native zoom controls on creating webviews.\n- [`4973d73a2`](https://www.github.com/tauri-apps/tauri/commit/4973d73a237dc5c60618c1011e202278e7a29b5c)([#9386](https://www.github.com/tauri-apps/tauri/pull/9386)) Provide a basic zoom hotkey polyfill for non-Windows platforms\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.12`\n\n## \\[2.0.0-beta.11]\n\n### What's Changed\n\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Updated `http` crate to `1.1`\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.11`\n\n### Breaking Changes\n\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) The IPC handler closure now receives a `http::Request` instead of a String representing the request body.\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Rename `FileDrop` to `DragDrop` on structs, enums and enum variants. Also renamed `file_drop` to `drag_drop` on fields and function names.\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Moved `window::dpi` module to the root of the crate.\n\n## \\[2.0.0-beta.10]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.10`\n\n## \\[2.0.0-beta.9]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.9`\n\n## \\[2.0.0-beta.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.8`\n\n## \\[2.0.0-beta.7]\n\n### New Features\n\n- [`46de49aaa`](https://www.github.com/tauri-apps/tauri/commit/46de49aaad4a148fafc31d591be0e2ed12256507)([#9059](https://www.github.com/tauri-apps/tauri/pull/9059)) Added `set_auto_resize` method for the webview.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.7`\n\n### Breaking Changes\n\n- [`d1e77acd8`](https://www.github.com/tauri-apps/tauri/commit/d1e77acd8dfdf554b90b542513a58a2de1ef2360)([#9011](https://www.github.com/tauri-apps/tauri/pull/9011)) Add a lifetime parameter for `Icon` type. Also changed `rgba` field to be `Cow<'a, [u8]>`\n\n## \\[2.0.0-beta.6]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.6`\n\n## \\[2.0.0-beta.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.5`\n\n## \\[2.0.0-beta.4]\n\n### New Features\n\n- [`fdcaf935`](https://www.github.com/tauri-apps/tauri/commit/fdcaf935fa75ecfa2806939c4faad4fe9e880386)([#8939](https://www.github.com/tauri-apps/tauri/pull/8939)) Added the `reparent` function to the webview API.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.4`\n\n## \\[2.0.0-beta.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.3`\n\n## \\[2.0.0-beta.2]\n\n### What's Changed\n\n- [`16e550ec`](https://www.github.com/tauri-apps/tauri/commit/16e550ec1503765158cdc3bb2a20e70ec710e981)([#8844](https://www.github.com/tauri-apps/tauri/pull/8844)) Add `WebviewEvent`, `RunEvent::WebviewEvent` and `WebviewDispatch::on_webview_event`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.2`\n\n### Breaking Changes\n\n- [`2f55bfec`](https://www.github.com/tauri-apps/tauri/commit/2f55bfecbf0244f3b5aa1ad7622182fca3fcdcbb)([#8795](https://www.github.com/tauri-apps/tauri/pull/8795)) Update raw-window-handle to 0.6.\n\n## \\[2.0.0-beta.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.1`\n\n## \\[2.0.0-beta.0]\n\n### New Features\n\n- [`af610232`](https://www.github.com/tauri-apps/tauri/commit/af6102327376884364b2075b468bdf08ee0d02aa)([#8710](https://www.github.com/tauri-apps/tauri/pull/8710)) Added `Window::destroy` to force close a window.\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Add multiwebview support behind the `unstable` feature flag. See `WindowBuilder` and `WebviewBuilder` for more information.\n- [`00e15675`](https://www.github.com/tauri-apps/tauri/commit/00e1567584721644797b587205187f9cbe4e5cd1)([#8708](https://www.github.com/tauri-apps/tauri/pull/8708)) Added `RuntimeHandle::request_exit` function.\n\n### Bug Fixes\n\n- [`95da1a27`](https://www.github.com/tauri-apps/tauri/commit/95da1a27476e01e06f6ce0335df8535b662dd9c4)([#8713](https://www.github.com/tauri-apps/tauri/pull/8713)) Fix calling `set_activation_policy` when the event loop is running.\n\n### What's Changed\n\n- [`9eaeb5a8`](https://www.github.com/tauri-apps/tauri/commit/9eaeb5a8cd95ae24b5e66205bdc2763cb7f965ce)([#8622](https://www.github.com/tauri-apps/tauri/pull/8622)) Added `WindowBuilder::transient_for` and Renamed `WindowBuilder::owner_window` to `WindowBuilder::owner` and `WindowBuilder::parent_window` to `WindowBuilder::parent`.\n- [`7f033f6d`](https://www.github.com/tauri-apps/tauri/commit/7f033f6dcd54c69a4193765a5c1584755ba92c61)([#8537](https://www.github.com/tauri-apps/tauri/pull/8537)) Add `Window::start_resize_dragging` and `ResizeDirection` enum.\n- [`6639a579`](https://www.github.com/tauri-apps/tauri/commit/6639a579c76d45210f33a72d37e21d4c5a9d334b)([#8441](https://www.github.com/tauri-apps/tauri/pull/8441)) Added the `WindowConfig::proxy_url` `WebviewBuilder::proxy_url() / WebviewWindowBuilder::proxy_url()` options when creating a webview.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.0`\n\n### Breaking Changes\n\n- [`9eaeb5a8`](https://www.github.com/tauri-apps/tauri/commit/9eaeb5a8cd95ae24b5e66205bdc2763cb7f965ce)([#8622](https://www.github.com/tauri-apps/tauri/pull/8622)) Changed `WindowBuilder::with_config` to take a reference to a `WindowConfig` instead of an owned value.\n\n## \\[1.0.0-alpha.8]\n\n### New Features\n\n- [`29ced5ce`](https://www.github.com/tauri-apps/tauri/commit/29ced5ceec40b2934094ade2db9a8855f294e1d1)([#8159](https://www.github.com/tauri-apps/tauri/pull/8159)) Added download event closure via `PendingWindow::download_handler`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.13`\n\n## \\[1.0.0-alpha.7]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.12`\n\n## \\[1.0.0-alpha.6]\n\n### Dependencies\n\n- [\\`\\`](https://www.github.com/tauri-apps/tauri/commit/undefined) Update dependencies.\n\n## \\[1.0.0-alpha.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.11`\n\n## \\[1.0.0-alpha.4]\n\n### New Features\n\n- [`74d2464d`](https://www.github.com/tauri-apps/tauri/commit/74d2464d0e490fae341ad73bdf2964cf215fe6c5)([#8116](https://www.github.com/tauri-apps/tauri/pull/8116)) Added `on_page_load` hook for `PendingWindow`.\n\n### Enhancements\n\n- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.10`\n- [`9580df1d`](https://www.github.com/tauri-apps/tauri/commit/9580df1d7b027befb9e5f025ea2cbaf2dcc82c8e)([#8084](https://www.github.com/tauri-apps/tauri/pull/8084)) Upgrade `gtk` to 0.18.\n- [`c7c2507d`](https://www.github.com/tauri-apps/tauri/commit/c7c2507da16a9beb71bf06745fe7ac1325ab7c2a)([#8035](https://www.github.com/tauri-apps/tauri/pull/8035)) Update `windows` to version `0.51` and `webview2-com` to version `0.27`\n\n## \\[1.0.0-alpha.3]\n\n### New Features\n\n- [`c085adda`](https://www.github.com/tauri-apps/tauri/commit/c085addab58ba851398373c6fd13f9cb026d71e8)([#8009](https://www.github.com/tauri-apps/tauri/pull/8009)) Added `set_progress_bar` to `Window`.\n- [`c1ec0f15`](https://www.github.com/tauri-apps/tauri/commit/c1ec0f155118527361dd5645d920becbc8afd569)([#7933](https://www.github.com/tauri-apps/tauri/pull/7933)) Added `Window::set_always_on_bottom` and the `always_on_bottom` option when creating a window.\n- [`880266a7`](https://www.github.com/tauri-apps/tauri/commit/880266a7f697e1fe58d685de3bb6836ce5251e92)([#8031](https://www.github.com/tauri-apps/tauri/pull/8031)) Bump the MSRV to 1.70.\n\n### Enhancements\n\n- [`46dcb941`](https://www.github.com/tauri-apps/tauri/commit/46dcb94110ac16d0d4328fa149bb86975b658f59)([#8006](https://www.github.com/tauri-apps/tauri/pull/8006)) Include mobile on docs.rs targets.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.9`\n\n### Breaking Changes\n\n- [`2558fab8`](https://www.github.com/tauri-apps/tauri/commit/2558fab861006936296e8511e43ccd69a38f61b0)([#7939](https://www.github.com/tauri-apps/tauri/pull/7939)) Added `WindowEventId` type and Changed `Dispatch::on_window_event` return type from `Uuid` to `WindowEventId`\n\n## \\[1.0.0-alpha.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.8`\n\n## \\[1.0.0-alpha.1]\n\n### Enhancements\n\n- [`0d63732b`](https://www.github.com/tauri-apps/tauri/commit/0d63732b962e71b98430f8d7b34ea5b59a2e8bb4)([#7754](https://www.github.com/tauri-apps/tauri/pull/7754)) Changed custom protocol closure type to enable asynchronous usage.\n\n### What's Changed\n\n- [`6177150b`](https://www.github.com/tauri-apps/tauri/commit/6177150b6f83b52ca359d6e20f7e540f7554e4eb)([#7601](https://www.github.com/tauri-apps/tauri/pull/7601)) Changed `FileDropEvent` to include drop and hover position.\n\n### Breaking Changes\n\n- [`0d63732b`](https://www.github.com/tauri-apps/tauri/commit/0d63732b962e71b98430f8d7b34ea5b59a2e8bb4)([#7754](https://www.github.com/tauri-apps/tauri/pull/7754)) `tauri-runtime` no longer implements its own HTTP types and relies on the `http` crate instead.\n\n## \\[1.0.0-alpha.0]\n\n### New Features\n\n- [`4db363a0`](https://www.github.com/tauri-apps/tauri/commit/4db363a03c182349f8491f46ced258d84723b11f)([#6589](https://www.github.com/tauri-apps/tauri/pull/6589)) Added `visible_on_all_workspaces` configuration option to `WindowBuilder`, `Window`, and `WindowConfig`.\n- [`84c41597`](https://www.github.com/tauri-apps/tauri/commit/84c4159754b2e59244211ed9e1fc702d851a0562)([#6394](https://www.github.com/tauri-apps/tauri/pull/6394)) Added `primary_monitor` and `available_monitors` to `Runtime` and `RuntimeHandle`.\n- [`2a000e15`](https://www.github.com/tauri-apps/tauri/commit/2a000e150d02dff28c8b20ad097b29e209160045)([#7235](https://www.github.com/tauri-apps/tauri/pull/7235)) Added `navigate` function to `Dispatch` trait.\n- [`3b98141a`](https://www.github.com/tauri-apps/tauri/commit/3b98141aa26f74c641a4090874247b97079bd58a)([#3736](https://www.github.com/tauri-apps/tauri/pull/3736)) Added the `Opened` variant to `RunEvent`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.7`\n\n### Breaking Changes\n\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) `Dispatch::create_window`, `Runtime::create_window` and `RuntimeHandle::create_window` has been changed to accept a 3rd parameter which is a closure that takes `RawWindow` and to be executed right after the window is created and before the webview is added to the window.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) System tray and menu related APIs and structs have all been removed and are now implemented in tauri outside of the runtime-space.\n- [`3a2c3e74`](https://www.github.com/tauri-apps/tauri/commit/3a2c3e74710bef9a14932dce74c351cca6215429)([#7306](https://www.github.com/tauri-apps/tauri/pull/7306)) The `PendingWindow#navigation_handler` closure now receives a `&Url` argument instead of `Url`.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) `Runtime::new` and `Runtime::new_any_thread` now accept a `RuntimeInitArgs`.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) Removed `system-tray` feature flag\n\n## \\[0.13.0-alpha.6]\n\n### New Features\n\n- [`e0f0dce2`](https://www.github.com/tauri-apps/tauri/commit/e0f0dce220730e2822fc202463aedf0166145de7)([#6442](https://www.github.com/tauri-apps/tauri/pull/6442)) Added the `window_effects` option when creating a window and `Window::set_effects` to change it at runtime.\n\n## \\[0.13.0-alpha.5]\n\n- [`39f1b04f`](https://www.github.com/tauri-apps/tauri/commit/39f1b04f7be4966488484829cd54c8ce72a04200)([#6943](https://www.github.com/tauri-apps/tauri/pull/6943)) Moved the `event` JS APIs to a plugin.\n- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65.\n- [`cebd7526`](https://www.github.com/tauri-apps/tauri/commit/cebd75261ac71b98976314a450cb292eeeec1515)([#6728](https://www.github.com/tauri-apps/tauri/pull/6728)) Moved the `clipboard` feature to its own plugin in the plugins-workspace repository.\n- [`3f17ee82`](https://www.github.com/tauri-apps/tauri/commit/3f17ee82f6ff21108806edb7b00500b8512b8dc7)([#6737](https://www.github.com/tauri-apps/tauri/pull/6737)) Moved the `global-shortcut` feature to its own plugin in the plugins-workspace repository.\n\n## \\[0.13.0-alpha.4]\n\n- Added `android` configuration object under `tauri > bundle`.\n  - Bumped due to a bump in tauri-utils.\n  - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07\n\n## \\[0.13.0-alpha.3]\n\n- Pull changes from Tauri 1.3 release.\n  - [](https://www.github.com/tauri-apps/tauri/commit/undefined)  on undefined\n\n## \\[0.13.0-alpha.2]\n\n- Add `find_class`, `run_on_android_context` on `RuntimeHandle`.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n- Added the `shadow` option when creating a window and `Window::set_shadow`.\n  - [a81750d7](https://www.github.com/tauri-apps/tauri/commit/a81750d779bc72f0fdb7de90b7fbddfd8049b328) feat(core): add shadow APIs ([#6206](https://www.github.com/tauri-apps/tauri/pull/6206)) on 2023-02-08\n- Implemented `with_webview` on Android and iOS.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n\n## \\[0.13.0-alpha.1]\n\n- Update gtk to 0.16.\n  - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30\n- Bump the MSRV to 1.64.\n  - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30\n\n## \\[0.13.0-alpha.0]\n\n- Parse `android` and `ios` Tauri configuration files.\n  - Bumped due to a bump in tauri-utils.\n  - [b3a3afc7](https://www.github.com/tauri-apps/tauri/commit/b3a3afc7de8de4021d73559288f5192732a706cf) feat(core): detect android and ios platform configuration files ([#4997](https://www.github.com/tauri-apps/tauri/pull/4997)) on 2022-08-22\n- First mobile alpha release!\n  - Bumped due to a bump in tauri-utils.\n  - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08\n\n## \\[0.14.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.2`\n\n## \\[0.14.1]\n\n### Enhancements\n\n- [`9aa34ada`](https://www.github.com/tauri-apps/tauri/commit/9aa34ada5769dbefa7dfe5f7a6288b3d20b294e4)([#7645](https://www.github.com/tauri-apps/tauri/pull/7645)) Add setting to switch to `http://<scheme>.localhost/` for custom protocols on Windows.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.0`\n\n## \\[0.14.0]\n\n### New Features\n\n- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `maximizable`, `minimizable` and `closable` methods to `WindowBuilder`.\n- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added `set_maximizable`, `set_minimizable`, `set_closable`, `is_maximizable`, `is_minimizable` and `is_closable` methods to the `Dispatch` trait.\n- [`000104bc`](https://www.github.com/tauri-apps/tauri/commit/000104bc3bc0c9ff3d20558ab9cf2080f126e9e0)([#6472](https://www.github.com/tauri-apps/tauri/pull/6472)) Add `Window::is_focused` getter.\n\n### Enhancements\n\n- [`d2710e9d`](https://www.github.com/tauri-apps/tauri/commit/d2710e9d2e8fd93975ef6494512370faa8cb3b7e)([#6944](https://www.github.com/tauri-apps/tauri/pull/6944)) Unpin `time`, `ignore`, and `winnow` crate versions. Developers now have to pin crates if needed themselves. A list of crates that need pinning to adhere to Tauri's MSRV will be visible in Tauri's GitHub workflow: https://github.com/tauri-apps/tauri/blob/dev/.github/workflows/test-core.yml#L85.\n\n### Bug Fixes\n\n- [`2b487c94`](https://www.github.com/tauri-apps/tauri/commit/2b487c946737352187d7e042dd6142873e62a4ca)([#7012](https://www.github.com/tauri-apps/tauri/pull/7012)) Fixes typo in `CursorIcon` deserialization of the `ZoomIn` variant.\n\n### What's Changed\n\n- [`076e1a81`](https://www.github.com/tauri-apps/tauri/commit/076e1a81a50468e3dfb34ae9ca7e77c5e1758daa)([#7119](https://www.github.com/tauri-apps/tauri/pull/7119)) Use `u32` instead of `u64` for js event listener ids\n- [`ff5e4dbb`](https://www.github.com/tauri-apps/tauri/commit/ff5e4dbbb01bf3fc9c5143df732c75eef6fd98cb)([#6794](https://www.github.com/tauri-apps/tauri/pull/6794)) impl `From<&WindowConfig>` for `WebviewAttributes`.\n\n## \\[0.13.0]\n\n- Added the `additional_browser_args` option when creating a window.\n  - [3dc38b15](https://www.github.com/tauri-apps/tauri/commit/3dc38b150ea8c59c8ba67fd586f921016928f47c) feat(core): expose additional_browser_args to window config (fix: [#5757](https://www.github.com/tauri-apps/tauri/pull/5757)) ([#5799](https://www.github.com/tauri-apps/tauri/pull/5799)) on 2022-12-14\n- Added the `content_protected` option when creating a window and `Window::set_content_protected` to change it at runtime.\n  - [4ab5545b](https://www.github.com/tauri-apps/tauri/commit/4ab5545b7a831c549f3c65e74de487ede3ab7ce5) feat: add content protection api, closes [#5132](https://www.github.com/tauri-apps/tauri/pull/5132) ([#5513](https://www.github.com/tauri-apps/tauri/pull/5513)) on 2022-12-13\n- Added `Builder::device_event_filter` and `App::set_device_event_filter` methods.\n  - [73fd60ee](https://www.github.com/tauri-apps/tauri/commit/73fd60eef2b60f5dc84525ef9c315f4d80c4414f) expose set_device_event_filter in tauri ([#5562](https://www.github.com/tauri-apps/tauri/pull/5562)) on 2022-12-13\n- Add `is_minimized()` window method.\n  - [62144ef3](https://www.github.com/tauri-apps/tauri/commit/62144ef3be63b237869e511826edfb938e2c7174) feat: add is_minimized (fix [#3878](https://www.github.com/tauri-apps/tauri/pull/3878)) ([#5618](https://www.github.com/tauri-apps/tauri/pull/5618)) on 2022-12-13\n- Bump minimum supported Rust version to 1.60.\n  - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31\n- Pin raw-window-handle to 0.5.0 to keep MSRV.\n  - [c46c09f3](https://www.github.com/tauri-apps/tauri/commit/c46c09f31d9f5169ca8a7e62406a9ea170e3a5c5) fix(deps): pin raw-window-handle to 0.5.0 ([#6480](https://www.github.com/tauri-apps/tauri/pull/6480)) on 2023-03-17\n- Added `navigation_handler` field on `PendingWindow`.\n  - [3f35b452](https://www.github.com/tauri-apps/tauri/commit/3f35b452637ef1c794a423f1eda62a15d2ddaf42) Expose wry navigation_handler via WindowBuilder closes [#4080](https://www.github.com/tauri-apps/tauri/pull/4080) ([#5686](https://www.github.com/tauri-apps/tauri/pull/5686)) on 2022-12-27\n- Add `title` getter on window.\n  - [233e43b0](https://www.github.com/tauri-apps/tauri/commit/233e43b0c34fada1ca025378533a0b76931a6540) feat: add `title` getter on window, closes [#5023](https://www.github.com/tauri-apps/tauri/pull/5023) ([#5515](https://www.github.com/tauri-apps/tauri/pull/5515)) on 2022-12-13\n- Added `TrayHandle::set_tooltip` and `SystemTray::with_tooltip`.\n  - [2265e097](https://www.github.com/tauri-apps/tauri/commit/2265e09718f6ebfeb1d200f11e1e1e069075af6e) feat(windows): implement `with_tooltip` ([#5938](https://www.github.com/tauri-apps/tauri/pull/5938)) on 2023-01-01\n- Added window's `url()` getter.\n  - [d17027e1](https://www.github.com/tauri-apps/tauri/commit/d17027e1a0db3e8c5ae81fc4f472c5918fbce611) feat: expose url method ([#5914](https://www.github.com/tauri-apps/tauri/pull/5914)) on 2022-12-26\n- On Windows, change webview theme based on Window theme for more accurate `prefers-color-scheme` support.\n  - [7a8d570d](https://www.github.com/tauri-apps/tauri/commit/7a8d570db72667367eb24b75ddc5dd07a968f7c0) fix: sync webview theme with window theme on Windows, closes [#5802](https://www.github.com/tauri-apps/tauri/pull/5802) ([#5874](https://www.github.com/tauri-apps/tauri/pull/5874)) on 2022-12-27\n\n## \\[0.12.2]\n\n- Block remote URLs from accessing the IPC.\n  - [9c0593c33](https://www.github.com/tauri-apps/tauri/commit/9c0593c33af52cd9e00ec784d15f63efebdf039c) feat(core): block remote URLs from accessing the IPC on 2023-04-12\n\n## \\[0.12.1]\n\n- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`.\n  - Bumped due to a bump in tauri-utils.\n  - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18\n\n## \\[0.12.0]\n\n- Readd the option to create an unfocused window via the `focused` method. The `focus` function has been deprecated.\n  - [4036e15f](https://www.github.com/tauri-apps/tauri/commit/4036e15f5af933bdc0d0913508b5103958afc143) feat(core): reimplement window initial focus flag, closes [#5120](https://www.github.com/tauri-apps/tauri/pull/5120) ([#5338](https://www.github.com/tauri-apps/tauri/pull/5338)) on 2022-10-08\n- Added `Runtime::show()`, `RuntimeHandle::show()`, `Runtime::hide()`, `RuntimeHandle::hide()` for hiding/showing the entire application on macOS.\n  - [39bf895b](https://www.github.com/tauri-apps/tauri/commit/39bf895b73ec6b53f5758815396ba85dda6b9c67) feat(macOS): Add application `show` and `hide` methods ([#3689](https://www.github.com/tauri-apps/tauri/pull/3689)) on 2022-10-03\n- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28\n- Added `tabbing_identifier` to the window builder on macOS.\n  - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19\n- Added methods to set the system tray title on macOS.\n  - [8f1ace77](https://www.github.com/tauri-apps/tauri/commit/8f1ace77956ac3477826ceb059a191e55b3fff93) feat: expose `set_title` for MacOS tray ([#5182](https://www.github.com/tauri-apps/tauri/pull/5182)) on 2022-09-30\n- Added the `user_agent` option when creating a window.\n  - [a6c94119](https://www.github.com/tauri-apps/tauri/commit/a6c94119d8545d509723b147c273ca5edfe3729f) feat(core): expose user_agent to window config ([#5317](https://www.github.com/tauri-apps/tauri/pull/5317)) on 2022-10-02\n\n## \\[0.11.2]\n\n- Block remote URLs from accessing the IPC.\n  - [58ea0b452](https://www.github.com/tauri-apps/tauri/commit/58ea0b45268dbd46cbac0ebb0887353d057ca767) feat(core): block remote URLs from accessing the IPC on 2023-04-12\n\n## \\[0.11.1]\n\n- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs.\n  - Bumped due to a bump in tauri-utils.\n  - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16\n\n## \\[0.11.0]\n\n- Added APIs to create a system tray at runtime.\n  - [4d063ae9](https://www.github.com/tauri-apps/tauri/commit/4d063ae9ee9538cd6fa5e01b80070c6edf8eaeb9) feat(core): create system tray at runtime, closes [#2278](https://www.github.com/tauri-apps/tauri/pull/2278) ([#4862](https://www.github.com/tauri-apps/tauri/pull/4862)) on 2022-08-09\n- Update windows to 0.39.0 and webview2-com to 0.19.1.\n  - [e6d9b670](https://www.github.com/tauri-apps/tauri/commit/e6d9b670b0b314ed667b0e164f2c8d27048e678f) refactor: remove unneeded focus code ([#5065](https://www.github.com/tauri-apps/tauri/pull/5065)) on 2022-09-03\n\n## \\[0.10.3]\n\n- Block remote URLs from accessing the IPC.\n  - [fa90214b0](https://www.github.com/tauri-apps/tauri/commit/fa90214b052b1a5d38d54fbf1ca422b4c37cfd1f) feat(core): block remote URLs from accessing the IPC on 2023-04-12\n\n## \\[0.10.2]\n\n- Added option to disable tray menu on left click on macOS.\n  - [f8a3becb](https://www.github.com/tauri-apps/tauri/commit/f8a3becb287942db7f7b551b5db6aeb5a2e939ee) feat(core): add option to disable tray menu on left click, closes [#4584](https://www.github.com/tauri-apps/tauri/pull/4584) ([#4587](https://www.github.com/tauri-apps/tauri/pull/4587)) on 2022-07-05\n\n## \\[0.10.1]\n\n- Expose `platform::windows_version` function.\n  - Bumped due to a bump in tauri-utils.\n  - [bf764e83](https://www.github.com/tauri-apps/tauri/commit/bf764e83e01e7443e6cc54572001e1c98c357465) feat(utils): expose `windows_version` function ([#4534](https://www.github.com/tauri-apps/tauri/pull/4534)) on 2022-06-30\n\n## \\[0.10.0]\n\n- Added `fn new` constructors for `PhysicalSize`, `LogicalSize`, `PhysicalPosition` and `LogicalPosition` and missing conversion methods.\n  - [c7d13a1c](https://www.github.com/tauri-apps/tauri/commit/c7d13a1c60cdbe0c42834ea059321d7a3a7f01a0) feat(core): add missing methods to the dpi module ([#4393](https://www.github.com/tauri-apps/tauri/pull/4393)) on 2022-06-19\n- Implement `raw_window_handle::HasRawWindowHandle` on Linux.\n  - [3efbc67f](https://www.github.com/tauri-apps/tauri/commit/3efbc67f7469ce65a2d9ea4ff2b60b51d2a36aa5) feat: implement `raw_window_handle` on Linux ([#4469](https://www.github.com/tauri-apps/tauri/pull/4469)) on 2022-06-26\n- Removed the `hwnd` and `ns_window` functions from `Dispatch` in favor of `raw_window_handle`.\n  - [3efbc67f](https://www.github.com/tauri-apps/tauri/commit/3efbc67f7469ce65a2d9ea4ff2b60b51d2a36aa5) feat: implement `raw_window_handle` on Linux ([#4469](https://www.github.com/tauri-apps/tauri/pull/4469)) on 2022-06-26\n- The theme API is now implemented on macOS 10.14+.\n  - [6d94ce42](https://www.github.com/tauri-apps/tauri/commit/6d94ce42353204a02fe9c82ed397d349439f75ef) feat(core): theme is now implemented on macOS ([#4380](https://www.github.com/tauri-apps/tauri/pull/4380)) on 2022-06-17\n\n## \\[0.9.0]\n\n- Upgrade to `stable`!\n  - Bumped due to a bump in tauri-utils.\n  - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15\n\n## \\[0.8.1]\n\n- Add `Menu::os_default` which will create a menu filled with default menu items and submenus.\n  - [4c4acc30](https://www.github.com/tauri-apps/tauri/commit/4c4acc3094218dd9cee0f1ad61810c979e0b41fa) feat: implement `Default` for `Menu`, closes [#2398](https://www.github.com/tauri-apps/tauri/pull/2398) ([#4291](https://www.github.com/tauri-apps/tauri/pull/4291)) on 2022-06-15\n\n## \\[0.8.0]\n\n- Removed `TrayIcon` and renamed `WindowIcon` to `Icon`, a shared type for both icons.\n  - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14\n\n## \\[0.7.0]\n\n- Added a config flag to bundle the media framework used by webkit2gtk `tauri.conf.json > tauri > bundle > appimage > bundleMediaFramework`.\n  - Bumped due to a bump in tauri-utils.\n  - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10\n\n## \\[0.6.0]\n\n- Update `windows-rs` to `0.37.0`, which requires Rust 1.61.0+.\n  - [2326be39](https://www.github.com/tauri-apps/tauri/commit/2326be39821890cdd4de76e7029a531424dcb26f) feat(core): update windows-rs to 0.37.0 ([#4199](https://www.github.com/tauri-apps/tauri/pull/4199)) on 2022-05-24\n\n## \\[0.5.1]\n\n- Fix `.mjs` not being recognised as a file extension for JavaScript files (`text/javascript`).\n  - [45c45253](https://www.github.com/tauri-apps/tauri/commit/45c45253866ce0de317a6a547af3ea0434d4bcac) fix: add mjs mime type (fix: [#4098](https://www.github.com/tauri-apps/tauri/pull/4098)) ([#4108](https://www.github.com/tauri-apps/tauri/pull/4108)) on 2022-05-13\n\n## \\[0.5.0]\n\n- Expose methods to access the underlying native handles of the webview.\n  - [c82b4761](https://www.github.com/tauri-apps/tauri/commit/c82b4761e1660592472dc55308ad69d9efc5855b) feat(core): expose `with_webview` API to access the platform webview ([#4058](https://www.github.com/tauri-apps/tauri/pull/4058)) on 2022-05-04\n\n## \\[0.4.0]\n\n- The `AboutMetadata` string setters now take `impl Into<String>`.\n  - [b14aa896](https://www.github.com/tauri-apps/tauri/commit/b14aa89673c3563522e5c04baf9630fa1c4739b0) feat(core): improve `AboutMetadata` setters on 2022-03-29\n- \\**Breaking change::* Added the `clipboard` Cargo feature.\n  - [24e4ff20](https://www.github.com/tauri-apps/tauri/commit/24e4ff208ee0fe1a4cc5b10667ea0922ac63dfb5) refactor(core): add clipboard Cargo feature, enhancing binary size ([#3957](https://www.github.com/tauri-apps/tauri/pull/3957)) on 2022-04-24\n- Expose Window cursor APIs `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position`.\n  - [c54ddfe9](https://www.github.com/tauri-apps/tauri/commit/c54ddfe9338e7eb90b4d5b02dfde687d432d5bc1) feat: expose window cursor APIs, closes [#3888](https://www.github.com/tauri-apps/tauri/pull/3888) [#3890](https://www.github.com/tauri-apps/tauri/pull/3890) ([#3935](https://www.github.com/tauri-apps/tauri/pull/3935)) on 2022-04-21\n- \\**Breaking change::* Added the `global-shortcut` Cargo feature.\n  - [e11878bc](https://www.github.com/tauri-apps/tauri/commit/e11878bcf7174b261a1fa146fc7d564d12e6312a) refactor(core): add global-shortcut Cargo feature, enhancing binary size ([#3956](https://www.github.com/tauri-apps/tauri/pull/3956)) on 2022-04-24\n- Added `WindowEvent::ThemeChanged(theme)`.\n  - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21\n- Added `theme` getter on `Window`.\n  - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21\n- Added `theme` setter to the WindowBuilder.\n  - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21\n\n## \\[0.3.4]\n\n- Added `close_devtools` and `is_devtools_open` APIs to the `Dispatch` trait.\n  - [e05d718a](https://www.github.com/tauri-apps/tauri/commit/e05d718a7b46476d1fe4817c169008080e84f959) feat(core): add hotkey to toggle devtools, closes [#3776](https://www.github.com/tauri-apps/tauri/pull/3776) ([#3791](https://www.github.com/tauri-apps/tauri/pull/3791)) on 2022-03-28\n- **Breaking change:** The `MenuItem::About` variant is now associated with a tuple value `(String, AboutMetadata)`.\n  - [5fb74332](https://www.github.com/tauri-apps/tauri/commit/5fb74332ab9210ac062d96b0e9afd1c942ee2911) chore(deps): update wry to 0.14, tao to 0.7 ([#3790](https://www.github.com/tauri-apps/tauri/pull/3790)) on 2022-03-28\n- Support window parenting on macOS\n  - [4e807a53](https://www.github.com/tauri-apps/tauri/commit/4e807a53e2d6d3f3cd5293d90013d5cdded5454e) Support window parenting on macOS, closes [#3751](https://www.github.com/tauri-apps/tauri/pull/3751) ([#3754](https://www.github.com/tauri-apps/tauri/pull/3754)) on 2022-03-23\n- The file drop event is now part of the `WindowEvent` enum instead of a having a dedicated handler.\n  - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13\n- **Breaking change:** Use the dedicated `WindowEvent` enum on `RunEvent`.\n  - [edad9f4f](https://www.github.com/tauri-apps/tauri/commit/edad9f4f55dcc69a06cd9d6d5a5068c94ecb77dd) refactor(core): add `RunEvent::WindowEvent` ([#3793](https://www.github.com/tauri-apps/tauri/pull/3793)) on 2022-03-28\n- Added `create_proxy` to the `Runtime` and `RuntimeHandle` traits.\n  - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15\n- Force `Error` boxed errors to be `Sync`.\n  - [da1e8793](https://www.github.com/tauri-apps/tauri/commit/da1e879358895f7b190b1c1b20d23da23666a74b) feat(core): improve and cleanup the `Error` enum ([#3748](https://www.github.com/tauri-apps/tauri/pull/3748)) on 2022-03-22\n- **Breaking change:** Move the `FileDropEvent` struct to the `window` module.\n  - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13\n- Allow specifying a user event type for the event loop message.\n  - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15\n- Added the `WindowEvent::FileDrop` variant.\n  - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13\n\n## \\[0.3.3]\n\n- **Breaking change:** Move `ico` and `png` parsing behind `icon-ico` and `icon-png` Cargo features.\n  - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05\n- The `PendingWindow::new` and `PendingWindow::with_config` functions now return `Result<Self>` validating the window label.\n  - [64e00542](https://www.github.com/tauri-apps/tauri/commit/64e0054299c95f10ef5a1a9d3f914bbaeff3d73f) refactor(core): do not panic on invalid window labels,[#3544](https://www.github.com/tauri-apps/tauri/pull/3544) ([#3596](https://www.github.com/tauri-apps/tauri/pull/3596)) on 2022-03-03\n\n## \\[0.3.2]\n\n- Fix requirements for `RuntimeHandle`, `ClipboardManager`, `GlobalShortcutHandle` and `TrayHandle`.\n  - [84895a9c](https://www.github.com/tauri-apps/tauri/commit/84895a9cd270fc743e236d0f4d4cd6210b24a30f) fix(runtime): trait requirements ([#3489](https://www.github.com/tauri-apps/tauri/pull/3489)) on 2022-02-17\n\n## \\[0.3.1]\n\n- Change default value for the `freezePrototype` configuration to `false`.\n  - Bumped due to a bump in tauri-utils.\n  - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12\n\n## \\[0.3.0]\n\n- Replace `WindowBuilder`'s `has_menu` with `get_menu`.\n  - [ac37b56e](https://www.github.com/tauri-apps/tauri/commit/ac37b56ef43c9e97039967a5fd99f0d2dccb5b5a) fix(core): menu id map not reflecting the current window menu ([#2726](https://www.github.com/tauri-apps/tauri/pull/2726)) on 2021-10-08\n- The `run_return` API is now available on Linux.\n  - [8483fde9](https://www.github.com/tauri-apps/tauri/commit/8483fde975aac8833d2ce426e42fb40aeaeecba9) feat(core): expose `run_return` on Linux ([#3352](https://www.github.com/tauri-apps/tauri/pull/3352)) on 2022-02-07\n- Add `Menu::with_items` constructor, taking an iterator of `MenuEntry`.\n  - [7cc95e10](https://www.github.com/tauri-apps/tauri/commit/7cc95e10ec66d8b155e9bb7f89cf73df56d1f107) feat(core): add `Menu::with_items`, closes [#2807](https://www.github.com/tauri-apps/tauri/pull/2807) ([#2966](https://www.github.com/tauri-apps/tauri/pull/2966)) on 2021-12-27\n- Change event loop callbacks definition to allow callers to move in mutable values.\n  - [bdbf905e](https://www.github.com/tauri-apps/tauri/commit/bdbf905e5d802b58693d2bd27582ce4269faf79c) Transformed event-loop callback to FnMut to allow mutable values ([#2667](https://www.github.com/tauri-apps/tauri/pull/2667)) on 2021-09-27\n- Added `any_thread` constructor on the `Runtime` trait (only possible on Linux and Windows).\n  - [af44bf81](https://www.github.com/tauri-apps/tauri/commit/af44bf8168310cf77fbe102a53e7c433f11641a3) feat(core): allow app run on any thread on Linux & Windows, closes [#3172](https://www.github.com/tauri-apps/tauri/pull/3172) ([#3353](https://www.github.com/tauri-apps/tauri/pull/3353)) on 2022-02-07\n- Added `run_on_main_thread` API on `RuntimeHandle`.\n  - [53fdfe52](https://www.github.com/tauri-apps/tauri/commit/53fdfe52bb30d52653c72ca9f42506c3863dcf4a) feat(core): expose `run_on_main_thread` API ([#2711](https://www.github.com/tauri-apps/tauri/pull/2711)) on 2021-10-04\n- **Breaking change:** Renamed the `RPC` interface to `IPC`.\n  - [3420aa50](https://www.github.com/tauri-apps/tauri/commit/3420aa5031b3274a95c6c5fa0f8683ca13213396) refactor: IPC handler \\[TRI-019] ([#9](https://www.github.com/tauri-apps/tauri/pull/9)) on 2022-01-09\n- Added `open_devtools` to the `Dispatcher` trait.\n  - [55aa22de](https://www.github.com/tauri-apps/tauri/commit/55aa22de80c3de873e29bcffcb5b2fe236a637a6) feat(core): add `Window#open_devtools` API, closes [#1213](https://www.github.com/tauri-apps/tauri/pull/1213) ([#3350](https://www.github.com/tauri-apps/tauri/pull/3350)) on 2022-02-07\n- The minimum Rust version is now `1.56`.\n  - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22\n- The window label is now validated and must be alphanumeric, resulting in a panic if it isn't.\n  - [680554de](https://www.github.com/tauri-apps/tauri/commit/680554de3ef6b7fccf87c441ad355cfef7aab6fe) feat: validate window label \\[TRI-021] ([#13](https://www.github.com/tauri-apps/tauri/pull/13)) on 2021-10-23\n- Added `clipboard` field on the `WebviewAttributes` struct, which must be set to `true` to enable clipboard access on the webview.\n  - [d42ccfb3](https://www.github.com/tauri-apps/tauri/commit/d42ccfb34f71851dfeb22fe74c83a8bdbddb5550) feat: add `clipboard` flag to `WebviewAttributes` \\[TRI-032] ([#12](https://www.github.com/tauri-apps/tauri/pull/12)) on 2021-10-23\n- Replace all of the `winapi` crate references with the `windows` crate, and replace `webview2` and `webview2-sys` with `webview2-com` and `webview2-com-sys` built with the `windows` crate. This goes along with updates to the TAO and WRY `next` branches.\n  - [bb00d5bd](https://www.github.com/tauri-apps/tauri/commit/bb00d5bd6c9dfcb6bdd0d308dadb70e6c6aafe5c) Replace winapi with windows crate and use webview2-com instead of webview2 ([#2615](https://www.github.com/tauri-apps/tauri/pull/2615)) on 2021-09-24\n- Update the `windows` crate to 0.25.0, which comes with pre-built libraries. WRY and Tao can both reference the same types directly from the `windows` crate instead of sharing bindings in `webview2-com-sys`.\n  - [34be6cf3](https://www.github.com/tauri-apps/tauri/commit/34be6cf37a98ee7cbd66623ebddae08e5a6520fd) Update webview2-com and windows crates ([#2875](https://www.github.com/tauri-apps/tauri/pull/2875)) on 2021-11-11\n\n## \\[0.2.1]\n\n- **Breaking change:** Removed `register_uri_scheme_protocol` from the `WebviewAttributes` struct and renamed `register_global_uri_scheme_protocol` to `register_uri_scheme_protocol` on the `Builder` struct, which now takes a `Fn(&AppHandle, &http::Request) -> http::Response` closure.\n  - [539e4489](https://www.github.com/tauri-apps/tauri/commit/539e4489e0bac7029d86917e9982ea49e02fe489) refactor: custom protocol ([#2503](https://www.github.com/tauri-apps/tauri/pull/2503)) on 2021-08-23\n- Migrate to latest custom protocol allowing `Partial content` streaming and Header parsing.\n  - [539e4489](https://www.github.com/tauri-apps/tauri/commit/539e4489e0bac7029d86917e9982ea49e02fe489) refactor: custom protocol ([#2503](https://www.github.com/tauri-apps/tauri/pull/2503)) on 2021-08-23\n\n## \\[0.2.0]\n\n- Fix blur/focus events being incorrect on Windows.\n  - [d832d575](https://www.github.com/tauri-apps/tauri/commit/d832d575d9b03a0ff78accabe4631cc638c08c3b) fix(windows): use webview events on windows ([#2277](https://www.github.com/tauri-apps/tauri/pull/2277)) on 2021-07-23\n\n- Add `ExitRequested` event that allows preventing the app from exiting when all windows are closed, and an `AppHandle.exit()` function to exit the app manually.\n  - [892c63a0](https://www.github.com/tauri-apps/tauri/commit/892c63a0538f8d62680dce5848657128ad6b7af3) feat([#2287](https://www.github.com/tauri-apps/tauri/pull/2287)): Add `ExitRequested` event to let users prevent app from exiting ([#2293](https://www.github.com/tauri-apps/tauri/pull/2293)) on 2021-08-09\n\n- Fixes minimum window height being used as maximum height.\n  - [e3f99165](https://www.github.com/tauri-apps/tauri/commit/e3f9916526b226866137cb663e5cafab2b6a0e01) fix(core) minHeight being used as maxHeight ([#2247](https://www.github.com/tauri-apps/tauri/pull/2247)) on 2021-07-19\n\n- Update gtk and its related libraries to v0.14. This also remove requirements of `clang` as build dependency.\n  - [63ad3039](https://www.github.com/tauri-apps/tauri/commit/63ad303903bbee7c9a7382413b342e2a05d3ea75) chore(linux): bump gtk to v0.14 ([#2361](https://www.github.com/tauri-apps/tauri/pull/2361)) on 2021-08-07\n\n- Implement `Debug` on public API structs and enums.\n  - [fa9341ba](https://www.github.com/tauri-apps/tauri/commit/fa9341ba18ba227735341530900714dba0f27291) feat(core): implement `Debug` on public API structs/enums, closes [#2292](https://www.github.com/tauri-apps/tauri/pull/2292) ([#2387](https://www.github.com/tauri-apps/tauri/pull/2387)) on 2021-08-11\n\n- Panic when a dispatcher getter method (`Window`, `GlobalShortcutHandle`, `ClipboardManager` and `MenuHandle` APIs) is called on the main thread.\n  - [50ffdc06](https://www.github.com/tauri-apps/tauri/commit/50ffdc06fbde56aba32b4291fd130104935d1408) feat(core): panic when a dispatcher getter is used on the main thread ([#2455](https://www.github.com/tauri-apps/tauri/pull/2455)) on 2021-08-16\n\n- Remove menu feature flag since there's no package dependency need to be installed on any platform anymore.\n  - [f81ebddf](https://www.github.com/tauri-apps/tauri/commit/f81ebddfcc1aea0d4989706aef43538e8ea98bea) feat: remove menu feature flag ([#2415](https://www.github.com/tauri-apps/tauri/pull/2415)) on 2021-08-13\n\n- Adds `Resumed` and `MainEventsCleared` variants to the `RunEvent` enum.\n  - [6be3f433](https://www.github.com/tauri-apps/tauri/commit/6be3f4339168651fe4e003b09f7d181fd12cd5a8) feat(core): add `Resumed` and `MainEventsCleared` events, closes [#2127](https://www.github.com/tauri-apps/tauri/pull/2127) ([#2439](https://www.github.com/tauri-apps/tauri/pull/2439)) on 2021-08-15\n\n- Adds `set_activation_policy` API to the `Runtime` trait (macOS only).\n  - [4a031add](https://www.github.com/tauri-apps/tauri/commit/4a031add69014a1f3823f4ea19b172a2557f6794) feat(core): expose `set_activation_policy`, closes [#2258](https://www.github.com/tauri-apps/tauri/pull/2258) ([#2420](https://www.github.com/tauri-apps/tauri/pull/2420)) on 2021-08-13\n\n- Allow creation of empty Window with `create_tao_window()` and management with `send_tao_window_event()` on the AppHandler.\n  - [88080855](https://www.github.com/tauri-apps/tauri/commit/8808085541a629b8e22b612a06cef01cf9b3722e) feat(window): Allow creation of Window without `wry` ([#2321](https://www.github.com/tauri-apps/tauri/pull/2321)) on 2021-07-29\n  - [15566cfd](https://www.github.com/tauri-apps/tauri/commit/15566cfd64f5072fa4980a6ce5b33259958e9021) feat(core): add API to send wry window message to the event loop ([#2339](https://www.github.com/tauri-apps/tauri/pull/2339)) on 2021-08-02\n\n- - Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color.\n\n- Images you mark as template images should consist of only black and clear colors. You can use the alpha channel in the image to adjust the opacity of black content, however.\n\n- [426a6b49](https://www.github.com/tauri-apps/tauri/commit/426a6b49962de8faf061db2e820ac10fcbb300d6) feat(macOS): Implement tray icon template ([#2322](https://www.github.com/tauri-apps/tauri/pull/2322)) on 2021-07-29\n\n- Add `Event::Ready` on the `run()` callback. Triggered once when the event loop is ready.\n  - [28c6b7ad](https://www.github.com/tauri-apps/tauri/commit/28c6b7adfe98e701b158e936eafb7541ddc700e0) feat: add `Event::Ready` ([#2433](https://www.github.com/tauri-apps/tauri/pull/2433)) on 2021-08-15\n\n## \\[0.1.4]\n\n- Allow preventing window close when the user requests it.\n  - [8157a68a](https://www.github.com/tauri-apps/tauri/commit/8157a68af1d94de1b90a14aa44139bb123b3436b) feat(core): allow listening to event loop events & prevent window close ([#2131](https://www.github.com/tauri-apps/tauri/pull/2131)) on 2021-07-06\n- Expose `gtk_window` getter.\n  - [e0a8e09c](https://www.github.com/tauri-apps/tauri/commit/e0a8e09cab6799eeb9ec524b5f7780d1e5a84299) feat(core): expose `gtk_window`, closes [#2083](https://www.github.com/tauri-apps/tauri/pull/2083) ([#2141](https://www.github.com/tauri-apps/tauri/pull/2141)) on 2021-07-02\n- `Params` has been removed, along with all the associated types on it. Functions that previously accepted those\n  associated types now accept strings instead. Type that used a generic parameter `Params` now use `Runtime` instead. If\n  you use the `wry` feature, then types with a `Runtime` generic parameter should default to `Wry`, letting you omit the\n  explicit type and let the compiler infer it instead.\n\n`tauri`:\n\n- See `Params` note\n- If you were using `Params` inside a function parameter or definition, all references to it have been replaced with a\n  simple runtime that defaults to `Wry`. If you are not using a custom runtime, just remove `Params` from the definition\n  of functions/items that previously took it. If you are using a custom runtime, you *may* need to pass the runtime type\n  to these functions.\n- If you were using custom types for `Params` (uncommon and if you don't understand you probably were not using it), all\n  methods that were previously taking the custom type now takes an `Into<String>` or a `&str`. The types were already\n  required to be string-able, so just make sure to convert it into a string before passing it in if this breaking change\n  affects you.\n\n`tauri-macros`:\n\n- (internal) Added private `default_runtime` proc macro to allow us to give item definitions a custom runtime only when\n  the specified feature is enabled.\n\n`tauri-runtime`:\n\n- See `Params` note\n- Removed `Params`, `MenuId`, `Tag`, `TagRef`.\n- Added `menu::{MenuHash, MenuId, MenuIdRef}` as type aliases for the internal type that menu types now use.\n  - All previous menu items that had a `MenuId` generic now use the underlying `MenuId` type without a generic.\n- `Runtime`, `RuntimeHandle`, and `Dispatch` have no more generic parameter on `create_window(...)` and instead use the\n  `Runtime` type directly\n- `Runtime::system_tray` has no more `MenuId` generic and uses the string based `SystemTray` type directly.\n- (internal) `CustomMenuItem::id_value()` is now hashed on creation and exposed as the `id` field with type `MenuHash`.\n\n`tauri-runtime-wry`:\n\n- See `Params` note\n- update menu and runtime related types to the ones changed in `tauri-runtime`.\n\n`tauri-utils`:\n\n- `Assets::get` signature has changed to take a `&AssetKey` instead of `impl Into<AssetKey>` to become trait object\n  safe.\n- [fd8fab50](https://www.github.com/tauri-apps/tauri/commit/fd8fab507c8fa1b113b841af14c6693eb3955f6b) refactor(core): remove `Params` and replace with strings ([#2191](https://www.github.com/tauri-apps/tauri/pull/2191)) on 2021-07-15\n\n## \\[0.1.3]\n\n- `Window` is now `Send + Sync` on Windows.\n  - [fe32afcc](https://www.github.com/tauri-apps/tauri/commit/fe32afcc933920d6282ae1d63b041b182278a031) fix(core): `Window` must be `Send + Sync` on Windows, closes [#2078](https://www.github.com/tauri-apps/tauri/pull/2078) ([#2093](https://www.github.com/tauri-apps/tauri/pull/2093)) on 2021-06-27\n\n## \\[0.1.2]\n\n- Adds `clipboard` APIs (write and read text).\n  - [285bf64b](https://www.github.com/tauri-apps/tauri/commit/285bf64bf9569efb2df904c69c6df405ff0d62e2) feat(core): add clipboard writeText and readText APIs ([#2035](https://www.github.com/tauri-apps/tauri/pull/2035)) on 2021-06-21\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `focus` API to the WindowBuilder.\n  - [5f351622](https://www.github.com/tauri-apps/tauri/commit/5f351622c7812ad1bb56ddb37364ccaa4124c24b) feat(core): add focus API to the WindowBuilder and WindowOptions, [#1737](https://www.github.com/tauri-apps/tauri/pull/1737) on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `is_decorated` getter on Window.\n  - [f58a2114](https://www.github.com/tauri-apps/tauri/commit/f58a2114fbfd5307c349f05c88f2e08fd8baa8aa) feat(core): add `is_decorated` Window getter on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `is_resizable` getter on Window.\n  - [1e8af280](https://www.github.com/tauri-apps/tauri/commit/1e8af280c27f381828d6209722b10e889082fa00) feat(core): add `is_resizable` Window getter on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `is_visible` getter on Window.\n  - [36506c96](https://www.github.com/tauri-apps/tauri/commit/36506c967de82bc7ff453d11e6104ecf66d7a588) feat(core): add `is_visible` API on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `accelerator` method to the `CustomMenuItem` struct to define a keyboard shortcut for the menu item.\n  - [034c2601](https://www.github.com/tauri-apps/tauri/commit/034c26013bce0c7bbe6db067ea7fd24a53a5c998) feat(core): add `accelerator` method to `CustomMenuItem` ([#2043](https://www.github.com/tauri-apps/tauri/pull/2043)) on 2021-06-22\n- Adds global shortcut interfaces.\n  - [3280c4aa](https://www.github.com/tauri-apps/tauri/commit/3280c4aa91e50a8ccdd561a8b48a12a4a13ea8d5) refactor(core): global shortcut is now provided by `tao` ([#2031](https://www.github.com/tauri-apps/tauri/pull/2031)) on 2021-06-21\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `request_user_attention` API to the `Dispatcher` trait.\n  - [7dcca6e9](https://www.github.com/tauri-apps/tauri/commit/7dcca6e9281182b11ad3d4a79871f09b30b9b419) feat(core): add `request_user_attention` API, closes [#2023](https://www.github.com/tauri-apps/tauri/pull/2023) ([#2026](https://www.github.com/tauri-apps/tauri/pull/2026)) on 2021-06-20\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `fn run_iteration` (macOS and Windows only) to the Runtime trait.\n  - [8c0d0739](https://www.github.com/tauri-apps/tauri/commit/8c0d0739eebf7286b64a5380e922746411eb52c6) feat(core): add `run_iteration`, `parent_window` and `owner_window` APIs, closes [#1872](https://www.github.com/tauri-apps/tauri/pull/1872) ([#1874](https://www.github.com/tauri-apps/tauri/pull/1874)) on 2021-05-21\n- Adds `show_menu`, `hide_menu` and `is_menu_visible` APIs to the `Dispatcher` trait.\n  - [954460c5](https://www.github.com/tauri-apps/tauri/commit/954460c5205d57444ef4b1412051fbedf3e38676) feat(core): MenuHandle `show`, `hide`, `is_visible` and `toggle` APIs ([#1958](https://www.github.com/tauri-apps/tauri/pull/1958)) on 2021-06-15\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `set_focus` API on Window.\n  - [bb6992f8](https://www.github.com/tauri-apps/tauri/commit/bb6992f888196ca7c87bb2fe74ad2bd8bf393e05) feat(core): add `set_focus` window API, fixes [#1737](https://www.github.com/tauri-apps/tauri/pull/1737) on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `set_skip_taskbar` API on Window.\n  - [e06aa277](https://www.github.com/tauri-apps/tauri/commit/e06aa277384450cfef617c0e57b0d5d403bb1e7f) feat(core): add `set_skip_taskbar` API on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `skip_taskbar` API to the WindowBuilder.\n  - [5525b03a](https://www.github.com/tauri-apps/tauri/commit/5525b03a78a2232c650043fbd9894ce1553cad41) feat(core): add `skip_taskbar` API to the WindowBuilder/WindowOptions on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `Window#center` and `WindowBuilder#center` APIs.\n  - [5cba6eb4](https://www.github.com/tauri-apps/tauri/commit/5cba6eb4d28d53f06855d60d4d0eae6b95233ccf) feat(core): add window `center` API, closes [#1822](https://www.github.com/tauri-apps/tauri/pull/1822) ([#1954](https://www.github.com/tauri-apps/tauri/pull/1954)) on 2021-06-05\n- Adds `parent_window` and `owner_window` setters to the `WindowBuilder` (Windows only).\n  - [8c0d0739](https://www.github.com/tauri-apps/tauri/commit/8c0d0739eebf7286b64a5380e922746411eb52c6) feat(core): add `run_iteration`, `parent_window` and `owner_window` APIs, closes [#1872](https://www.github.com/tauri-apps/tauri/pull/1872) ([#1874](https://www.github.com/tauri-apps/tauri/pull/1874)) on 2021-05-21\n- Adds window native handle getter (HWND on Windows).\n  - [abf78c58](https://www.github.com/tauri-apps/tauri/commit/abf78c5860cdc52fbfd2bc5dbca29a864e2da8f9) fix(core): set parent window handle on dialogs, closes [#1876](https://www.github.com/tauri-apps/tauri/pull/1876) ([#1889](https://www.github.com/tauri-apps/tauri/pull/1889)) on 2021-05-21\n\n## \\[0.1.1]\n\n- Fixes `system-tray` feature usage.\n  - [1ab8dd9](https://www.github.com/tauri-apps/tauri/commit/1ab8dd93e670d2a2d070c7a6ec48308a0ab32f1a) fix(core): `system-tray` cargo feature usage, fixes [#1798](https://www.github.com/tauri-apps/tauri/pull/1798) ([#1801](https://www.github.com/tauri-apps/tauri/pull/1801)) on 2021-05-12\n\n## \\[0.1.0]\n\n- **Breaking:** `Context` fields are now private, and is expected to be created through `Context::new(...)`.\n  All fields previously available through `Context` are now public methods.\n  - [5542359](https://www.github.com/tauri-apps/tauri/commit/55423590ddbf560684dab6a0214acf95aadfa8d2) refactor(core): Context fields now private, Icon used on all platforms ([#1774](https://www.github.com/tauri-apps/tauri/pull/1774)) on 2021-05-11\n- `tauri-runtime` crate initial release.\n  - [665ec1d](https://www.github.com/tauri-apps/tauri/commit/665ec1d4a199ad06e369221da187dc838da71cbf) refactor: move runtime to `tauri-runtime` crate ([#1751](https://www.github.com/tauri-apps/tauri/pull/1751)) on 2021-05-09\n  - [45a7a11](https://www.github.com/tauri-apps/tauri/commit/45a7a111e0cf9d9956d713cc9a99fa7a5313eec7) feat(core): add `tauri-wry` crate ([#1756](https://www.github.com/tauri-apps/tauri/pull/1756)) on 2021-05-09\n"
  },
  {
    "path": "crates/tauri-runtime/Cargo.toml",
    "content": "[package]\nname = \"tauri-runtime\"\nversion = \"2.10.1\"\ndescription = \"Runtime for Tauri applications\"\nexclude = [\"CHANGELOG.md\", \"/target\"]\nreadme = \"README.md\"\nauthors.workspace = true\nhomepage.workspace = true\nrepository.workspace = true\ncategories.workspace = true\nlicense.workspace = true\nedition.workspace = true\nrust-version.workspace = true\n\n[package.metadata.docs.rs]\nall-features = true\ndefault-target = \"x86_64-unknown-linux-gnu\"\ntargets = [\n  \"x86_64-pc-windows-msvc\",\n  \"x86_64-unknown-linux-gnu\",\n  \"x86_64-apple-darwin\",\n  \"x86_64-linux-android\",\n  \"x86_64-apple-ios\",\n]\n\n[dependencies]\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\nthiserror = \"2\"\ntauri-utils = { version = \"2.8.3\", path = \"../tauri-utils\" }\nhttp = \"1\"\nraw-window-handle = \"0.6\"\nurl = { version = \"2\" }\ndpi = { version = \"0.1\", features = [\"serde\"] }\n# WARNING: cookie::Cookie is re-exported so bumping this is a breaking change, documented to be done as a minor bump\ncookie = \"0.18\"\n\n[target.\"cfg(windows)\".dependencies.windows]\nversion = \"0.61\"\nfeatures = [\"Win32_Foundation\", \"Win32_System_WinRT\"]\n\n[target.\"cfg(windows)\".dependencies]\nwebview2-com = \"0.38\"\n\n[target.\"cfg(any(target_os = \\\"linux\\\", target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"openbsd\\\", target_os = \\\"netbsd\\\"))\".dependencies]\ngtk = { version = \"0.18\", features = [\"v3_24\"] }\nwebkit2gtk = { version = \"=2.0\", features = [\"v2_40\"] }\n\n[target.\"cfg(target_os = \\\"android\\\")\".dependencies]\njni = \"0.21\"\n\n[target.'cfg(all(target_vendor = \"apple\", not(target_os = \"macos\")))'.dependencies]\nobjc2 = \"0.6\"\nobjc2-ui-kit = { version = \"0.3.0\", default-features = false, features = [\n  \"UIView\",\n  \"UIResponder\",\n] }\n\n[target.\"cfg(target_os = \\\"macos\\\")\".dependencies]\nurl = \"2\"\nobjc2 = \"0.6\"\nobjc2-web-kit = { version = \"0.3\", default-features = false, features = [\n  \"objc2-app-kit\",\n  \"WKWebView\",\n  \"WKWebViewConfiguration\",\n] }\n\n[features]\ndevtools = []\nmacos-private-api = []\n"
  },
  {
    "path": "crates/tauri-runtime/LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/tauri-runtime/LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2017 - Present Tauri Apps Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "crates/tauri-runtime/README.md",
    "content": "# tauri-runtime\n\n <img align=\"right\" src=\"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\" height=\"128\" width=\"128\">\n\n[![status](https://img.shields.io/badge/Status-Beta-green.svg)](https://github.com/tauri-apps/tauri)\n[![Chat Server](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/SpmNs4S)\n\n[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml)\n[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app)\n\n[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)\n[![support](https://img.shields.io/badge/sponsor-Opencollective-blue.svg)](https://opencollective.com/tauri)\n\n| Component     | Version                                                                                                        |\n| ------------- | -------------------------------------------------------------------------------------------------------------- |\n| tauri-runtime | [![](https://img.shields.io/crates/v/tauri-runtime?style=flat-square)](https://crates.io/crates/tauri-runtime) |\n\n## About Tauri\n\nTauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.\n\nTauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.\n\n## This module\n\nThis is the glue layer between tauri itself and lower level webview libraries.\nNone of the exposed API of this crate is stable, and it may break semver\ncompatibility in the future. The major version only signifies the intended Tauri version.\n\nTo learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.\n\n## Semver\n\n**tauri** is following [Semantic Versioning 2.0](https://semver.org/).\n\n## Licenses\n\nCode: (c) 2021 - The Tauri Programme within The Commons Conservancy.\n\nMIT or MIT/Apache 2.0 where applicable.\n\nLogo: CC-BY-NC-ND\n\n- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)\n"
  },
  {
    "path": "crates/tauri-runtime/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// creates a cfg alias if `has_feature` is true.\n// `alias` must be a snake case string.\nfn alias(alias: &str, has_feature: bool) {\n  println!(\"cargo:rustc-check-cfg=cfg({alias})\");\n  if has_feature {\n    println!(\"cargo:rustc-cfg={alias}\");\n  }\n}\n\nfn main() {\n  let target_os = std::env::var(\"CARGO_CFG_TARGET_OS\").unwrap();\n  let mobile = target_os == \"ios\" || target_os == \"android\";\n  alias(\"desktop\", !mobile);\n  alias(\"mobile\", mobile);\n}\n"
  },
  {
    "path": "crates/tauri-runtime/src/dpi.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npub use dpi::*;\nuse serde::Serialize;\n\n/// A rectangular region.\n#[derive(Clone, Copy, Debug, Serialize)]\npub struct Rect {\n  /// Rect position.\n  pub position: dpi::Position,\n  /// Rect size.\n  pub size: dpi::Size,\n}\n\nimpl Default for Rect {\n  fn default() -> Self {\n    Self {\n      position: Position::Logical((0, 0).into()),\n      size: Size::Logical((0, 0).into()),\n    }\n  }\n}\n\n/// A rectangular region in physical pixels.\n#[derive(Clone, Copy, Debug, Serialize)]\npub struct PhysicalRect<P: dpi::Pixel, S: dpi::Pixel> {\n  /// Rect position.\n  pub position: dpi::PhysicalPosition<P>,\n  /// Rect size.\n  pub size: dpi::PhysicalSize<S>,\n}\n\nimpl<P: dpi::Pixel, S: dpi::Pixel> Default for PhysicalRect<P, S> {\n  fn default() -> Self {\n    Self {\n      position: (0, 0).into(),\n      size: (0, 0).into(),\n    }\n  }\n}\n\n/// A rectangular region in logical pixels.\n#[derive(Clone, Copy, Debug, Serialize)]\npub struct LogicalRect<P: dpi::Pixel, S: dpi::Pixel> {\n  /// Rect position.\n  pub position: dpi::LogicalPosition<P>,\n  /// Rect size.\n  pub size: dpi::LogicalSize<S>,\n}\n\nimpl<P: dpi::Pixel, S: dpi::Pixel> Default for LogicalRect<P, S> {\n  fn default() -> Self {\n    Self {\n      position: (0, 0).into(),\n      size: (0, 0).into(),\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-runtime/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Internal runtime between Tauri and the underlying webview runtime.\n//!\n//! None of the exposed API of this crate is stable, and it may break semver\n//! compatibility in the future. The major version only signifies the intended Tauri version.\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n#![cfg_attr(docsrs, feature(doc_cfg))]\n\nuse raw_window_handle::DisplayHandle;\nuse serde::Deserialize;\nuse std::{borrow::Cow, fmt::Debug, sync::mpsc::Sender};\nuse tauri_utils::config::Color;\nuse tauri_utils::Theme;\nuse url::Url;\nuse webview::{DetachedWebview, PendingWebview};\n\n/// UI scaling utilities.\npub mod dpi;\n/// Types useful for interacting with a user's monitors.\npub mod monitor;\npub mod webview;\npub mod window;\n\nuse dpi::{PhysicalPosition, PhysicalSize, Position, Rect, Size};\nuse monitor::Monitor;\nuse window::{\n  CursorIcon, DetachedWindow, PendingWindow, RawWindow, WebviewEvent, WindowEvent,\n  WindowSizeConstraints,\n};\nuse window::{WindowBuilder, WindowId};\n\nuse http::{\n  header::{InvalidHeaderName, InvalidHeaderValue},\n  method::InvalidMethod,\n  status::InvalidStatusCode,\n};\n\n/// Cookie extraction\npub use cookie::Cookie;\n\npub type WindowEventId = u32;\npub type WebviewEventId = u32;\n\n/// Progress bar status.\n#[derive(Debug, Clone, Copy, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum ProgressBarStatus {\n  /// Hide progress bar.\n  None,\n  /// Normal state.\n  Normal,\n  /// Indeterminate state. **Treated as Normal on Linux and macOS**\n  Indeterminate,\n  /// Paused state. **Treated as Normal on Linux**\n  Paused,\n  /// Error state. **Treated as Normal on Linux**\n  Error,\n}\n\n/// Progress Bar State\n#[derive(Debug, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct ProgressBarState {\n  /// The progress bar status.\n  pub status: Option<ProgressBarStatus>,\n  /// The progress bar progress. This can be a value ranging from `0` to `100`\n  pub progress: Option<u64>,\n  /// The `.desktop` filename with the Unity desktop window manager, for example `myapp.desktop` **Linux Only**\n  pub desktop_filename: Option<String>,\n}\n\n/// Type of user attention requested on a window.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]\n#[serde(tag = \"type\")]\npub enum UserAttentionType {\n  /// ## Platform-specific\n  /// - **macOS:** Bounces the dock icon until the application is in focus.\n  /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus.\n  Critical,\n  /// ## Platform-specific\n  /// - **macOS:** Bounces the dock icon once.\n  /// - **Windows:** Flashes the taskbar button until the application is in focus.\n  Informational,\n}\n\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Deserialize)]\n#[serde(tag = \"type\")]\npub enum DeviceEventFilter {\n  /// Always filter out device events.\n  Always,\n  /// Filter out device events while the window is not focused.\n  #[default]\n  Unfocused,\n  /// Report all device events regardless of window focus.\n  Never,\n}\n\n/// Defines the orientation that a window resize will be performed.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]\npub enum ResizeDirection {\n  East,\n  North,\n  NorthEast,\n  NorthWest,\n  South,\n  SouthEast,\n  SouthWest,\n  West,\n}\n\n#[derive(Debug, thiserror::Error)]\n#[non_exhaustive]\npub enum Error {\n  /// Failed to create webview.\n  #[error(\"failed to create webview: {0}\")]\n  CreateWebview(Box<dyn std::error::Error + Send + Sync>),\n  // TODO: Make it take an error like `CreateWebview` in v3\n  /// Failed to create window.\n  #[error(\"failed to create window\")]\n  CreateWindow,\n  /// The given window label is invalid.\n  #[error(\"Window labels must only include alphanumeric characters, `-`, `/`, `:` and `_`.\")]\n  InvalidWindowLabel,\n  /// Failed to send message to webview.\n  #[error(\"failed to send message to the webview\")]\n  FailedToSendMessage,\n  /// Failed to receive message from webview.\n  #[error(\"failed to receive message from webview\")]\n  FailedToReceiveMessage,\n  /// Failed to serialize/deserialize.\n  #[error(\"JSON error: {0}\")]\n  Json(#[from] serde_json::Error),\n  /// Failed to load window icon.\n  #[error(\"invalid icon: {0}\")]\n  InvalidIcon(Box<dyn std::error::Error + Send + Sync>),\n  /// Failed to get monitor on window operation.\n  #[error(\"failed to get monitor\")]\n  FailedToGetMonitor,\n  /// Failed to get cursor position.\n  #[error(\"failed to get cursor position\")]\n  FailedToGetCursorPosition,\n  #[error(\"Invalid header name: {0}\")]\n  InvalidHeaderName(#[from] InvalidHeaderName),\n  #[error(\"Invalid header value: {0}\")]\n  InvalidHeaderValue(#[from] InvalidHeaderValue),\n  #[error(\"Invalid status code: {0}\")]\n  InvalidStatusCode(#[from] InvalidStatusCode),\n  #[error(\"Invalid method: {0}\")]\n  InvalidMethod(#[from] InvalidMethod),\n  #[error(\"Infallible error, something went really wrong: {0}\")]\n  Infallible(#[from] std::convert::Infallible),\n  #[error(\"the event loop has been closed\")]\n  EventLoopClosed,\n  #[error(\"Invalid proxy url\")]\n  InvalidProxyUrl,\n  #[error(\"window not found\")]\n  WindowNotFound,\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  #[error(\"failed to remove data store\")]\n  FailedToRemoveDataStore,\n  #[error(\"Could not find the webview runtime, make sure it is installed\")]\n  WebviewRuntimeNotInstalled,\n}\n\n/// Result type.\npub type Result<T> = std::result::Result<T, Error>;\n\n/// Window icon.\n#[derive(Debug, Clone)]\npub struct Icon<'a> {\n  /// RGBA bytes of the icon.\n  pub rgba: Cow<'a, [u8]>,\n  /// Icon width.\n  pub width: u32,\n  /// Icon height.\n  pub height: u32,\n}\n\n/// A type that can be used as an user event.\npub trait UserEvent: Debug + Clone + Send + 'static {}\n\nimpl<T: Debug + Clone + Send + 'static> UserEvent for T {}\n\n/// Event triggered on the event loop run.\n#[derive(Debug)]\n#[non_exhaustive]\npub enum RunEvent<T: UserEvent> {\n  /// Event loop is exiting.\n  Exit,\n  /// Event loop is about to exit\n  ExitRequested {\n    /// The exit code.\n    code: Option<i32>,\n    tx: Sender<ExitRequestedEventAction>,\n  },\n  /// An event associated with a window.\n  WindowEvent {\n    /// The window label.\n    label: String,\n    /// The detailed event.\n    event: WindowEvent,\n  },\n  /// An event associated with a webview.\n  WebviewEvent {\n    /// The webview label.\n    label: String,\n    /// The detailed event.\n    event: WebviewEvent,\n  },\n  /// Application ready.\n  Ready,\n  /// Sent if the event loop is being resumed.\n  Resumed,\n  /// Emitted when all of the event loop's input events have been processed and redraw processing is about to begin.\n  ///\n  /// This event is useful as a place to put your code that should be run after all state-changing events have been handled and you want to do stuff (updating state, performing calculations, etc) that happens as the \"main body\" of your event loop.\n  MainEventsCleared,\n  /// Emitted when the user wants to open the specified resource with the app.\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  Opened { urls: Vec<url::Url> },\n  /// Emitted when the NSApplicationDelegate's applicationShouldHandleReopen gets called\n  #[cfg(target_os = \"macos\")]\n  Reopen {\n    /// Indicates whether the NSApplication object found any visible windows in your application.\n    has_visible_windows: bool,\n  },\n  /// A custom event defined by the user.\n  UserEvent(T),\n}\n\n/// Action to take when the event loop is about to exit\n#[derive(Debug)]\npub enum ExitRequestedEventAction {\n  /// Prevent the event loop from exiting\n  Prevent,\n}\n\n/// Application's activation policy. Corresponds to NSApplicationActivationPolicy.\n#[cfg(target_os = \"macos\")]\n#[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n#[non_exhaustive]\npub enum ActivationPolicy {\n  /// Corresponds to NSApplicationActivationPolicyRegular.\n  Regular,\n  /// Corresponds to NSApplicationActivationPolicyAccessory.\n  Accessory,\n  /// Corresponds to NSApplicationActivationPolicyProhibited.\n  Prohibited,\n}\n\n/// A [`Send`] handle to the runtime.\npub trait RuntimeHandle<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static {\n  type Runtime: Runtime<T, Handle = Self>;\n\n  /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.\n  fn create_proxy(&self) -> <Self::Runtime as Runtime<T>>::EventLoopProxy;\n\n  /// Sets the activation policy for the application.\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn set_activation_policy(&self, activation_policy: ActivationPolicy) -> Result<()>;\n\n  /// Sets the dock visibility for the application.\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn set_dock_visibility(&self, visible: bool) -> Result<()>;\n\n  /// Requests an exit of the event loop.\n  fn request_exit(&self, code: i32) -> Result<()>;\n\n  /// Create a new window.\n  fn create_window<F: Fn(RawWindow) + Send + 'static>(\n    &self,\n    pending: PendingWindow<T, Self::Runtime>,\n    after_window_creation: Option<F>,\n  ) -> Result<DetachedWindow<T, Self::Runtime>>;\n\n  /// Create a new webview.\n  fn create_webview(\n    &self,\n    window_id: WindowId,\n    pending: PendingWebview<T, Self::Runtime>,\n  ) -> Result<DetachedWebview<T, Self::Runtime>>;\n\n  /// Run a task on the main thread.\n  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()>;\n\n  /// Get a handle to the display controller of the windowing system.\n  fn display_handle(\n    &self,\n  ) -> std::result::Result<DisplayHandle<'_>, raw_window_handle::HandleError>;\n\n  /// Returns the primary monitor of the system.\n  ///\n  /// Returns None if it can't identify any monitor as a primary one.\n  fn primary_monitor(&self) -> Option<Monitor>;\n\n  /// Returns the monitor that contains the given point.\n  fn monitor_from_point(&self, x: f64, y: f64) -> Option<Monitor>;\n\n  /// Returns the list of all the monitors available on the system.\n  fn available_monitors(&self) -> Vec<Monitor>;\n\n  /// Get the cursor position relative to the top-left hand corner of the desktop.\n  fn cursor_position(&self) -> Result<PhysicalPosition<f64>>;\n\n  /// Sets the app theme.\n  fn set_theme(&self, theme: Option<Theme>);\n\n  /// Shows the application, but does not automatically focus it.\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn show(&self) -> Result<()>;\n\n  /// Hides the application.\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn hide(&self) -> Result<()>;\n\n  /// Change the device event filter mode.\n  ///\n  /// See [Runtime::set_device_event_filter] for details.\n  ///\n  /// ## Platform-specific\n  ///\n  /// See [Runtime::set_device_event_filter] for details.\n  fn set_device_event_filter(&self, filter: DeviceEventFilter);\n\n  /// Finds an Android class in the project scope.\n  #[cfg(target_os = \"android\")]\n  fn find_class<'a>(\n    &self,\n    env: &mut jni::JNIEnv<'a>,\n    activity: &jni::objects::JObject<'_>,\n    name: impl Into<String>,\n  ) -> std::result::Result<jni::objects::JClass<'a>, jni::errors::Error>;\n\n  /// Dispatch a closure to run on the Android context.\n  ///\n  /// The closure takes the JNI env, the Android activity instance and the possibly null webview.\n  #[cfg(target_os = \"android\")]\n  fn run_on_android_context<F>(&self, f: F)\n  where\n    F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject, &jni::objects::JObject) + Send + 'static;\n\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(target_os = \"macos\", target_os = \"ios\"))))]\n  fn fetch_data_store_identifiers<F: FnOnce(Vec<[u8; 16]>) + Send + 'static>(\n    &self,\n    cb: F,\n  ) -> Result<()>;\n\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  #[cfg_attr(docsrs, doc(cfg(any(target_os = \"macos\", target_os = \"ios\"))))]\n  fn remove_data_store<F: FnOnce(Result<()>) + Send + 'static>(\n    &self,\n    uuid: [u8; 16],\n    cb: F,\n  ) -> Result<()>;\n}\n\npub trait EventLoopProxy<T: UserEvent>: Debug + Clone + Send + Sync {\n  fn send_event(&self, event: T) -> Result<()>;\n}\n\n#[derive(Default)]\npub struct RuntimeInitArgs {\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  pub app_id: Option<String>,\n  #[cfg(windows)]\n  pub msg_hook: Option<Box<dyn FnMut(*const std::ffi::c_void) -> bool + 'static>>,\n}\n\n/// The webview runtime interface.\npub trait Runtime<T: UserEvent>: Debug + Sized + 'static {\n  /// The window message dispatcher.\n  type WindowDispatcher: WindowDispatch<T, Runtime = Self>;\n  /// The webview message dispatcher.\n  type WebviewDispatcher: WebviewDispatch<T, Runtime = Self>;\n  /// The runtime handle type.\n  type Handle: RuntimeHandle<T, Runtime = Self>;\n  /// The proxy type.\n  type EventLoopProxy: EventLoopProxy<T>;\n\n  /// Creates a new webview runtime. Must be used on the main thread.\n  fn new(args: RuntimeInitArgs) -> Result<Self>;\n\n  /// Creates a new webview runtime on any thread.\n  #[cfg(any(\n    windows,\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  #[cfg_attr(\n    docsrs,\n    doc(cfg(any(\n      windows,\n      target_os = \"linux\",\n      target_os = \"dragonfly\",\n      target_os = \"freebsd\",\n      target_os = \"netbsd\",\n      target_os = \"openbsd\"\n    )))\n  )]\n  fn new_any_thread(args: RuntimeInitArgs) -> Result<Self>;\n\n  /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.\n  fn create_proxy(&self) -> Self::EventLoopProxy;\n\n  /// Gets a runtime handle.\n  fn handle(&self) -> Self::Handle;\n\n  /// Create a new window.\n  fn create_window<F: Fn(RawWindow) + Send + 'static>(\n    &self,\n    pending: PendingWindow<T, Self>,\n    after_window_creation: Option<F>,\n  ) -> Result<DetachedWindow<T, Self>>;\n\n  /// Create a new webview.\n  fn create_webview(\n    &self,\n    window_id: WindowId,\n    pending: PendingWebview<T, Self>,\n  ) -> Result<DetachedWebview<T, Self>>;\n\n  /// Returns the primary monitor of the system.\n  ///\n  /// Returns None if it can't identify any monitor as a primary one.\n  fn primary_monitor(&self) -> Option<Monitor>;\n\n  /// Returns the monitor that contains the given point.\n  fn monitor_from_point(&self, x: f64, y: f64) -> Option<Monitor>;\n\n  /// Returns the list of all the monitors available on the system.\n  fn available_monitors(&self) -> Vec<Monitor>;\n\n  /// Get the cursor position relative to the top-left hand corner of the desktop.\n  fn cursor_position(&self) -> Result<PhysicalPosition<f64>>;\n\n  /// Sets the app theme.\n  fn set_theme(&self, theme: Option<Theme>);\n\n  /// Sets the activation policy for the application.\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn set_activation_policy(&mut self, activation_policy: ActivationPolicy);\n\n  /// Sets the dock visibility for the application.\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn set_dock_visibility(&mut self, visible: bool);\n\n  /// Shows the application, but does not automatically focus it.\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn show(&self);\n\n  /// Hides the application.\n  #[cfg(target_os = \"macos\")]\n  #[cfg_attr(docsrs, doc(cfg(target_os = \"macos\")))]\n  fn hide(&self);\n\n  /// Change the device event filter mode.\n  ///\n  /// Since the DeviceEvent capture can lead to high CPU usage for unfocused windows, [`tao`]\n  /// will ignore them by default for unfocused windows on Windows. This method allows changing\n  /// the filter to explicitly capture them again.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - ** Linux / macOS / iOS / Android**: Unsupported.\n  ///\n  /// [`tao`]: https://crates.io/crates/tao\n  fn set_device_event_filter(&mut self, filter: DeviceEventFilter);\n\n  /// Runs an iteration of the runtime event loop and returns control flow to the caller.\n  #[cfg(desktop)]\n  fn run_iteration<F: FnMut(RunEvent<T>) + 'static>(&mut self, callback: F);\n\n  /// Equivalent to [`Runtime::run`] but returns the exit code instead of exiting the process.\n  fn run_return<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) -> i32;\n\n  /// Run the webview runtime.\n  fn run<F: FnMut(RunEvent<T>) + 'static>(self, callback: F);\n}\n\n/// Webview dispatcher. A thread-safe handle to the webview APIs.\npub trait WebviewDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static {\n  /// The runtime this [`WebviewDispatch`] runs under.\n  type Runtime: Runtime<T>;\n\n  /// Run a task on the main thread.\n  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()>;\n\n  /// Registers a webview event handler.\n  fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) -> WebviewEventId;\n\n  /// Runs a closure with the platform webview object as argument.\n  fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()>;\n\n  /// Open the web inspector which is usually called devtools.\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  fn open_devtools(&self);\n\n  /// Close the web inspector which is usually called devtools.\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  fn close_devtools(&self);\n\n  /// Gets the devtools window's current open state.\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  fn is_devtools_open(&self) -> Result<bool>;\n\n  // GETTERS\n\n  /// Returns the webview's current URL.\n  fn url(&self) -> Result<String>;\n\n  /// Returns the webview's bounds.\n  fn bounds(&self) -> Result<Rect>;\n\n  /// Returns the position of the top-left hand corner of the webviews's client area relative to the top-left hand corner of the window.\n  fn position(&self) -> Result<PhysicalPosition<i32>>;\n\n  /// Returns the physical size of the webviews's client area.\n  fn size(&self) -> Result<PhysicalSize<u32>>;\n\n  // SETTER\n\n  /// Navigate to the given URL.\n  fn navigate(&self, url: Url) -> Result<()>;\n\n  /// Reloads the current page.\n  fn reload(&self) -> Result<()>;\n\n  /// Opens the dialog to prints the contents of the webview.\n  fn print(&self) -> Result<()>;\n\n  /// Closes the webview.\n  fn close(&self) -> Result<()>;\n\n  /// Sets the webview's bounds.\n  fn set_bounds(&self, bounds: Rect) -> Result<()>;\n\n  /// Resizes the webview.\n  fn set_size(&self, size: Size) -> Result<()>;\n\n  /// Updates the webview position.\n  fn set_position(&self, position: Position) -> Result<()>;\n\n  /// Bring the window to front and focus the webview.\n  fn set_focus(&self) -> Result<()>;\n\n  /// Hide the webview\n  fn hide(&self) -> Result<()>;\n\n  /// Show the webview\n  fn show(&self) -> Result<()>;\n\n  /// Executes javascript on the window this [`WindowDispatch`] represents.\n  fn eval_script<S: Into<String>>(&self, script: S) -> Result<()>;\n\n  /// Moves the webview to the given window.\n  fn reparent(&self, window_id: WindowId) -> Result<()>;\n\n  /// Get cookies for a particular url.\n  ///\n  /// # Stability\n  ///\n  /// See [WebviewDispatch::cookies].\n  fn cookies_for_url(&self, url: Url) -> Result<Vec<Cookie<'static>>>;\n\n  /// Return all cookies in the cookie store.\n  ///\n  /// # Stability\n  ///\n  /// The return value of this function leverages [`cookie::Cookie`] which re-exports the cookie crate.\n  /// This dependency might receive updates in minor Tauri releases.\n  fn cookies(&self) -> Result<Vec<Cookie<'static>>>;\n\n  /// Set a cookie for the webview.\n  ///\n  /// # Stability\n  ///\n  /// See [WebviewDispatch::cookies].\n  fn set_cookie(&self, cookie: cookie::Cookie<'_>) -> Result<()>;\n\n  /// Delete a cookie for the webview.\n  ///\n  /// # Stability\n  ///\n  /// See [WebviewDispatch::cookies].\n  fn delete_cookie(&self, cookie: cookie::Cookie<'_>) -> Result<()>;\n\n  /// Sets whether the webview should automatically grow and shrink its size and position when the parent window resizes.\n  fn set_auto_resize(&self, auto_resize: bool) -> Result<()>;\n\n  /// Set the webview zoom level\n  fn set_zoom(&self, scale_factor: f64) -> Result<()>;\n\n  /// Set the webview background.\n  fn set_background_color(&self, color: Option<Color>) -> Result<()>;\n\n  /// Clear all browsing data for this webview.\n  fn clear_all_browsing_data(&self) -> Result<()>;\n}\n\n/// Window dispatcher. A thread-safe handle to the window APIs.\npub trait WindowDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static {\n  /// The runtime this [`WindowDispatch`] runs under.\n  type Runtime: Runtime<T>;\n\n  /// The window builder type.\n  type WindowBuilder: WindowBuilder;\n\n  /// Run a task on the main thread.\n  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()>;\n\n  /// Registers a window event handler.\n  fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> WindowEventId;\n\n  // GETTERS\n\n  /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.\n  fn scale_factor(&self) -> Result<f64>;\n\n  /// Returns the position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop.\n  fn inner_position(&self) -> Result<PhysicalPosition<i32>>;\n\n  /// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop.\n  fn outer_position(&self) -> Result<PhysicalPosition<i32>>;\n\n  /// Returns the physical size of the window's client area.\n  ///\n  /// The client area is the content of the window, excluding the title bar and borders.\n  fn inner_size(&self) -> Result<PhysicalSize<u32>>;\n\n  /// Returns the physical size of the entire window.\n  ///\n  /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead.\n  fn outer_size(&self) -> Result<PhysicalSize<u32>>;\n\n  /// Gets the window's current fullscreen state.\n  fn is_fullscreen(&self) -> Result<bool>;\n\n  /// Gets the window's current minimized state.\n  fn is_minimized(&self) -> Result<bool>;\n\n  /// Gets the window's current maximized state.\n  fn is_maximized(&self) -> Result<bool>;\n\n  /// Gets the window's current focus state.\n  fn is_focused(&self) -> Result<bool>;\n\n  /// Gets the window's current decoration state.\n  fn is_decorated(&self) -> Result<bool>;\n\n  /// Gets the window's current resizable state.\n  fn is_resizable(&self) -> Result<bool>;\n\n  /// Gets the window's native maximize button state.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  fn is_maximizable(&self) -> Result<bool>;\n\n  /// Gets the window's native minimize button state.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  fn is_minimizable(&self) -> Result<bool>;\n\n  /// Gets the window's native close button state.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  fn is_closable(&self) -> Result<bool>;\n\n  /// Gets the window's current visibility state.\n  fn is_visible(&self) -> Result<bool>;\n\n  /// Whether the window is enabled or disable.\n  fn is_enabled(&self) -> Result<bool>;\n\n  /// Gets the window alwaysOnTop flag state.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  fn is_always_on_top(&self) -> Result<bool>;\n\n  /// Gets the window's current title.\n  fn title(&self) -> Result<String>;\n\n  /// Returns the monitor on which the window currently resides.\n  ///\n  /// Returns None if current monitor can't be detected.\n  fn current_monitor(&self) -> Result<Option<Monitor>>;\n\n  /// Returns the primary monitor of the system.\n  ///\n  /// Returns None if it can't identify any monitor as a primary one.\n  fn primary_monitor(&self) -> Result<Option<Monitor>>;\n\n  /// Returns the monitor that contains the given point.\n  fn monitor_from_point(&self, x: f64, y: f64) -> Result<Option<Monitor>>;\n\n  /// Returns the list of all the monitors available on the system.\n  fn available_monitors(&self) -> Result<Vec<Monitor>>;\n\n  /// Returns the `ApplicationWindow` from gtk crate that is used by this window.\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn gtk_window(&self) -> Result<gtk::ApplicationWindow>;\n\n  /// Returns the vertical [`gtk::Box`] that is added by default as the sole child of this window.\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn default_vbox(&self) -> Result<gtk::Box>;\n\n  /// Raw window handle.\n  fn window_handle(\n    &self,\n  ) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError>;\n\n  /// Returns the current window theme.\n  fn theme(&self) -> Result<Theme>;\n\n  // SETTERS\n\n  /// Centers the window.\n  fn center(&self) -> Result<()>;\n\n  /// Requests user attention to the window.\n  ///\n  /// Providing `None` will unset the request for user attention.\n  fn request_user_attention(&self, request_type: Option<UserAttentionType>) -> Result<()>;\n\n  /// Create a new window.\n  fn create_window<F: Fn(RawWindow) + Send + 'static>(\n    &mut self,\n    pending: PendingWindow<T, Self::Runtime>,\n    after_window_creation: Option<F>,\n  ) -> Result<DetachedWindow<T, Self::Runtime>>;\n\n  /// Create a new webview.\n  fn create_webview(\n    &mut self,\n    pending: PendingWebview<T, Self::Runtime>,\n  ) -> Result<DetachedWebview<T, Self::Runtime>>;\n\n  /// Updates the window resizable flag.\n  fn set_resizable(&self, resizable: bool) -> Result<()>;\n\n  /// Enable or disable the window.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Android / iOS**: Unsupported.\n  fn set_enabled(&self, enabled: bool) -> Result<()>;\n\n  /// Updates the window's native maximize button state.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Disables the \"zoom\" button in the window titlebar, which is also used to enter fullscreen mode.\n  /// - **Linux / iOS / Android:** Unsupported.\n  fn set_maximizable(&self, maximizable: bool) -> Result<()>;\n\n  /// Updates the window's native minimize button state.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  fn set_minimizable(&self, minimizable: bool) -> Result<()>;\n\n  /// Updates the window's native close button state.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** \"GTK+ will do its best to convince the window manager not to show a close button.\n  ///   Depending on the system, this function may not have any effect when called on a window that is already visible\"\n  /// - **iOS / Android:** Unsupported.\n  fn set_closable(&self, closable: bool) -> Result<()>;\n\n  /// Updates the window title.\n  fn set_title<S: Into<String>>(&self, title: S) -> Result<()>;\n\n  /// Maximizes the window.\n  fn maximize(&self) -> Result<()>;\n\n  /// Unmaximizes the window.\n  fn unmaximize(&self) -> Result<()>;\n\n  /// Minimizes the window.\n  fn minimize(&self) -> Result<()>;\n\n  /// Unminimizes the window.\n  fn unminimize(&self) -> Result<()>;\n\n  /// Shows the window.\n  fn show(&self) -> Result<()>;\n\n  /// Hides the window.\n  fn hide(&self) -> Result<()>;\n\n  /// Closes the window.\n  fn close(&self) -> Result<()>;\n\n  /// Destroys the window.\n  fn destroy(&self) -> Result<()>;\n\n  /// Updates the decorations flag.\n  fn set_decorations(&self, decorations: bool) -> Result<()>;\n\n  /// Updates the shadow flag.\n  fn set_shadow(&self, enable: bool) -> Result<()>;\n\n  /// Updates the window alwaysOnBottom flag.\n  fn set_always_on_bottom(&self, always_on_bottom: bool) -> Result<()>;\n\n  /// Updates the window alwaysOnTop flag.\n  fn set_always_on_top(&self, always_on_top: bool) -> Result<()>;\n\n  /// Updates the window visibleOnAllWorkspaces flag.\n  fn set_visible_on_all_workspaces(&self, visible_on_all_workspaces: bool) -> Result<()>;\n\n  /// Set the window background.\n  fn set_background_color(&self, color: Option<Color>) -> Result<()>;\n\n  /// Prevents the window contents from being captured by other apps.\n  fn set_content_protected(&self, protected: bool) -> Result<()>;\n\n  /// Resizes the window.\n  fn set_size(&self, size: Size) -> Result<()>;\n\n  /// Updates the window min inner size.\n  fn set_min_size(&self, size: Option<Size>) -> Result<()>;\n\n  /// Updates the window max inner size.\n  fn set_max_size(&self, size: Option<Size>) -> Result<()>;\n\n  /// Sets this window's minimum inner width.\n  fn set_size_constraints(&self, constraints: WindowSizeConstraints) -> Result<()>;\n\n  /// Updates the window position.\n  fn set_position(&self, position: Position) -> Result<()>;\n\n  /// Updates the window fullscreen state.\n  fn set_fullscreen(&self, fullscreen: bool) -> Result<()>;\n\n  #[cfg(target_os = \"macos\")]\n  fn set_simple_fullscreen(&self, enable: bool) -> Result<()>;\n\n  /// Bring the window to front and focus.\n  fn set_focus(&self) -> Result<()>;\n\n  /// Sets whether the window can be focused.\n  fn set_focusable(&self, focusable: bool) -> Result<()>;\n\n  /// Updates the window icon.\n  fn set_icon(&self, icon: Icon) -> Result<()>;\n\n  /// Whether to hide the window icon from the taskbar or not.\n  fn set_skip_taskbar(&self, skip: bool) -> Result<()>;\n\n  /// Grabs the cursor, preventing it from leaving the window.\n  ///\n  /// There's no guarantee that the cursor will be hidden. You should\n  /// hide it by yourself if you want so.\n  fn set_cursor_grab(&self, grab: bool) -> Result<()>;\n\n  /// Modifies the cursor's visibility.\n  ///\n  /// If `false`, this will hide the cursor. If `true`, this will show the cursor.\n  fn set_cursor_visible(&self, visible: bool) -> Result<()>;\n\n  // Modifies the cursor icon of the window.\n  fn set_cursor_icon(&self, icon: CursorIcon) -> Result<()>;\n\n  /// Changes the position of the cursor in window coordinates.\n  fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> Result<()>;\n\n  /// Ignores the window cursor events.\n  fn set_ignore_cursor_events(&self, ignore: bool) -> Result<()>;\n\n  /// Starts dragging the window.\n  fn start_dragging(&self) -> Result<()>;\n\n  /// Starts resize-dragging the window.\n  fn start_resize_dragging(&self, direction: ResizeDirection) -> Result<()>;\n\n  /// Sets the badge count on the taskbar\n  /// The badge count appears as a whole for the application\n  /// Using `0` or using `None` will remove the badge\n  ///\n  /// ## Platform-specific\n  /// - **Windows:** Unsupported, use [`WindowDispatch::set_overlay_icon`] instead.\n  /// - **Android:** Unsupported.\n  /// - **iOS:** iOS expects i32, if the value is larger than i32::MAX, it will be clamped to i32::MAX.\n  fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>) -> Result<()>;\n\n  /// Sets the badge count on the taskbar **macOS only**. Using `None` will remove the badge\n  fn set_badge_label(&self, label: Option<String>) -> Result<()>;\n\n  /// Sets the overlay icon on the taskbar **Windows only**. Using `None` will remove the icon\n  ///\n  /// The overlay icon can be unique for each window.\n  fn set_overlay_icon(&self, icon: Option<Icon>) -> Result<()>;\n\n  /// Sets the taskbar progress state.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / macOS**: Progress bar is app-wide and not specific to this window. Only supported desktop environments with `libunity` (e.g. GNOME).\n  /// - **iOS / Android:** Unsupported.\n  fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()>;\n\n  /// Sets the title bar style. Available on macOS only.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / Windows / iOS / Android:** Unsupported.\n  fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> Result<()>;\n\n  /// Change the position of the window controls. Available on macOS only.\n  ///\n  /// Requires titleBarStyle: Overlay and decorations: true.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / Windows / iOS / Android:** Unsupported.\n  fn set_traffic_light_position(&self, position: Position) -> Result<()>;\n\n  /// Sets the theme for this window.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / macOS**: Theme is app-wide and not specific to this window.\n  /// - **iOS / Android:** Unsupported.\n  fn set_theme(&self, theme: Option<Theme>) -> Result<()>;\n}\n"
  },
  {
    "path": "crates/tauri-runtime/src/monitor.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::dpi::{PhysicalPosition, PhysicalRect, PhysicalSize};\n\n/// Monitor descriptor.\n#[derive(Debug, Clone)]\npub struct Monitor {\n  /// A human-readable name of the monitor.\n  /// `None` if the monitor doesn't exist anymore.\n  pub name: Option<String>,\n  /// The monitor's resolution.\n  pub size: PhysicalSize<u32>,\n  /// The top-left corner position of the monitor relative to the larger full screen area.\n  pub position: PhysicalPosition<i32>,\n  /// The monitor's work_area.\n  pub work_area: PhysicalRect<i32, u32>,\n  /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.\n  pub scale_factor: f64,\n}\n"
  },
  {
    "path": "crates/tauri-runtime/src/webview.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! A layer between raw [`Runtime`] webviews and Tauri.\n//!\n#[cfg(not(any(target_os = \"android\", target_os = \"ios\")))]\nuse crate::window::WindowId;\nuse crate::{window::is_label_valid, Rect, Runtime, UserEvent};\n\nuse http::Request;\nuse tauri_utils::config::{\n  BackgroundThrottlingPolicy, Color, ScrollBarStyle as ConfigScrollBarStyle, WebviewUrl,\n  WindowConfig, WindowEffectsConfig,\n};\nuse url::Url;\n\nuse std::{\n  borrow::Cow,\n  collections::HashMap,\n  hash::{Hash, Hasher},\n  path::PathBuf,\n  sync::Arc,\n};\n\ntype UriSchemeProtocolHandler = dyn Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)\n  + Send\n  + Sync\n  + 'static;\n\ntype WebResourceRequestHandler =\n  dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;\n\ntype NavigationHandler = dyn Fn(&Url) -> bool + Send;\n\ntype NewWindowHandler = dyn Fn(Url, NewWindowFeatures) -> NewWindowResponse + Send + Sync;\n\ntype OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;\n\ntype DocumentTitleChangedHandler = dyn Fn(String) + Send + 'static;\n\ntype DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;\n\n#[cfg(target_os = \"ios\")]\ntype InputAccessoryViewBuilderFn = dyn Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>\n  + Send\n  + Sync\n  + 'static;\n\n/// Download event.\npub enum DownloadEvent<'a> {\n  /// Download requested.\n  Requested {\n    /// The url being downloaded.\n    url: Url,\n    /// Represents where the file will be downloaded to.\n    /// Can be used to set the download location by assigning a new path to it.\n    /// The assigned path _must_ be absolute.\n    destination: &'a mut PathBuf,\n  },\n  /// Download finished.\n  Finished {\n    /// The URL of the original download request.\n    url: Url,\n    /// Potentially representing the filesystem path the file was downloaded to.\n    path: Option<PathBuf>,\n    /// Indicates if the download succeeded or not.\n    success: bool,\n  },\n}\n\n#[cfg(target_os = \"android\")]\npub struct CreationContext<'a, 'b> {\n  pub env: &'a mut jni::JNIEnv<'b>,\n  pub activity: &'a jni::objects::JObject<'b>,\n  pub webview: &'a jni::objects::JObject<'b>,\n}\n\n/// Kind of event for the page load handler.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum PageLoadEvent {\n  /// Page started to load.\n  Started,\n  /// Page finished loading.\n  Finished,\n}\n\n/// Information about the webview that initiated a new window request.\n#[derive(Debug)]\npub struct NewWindowOpener {\n  /// The instance of the webview that initiated the new window request.\n  ///\n  /// This must be set as the related view of the new webview. See [`WebviewAttributes::related_view`].\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\",\n  ))]\n  pub webview: webkit2gtk::WebView,\n  /// The instance of the webview that initiated the new window request.\n  ///\n  /// The target webview environment **MUST** match the environment of the opener webview. See [`WebviewAttributes::environment`].\n  #[cfg(windows)]\n  pub webview: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2,\n  #[cfg(windows)]\n  pub environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,\n  /// The instance of the webview that initiated the new window request.\n  #[cfg(target_os = \"macos\")]\n  pub webview: objc2::rc::Retained<objc2_web_kit::WKWebView>,\n  /// Configuration of the target webview.\n  ///\n  /// This **MUST** be used when creating the target webview. See [`WebviewAttributes::webview_configuration`].\n  #[cfg(target_os = \"macos\")]\n  pub target_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,\n}\n\n/// Window features of a window requested to open.\n#[derive(Debug)]\npub struct NewWindowFeatures {\n  pub(crate) size: Option<crate::dpi::LogicalSize<f64>>,\n  pub(crate) position: Option<crate::dpi::LogicalPosition<f64>>,\n  pub(crate) opener: NewWindowOpener,\n}\n\nimpl NewWindowFeatures {\n  pub fn new(\n    size: Option<crate::dpi::LogicalSize<f64>>,\n    position: Option<crate::dpi::LogicalPosition<f64>>,\n    opener: NewWindowOpener,\n  ) -> Self {\n    Self {\n      size,\n      position,\n      opener,\n    }\n  }\n\n  /// Specifies the size of the content area\n  /// as defined by the user's operating system where the new window will be generated.\n  pub fn size(&self) -> Option<crate::dpi::LogicalSize<f64>> {\n    self.size\n  }\n\n  /// Specifies the position of the window relative to the work area\n  /// as defined by the user's operating system where the new window will be generated.\n  pub fn position(&self) -> Option<crate::dpi::LogicalPosition<f64>> {\n    self.position\n  }\n\n  /// Returns information about the webview that initiated a new window request.\n  pub fn opener(&self) -> &NewWindowOpener {\n    &self.opener\n  }\n}\n\n/// Response for the new window request handler.\npub enum NewWindowResponse {\n  /// Allow the window to be opened with the default implementation.\n  Allow,\n  /// Allow the window to be opened, with the given window.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// **Linux**: The webview must be related to the caller webview. See [`WebviewAttributes::related_view`].\n  /// **Windows**: The webview must use the same environment as the caller webview. See [`WebviewAttributes::environment`].\n  #[cfg(not(any(target_os = \"android\", target_os = \"ios\")))]\n  Create { window_id: WindowId },\n  /// Deny the window from being opened.\n  Deny,\n}\n\n/// The scrollbar style to use in the webview.\n///\n/// ## Platform-specific\n///\n/// - **Windows**: This option must be given the same value for all webviews that target the same data directory.\n#[non_exhaustive]\n#[derive(Debug, Clone, Copy, Default)]\npub enum ScrollBarStyle {\n  #[default]\n  /// The default scrollbar style for the webview.\n  Default,\n\n  #[cfg(windows)]\n  /// Fluent UI style overlay scrollbars. **Windows Only**\n  ///\n  /// Requires WebView2 Runtime version 125.0.2535.41 or higher, does nothing on older versions,\n  /// see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/?tabs=dotnetcsharp#10253541\n  FluentOverlay,\n}\n\n/// A webview that has yet to be built.\npub struct PendingWebview<T: UserEvent, R: Runtime<T>> {\n  /// The label that the webview will be named.\n  pub label: String,\n\n  /// The [`WebviewAttributes`] that the webview will be created with.\n  pub webview_attributes: WebviewAttributes,\n\n  /// Custom protocols to register on the webview\n  pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocolHandler>>,\n\n  /// How to handle IPC calls on the webview.\n  pub ipc_handler: Option<WebviewIpcHandler<T, R>>,\n\n  /// A handler to decide if incoming url is allowed to navigate.\n  pub navigation_handler: Option<Box<NavigationHandler>>,\n\n  pub new_window_handler: Option<Box<NewWindowHandler>>,\n\n  pub document_title_changed_handler: Option<Box<DocumentTitleChangedHandler>>,\n\n  /// The resolved URL to load on the webview.\n  pub url: String,\n\n  #[cfg(target_os = \"android\")]\n  #[allow(clippy::type_complexity)]\n  pub on_webview_created:\n    Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>,\n\n  pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,\n\n  pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,\n\n  pub download_handler: Option<Arc<DownloadHandler>>,\n}\n\nimpl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {\n  /// Create a new [`PendingWebview`] with a label from the given [`WebviewAttributes`].\n  pub fn new(\n    webview_attributes: WebviewAttributes,\n    label: impl Into<String>,\n  ) -> crate::Result<Self> {\n    let label = label.into();\n    if !is_label_valid(&label) {\n      Err(crate::Error::InvalidWindowLabel)\n    } else {\n      Ok(Self {\n        webview_attributes,\n        uri_scheme_protocols: Default::default(),\n        label,\n        ipc_handler: None,\n        navigation_handler: None,\n        new_window_handler: None,\n        document_title_changed_handler: None,\n        url: \"tauri://localhost\".to_string(),\n        #[cfg(target_os = \"android\")]\n        on_webview_created: None,\n        web_resource_request_handler: None,\n        on_page_load_handler: None,\n        download_handler: None,\n      })\n    }\n  }\n\n  pub fn register_uri_scheme_protocol<\n    N: Into<String>,\n    H: Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)\n      + Send\n      + Sync\n      + 'static,\n  >(\n    &mut self,\n    uri_scheme: N,\n    protocol_handler: H,\n  ) {\n    let uri_scheme = uri_scheme.into();\n    self\n      .uri_scheme_protocols\n      .insert(uri_scheme, Box::new(protocol_handler));\n  }\n\n  #[cfg(target_os = \"android\")]\n  pub fn on_webview_created<\n    F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static,\n  >(\n    mut self,\n    f: F,\n  ) -> Self {\n    self.on_webview_created.replace(Box::new(f));\n    self\n  }\n}\n\n/// A webview that is not yet managed by Tauri.\n#[derive(Debug)]\npub struct DetachedWebview<T: UserEvent, R: Runtime<T>> {\n  /// Name of the window\n  pub label: String,\n\n  /// The [`crate::WebviewDispatch`] associated with the window.\n  pub dispatcher: R::WebviewDispatcher,\n}\n\nimpl<T: UserEvent, R: Runtime<T>> Clone for DetachedWebview<T, R> {\n  fn clone(&self) -> Self {\n    Self {\n      label: self.label.clone(),\n      dispatcher: self.dispatcher.clone(),\n    }\n  }\n}\n\nimpl<T: UserEvent, R: Runtime<T>> Hash for DetachedWebview<T, R> {\n  /// Only use the [`DetachedWebview`]'s label to represent its hash.\n  fn hash<H: Hasher>(&self, state: &mut H) {\n    self.label.hash(state)\n  }\n}\n\nimpl<T: UserEvent, R: Runtime<T>> Eq for DetachedWebview<T, R> {}\nimpl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {\n  /// Only use the [`DetachedWebview`]'s label to compare equality.\n  fn eq(&self, other: &Self) -> bool {\n    self.label.eq(&other.label)\n  }\n}\n\n/// The attributes used to create an webview.\n#[derive(Debug)]\npub struct WebviewAttributes {\n  pub url: WebviewUrl,\n  pub user_agent: Option<String>,\n  /// A list of initialization javascript scripts to run when loading new pages.\n  /// When webview load a new page, this initialization code will be executed.\n  /// It is guaranteed that code is executed before `window.onload`.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:** scripts are always added to subframes.\n  /// - **Android:** When [addDocumentStartJavaScript] is not supported,\n  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).\n  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.\n  ///\n  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)\n  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)\n  pub initialization_scripts: Vec<InitializationScript>,\n  pub data_directory: Option<PathBuf>,\n  pub drag_drop_handler_enabled: bool,\n  pub clipboard: bool,\n  pub accept_first_mouse: bool,\n  pub additional_browser_args: Option<String>,\n  pub window_effects: Option<WindowEffectsConfig>,\n  pub incognito: bool,\n  pub transparent: bool,\n  pub focus: bool,\n  pub bounds: Option<Rect>,\n  pub auto_resize: bool,\n  pub proxy_url: Option<Url>,\n  pub zoom_hotkeys_enabled: bool,\n  pub browser_extensions_enabled: bool,\n  pub extensions_path: Option<PathBuf>,\n  pub data_store_identifier: Option<[u8; 16]>,\n  pub use_https_scheme: bool,\n  pub devtools: Option<bool>,\n  pub background_color: Option<Color>,\n  pub traffic_light_position: Option<dpi::Position>,\n  pub background_throttling: Option<BackgroundThrottlingPolicy>,\n  pub javascript_disabled: bool,\n  /// on macOS and iOS there is a link preview on long pressing links, this is enabled by default.\n  /// see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview\n  pub allow_link_preview: bool,\n  pub scroll_bar_style: ScrollBarStyle,\n  /// Allows overriding the keyboard accessory view on iOS.\n  /// Returning `None` effectively removes the view.\n  ///\n  /// The closure parameter is the webview instance.\n  ///\n  /// The accessory view is the view that appears above the keyboard when a text input element is focused.\n  /// It usually displays a view with \"Done\", \"Next\" buttons.\n  ///\n  /// # Stability\n  ///\n  /// This relies on [`objc2_ui_kit`] which does not provide a stable API yet, so it can receive breaking changes in minor releases.\n  #[cfg(target_os = \"ios\")]\n  pub input_accessory_view_builder: Option<InputAccessoryViewBuilder>,\n\n  /// Set the environment for the webview.\n  /// Useful if you need to share the same environment, for instance when using the [`PendingWebview::new_window_handler`].\n  #[cfg(windows)]\n  pub environment: Option<webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment>,\n\n  /// Creates a new webview sharing the same web process with the provided webview.\n  /// Useful if you need to link a webview to another, for instance when using the [`PendingWebview::new_window_handler`].\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\",\n  ))]\n  pub related_view: Option<webkit2gtk::WebView>,\n\n  #[cfg(target_os = \"macos\")]\n  pub webview_configuration: Option<objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>>,\n}\n\nunsafe impl Send for WebviewAttributes {}\nunsafe impl Sync for WebviewAttributes {}\n\n#[cfg(target_os = \"ios\")]\n#[non_exhaustive]\npub struct InputAccessoryViewBuilder(pub Box<InputAccessoryViewBuilderFn>);\n\n#[cfg(target_os = \"ios\")]\nimpl std::fmt::Debug for InputAccessoryViewBuilder {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {\n    f.debug_struct(\"InputAccessoryViewBuilder\").finish()\n  }\n}\n\n#[cfg(target_os = \"ios\")]\nimpl InputAccessoryViewBuilder {\n  pub fn new(builder: Box<InputAccessoryViewBuilderFn>) -> Self {\n    Self(builder)\n  }\n}\n\nimpl From<&WindowConfig> for WebviewAttributes {\n  fn from(config: &WindowConfig) -> Self {\n    let mut builder = Self::new(config.url.clone())\n      .incognito(config.incognito)\n      .focused(config.focus)\n      .zoom_hotkeys_enabled(config.zoom_hotkeys_enabled)\n      .use_https_scheme(config.use_https_scheme)\n      .browser_extensions_enabled(config.browser_extensions_enabled)\n      .background_throttling(config.background_throttling.clone())\n      .devtools(config.devtools)\n      .scroll_bar_style(match config.scroll_bar_style {\n        ConfigScrollBarStyle::Default => ScrollBarStyle::Default,\n        #[cfg(windows)]\n        ConfigScrollBarStyle::FluentOverlay => ScrollBarStyle::FluentOverlay,\n        _ => ScrollBarStyle::Default,\n      });\n\n    #[cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\"))]\n    {\n      builder = builder.transparent(config.transparent);\n    }\n    #[cfg(target_os = \"macos\")]\n    {\n      if let Some(position) = &config.traffic_light_position {\n        builder =\n          builder.traffic_light_position(dpi::LogicalPosition::new(position.x, position.y).into());\n      }\n    }\n    builder = builder.accept_first_mouse(config.accept_first_mouse);\n    if !config.drag_drop_enabled {\n      builder = builder.disable_drag_drop_handler();\n    }\n    if let Some(user_agent) = &config.user_agent {\n      builder = builder.user_agent(user_agent);\n    }\n    if let Some(additional_browser_args) = &config.additional_browser_args {\n      builder = builder.additional_browser_args(additional_browser_args);\n    }\n    if let Some(effects) = &config.window_effects {\n      builder = builder.window_effects(effects.clone());\n    }\n    if let Some(url) = &config.proxy_url {\n      builder = builder.proxy_url(url.to_owned());\n    }\n    if let Some(color) = config.background_color {\n      builder = builder.background_color(color);\n    }\n    builder.javascript_disabled = config.javascript_disabled;\n    builder.allow_link_preview = config.allow_link_preview;\n    #[cfg(target_os = \"ios\")]\n    if config.disable_input_accessory_view {\n      builder\n        .input_accessory_view_builder\n        .replace(InputAccessoryViewBuilder::new(Box::new(|_webview| None)));\n    }\n    builder\n  }\n}\n\nimpl WebviewAttributes {\n  /// Initializes the default attributes for a webview.\n  pub fn new(url: WebviewUrl) -> Self {\n    Self {\n      url,\n      user_agent: None,\n      initialization_scripts: Vec::new(),\n      data_directory: None,\n      drag_drop_handler_enabled: true,\n      clipboard: false,\n      accept_first_mouse: false,\n      additional_browser_args: None,\n      window_effects: None,\n      incognito: false,\n      transparent: false,\n      focus: true,\n      bounds: None,\n      auto_resize: false,\n      proxy_url: None,\n      zoom_hotkeys_enabled: false,\n      browser_extensions_enabled: false,\n      data_store_identifier: None,\n      extensions_path: None,\n      use_https_scheme: false,\n      devtools: None,\n      background_color: None,\n      traffic_light_position: None,\n      background_throttling: None,\n      javascript_disabled: false,\n      allow_link_preview: true,\n      scroll_bar_style: ScrollBarStyle::Default,\n      #[cfg(target_os = \"ios\")]\n      input_accessory_view_builder: None,\n      #[cfg(windows)]\n      environment: None,\n      #[cfg(any(\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\",\n      ))]\n      related_view: None,\n      #[cfg(target_os = \"macos\")]\n      webview_configuration: None,\n    }\n  }\n\n  /// Sets the user agent\n  #[must_use]\n  pub fn user_agent(mut self, user_agent: &str) -> Self {\n    self.user_agent = Some(user_agent.to_string());\n    self\n  }\n\n  /// Adds an init script for the main frame.\n  ///\n  /// When webview load a new page, this initialization code will be executed.\n  /// It is guaranteed that code is executed before `window.onload`.\n  ///\n  /// This is executed only on the main frame.\n  /// If you only want to run it in all frames, use [`Self::initialization_script_on_all_frames`] instead.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:** scripts are always added to subframes.\n  /// - **Android:** When [addDocumentStartJavaScript] is not supported,\n  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).\n  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.\n  ///\n  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)\n  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)\n  #[must_use]\n  pub fn initialization_script(mut self, script: impl Into<String>) -> Self {\n    self.initialization_scripts.push(InitializationScript {\n      script: script.into(),\n      for_main_frame_only: true,\n    });\n    self\n  }\n\n  /// Adds an init script for all frames.\n  ///\n  /// When webview load a new page, this initialization code will be executed.\n  /// It is guaranteed that code is executed before `window.onload`.\n  ///\n  /// This is executed on all frames, main frame and also sub frames.\n  /// If you only want to run it in the main frame, use [`Self::initialization_script`] instead.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:** scripts are always added to subframes.\n  /// - **Android:** When [addDocumentStartJavaScript] is not supported,\n  ///   we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).\n  ///   For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.\n  ///\n  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)\n  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)\n  #[must_use]\n  pub fn initialization_script_on_all_frames(mut self, script: impl Into<String>) -> Self {\n    self.initialization_scripts.push(InitializationScript {\n      script: script.into(),\n      for_main_frame_only: false,\n    });\n    self\n  }\n\n  /// Data directory for the webview.\n  #[must_use]\n  pub fn data_directory(mut self, data_directory: PathBuf) -> Self {\n    self.data_directory.replace(data_directory);\n    self\n  }\n\n  /// Disables the drag and drop handler. This is required to use HTML5 drag and drop APIs on the frontend on Windows.\n  #[must_use]\n  pub fn disable_drag_drop_handler(mut self) -> Self {\n    self.drag_drop_handler_enabled = false;\n    self\n  }\n\n  /// Enables clipboard access for the page rendered on **Linux** and **Windows**.\n  ///\n  /// **macOS** doesn't provide such method and is always enabled by default,\n  /// but you still need to add menu item accelerators to use shortcuts.\n  #[must_use]\n  pub fn enable_clipboard_access(mut self) -> Self {\n    self.clipboard = true;\n    self\n  }\n\n  /// Sets whether clicking an inactive window also clicks through to the webview.\n  #[must_use]\n  pub fn accept_first_mouse(mut self, accept: bool) -> Self {\n    self.accept_first_mouse = accept;\n    self\n  }\n\n  /// Sets additional browser arguments. **Windows Only**\n  #[must_use]\n  pub fn additional_browser_args(mut self, additional_args: &str) -> Self {\n    self.additional_browser_args = Some(additional_args.to_string());\n    self\n  }\n\n  /// Sets window effects\n  #[must_use]\n  pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self {\n    self.window_effects = Some(effects);\n    self\n  }\n\n  /// Enable or disable incognito mode for the WebView.\n  #[must_use]\n  pub fn incognito(mut self, incognito: bool) -> Self {\n    self.incognito = incognito;\n    self\n  }\n\n  /// Enable or disable transparency for the WebView.\n  #[cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\"))]\n  #[must_use]\n  pub fn transparent(mut self, transparent: bool) -> Self {\n    self.transparent = transparent;\n    self\n  }\n\n  /// Whether the webview should be focused or not.\n  #[must_use]\n  pub fn focused(mut self, focus: bool) -> Self {\n    self.focus = focus;\n    self\n  }\n\n  /// Sets the webview to automatically grow and shrink its size and position when the parent window resizes.\n  #[must_use]\n  pub fn auto_resize(mut self) -> Self {\n    self.auto_resize = true;\n    self\n  }\n\n  /// Enable proxy for the WebView\n  #[must_use]\n  pub fn proxy_url(mut self, url: Url) -> Self {\n    self.proxy_url = Some(url);\n    self\n  }\n\n  /// Whether page zooming by hotkeys is enabled\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.\n  /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,\n  ///   20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission\n  ///\n  /// - **Android / iOS**: Unsupported.\n  #[must_use]\n  pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {\n    self.zoom_hotkeys_enabled = enabled;\n    self\n  }\n\n  /// Whether browser extensions can be installed for the webview process\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)\n  /// - **MacOS / Linux / iOS / Android** - Unsupported.\n  #[must_use]\n  pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {\n    self.browser_extensions_enabled = enabled;\n    self\n  }\n\n  /// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.\n  ///\n  /// ## Note\n  ///\n  /// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.\n  ///\n  /// ## Warning\n  ///\n  /// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.\n  #[must_use]\n  pub fn use_https_scheme(mut self, enabled: bool) -> Self {\n    self.use_https_scheme = enabled;\n    self\n  }\n\n  /// Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default.\n  ///\n  /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - macOS: This will call private functions on **macOS**.\n  /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.\n  /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.\n  #[must_use]\n  pub fn devtools(mut self, enabled: Option<bool>) -> Self {\n    self.devtools = enabled;\n    self\n  }\n\n  /// Set the window and webview background color.\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.\n  /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored.\n  #[must_use]\n  pub fn background_color(mut self, color: Color) -> Self {\n    self.background_color = Some(color);\n    self\n  }\n\n  /// Change the position of the window controls. Available on macOS only.\n  ///\n  /// Requires titleBarStyle: Overlay and decorations: true.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / Windows / iOS / Android:** Unsupported.\n  #[must_use]\n  pub fn traffic_light_position(mut self, position: dpi::Position) -> Self {\n    self.traffic_light_position = Some(position);\n    self\n  }\n\n  /// Whether to show a link preview when long pressing on links. Available on macOS and iOS only.\n  ///\n  /// Default is true.\n  ///\n  /// See https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / Windows / Android:** Unsupported.\n  #[must_use]\n  pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {\n    self.allow_link_preview = allow_link_preview;\n    self\n  }\n\n  /// Change the default background throttling behavior.\n  ///\n  /// By default, browsers use a suspend policy that will throttle timers and even unload\n  /// the whole tab (view) to free resources after roughly 5 minutes when a view became\n  /// minimized or hidden. This will pause all tasks until the documents visibility state\n  /// changes back from hidden to visible by bringing the view back to the foreground.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.\n  /// - **iOS**: Supported since version 17.0+.\n  /// - **macOS**: Supported since version 14.0+.\n  ///\n  /// see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578\n  #[must_use]\n  pub fn background_throttling(mut self, policy: Option<BackgroundThrottlingPolicy>) -> Self {\n    self.background_throttling = policy;\n    self\n  }\n\n  /// Specifies the native scrollbar style to use with the webview.\n  /// CSS styles that modify the scrollbar are applied on top of the native appearance configured here.\n  ///\n  /// Defaults to [`ScrollBarStyle::Default`], which is the browser default.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows**:\n  ///   - [`ScrollBarStyle::FluentOverlay`] requires WebView2 Runtime version 125.0.2535.41 or higher,\n  ///     and does nothing on older versions.\n  ///   - This option must be given the same value for all webviews that target the same data directory. Use\n  ///     [`WebviewAttributes::data_directory`] to change data directories if needed.\n  /// - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.\n  #[must_use]\n  pub fn scroll_bar_style(mut self, style: ScrollBarStyle) -> Self {\n    self.scroll_bar_style = style;\n    self\n  }\n}\n\n/// IPC handler.\npub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;\n\n/// An initialization script\n#[derive(Debug, Clone)]\npub struct InitializationScript {\n  /// The script to run\n  pub script: String,\n  /// Whether the script should be injected to main frame only\n  pub for_main_frame_only: bool,\n}\n"
  },
  {
    "path": "crates/tauri-runtime/src/window.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! A layer between raw [`Runtime`] windows and Tauri.\n\nuse crate::{\n  webview::{DetachedWebview, PendingWebview},\n  Icon, Runtime, UserEvent, WindowDispatch,\n};\n\nuse dpi::PixelUnit;\nuse serde::{Deserialize, Deserializer, Serialize};\nuse tauri_utils::{\n  config::{Color, WindowConfig},\n  Theme,\n};\n#[cfg(windows)]\nuse windows::Win32::Foundation::HWND;\n\nuse std::{\n  hash::{Hash, Hasher},\n  marker::PhantomData,\n  path::PathBuf,\n  sync::mpsc::Sender,\n};\n\n/// An event from a window.\n#[derive(Debug, Clone)]\npub enum WindowEvent {\n  /// The size of the window has changed. Contains the client area's new dimensions.\n  Resized(dpi::PhysicalSize<u32>),\n  /// The position of the window has changed. Contains the window's new position.\n  Moved(dpi::PhysicalPosition<i32>),\n  /// The window has been requested to close.\n  CloseRequested {\n    /// A signal sender. If a `true` value is emitted, the window won't be closed.\n    signal_tx: Sender<bool>,\n  },\n  /// The window has been destroyed.\n  Destroyed,\n  /// The window gained or lost focus.\n  ///\n  /// The parameter is true if the window has gained focus, and false if it has lost focus.\n  Focused(bool),\n  /// The window's scale factor has changed.\n  ///\n  /// The following user actions can cause DPI changes:\n  ///\n  /// - Changing the display's resolution.\n  /// - Changing the display's scale factor (e.g. in Control Panel on Windows).\n  /// - Moving the window to a display with a different scale factor.\n  ScaleFactorChanged {\n    /// The new scale factor.\n    scale_factor: f64,\n    /// The window inner size.\n    new_inner_size: dpi::PhysicalSize<u32>,\n  },\n  /// An event associated with the drag and drop action.\n  DragDrop(DragDropEvent),\n  /// The system window theme has changed.\n  ///\n  /// Applications might wish to react to this to change the theme of the content of the window when the system changes the window theme.\n  ThemeChanged(Theme),\n}\n\n/// An event from a window.\n#[derive(Debug, Clone)]\npub enum WebviewEvent {\n  /// An event associated with the drag and drop action.\n  DragDrop(DragDropEvent),\n}\n\n/// The drag drop event payload.\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub enum DragDropEvent {\n  /// A drag operation has entered the webview.\n  Enter {\n    /// List of paths that are being dragged onto the webview.\n    paths: Vec<PathBuf>,\n    /// The position of the mouse cursor.\n    position: dpi::PhysicalPosition<f64>,\n  },\n  /// A drag operation is moving over the webview.\n  Over {\n    /// The position of the mouse cursor.\n    position: dpi::PhysicalPosition<f64>,\n  },\n  /// The file(s) have been dropped onto the webview.\n  Drop {\n    /// List of paths that are being dropped onto the window.\n    paths: Vec<PathBuf>,\n    /// The position of the mouse cursor.\n    position: dpi::PhysicalPosition<f64>,\n  },\n  /// The drag operation has been cancelled or left the window.\n  Leave,\n}\n\n/// Describes the appearance of the mouse cursor.\n#[non_exhaustive]\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]\npub enum CursorIcon {\n  /// The platform-dependent default cursor.\n  #[default]\n  Default,\n  /// A simple crosshair.\n  Crosshair,\n  /// A hand (often used to indicate links in web browsers).\n  Hand,\n  /// Self explanatory.\n  Arrow,\n  /// Indicates something is to be moved.\n  Move,\n  /// Indicates text that may be selected or edited.\n  Text,\n  /// Program busy indicator.\n  Wait,\n  /// Help indicator (often rendered as a \"?\")\n  Help,\n  /// Progress indicator. Shows that processing is being done. But in contrast\n  /// with \"Wait\" the user may still interact with the program. Often rendered\n  /// as a spinning beach ball, or an arrow with a watch or hourglass.\n  Progress,\n\n  /// Cursor showing that something cannot be done.\n  NotAllowed,\n  ContextMenu,\n  Cell,\n  VerticalText,\n  Alias,\n  Copy,\n  NoDrop,\n  /// Indicates something can be grabbed.\n  Grab,\n  /// Indicates something is grabbed.\n  Grabbing,\n  AllScroll,\n  ZoomIn,\n  ZoomOut,\n\n  /// Indicate that some edge is to be moved. For example, the 'SeResize' cursor\n  /// is used when the movement starts from the south-east corner of the box.\n  EResize,\n  NResize,\n  NeResize,\n  NwResize,\n  SResize,\n  SeResize,\n  SwResize,\n  WResize,\n  EwResize,\n  NsResize,\n  NeswResize,\n  NwseResize,\n  ColResize,\n  RowResize,\n}\n\nimpl<'de> Deserialize<'de> for CursorIcon {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let s = String::deserialize(deserializer)?;\n    Ok(match s.to_lowercase().as_str() {\n      \"default\" => CursorIcon::Default,\n      \"crosshair\" => CursorIcon::Crosshair,\n      \"hand\" => CursorIcon::Hand,\n      \"arrow\" => CursorIcon::Arrow,\n      \"move\" => CursorIcon::Move,\n      \"text\" => CursorIcon::Text,\n      \"wait\" => CursorIcon::Wait,\n      \"help\" => CursorIcon::Help,\n      \"progress\" => CursorIcon::Progress,\n      \"notallowed\" => CursorIcon::NotAllowed,\n      \"contextmenu\" => CursorIcon::ContextMenu,\n      \"cell\" => CursorIcon::Cell,\n      \"verticaltext\" => CursorIcon::VerticalText,\n      \"alias\" => CursorIcon::Alias,\n      \"copy\" => CursorIcon::Copy,\n      \"nodrop\" => CursorIcon::NoDrop,\n      \"grab\" => CursorIcon::Grab,\n      \"grabbing\" => CursorIcon::Grabbing,\n      \"allscroll\" => CursorIcon::AllScroll,\n      \"zoomin\" => CursorIcon::ZoomIn,\n      \"zoomout\" => CursorIcon::ZoomOut,\n      \"eresize\" => CursorIcon::EResize,\n      \"nresize\" => CursorIcon::NResize,\n      \"neresize\" => CursorIcon::NeResize,\n      \"nwresize\" => CursorIcon::NwResize,\n      \"sresize\" => CursorIcon::SResize,\n      \"seresize\" => CursorIcon::SeResize,\n      \"swresize\" => CursorIcon::SwResize,\n      \"wresize\" => CursorIcon::WResize,\n      \"ewresize\" => CursorIcon::EwResize,\n      \"nsresize\" => CursorIcon::NsResize,\n      \"neswresize\" => CursorIcon::NeswResize,\n      \"nwseresize\" => CursorIcon::NwseResize,\n      \"colresize\" => CursorIcon::ColResize,\n      \"rowresize\" => CursorIcon::RowResize,\n      _ => CursorIcon::Default,\n    })\n  }\n}\n\n/// Window size constraints\n#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct WindowSizeConstraints {\n  /// The minimum width a window can be, If this is `None`, the window will have no minimum width.\n  ///\n  /// The default is `None`.\n  pub min_width: Option<PixelUnit>,\n  /// The minimum height a window can be, If this is `None`, the window will have no minimum height.\n  ///\n  /// The default is `None`.\n  pub min_height: Option<PixelUnit>,\n  /// The maximum width a window can be, If this is `None`, the window will have no maximum width.\n  ///\n  /// The default is `None`.\n  pub max_width: Option<PixelUnit>,\n  /// The maximum height a window can be, If this is `None`, the window will have no maximum height.\n  ///\n  /// The default is `None`.\n  pub max_height: Option<PixelUnit>,\n}\n\n/// Do **NOT** implement this trait except for use in a custom [`Runtime`]\n///\n/// This trait is separate from [`WindowBuilder`] to prevent \"accidental\" implementation.\npub trait WindowBuilderBase: std::fmt::Debug + Clone + Sized {}\n\n/// A builder for all attributes related to a single window.\n///\n/// This trait is only meant to be implemented by a custom [`Runtime`]\n/// and not by applications.\npub trait WindowBuilder: WindowBuilderBase {\n  /// Initializes a new window attributes builder.\n  fn new() -> Self;\n\n  /// Initializes a new window builder from a [`WindowConfig`]\n  fn with_config(config: &WindowConfig) -> Self;\n\n  /// Show window in the center of the screen.\n  #[must_use]\n  fn center(self) -> Self;\n\n  /// The initial position of the window in logical pixels.\n  #[must_use]\n  fn position(self, x: f64, y: f64) -> Self;\n\n  /// Window size in logical pixels.\n  #[must_use]\n  fn inner_size(self, width: f64, height: f64) -> Self;\n\n  /// Window min inner size in logical pixels.\n  #[must_use]\n  fn min_inner_size(self, min_width: f64, min_height: f64) -> Self;\n\n  /// Window max inner size in logical pixels.\n  #[must_use]\n  fn max_inner_size(self, max_width: f64, max_height: f64) -> Self;\n\n  /// Window inner size constraints.\n  #[must_use]\n  fn inner_size_constraints(self, constraints: WindowSizeConstraints) -> Self;\n\n  /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size) on creation\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  #[must_use]\n  fn prevent_overflow(self) -> Self;\n\n  /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size)\n  /// on creation with a margin\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  #[must_use]\n  fn prevent_overflow_with_margin(self, margin: dpi::Size) -> Self;\n\n  /// Whether the window is resizable or not.\n  /// When resizable is set to false, native window's maximize button is automatically disabled.\n  #[must_use]\n  fn resizable(self, resizable: bool) -> Self;\n\n  /// Whether the window's native maximize button is enabled or not.\n  /// If resizable is set to false, this setting is ignored.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Disables the \"zoom\" button in the window titlebar, which is also used to enter fullscreen mode.\n  /// - **Linux / iOS / Android:** Unsupported.\n  #[must_use]\n  fn maximizable(self, maximizable: bool) -> Self;\n\n  /// Whether the window's native minimize button is enabled or not.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  #[must_use]\n  fn minimizable(self, minimizable: bool) -> Self;\n\n  /// Whether the window's native close button is enabled or not.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** \"GTK+ will do its best to convince the window manager not to show a close button.\n  ///   Depending on the system, this function may not have any effect when called on a window that is already visible\"\n  /// - **iOS / Android:** Unsupported.\n  #[must_use]\n  fn closable(self, closable: bool) -> Self;\n\n  /// The title of the window in the title bar.\n  #[must_use]\n  fn title<S: Into<String>>(self, title: S) -> Self;\n\n  /// Whether to start the window in fullscreen or not.\n  #[must_use]\n  fn fullscreen(self, fullscreen: bool) -> Self;\n\n  /// Whether the window will be initially focused or not.\n  #[must_use]\n  fn focused(self, focused: bool) -> Self;\n\n  /// Whether the window will be focusable or not.\n  #[must_use]\n  fn focusable(self, focusable: bool) -> Self;\n\n  /// Whether the window should be maximized upon creation.\n  #[must_use]\n  fn maximized(self, maximized: bool) -> Self;\n\n  /// Whether the window should be immediately visible upon creation.\n  #[must_use]\n  fn visible(self, visible: bool) -> Self;\n\n  /// Whether the window should be transparent. If this is true, writing colors\n  /// with alpha values different than `1.0` will produce a transparent window.\n  #[cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\"))]\n  #[cfg_attr(\n    docsrs,\n    doc(cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\")))\n  )]\n  #[must_use]\n  fn transparent(self, transparent: bool) -> Self;\n\n  /// Whether the window should have borders and bars.\n  #[must_use]\n  fn decorations(self, decorations: bool) -> Self;\n\n  /// Whether the window should always be below other windows.\n  #[must_use]\n  fn always_on_bottom(self, always_on_bottom: bool) -> Self;\n\n  /// Whether the window should always be on top of other windows.\n  #[must_use]\n  fn always_on_top(self, always_on_top: bool) -> Self;\n\n  /// Whether the window should be visible on all workspaces or virtual desktops.\n  #[must_use]\n  fn visible_on_all_workspaces(self, visible_on_all_workspaces: bool) -> Self;\n\n  /// Prevents the window contents from being captured by other apps.\n  #[must_use]\n  fn content_protected(self, protected: bool) -> Self;\n\n  /// Sets the window icon.\n  fn icon(self, icon: Icon) -> crate::Result<Self>;\n\n  /// Sets whether or not the window icon should be added to the taskbar.\n  #[must_use]\n  fn skip_taskbar(self, skip: bool) -> Self;\n\n  /// Set the window background color.\n  #[must_use]\n  fn background_color(self, color: Color) -> Self;\n\n  /// Sets whether or not the window has shadow.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:**\n  ///   - `false` has no effect on decorated window, shadows are always ON.\n  ///   - `true` will make undecorated window have a 1px white border,\n  ///     and on Windows 11, it will have a rounded corners.\n  /// - **Linux:** Unsupported.\n  #[must_use]\n  fn shadow(self, enable: bool) -> Self;\n\n  /// Set an owner to the window to be created.\n  ///\n  /// From MSDN:\n  /// - An owned window is always above its owner in the z-order.\n  /// - The system automatically destroys an owned window when its owner is destroyed.\n  /// - An owned window is hidden when its owner is minimized.\n  ///\n  /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>\n  #[cfg(windows)]\n  #[must_use]\n  fn owner(self, owner: HWND) -> Self;\n\n  /// Sets a parent to the window to be created.\n  ///\n  /// A child window has the WS_CHILD style and is confined to the client area of its parent window.\n  ///\n  /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>\n  #[cfg(windows)]\n  #[must_use]\n  fn parent(self, parent: HWND) -> Self;\n\n  /// Sets a parent to the window to be created.\n  ///\n  /// See <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  fn parent(self, parent: *mut std::ffi::c_void) -> Self;\n\n  /// Sets the window to be created transient for parent.\n  ///\n  /// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn transient_for(self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self;\n\n  /// Enables or disables drag and drop support.\n  #[cfg(windows)]\n  #[must_use]\n  fn drag_and_drop(self, enabled: bool) -> Self;\n\n  /// Hide the titlebar. Titlebar buttons will still be visible.\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  fn title_bar_style(self, style: tauri_utils::TitleBarStyle) -> Self;\n\n  /// Change the position of the window controls on macOS.\n  ///\n  /// Requires titleBarStyle: Overlay and decorations: true.\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  fn traffic_light_position<P: Into<dpi::Position>>(self, position: P) -> Self;\n\n  /// Hide the window title.\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  fn hidden_title(self, hidden: bool) -> Self;\n\n  /// Defines the window [tabbing identifier] for macOS.\n  ///\n  /// Windows with matching tabbing identifiers will be grouped together.\n  /// If the tabbing identifier is not set, automatic tabbing will be disabled.\n  ///\n  /// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>\n  #[cfg(target_os = \"macos\")]\n  #[must_use]\n  fn tabbing_identifier(self, identifier: &str) -> Self;\n\n  /// Forces a theme or uses the system settings if None was provided.\n  fn theme(self, theme: Option<Theme>) -> Self;\n\n  /// Whether the icon was set or not.\n  fn has_icon(&self) -> bool;\n\n  fn get_theme(&self) -> Option<Theme>;\n\n  /// Sets custom name for Windows' window class. **Windows only**.\n  #[must_use]\n  fn window_classname<S: Into<String>>(self, window_classname: S) -> Self;\n}\n\n/// A window that has yet to be built.\npub struct PendingWindow<T: UserEvent, R: Runtime<T>> {\n  /// The label that the window will be named.\n  pub label: String,\n\n  /// The [`WindowBuilder`] that the window will be created with.\n  pub window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,\n\n  /// The webview that gets added to the window. Optional in case you want to use child webviews or other window content instead.\n  pub webview: Option<PendingWebview<T, R>>,\n}\n\npub fn is_label_valid(label: &str) -> bool {\n  label\n    .chars()\n    .all(|c| char::is_alphanumeric(c) || c == '-' || c == '/' || c == ':' || c == '_')\n}\n\npub fn assert_label_is_valid(label: &str) {\n  assert!(\n    is_label_valid(label),\n    \"Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`.\"\n  );\n}\n\nimpl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {\n  /// Create a new [`PendingWindow`] with a label from the given [`WindowBuilder`].\n  pub fn new(\n    window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,\n    label: impl Into<String>,\n  ) -> crate::Result<Self> {\n    let label = label.into();\n    if !is_label_valid(&label) {\n      Err(crate::Error::InvalidWindowLabel)\n    } else {\n      Ok(Self {\n        window_builder,\n        label,\n        webview: None,\n      })\n    }\n  }\n\n  /// Sets a webview to be created on the window.\n  pub fn set_webview(&mut self, webview: PendingWebview<T, R>) -> &mut Self {\n    self.webview.replace(webview);\n    self\n  }\n}\n\n/// Identifier of a window.\n#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]\npub struct WindowId(u32);\n\nimpl From<u32> for WindowId {\n  fn from(value: u32) -> Self {\n    Self(value)\n  }\n}\n\n/// A window that is not yet managed by Tauri.\n#[derive(Debug)]\npub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {\n  /// The identifier of the window.\n  pub id: WindowId,\n  /// Name of the window\n  pub label: String,\n\n  /// The [`WindowDispatch`] associated with the window.\n  pub dispatcher: R::WindowDispatcher,\n\n  /// The webview dispatcher in case this window has an attached webview.\n  pub webview: Option<DetachedWindowWebview<T, R>>,\n}\n\n/// A detached webview associated with a window.\n#[derive(Debug)]\npub struct DetachedWindowWebview<T: UserEvent, R: Runtime<T>> {\n  pub webview: DetachedWebview<T, R>,\n  pub use_https_scheme: bool,\n}\n\nimpl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindowWebview<T, R> {\n  fn clone(&self) -> Self {\n    Self {\n      webview: self.webview.clone(),\n      use_https_scheme: self.use_https_scheme,\n    }\n  }\n}\n\nimpl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {\n  fn clone(&self) -> Self {\n    Self {\n      id: self.id,\n      label: self.label.clone(),\n      dispatcher: self.dispatcher.clone(),\n      webview: self.webview.clone(),\n    }\n  }\n}\n\nimpl<T: UserEvent, R: Runtime<T>> Hash for DetachedWindow<T, R> {\n  /// Only use the [`DetachedWindow`]'s label to represent its hash.\n  fn hash<H: Hasher>(&self, state: &mut H) {\n    self.label.hash(state)\n  }\n}\n\nimpl<T: UserEvent, R: Runtime<T>> Eq for DetachedWindow<T, R> {}\nimpl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWindow<T, R> {\n  /// Only use the [`DetachedWindow`]'s label to compare equality.\n  fn eq(&self, other: &Self) -> bool {\n    self.label.eq(&other.label)\n  }\n}\n\n/// A raw window type that contains fields to access\n/// the HWND on Windows, gtk::ApplicationWindow on Linux\npub struct RawWindow<'a> {\n  #[cfg(windows)]\n  pub hwnd: isize,\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  pub gtk_window: &'a gtk::ApplicationWindow,\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  pub default_vbox: Option<&'a gtk::Box>,\n  pub _marker: &'a PhantomData<()>,\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.10.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.3`\n- Upgraded to `tauri-runtime@2.10.1`\n\n## \\[2.10.0]\n\n### Bug Fixes\n\n- [`c1d82eb3a`](https://www.github.com/tauri-apps/tauri/commit/c1d82eb3a3fa4b555745ba699edf1cc532030117) ([#14628](https://www.github.com/tauri-apps/tauri/pull/14628) by [@KushalMeghani1644](https://www.github.com/tauri-apps/tauri/../../KushalMeghani1644)) On Linux, keep the WebContext alive to prevent zombie WebKit processes after repeatedly closing all windows and re-opening them.\n- [`9b242e40c`](https://www.github.com/tauri-apps/tauri/commit/9b242e40c844189c877a91e513ae6196202d5ae9) ([#14700](https://www.github.com/tauri-apps/tauri/pull/14700) by [@mewi99](https://www.github.com/tauri-apps/tauri/../../mewi99)) Fix compilation errors when targeting BSD.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.2`\n- Upgraded to `tauri-runtime@2.10.0`\n- [`75057c7c0`](https://www.github.com/tauri-apps/tauri/commit/75057c7c08f0d4d3dd8d10cea4e2217e5d61fe1a) ([#14778](https://www.github.com/tauri-apps/tauri/pull/14778) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) **Breaking Change** for `with_webview` users: Updated webkit2gtk-rs crates to `v2.0.2`.\n- [`75057c7c0`](https://www.github.com/tauri-apps/tauri/commit/75057c7c08f0d4d3dd8d10cea4e2217e5d61fe1a) ([#14778](https://www.github.com/tauri-apps/tauri/pull/14778) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Update wry to `v0.54`.\n\n## \\[2.9.3]\n\n### Bug Fixes\n\n- [`251203b89`](https://www.github.com/tauri-apps/tauri/commit/251203b8963419cb3b40741767393e8f3c909ef9) ([#14637](https://www.github.com/tauri-apps/tauri/pull/14637) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `Monitor::work_area` returns logical position and size inside the `PhysicalRect` on Linux\n\n## \\[2.9.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.1`\n- Upgraded to `tauri-runtime@2.9.2`\n\n## \\[2.9.1]\n\n### Bug Fixes\n\n- [`4b6b8690a`](https://www.github.com/tauri-apps/tauri/commit/4b6b8690ab886ebdf1307951cffbe03e31280baa) ([#14347](https://www.github.com/tauri-apps/tauri/pull/14347) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused docs.rs builds to fail. No user facing changes.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.9.1`\n\n## \\[2.9.0]\n\n### New Features\n\n- [`f5851ee00`](https://www.github.com/tauri-apps/tauri/commit/f5851ee00d6d1f4d560a220ca5a728fedd525092) ([#14089](https://www.github.com/tauri-apps/tauri/pull/14089)) Adds the `scroll_bar_style` option to the Webview and WebviewWindow builders.\n  The possible values for this option are gated behind conditional compilation\n  flags, and will need to be applied using conditional compilation if customised.\n\n### Bug Fixes\n\n- [`684791efa`](https://www.github.com/tauri-apps/tauri/commit/684791efa6f3c671a0435d456ac208bca871d8c1) ([#14276](https://www.github.com/tauri-apps/tauri/pull/14276)) Always try to create macOS WebKit webview, even if webkit runtime doesn't get detected correctly\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.8.0`\n- Upgraded to `tauri-runtime@2.9.0`\n\n## \\[2.8.1]\n\n### Bug Fixes\n\n- [`03e7c1193`](https://www.github.com/tauri-apps/tauri/commit/03e7c1193208716170f120a1d4a39cea0bc21064) ([#14080](https://www.github.com/tauri-apps/tauri/pull/14080) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ignore initial navigation to `about:blank` so `on_new_window` does not give a warning on first navigation on macOS.\n\n## \\[2.8.0]\n\n### New Features\n\n- [`68874c68c`](https://www.github.com/tauri-apps/tauri/commit/68874c68c566638b4c21a3aa67844d1bdaeb6dab) ([#13564](https://www.github.com/tauri-apps/tauri/pull/13564) by [@robertrpf](https://www.github.com/tauri-apps/tauri/../../robertrpf)) Add window focusable attribute and set_focusable API.\n- [`22d6bcacb`](https://www.github.com/tauri-apps/tauri/commit/22d6bcacbb2001eb292ebd8c5d021447700f9512) ([#14008](https://www.github.com/tauri-apps/tauri/pull/14008) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Implement `App::set_device_event_filter` for `AppHandle` also.\n- [`33d0b3f0c`](https://www.github.com/tauri-apps/tauri/commit/33d0b3f0c133edebb1c716e2f5942d70509ae347) ([#13876](https://www.github.com/tauri-apps/tauri/pull/13876) by [@thlstsul](https://www.github.com/tauri-apps/tauri/../../thlstsul)) Added `WebviewBuilder::on_document_title_changed` and `WebviewWindowBuilder::on_document_title_changed`.\n- [`33d0b3f0c`](https://www.github.com/tauri-apps/tauri/commit/33d0b3f0c133edebb1c716e2f5942d70509ae347) ([#13876](https://www.github.com/tauri-apps/tauri/pull/13876) by [@thlstsul](https://www.github.com/tauri-apps/tauri/../../thlstsul)) Added `WebviewBuilder::on_new_window` and `WebviewWindowBuilder::on_new_window`.\n- [`dfadcb764`](https://www.github.com/tauri-apps/tauri/commit/dfadcb764bdf84089a5487005a7b4f3b7cf09494) ([#13661](https://www.github.com/tauri-apps/tauri/pull/13661) by [@WSH032](https://www.github.com/tauri-apps/tauri/../../WSH032)) Added `WebviewDispatch::set_cookie()` and `WebviewDispatch::delete_cookie()`.\n- [`5110a762e`](https://www.github.com/tauri-apps/tauri/commit/5110a762e9db978a28a15400bf76e3c864da2a86) ([#13830](https://www.github.com/tauri-apps/tauri/pull/13830) by [@Sky-walkerX](https://www.github.com/tauri-apps/tauri/../../Sky-walkerX)) Added `Window::set_simple_fullscreen`.\n\n### Bug Fixes\n\n- [`82e264552`](https://www.github.com/tauri-apps/tauri/commit/82e264552ed3e1d62135edc5803a2c5d92f9c5c7) ([#13968](https://www.github.com/tauri-apps/tauri/pull/13968) by [@s5bug](https://www.github.com/tauri-apps/tauri/../../s5bug)) Use WM_NCDESTROY instead of WM_DESTROY to free window userdata, fixing a double-free occurring in the Windows resizing handler for undecorated windows which caused STATUS_HEAP_CORRUPTION\n- [`196ace3c0`](https://www.github.com/tauri-apps/tauri/commit/196ace3c048f81b20ab4d38f09c2f200978722b8) ([#13970](https://www.github.com/tauri-apps/tauri/pull/13970) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Return `tauri_runtime::Error::CreateWindow` instead of panic on window creation failed\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.7.0`\n- Upgraded to `tauri-runtime@2.8.0`\n\n## \\[2.7.2]\n\n### What's Changed\n\n- [`3025d9095`](https://www.github.com/tauri-apps/tauri/commit/3025d90951bfa152fc6f4255ac5a3fdd0f6353ca) ([#13822](https://www.github.com/tauri-apps/tauri/pull/13822) by [@clearlysid](https://www.github.com/tauri-apps/tauri/../../clearlysid)) Makes some methods on `WindowIdStore`, `WindowsStore` and `WindowWrapper` public\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.6.0`\n- Upgraded to `tauri-runtime@2.7.1`\n\n## \\[2.7.1]\n\n### Bug Fixes\n\n- [`f010ca5e9`](https://www.github.com/tauri-apps/tauri/commit/f010ca5e9199e6baae65990ab75a83ea29e4fe18) ([#13719](https://www.github.com/tauri-apps/tauri/pull/13719) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed a compile error when using tauri without the `common-controls-v6` feature enabled.\n\n## \\[2.7.0]\n\n### New Features\n\n- [`9c16eefa3`](https://www.github.com/tauri-apps/tauri/commit/9c16eefa319b4697bac1d1019bbb5f93eca63173) ([#13629](https://www.github.com/tauri-apps/tauri/pull/13629) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Added `x11` Cargo feature (enabled by default). Disabling it is useful for apps that only support Wayland, reducing its size.\n  **NOTE**: When manually disabling tauri default features, you must enable the `x11` feature to support it.\n\n### Enhancements\n\n- [`96ecfca42`](https://www.github.com/tauri-apps/tauri/commit/96ecfca428e4e5d9ff5d5eeed3f94a06a466ed02) ([#13406](https://www.github.com/tauri-apps/tauri/pull/13406) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Check if the webview runtime is accessible when creating a webview, returning an error if it doesn't.\n\n### Bug Fixes\n\n- [`f0662e41f`](https://www.github.com/tauri-apps/tauri/commit/f0662e41f4f78ec5a3f88aa76a7367d37d740291) ([#13365](https://www.github.com/tauri-apps/tauri/pull/13365) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix monitor check on the window prevent overflow implementation.\n- [`4acae1ec0`](https://www.github.com/tauri-apps/tauri/commit/4acae1ec02f2e0b0501d6b79b0bb862893296abd) ([#13443](https://www.github.com/tauri-apps/tauri/pull/13443) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `AppHandle::set_theme` crashes or has no effect on macOS if not ran on main thread\n- [`78d15e892`](https://www.github.com/tauri-apps/tauri/commit/78d15e892d30fb3f8494f0c00685fb455740a181) ([#13558](https://www.github.com/tauri-apps/tauri/pull/13558) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Allow web fullscreen APIs to work on Windows (e.g. `video.requestFullscreen` and `document.exitFullscreen`)\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.5.0`\n- Upgraded to `tauri-runtime@2.7.0`\n- [`9c16eefa3`](https://www.github.com/tauri-apps/tauri/commit/9c16eefa319b4697bac1d1019bbb5f93eca63173) ([#13629](https://www.github.com/tauri-apps/tauri/pull/13629) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Updated tao to 0.34, wry to 0.52 and webview2-com to 0.38.\n\n## \\[2.6.0]\n\n### New Features\n\n- [`dd4f13ce4`](https://www.github.com/tauri-apps/tauri/commit/dd4f13ce4b3cd89cde2fa3f18a063c272f215621) ([#13185](https://www.github.com/tauri-apps/tauri/pull/13185)) MacOS: Add `set_dock_visibility` method to support setting the visibility of the application in the dock.\n- [`ea36294cb`](https://www.github.com/tauri-apps/tauri/commit/ea36294cbca98f7725c91d1464fd92e77c89698a) ([#13208](https://www.github.com/tauri-apps/tauri/pull/13208)) Added `WebviewAttributes::input_accessory_view_builder` on iOS.\n- [`c1cd0a2dd`](https://www.github.com/tauri-apps/tauri/commit/c1cd0a2ddb5bc3e99451cbe399b5fc9f0035f571) ([#13090](https://www.github.com/tauri-apps/tauri/pull/13090)) macOS/iOS: add option to disable or enable link previews when building a webview (the webkit api has it enabled by default)\n\n  - `WebViewBuilder.allow_link_preview(allow_link_preview: bool)`\n  - `WebviewWindowBuilder.allow_link_preview(allow_link_preview: bool)`\n- [`b072e2b29`](https://www.github.com/tauri-apps/tauri/commit/b072e2b2967640ae4fa1af466ae878c156551edd) ([#9687](https://www.github.com/tauri-apps/tauri/pull/9687)) Add `preventOverflow` config option to prevent the window from overflowing the monitor size on creation\n\n### Bug Fixes\n\n- [`07953fb9c`](https://www.github.com/tauri-apps/tauri/commit/07953fb9c3dc09e86657aa16020978e89b03ed56) ([#13222](https://www.github.com/tauri-apps/tauri/pull/13222)) Emit `WindowEvent::Focused` events when using the multiwebview (unstable feature flag) mode on Windows.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.4.0`\n- Upgraded to `tauri-runtime@2.6.0`\n- [`bb5faa21f`](https://www.github.com/tauri-apps/tauri/commit/bb5faa21f418dd765ce81b495b56e9c519251b6d) ([#13163](https://www.github.com/tauri-apps/tauri/pull/13163)) Update tao to 0.33.\n- [`bb5faa21f`](https://www.github.com/tauri-apps/tauri/commit/bb5faa21f418dd765ce81b495b56e9c519251b6d) ([#13163](https://www.github.com/tauri-apps/tauri/pull/13163)) Update webview2-com to 0.37.\n- [`bb5faa21f`](https://www.github.com/tauri-apps/tauri/commit/bb5faa21f418dd765ce81b495b56e9c519251b6d) ([#13163](https://www.github.com/tauri-apps/tauri/pull/13163)) Update windows to 0.61.\n- [`bb5faa21f`](https://www.github.com/tauri-apps/tauri/commit/bb5faa21f418dd765ce81b495b56e9c519251b6d) ([#13163](https://www.github.com/tauri-apps/tauri/pull/13163)) Update wry to 0.51.\n\n## \\[2.5.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.3.1`\n- Upgraded to `tauri-runtime@2.5.1`\n\n## \\[2.5.0]\n\n### New Features\n\n- [`be2e6b85f`](https://www.github.com/tauri-apps/tauri/commit/be2e6b85fed226732b4a98f68cc5d72b4f8f5a13) ([#12944](https://www.github.com/tauri-apps/tauri/pull/12944) by [@Simon-Laux](https://www.github.com/tauri-apps/tauri/../../Simon-Laux)) add `Window.is_always_on_top()` and `WebviewWindow.is_always_on_top()`\n- [`20c190691`](https://www.github.com/tauri-apps/tauri/commit/20c19069125c89b2d45a2127278c9ffc2df35fc2) ([#12821](https://www.github.com/tauri-apps/tauri/pull/12821) by [@Simon-Laux](https://www.github.com/tauri-apps/tauri/../../Simon-Laux)) Add `WebviewBuilder.disable_javascript` and `WebviewWindowBuilder.disable_javascript` api to disable JavaScript.\n- [`658e5f5d1`](https://www.github.com/tauri-apps/tauri/commit/658e5f5d1dc1bd970ae572a42447448d064a7fee) ([#12668](https://www.github.com/tauri-apps/tauri/pull/12668) by [@thomaseizinger](https://www.github.com/tauri-apps/tauri/../../thomaseizinger)) Add `App::run_return` function. Contrary to `App::run`, this will **not** exit the process but instead return the requested exit-code. This allows the host app to perform further cleanup after Tauri has exited. `App::run_return` is not available on iOS and fallbacks to the regular `App::run` functionality.\n\n  The `App::run_iteration` function is deprecated as part of this because calling it in a loop - as suggested by the name - will cause a busy-loop.\n- [`c698a6d6f`](https://www.github.com/tauri-apps/tauri/commit/c698a6d6f3e02548444a4aa0e5220bbc6fc05c74) ([#12818](https://www.github.com/tauri-apps/tauri/pull/12818) by [@Simon-Laux](https://www.github.com/tauri-apps/tauri/../../Simon-Laux)) feat: add `Webview.reload` and `WebviewWindow.reload`\n- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `trafficLightPosition` window configuration to set the traffic light buttons position on macOS.\n- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `traffic_light_position` window builder method to set the traffic light buttons position on macOS.\n- [`cedb24d49`](https://www.github.com/tauri-apps/tauri/commit/cedb24d494b84111daa3206c05196c8b89f1e994) ([#12665](https://www.github.com/tauri-apps/tauri/pull/12665) by [@charrondev](https://www.github.com/tauri-apps/tauri/../../charrondev)) Added `WebviewDispatch::cookies()` and `WebviewDispatch::cookies_for_url()`.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.5.0`\n- Upgraded to `tauri-utils@2.3.0`\n\n## \\[2.4.1]\n\n### Bug Fixes\n\n- [`e103e87f1`](https://www.github.com/tauri-apps/tauri/commit/e103e87f155cf7fa51baa0a48a476463216c0d62) ([#12848](https://www.github.com/tauri-apps/tauri/pull/12848) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix crash on Windows because of missing functions on older Windows systems, regression in 2.3.0\n\n## \\[2.4.0]\n\n### Enhancements\n\n- [`a2d36b8c3`](https://www.github.com/tauri-apps/tauri/commit/a2d36b8c34a8dcfc6736797ca5cd4665faf75e7e) ([#12181](https://www.github.com/tauri-apps/tauri/pull/12181) by [@bastiankistner](https://www.github.com/tauri-apps/tauri/../../bastiankistner)) Add an option to change the default background throttling policy (currently for WebKit only).\n- [`d6520a21c`](https://www.github.com/tauri-apps/tauri/commit/d6520a21ce02c3e2be2955999946c2cb7bdb07aa) ([#12541](https://www.github.com/tauri-apps/tauri/pull/12541) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Updated `wry` to 0.50, `windows` to 0.60, `webview2-com` to 0.36, and `objc2` to 0.6. This can be a **breaking change** if you use the `with_webview` API!\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.4.0`\n- Upgraded to `tauri-utils@2.2.0`\n\n## \\[2.3.0]\n\n### New Features\n\n- [`18bd639f6`](https://www.github.com/tauri-apps/tauri/commit/18bd639f6e22c0188aa219739f367b5bf5ab0398) ([#11798](https://www.github.com/tauri-apps/tauri/pull/11798) by [@lars-berger](https://www.github.com/tauri-apps/tauri/../../lars-berger)) Add `WebviewWindowBuilder/WebviewBuilder::data_store_identifier` on macOS.\n- [`dc4d79477`](https://www.github.com/tauri-apps/tauri/commit/dc4d79477665bc3bfefb4048772414cf5d78e3df) ([#11628](https://www.github.com/tauri-apps/tauri/pull/11628) by [@SpikeHD](https://www.github.com/tauri-apps/tauri/../../SpikeHD)) Add `WebviewWindowBuilder/WebviewBuilder::extensions_path` on Linux and Windows.\n- [`020ea0556`](https://www.github.com/tauri-apps/tauri/commit/020ea05561348dcd6d2a7df358f8a5190f661ba2) ([#11661](https://www.github.com/tauri-apps/tauri/pull/11661) by [@ahqsoftwares](https://www.github.com/tauri-apps/tauri/../../ahqsoftwares)) Add badging APIs:\n\n  - `Window/WebviewWindow::set_badge_count` for Linux, macOS and IOS.\n  - `Window/WebviewWindow::set_overlay_icon` for Windows Only.\n  - `Window/WebviewWindow::set_badge_label`for macOS Only.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.3.0`\n- Upgraded to `tauri-utils@2.1.1`\n\n## \\[2.2.0]\n\n### New Features\n\n- [`4d545ab3c`](https://www.github.com/tauri-apps/tauri/commit/4d545ab3ca228c8a21b966b709f84a0da2864479) ([#11486](https://www.github.com/tauri-apps/tauri/pull/11486) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Window::set_background_color` and `WindowBuilder::background_color`.\n- [`f37e97d41`](https://www.github.com/tauri-apps/tauri/commit/f37e97d410c4a219e99f97692da05ca9d8e0ba3a) ([#11477](https://www.github.com/tauri-apps/tauri/pull/11477) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder/WebviewBuilder::use_https_scheme` to choose whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android\n- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder::devtools` and `WebviewBuilder::devtools` to enable or disable devtools for a specific webview.\n- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `WindowBuilder/WebviewWindowBuilder::window_classname` method to specify the name of the window class on Windows.\n\n### Bug Fixes\n\n- [`229d7f8e2`](https://www.github.com/tauri-apps/tauri/commit/229d7f8e220cc8d5ca06eff1ed85cb7d047c1d6c) ([#11616](https://www.github.com/tauri-apps/tauri/pull/11616) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix regression in creating child webviews on macOS and Windows, covering the whole window.\n- [`8c6d1e8e6`](https://www.github.com/tauri-apps/tauri/commit/8c6d1e8e6c852667bb223b5f4823948868c26d98) ([#11401](https://www.github.com/tauri-apps/tauri/pull/11401) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `App/AppHandle/Window/Webview/WebviewWindow::cursor_position` getter method failing on Linux with `GDK may only be used from the main thread`.\n- [`129414faa`](https://www.github.com/tauri-apps/tauri/commit/129414faa4e027c9035d56614682cacc0335a6a0) ([#11569](https://www.github.com/tauri-apps/tauri/pull/11569) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix webview not focused by default.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.1.0`\n- Upgraded to `tauri-runtime@2.2.0`\n\n## \\[2.1.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.2`\n\n## \\[2.1.1]\n\n### Bug Fixes\n\n- [`ef2482dde`](https://www.github.com/tauri-apps/tauri/commit/ef2482ddecf533181211ee435931fac650495bc5) ([#11366](https://www.github.com/tauri-apps/tauri/pull/11366)) Update wry to 0.46.1 to fix a crash on macOS older than Sequoia.\n\n## \\[2.1.0]\n\n### Bug Fixes\n\n- [`2d087ee4b`](https://www.github.com/tauri-apps/tauri/commit/2d087ee4b7d3e8849933f81284e4f5ed1aaa6455) ([#11268](https://www.github.com/tauri-apps/tauri/pull/11268) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Linux, fix commands, that use `Webview` or `WebviewWindow` as an argument, receiving an incorrect webview when using multi webviews.\n- [`2d087ee4b`](https://www.github.com/tauri-apps/tauri/commit/2d087ee4b7d3e8849933f81284e4f5ed1aaa6455) ([#11268](https://www.github.com/tauri-apps/tauri/pull/11268) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Linux, fix events only emitted to first webview only when using multi webviews.\n- [`2d087ee4b`](https://www.github.com/tauri-apps/tauri/commit/2d087ee4b7d3e8849933f81284e4f5ed1aaa6455) ([#11268](https://www.github.com/tauri-apps/tauri/pull/11268) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) On Linux, fix custom protocols receiving an incorrect webview label when using multi webviews\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.1.0`\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.1`\n- Upgraded to `tauri-runtime@2.0.1`\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78.\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0`\n- Upgraded to `tauri-runtime@2.0.0`\n\n## \\[2.0.0-rc.14]\n\n### New Features\n\n- [`a247170e1`](https://www.github.com/tauri-apps/tauri/commit/a247170e1f620a9b012274b11cfe51e90327d6e9) ([#11056](https://www.github.com/tauri-apps/tauri/pull/11056) by [@SpikeHD](https://www.github.com/tauri-apps/tauri/../../SpikeHD)) Expose the ability to enabled browser extensions in WebView2 on Windows.\n- [`9014a3f17`](https://www.github.com/tauri-apps/tauri/commit/9014a3f1765ca406ea5c3e5224267a79c52cd53d) ([#11066](https://www.github.com/tauri-apps/tauri/pull/11066) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindow::clear_all_browsing_data` and `Webview::clear_all_browsing_data` to clear the webview browsing data.\n- [`95df53a2e`](https://www.github.com/tauri-apps/tauri/commit/95df53a2ed96873cd35a4b14a5e312d07e4e3004) ([#11143](https://www.github.com/tauri-apps/tauri/pull/11143) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Add the ability to set theme dynamically using `Window::set_theme`, `App::set_theme`\n- [`d9d2502b4`](https://www.github.com/tauri-apps/tauri/commit/d9d2502b41e39efde679e30c8955006e2ba9ea64) ([#11140](https://www.github.com/tauri-apps/tauri/pull/11140) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewDispatch::hide` and `WebviewDispatch::show` methods.\n- [`de7414aab`](https://www.github.com/tauri-apps/tauri/commit/de7414aab935e45540594ea930eb60bae4dbc979) ([#11154](https://www.github.com/tauri-apps/tauri/pull/11154) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `Window::set_enabled` and `Window::is_enabled` methods\n\n### Bug Fixes\n\n- [`62b3a5cd1`](https://www.github.com/tauri-apps/tauri/commit/62b3a5cd1c804440c2130ab36cc3eadb3baf61cb) ([#11043](https://www.github.com/tauri-apps/tauri/pull/11043) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `localStorage` not shared between webviews that use the same data directory.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.0.0-rc.13`\n- Upgraded to `tauri-utils@2.0.0-rc.13`\n\n## \\[2.0.0-rc.13]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.12`\n- Upgraded to `tauri-runtime@2.0.0-rc.12`\n\n## \\[2.0.0-rc.12]\n\n### Enhancements\n\n- [`bc4804d48`](https://www.github.com/tauri-apps/tauri/commit/bc4804d4841efefd57fd1f3e147550a3340e2b31) ([#10924](https://www.github.com/tauri-apps/tauri/pull/10924) by [@madsmtm](https://www.github.com/tauri-apps/tauri/../../madsmtm)) Use `objc2` internally and in examples, leading to better memory safety.\n\n### Breaking Changes\n\n- [`bc4804d48`](https://www.github.com/tauri-apps/tauri/commit/bc4804d4841efefd57fd1f3e147550a3340e2b31) ([#10924](https://www.github.com/tauri-apps/tauri/pull/10924) by [@madsmtm](https://www.github.com/tauri-apps/tauri/../../madsmtm)) Change the pointer type of `PlatformWebview`'s `inner`, `controller`, `ns_window` and `view_controller` to `c_void`, to avoid publically depending on `objc`.\n\n## \\[2.0.0-rc.11]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.11`\n- Upgraded to `tauri-runtime@2.0.0-rc.11`\n\n## \\[2.0.0-rc.10]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.10`\n- Upgraded to `tauri-runtime@2.0.0-rc.10`\n\n## \\[2.0.0-rc.9]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.9`\n- Upgraded to `tauri-runtime@2.0.0-rc.9`\n\n## \\[2.0.0-rc.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.8`\n- Upgraded to `tauri-runtime@2.0.0-rc.8`\n- [`77056b194`](https://www.github.com/tauri-apps/tauri/commit/77056b194a2aa8be1b9865d707b741a6ed72ec56) ([#10895](https://www.github.com/tauri-apps/tauri/pull/10895) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Update tao to 0.30 and wry to 0.43.\n\n### Breaking Changes\n\n- [`5048a7293`](https://www.github.com/tauri-apps/tauri/commit/5048a7293b87b5b93aaefd42dedc0e551e08086c) ([#10840](https://www.github.com/tauri-apps/tauri/pull/10840) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) The `linux-ipc-protocol` feature is now always enabled, so the Cargo feature flag was removed.\n  This increases the minimum webkit2gtk version to a release that does not affect the minimum target Linux distros for Tauri apps.\n\n## \\[2.0.0-rc.7]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.7`\n- Upgraded to `tauri-runtime@2.0.0-rc.7`\n\n## \\[2.0.0-rc.6]\n\n### Bug Fixes\n\n- [`793ee0531`](https://www.github.com/tauri-apps/tauri/commit/793ee0531730597e6008c9c0dedabbab7a2bef53) ([#10700](https://www.github.com/tauri-apps/tauri/pull/10700) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow hyphens and underscores on app identifiers.\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.6`\n- Upgraded to `tauri-runtime@2.0.0-rc.6`\n\n## \\[2.0.0-rc.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.5`\n- Upgraded to `tauri-runtime@2.0.0-rc.5`\n\n## \\[2.0.0-rc.4]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.4`\n- Upgraded to `tauri-runtime@2.0.0-rc.4`\n\n## \\[2.0.0-rc.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.3`\n- Upgraded to `tauri-runtime@2.0.0-rc.3`\n- [`d39c392b7`](https://www.github.com/tauri-apps/tauri/commit/d39c392b7cec746da423211f9c74632abe4b6af5) ([#10655](https://www.github.com/tauri-apps/tauri/pull/10655) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Update `tao` to 0.29 and `wry` to 0.42.\n\n## \\[2.0.0-rc.2]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.2`\n- Upgraded to `tauri-runtime@2.0.0-rc.2`\n\n## \\[2.0.0-rc.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.1`\n- Upgraded to `tauri-runtime@2.0.0-rc.1`\n\n## \\[2.0.0-rc.0]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-rc.0`\n- Upgraded to `tauri-runtime@2.0.0-rc.0`\n\n## \\[2.0.0-beta.21]\n\n### What's Changed\n\n- [`9546548ec`](https://www.github.com/tauri-apps/tauri/commit/9546548ec0c83ba620b1bc9d1d424a7009d0b423) ([#10297](https://www.github.com/tauri-apps/tauri/pull/10297) by [@pewsheen](https://www.github.com/tauri-apps/tauri/../../pewsheen)) On macOS, set default titlebar style to `Visible` to prevent webview move out of the view.\n- [`da25f7353`](https://www.github.com/tauri-apps/tauri/commit/da25f7353070477ba969851e974379d7666d6806) ([#10242](https://www.github.com/tauri-apps/tauri/pull/10242) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `inner_size_constraints` method on `WindowBuilder` trait and `set_size_constraints` method on `WindowDispatch` trait.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.0.0-beta.21`\n\n## \\[2.0.0-beta.20]\n\n### Bug Fixes\n\n- [`afb102c59`](https://www.github.com/tauri-apps/tauri/commit/afb102c59ba0de27e330589269001e0d2a01576d) ([#10211](https://www.github.com/tauri-apps/tauri/pull/10211) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix window edge not working after setting resizable false and decorated false dynamically\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.19`\n- Upgraded to `tauri-runtime@2.0.0-beta.20`\n\n## \\[2.0.0-beta.19]\n\n### Bug Fixes\n\n- [`f29b78811`](https://www.github.com/tauri-apps/tauri/commit/f29b78811080bc8313459f34545152d939c62bf6) ([#9862](https://www.github.com/tauri-apps/tauri/pull/9862)) On Windows, handle resizing undecorated windows natively which improves performance and fixes a couple of annoyances with previous JS implementation:\n\n  - No more cursor flickering when moving the cursor across an edge.\n  - Can resize from top even when `data-tauri-drag-region` element exists there.\n  - Upon starting rezing, clicks don't go through elements behind it so no longer accidental clicks.\n\n### What's Changed\n\n- [`669b9c6b5`](https://www.github.com/tauri-apps/tauri/commit/669b9c6b5af791129b77ee440dacaa98288c906b) ([#9621](https://www.github.com/tauri-apps/tauri/pull/9621)) Set the gtk application to the identifier defined in `tauri.conf.json` to ensure the app uniqueness.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.18`\n- Upgraded to `tauri-runtime@2.0.0-beta.19`\n- [`d4c908cfb`](https://www.github.com/tauri-apps/tauri/commit/d4c908cfb8c567abdaf99b85f65f482ea81967e5) ([#10048](https://www.github.com/tauri-apps/tauri/pull/10048)) Update `windows` crate to version `0.57` and `webview2-com` crate to version `0.31`\n\n## \\[2.0.0-beta.18]\n\n### Enhancements\n\n- [`276c4b143`](https://www.github.com/tauri-apps/tauri/commit/276c4b14385e17cff15a2e5b57fd2a7cddef9f08)([#9832](https://www.github.com/tauri-apps/tauri/pull/9832)) Added `WindowBuilder::get_theme`.\n\n### What's Changed\n\n- [`9ac930380`](https://www.github.com/tauri-apps/tauri/commit/9ac930380a5df3fe700e68e75df8684d261ca292)([#9850](https://www.github.com/tauri-apps/tauri/pull/9850)) Emit `cargo:rustc-check-cfg` instruction so Cargo validates custom cfg attributes on Rust 1.80 (or nightly-2024-05-05).\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.17`\n- Upgraded to `tauri-runtime@2.0.0-beta.18`\n\n## \\[2.0.0-beta.17]\n\n### Security fixes\n\n- [`d950ac123`](https://www.github.com/tauri-apps/tauri/commit/d950ac1239817d17324c035e5c4769ee71fc197d) Only process IPC commands from the main frame.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.16`\n- Upgraded to `tauri-runtime@2.0.0-beta.17`\n\n## \\[2.0.0-beta.16]\n\n### New Features\n\n- [`78839b6d2`](https://www.github.com/tauri-apps/tauri/commit/78839b6d2f1005a5e6e1a54b0305136bae0c3a7c)([#4865](https://www.github.com/tauri-apps/tauri/pull/4865)) Add `RunEvent::Reopen` for handle click on dock icon on macOS.\n\n### Bug Fixes\n\n- [`c0bcc6c0b`](https://www.github.com/tauri-apps/tauri/commit/c0bcc6c0b74ef7167de85002a5c29b6f731bae41)([#9717](https://www.github.com/tauri-apps/tauri/pull/9717)) Fixes redraw tracing span not closing.\n\n### What's Changed\n\n- [`783ef0f2d`](https://www.github.com/tauri-apps/tauri/commit/783ef0f2d331f520fa827c3112f36c0b519b9292)([#9647](https://www.github.com/tauri-apps/tauri/pull/9647)) Changed `WebviewDispatch::url` getter to return a result.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.0.0-beta.16`\n- Upgraded to `tauri-utils@2.0.0-beta.15`\n\n## \\[2.0.0-beta.15]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.14`\n- Upgraded to `tauri-runtime@2.0.0-beta.15`\n\n## \\[2.0.0-beta.14]\n\n### New Features\n\n- [`477bb8cd4`](https://www.github.com/tauri-apps/tauri/commit/477bb8cd4ea88ade3f6c1f268ad1701a68150161)([#9297](https://www.github.com/tauri-apps/tauri/pull/9297)) Add `App/AppHandle/Window/Webview/WebviewWindow::cursor_position` getter to get the current cursor position.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@2.0.0-beta.14`\n\n## \\[2.0.0-beta.13]\n\n### What's Changed\n\n- [`005fe8ce1`](https://www.github.com/tauri-apps/tauri/commit/005fe8ce1ef71ea46a7d86f98bdf397ca81eb920)([#9410](https://www.github.com/tauri-apps/tauri/pull/9410)) Fix `closable`, `maximizable` and `minimizable` options not taking effect when used in tauri.conf.json or from JS APIs.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.13`\n- Upgraded to `tauri-runtime@2.0.0-beta.13`\n\n## \\[2.0.0-beta.12]\n\n### New Features\n\n- [`58a7a552d`](https://www.github.com/tauri-apps/tauri/commit/58a7a552d739b77b71d61af11c53f7f2dc7a6e7e)([#9378](https://www.github.com/tauri-apps/tauri/pull/9378)) Added the `set_zoom` function to the webview API.\n- [`58a7a552d`](https://www.github.com/tauri-apps/tauri/commit/58a7a552d739b77b71d61af11c53f7f2dc7a6e7e)([#9378](https://www.github.com/tauri-apps/tauri/pull/9378)) Add `zoom_hotkeys_enabled` to enable browser native zoom controls on creating webviews.\n\n### Bug Fixes\n\n- [`2f20fdf1d`](https://www.github.com/tauri-apps/tauri/commit/2f20fdf1d6b92fa8b9b38caf7321c3ce3e895f1b)([#9361](https://www.github.com/tauri-apps/tauri/pull/9361)) Fixes an issue causing compilation to fail for i686 and armv7 32-bit targets.\n- [`4c2e7477e`](https://www.github.com/tauri-apps/tauri/commit/4c2e7477e6869e2ce0578265825bbd42a5f28393)([#9309](https://www.github.com/tauri-apps/tauri/pull/9309)) Fix window centering not taking taskbar into account on Windows\n- [`02eaf0787`](https://www.github.com/tauri-apps/tauri/commit/02eaf07872b90225eead22ecdd6ff0a9ed5dd0ff)([#9428](https://www.github.com/tauri-apps/tauri/pull/9428)) Fixes `inner_size` crash when the window has no webviews.\n- [`f22ea2998`](https://www.github.com/tauri-apps/tauri/commit/f22ea2998619cc09c2d426f7b42211a80eed578e)([#9465](https://www.github.com/tauri-apps/tauri/pull/9465)) Revert the [fix](https://github.com/tauri-apps/tauri/pull/9246) for webview's visibility doesn't change with the app window on Windows as it caused white flashes on show/restore.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.12`\n- Upgraded to `tauri-runtime@2.0.0-beta.12`\n\n## \\[2.0.0-beta.11]\n\n### Bug Fixes\n\n- [`4c0c780e0`](https://www.github.com/tauri-apps/tauri/commit/4c0c780e00d8851be38cb1c22f636d9e4ed34a23)([#2690](https://www.github.com/tauri-apps/tauri/pull/2690)) Fix window inner size evaluation on macOS.\n- [`5bd47b446`](https://www.github.com/tauri-apps/tauri/commit/5bd47b44673f74b1b4e8d704b7a95539915ede76)([#9246](https://www.github.com/tauri-apps/tauri/pull/9246)) Fix webview's visibility doesn't change with the app window\n\n### What's Changed\n\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Updated `http` crate to `1.1`\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.11`\n- Upgraded to `tauri-runtime@2.0.0-beta.11`\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Upgraded to `wry@0.38.0`\n\n### Breaking Changes\n\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) The IPC handler closure now receives a `http::Request` instead of a String representing the request body.\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Rename `FileDrop` to `DragDrop` on structs, enums and enum variants. Also renamed `file_drop` to `drag_drop` on fields and function names.\n\n## \\[2.0.0-beta.10]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.10`\n- Upgraded to `tauri-runtime@2.0.0-beta.10`\n\n## \\[2.0.0-beta.9]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.9`\n- Upgraded to `tauri-runtime@2.0.0-beta.9`\n\n## \\[2.0.0-beta.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.8`\n- Upgraded to `tauri-runtime@2.0.0-beta.8`\n\n## \\[2.0.0-beta.7]\n\n### New Features\n\n- [`46de49aaa`](https://www.github.com/tauri-apps/tauri/commit/46de49aaad4a148fafc31d591be0e2ed12256507)([#9059](https://www.github.com/tauri-apps/tauri/pull/9059)) Added `set_auto_resize` method for the webview.\n\n### Enhancements\n\n- [`46de49aaa`](https://www.github.com/tauri-apps/tauri/commit/46de49aaad4a148fafc31d591be0e2ed12256507)([#9059](https://www.github.com/tauri-apps/tauri/pull/9059)) When using the `unstable` feature flag, `WebviewWindow` will internally use the child webview interface for flexibility.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.7`\n- Upgraded to `tauri-runtime@2.0.0-beta.7`\n\n## \\[2.0.0-beta.6]\n\n### Bug Fixes\n\n- [`222a96b7`](https://www.github.com/tauri-apps/tauri/commit/222a96b74b145fb48d3f0c109897962d56fae57a)([#8999](https://www.github.com/tauri-apps/tauri/pull/8999)) Fixes auto resize and positioning when using the multiwebview mode.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.6`\n- Upgraded to `tauri-runtime@2.0.0-beta.6`\n\n## \\[2.0.0-beta.5]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.5`\n- Upgraded to `tauri-runtime@2.0.0-beta.5`\n\n## \\[2.0.0-beta.4]\n\n### New Features\n\n- [`fdcaf935`](https://www.github.com/tauri-apps/tauri/commit/fdcaf935fa75ecfa2806939c4faad4fe9e880386)([#8939](https://www.github.com/tauri-apps/tauri/pull/8939)) Added the `reparent` function to the webview API.\n\n### Bug Fixes\n\n- [`6e3bd4b9`](https://www.github.com/tauri-apps/tauri/commit/6e3bd4b9f815ddde8b5eaf9f69991d4de80bb584)([#8942](https://www.github.com/tauri-apps/tauri/pull/8942)) Fix window centering not taking monitor scale into account\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.4`\n- Upgraded to `tauri-runtime@2.0.0-beta.4`\n- [`d75713ac`](https://www.github.com/tauri-apps/tauri/commit/d75713ac6c6115534e520303f5c38aa78704de69)([#8936](https://www.github.com/tauri-apps/tauri/pull/8936)) Upgraded to `wry@0.37.0`\n\n## \\[2.0.0-beta.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.3`\n- Upgraded to `tauri-runtime@2.0.0-beta.3`\n\n## \\[2.0.0-beta.2]\n\n### What's Changed\n\n- [`76ce9f61`](https://www.github.com/tauri-apps/tauri/commit/76ce9f61dd3c5bdd589c7557543894e1f770dd16)([#3002](https://www.github.com/tauri-apps/tauri/pull/3002)) Enhance centering a newly created window, it will no longer jump to center after being visible.\n- [`16e550ec`](https://www.github.com/tauri-apps/tauri/commit/16e550ec1503765158cdc3bb2a20e70ec710e981)([#8844](https://www.github.com/tauri-apps/tauri/pull/8844)) Add `WebviewEvent`, `RunEvent::WebviewEvent` and `WebviewDispatch::on_webview_event`.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.2`\n- Upgraded to `tauri-runtime@2.0.0-beta.2`\n- [`2f55bfec`](https://www.github.com/tauri-apps/tauri/commit/2f55bfecbf0244f3b5aa1ad7622182fca3fcdcbb)([#8795](https://www.github.com/tauri-apps/tauri/pull/8795)) Update `wry` to 0.36.\n\n### Breaking Changes\n\n- [`2f55bfec`](https://www.github.com/tauri-apps/tauri/commit/2f55bfecbf0244f3b5aa1ad7622182fca3fcdcbb)([#8795](https://www.github.com/tauri-apps/tauri/pull/8795)) Update raw-window-handle to 0.6.\n\n## \\[2.0.0-beta.1]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.1`\n- Upgraded to `tauri-runtime@2.0.0-beta.1`\n\n## \\[2.0.0-beta.0]\n\n### New Features\n\n- [`af610232`](https://www.github.com/tauri-apps/tauri/commit/af6102327376884364b2075b468bdf08ee0d02aa)([#8710](https://www.github.com/tauri-apps/tauri/pull/8710)) Added `Window::destroy` to force close a window.\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Add multiwebview support behind the `unstable` feature flag. See `WindowBuilder` and `WebviewBuilder` for more information.\n- [`00e15675`](https://www.github.com/tauri-apps/tauri/commit/00e1567584721644797b587205187f9cbe4e5cd1)([#8708](https://www.github.com/tauri-apps/tauri/pull/8708)) Added `RuntimeHandle::request_exit` function.\n\n### Bug Fixes\n\n- [`95da1a27`](https://www.github.com/tauri-apps/tauri/commit/95da1a27476e01e06f6ce0335df8535b662dd9c4)([#8713](https://www.github.com/tauri-apps/tauri/pull/8713)) Fix calling `set_activation_policy` when the event loop is running.\n\n### What's Changed\n\n- [`9f8037c2`](https://www.github.com/tauri-apps/tauri/commit/9f8037c2882abac19582025001675370f0d7b669)([#8633](https://www.github.com/tauri-apps/tauri/pull/8633)) On Windows, fix decorated window not transparent initially until resized.\n- [`9eaeb5a8`](https://www.github.com/tauri-apps/tauri/commit/9eaeb5a8cd95ae24b5e66205bdc2763cb7f965ce)([#8622](https://www.github.com/tauri-apps/tauri/pull/8622)) Added `WindowBuilder::transient_for` and Renamed `WindowBuilder::owner_window` to `WindowBuilder::owner` and `WindowBuilder::parent_window` to `WindowBuilder::parent`.\n- [`7f033f6d`](https://www.github.com/tauri-apps/tauri/commit/7f033f6dcd54c69a4193765a5c1584755ba92c61)([#8537](https://www.github.com/tauri-apps/tauri/pull/8537)) Add `Window::start_resize_dragging` and `ResizeDirection` enum.\n- [`6639a579`](https://www.github.com/tauri-apps/tauri/commit/6639a579c76d45210f33a72d37e21d4c5a9d334b)([#8441](https://www.github.com/tauri-apps/tauri/pull/8441)) Added the `WindowConfig::proxy_url` `WebviewBuilder::proxy_url() / WebviewWindowBuilder::proxy_url()` options when creating a webview.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-beta.0`\n- Upgraded to `tauri-runtime@2.0.0-beta.0`\n- Upgrated to `tao@0.25`.\n\n### Breaking Changes\n\n- [`af610232`](https://www.github.com/tauri-apps/tauri/commit/af6102327376884364b2075b468bdf08ee0d02aa)([#8710](https://www.github.com/tauri-apps/tauri/pull/8710)) `WindowDispatch::close` now triggers the `CloseRequested` flow.\n- [`9eaeb5a8`](https://www.github.com/tauri-apps/tauri/commit/9eaeb5a8cd95ae24b5e66205bdc2763cb7f965ce)([#8622](https://www.github.com/tauri-apps/tauri/pull/8622)) Changed `WindowBuilder::with_config` to take a reference to a `WindowConfig` instead of an owned value.\n\n## \\[1.0.0-alpha.9]\n\n### New Features\n\n- [`29ced5ce`](https://www.github.com/tauri-apps/tauri/commit/29ced5ceec40b2934094ade2db9a8855f294e1d1)([#8159](https://www.github.com/tauri-apps/tauri/pull/8159)) Added download event closure via `PendingWindow::download_handler`.\n\n### Enhancements\n\n- [`d621d343`](https://www.github.com/tauri-apps/tauri/commit/d621d3437ce3947175eecf345b2c6d1c4c7ce020)([#8607](https://www.github.com/tauri-apps/tauri/pull/8607)) Added tracing for window startup, plugins, `Window::eval`, events, IPC, updater and custom protocol request handlers behind the `tracing` feature flag.\n\n### What's Changed\n\n- [`cb640c8e`](https://www.github.com/tauri-apps/tauri/commit/cb640c8e949a3d78d78162e2e61b51bf8afae983)([#8393](https://www.github.com/tauri-apps/tauri/pull/8393)) Fix `RunEvent::WindowEvent(event: WindowEvent::FileDrop(FileDropEvent))` never triggered and always prevent default OS behavior when `disable_file_drop_handler` is not used.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.13`\n- Upgraded to `tauri-runtime@1.0.0-alpha.8`\n\n## \\[1.0.0-alpha.8]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.12`\n- Upgraded to `tauri-runtime@1.0.0-alpha.7`\n\n## \\[1.0.0-alpha.7]\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@1.0.0-alpha.6`\n- [\\`\\`](https://www.github.com/tauri-apps/tauri/commit/undefined) Update to wry v0.35.\n\n## \\[1.0.0-alpha.6]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.11`\n- Upgraded to `tauri-runtime@1.0.0-alpha.5`\n\n## \\[1.0.0-alpha.5]\n\n### New Features\n\n- [`74d2464d`](https://www.github.com/tauri-apps/tauri/commit/74d2464d0e490fae341ad73bdf2964cf215fe6c5)([#8116](https://www.github.com/tauri-apps/tauri/pull/8116)) Added `on_page_load` hook for `PendingWindow`.\n\n### Enhancements\n\n- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.10`\n- Upgraded to `tauri-runtime@1.0.0-alpha.4`\n- [`9580df1d`](https://www.github.com/tauri-apps/tauri/commit/9580df1d7b027befb9e5f025ea2cbaf2dcc82c8e)([#8084](https://www.github.com/tauri-apps/tauri/pull/8084)) Upgrade `gtk` to 0.18.\n- [`c7c2507d`](https://www.github.com/tauri-apps/tauri/commit/c7c2507da16a9beb71bf06745fe7ac1325ab7c2a)([#8035](https://www.github.com/tauri-apps/tauri/pull/8035)) Update `windows` to version `0.51` and `webview2-com` to version `0.27`\n- [`9580df1d`](https://www.github.com/tauri-apps/tauri/commit/9580df1d7b027befb9e5f025ea2cbaf2dcc82c8e)([#8084](https://www.github.com/tauri-apps/tauri/pull/8084)) Updated to wry@0.34, removing the `dox` feature flag.\n\n## \\[1.0.0-alpha.4]\n\n### New Features\n\n- [`c085adda`](https://www.github.com/tauri-apps/tauri/commit/c085addab58ba851398373c6fd13f9cb026d71e8)([#8009](https://www.github.com/tauri-apps/tauri/pull/8009)) Added `set_progress_bar` to `Window`.\n- [`c1ec0f15`](https://www.github.com/tauri-apps/tauri/commit/c1ec0f155118527361dd5645d920becbc8afd569)([#7933](https://www.github.com/tauri-apps/tauri/pull/7933)) Added `Window::set_always_on_bottom` and the `always_on_bottom` option when creating a window.\n- [`880266a7`](https://www.github.com/tauri-apps/tauri/commit/880266a7f697e1fe58d685de3bb6836ce5251e92)([#8031](https://www.github.com/tauri-apps/tauri/pull/8031)) Bump the MSRV to 1.70.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@1.0.0-alpha.3`\n- Upgraded to `tauri-utils@2.0.0-alpha.9`\n\n### Breaking Changes\n\n- [`8b166e9b`](https://www.github.com/tauri-apps/tauri/commit/8b166e9bf82e69ddb3200a3a825614980bd8d433)([#7949](https://www.github.com/tauri-apps/tauri/pull/7949)) Check if automation is enabled with the `TAURI_WEBVIEW_AUTOMATION` environment variable instead of `TAURI_AUTOMATION`.\n- [`2558fab8`](https://www.github.com/tauri-apps/tauri/commit/2558fab861006936296e8511e43ccd69a38f61b0)([#7939](https://www.github.com/tauri-apps/tauri/pull/7939)) Changed `WebviewId` to be an alias for `u32` instead of `u64`\n\n## \\[1.0.0-alpha.3]\n\n### Dependencies\n\n- Upgraded to `tauri-utils@2.0.0-alpha.8`\n- Upgraded to `tauri-runtime@1.0.0-alpha.2`\n\n## \\[1.0.0-alpha.2]\n\n### Bug Fixes\n\n- [`d5074af5`](https://www.github.com/tauri-apps/tauri/commit/d5074af562b2b5cb6c5711442097c4058af32db6)([#7801](https://www.github.com/tauri-apps/tauri/pull/7801)) Fixes custom protocol not working on Windows.\n\n## \\[1.0.0-alpha.1]\n\n### Enhancements\n\n- [`0d63732b`](https://www.github.com/tauri-apps/tauri/commit/0d63732b962e71b98430f8d7b34ea5b59a2e8bb4)([#7754](https://www.github.com/tauri-apps/tauri/pull/7754)) Update wry to 0.32 to include asynchronous custom protocol support.\n\n### What's Changed\n\n- [`6177150b`](https://www.github.com/tauri-apps/tauri/commit/6177150b6f83b52ca359d6e20f7e540f7554e4eb)([#7601](https://www.github.com/tauri-apps/tauri/pull/7601)) Changed `FileDropEvent` to include drop and hover position.\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@1.0.0-alpha.1`\n\n### Breaking Changes\n\n- [`0d63732b`](https://www.github.com/tauri-apps/tauri/commit/0d63732b962e71b98430f8d7b34ea5b59a2e8bb4)([#7754](https://www.github.com/tauri-apps/tauri/pull/7754)) `tauri-runtime` no longer implements its own HTTP types and relies on the `http` crate instead.\n\n## \\[1.0.0-alpha.0]\n\n### New Features\n\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) Add `Dispatch::default_vbox`\n- [`84c41597`](https://www.github.com/tauri-apps/tauri/commit/84c4159754b2e59244211ed9e1fc702d851a0562)([#6394](https://www.github.com/tauri-apps/tauri/pull/6394)) Added `primary_monitor` and `available_monitors` to `Runtime` and `RuntimeHandle`.\n- [`3b98141a`](https://www.github.com/tauri-apps/tauri/commit/3b98141aa26f74c641a4090874247b97079bd58a)([#3736](https://www.github.com/tauri-apps/tauri/pull/3736)) Added the `Opened` variant to `RunEvent`.\n- [`2a000e15`](https://www.github.com/tauri-apps/tauri/commit/2a000e150d02dff28c8b20ad097b29e209160045)([#7235](https://www.github.com/tauri-apps/tauri/pull/7235)) Implement navigate method\n\n### Dependencies\n\n- Upgraded to `tauri-runtime@1.0.0-alpha.0`\n- Upgraded to `tauri-utils@2.0.0-alpha.7`\n\n### Breaking Changes\n\n- [`fbeb5b91`](https://www.github.com/tauri-apps/tauri/commit/fbeb5b9185baeda19e865228179e3e44c165f1d9)([#7170](https://www.github.com/tauri-apps/tauri/pull/7170)) Removed the `linux-headers` feature (now always enabled) and added `linux-protocol-body`.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) `Dispatch::create_window`, `Runtime::create_window` and `RuntimeHandle::create_window` has been changed to accept a 3rd parameter which is a closure that takes `RawWindow` and to be executed right after the window is created and before the webview is added to the window.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) System tray and menu related APIs and structs have all been removed and are now implemented in tauri outside of the runtime-space.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) `Runtime::new` and `Runtime::new_any_thread` now accept a `RuntimeInitArgs`.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) Removed `system-tray` feature flag\n\n## \\[0.13.0-alpha.6]\n\n### New Features\n\n- [`e0f0dce2`](https://www.github.com/tauri-apps/tauri/commit/e0f0dce220730e2822fc202463aedf0166145de7)([#6442](https://www.github.com/tauri-apps/tauri/pull/6442)) Added the `window_effects` option when creating a window and `Window::set_effects` to change it at runtime.\n\n## \\[0.13.0-alpha.5]\n\n- [`39f1b04f`](https://www.github.com/tauri-apps/tauri/commit/39f1b04f7be4966488484829cd54c8ce72a04200)([#6943](https://www.github.com/tauri-apps/tauri/pull/6943)) Moved the `event` JS APIs to a plugin.\n- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65.\n- [`cebd7526`](https://www.github.com/tauri-apps/tauri/commit/cebd75261ac71b98976314a450cb292eeeec1515)([#6728](https://www.github.com/tauri-apps/tauri/pull/6728)) Moved the `clipboard` feature to its own plugin in the plugins-workspace repository.\n- [`3f17ee82`](https://www.github.com/tauri-apps/tauri/commit/3f17ee82f6ff21108806edb7b00500b8512b8dc7)([#6737](https://www.github.com/tauri-apps/tauri/pull/6737)) Moved the `global-shortcut` feature to its own plugin in the plugins-workspace repository.\n- [`31444ac1`](https://www.github.com/tauri-apps/tauri/commit/31444ac196add770f2ad18012d7c18bce7538f22)([#6725](https://www.github.com/tauri-apps/tauri/pull/6725)) Update `wry` to `0.28`\n\n## \\[0.13.0-alpha.4]\n\n- Added `android` configuration object under `tauri > bundle`.\n  - Bumped due to a bump in tauri-utils.\n  - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07\n\n## \\[0.13.0-alpha.3]\n\n- Pull changes from Tauri 1.3 release.\n  - [](https://www.github.com/tauri-apps/tauri/commit/undefined)  on undefined\n\n## \\[0.13.0-alpha.2]\n\n- Add `find_class`, `run_on_android_context` on `RuntimeHandle`.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n- Allow a wry plugin to be registered at runtime.\n  - [ae296f3d](https://www.github.com/tauri-apps/tauri/commit/ae296f3de16fb6a8badbad5555075a5861681fe5) refactor(tauri-runtime-wry): register runtime plugin after run() ([#6478](https://www.github.com/tauri-apps/tauri/pull/6478)) on 2023-03-17\n- Added the `shadow` option when creating a window and `Window::set_shadow`.\n  - [a81750d7](https://www.github.com/tauri-apps/tauri/commit/a81750d779bc72f0fdb7de90b7fbddfd8049b328) feat(core): add shadow APIs ([#6206](https://www.github.com/tauri-apps/tauri/pull/6206)) on 2023-02-08\n- Implemented `with_webview` on Android and iOS.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n\n## \\[0.13.0-alpha.1]\n\n- Update gtk to 0.16.\n  - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30\n- Bump the MSRV to 1.64.\n  - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30\n- Update wry to 0.26.\n  - [f0a1d9cd](https://www.github.com/tauri-apps/tauri/commit/f0a1d9cdbcfb645ce1c5f1cdd597f764991772cd) chore: update rfd and wry versions ([#6174](https://www.github.com/tauri-apps/tauri/pull/6174)) on 2023-02-03\n\n## \\[0.13.0-alpha.0]\n\n- Support `with_webview` for Android platform alowing execution of JNI code in context.\n  - [8ea87e9c](https://www.github.com/tauri-apps/tauri/commit/8ea87e9c9ca8ba4c7017c8281f78aacd08f45785) feat(android): with_webview access for jni execution ([#5148](https://www.github.com/tauri-apps/tauri/pull/5148)) on 2022-09-08\n\n## \\[0.14.5]\n\n### What's Changed\n\n- [`d42668ce`](https://www.github.com/tauri-apps/tauri/commit/d42668ce17494ab778f436aaa9b216d6db3f0b31)([#9003](https://www.github.com/tauri-apps/tauri/pull/9003)) Fix panic during intialization on wayland when using `clipboard` feature, instead propagate the error during API usage.\n\n## \\[0.14.4]\n\n### Bug Fixes\n\n- [`24210735`](https://www.github.com/tauri-apps/tauri/commit/2421073576a6d45783176be57b0188668558aff7)([#8117](https://www.github.com/tauri-apps/tauri/pull/8117)) Fixes a crash on macOS when accessing the windows map.\n- [`510b6226`](https://www.github.com/tauri-apps/tauri/commit/510b62261c70331ce3f5bfd24137dac1bc4a0bbe)([#8822](https://www.github.com/tauri-apps/tauri/pull/8822)) Add missing `arboard` feature flag to prevent panics in wayland session.\n\n## \\[0.14.3]\n\n### Bug Fixes\n\n- [`0d0501cb`](https://www.github.com/tauri-apps/tauri/commit/0d0501cb7b5e767c51a3697a148acfe84211a7ad)([#8394](https://www.github.com/tauri-apps/tauri/pull/8394)) Use `arboard` instead of `tao` clipboard implementation to prevent a crash.\n- [`b2f83f03`](https://www.github.com/tauri-apps/tauri/commit/b2f83f03a872baa91e2b6bbb22a3e7a5cd975dc0)([#8402](https://www.github.com/tauri-apps/tauri/pull/8402)) Use `Arc` instead of `Rc` to prevent crashes on macOS.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.2`\n- Upgraded to `tauri-runtime@0.14.2`\n\n## \\[0.14.2]\n\n### Enhancements\n\n- [`5e05236b`](https://www.github.com/tauri-apps/tauri/commit/5e05236b4987346697c7caae0567d3c50714c198)([#8289](https://www.github.com/tauri-apps/tauri/pull/8289)) Added tracing for window startup, plugins, `Window::eval`, events, IPC, updater and custom protocol request handlers behind the `tracing` feature flag.\n\n## \\[0.14.1]\n\n### Enhancements\n\n- [`9aa34ada`](https://www.github.com/tauri-apps/tauri/commit/9aa34ada5769dbefa7dfe5f7a6288b3d20b294e4)([#7645](https://www.github.com/tauri-apps/tauri/pull/7645)) Add setting to switch to `http://<scheme>.localhost/` for custom protocols on Windows.\n\n### Bug Fixes\n\n- [`4bf1e85e`](https://www.github.com/tauri-apps/tauri/commit/4bf1e85e6bf85a7ec92d50c8465bc0588a6399d8)([#7722](https://www.github.com/tauri-apps/tauri/pull/7722)) Properly respect the `focused` option when creating the webview.\n\n### Dependencies\n\n- Upgraded to `tauri-utils@1.5.0`\n- Upgraded to `tauri-runtime@0.14.1`\n\n## \\[0.14.0]\n\n### New Features\n\n- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `maximizable`, `minimizable` and `closable` methods to `WindowBuilder`.\n- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added `set_maximizable`, `set_minimizable`, `set_closable`, `is_maximizable`, `is_minimizable` and `is_closable` methods to the `Dispatch` trait.\n- [`000104bc`](https://www.github.com/tauri-apps/tauri/commit/000104bc3bc0c9ff3d20558ab9cf2080f126e9e0)([#6472](https://www.github.com/tauri-apps/tauri/pull/6472)) Add `Window::is_focused` getter.\n\n### Enhancements\n\n- [`d2710e9d`](https://www.github.com/tauri-apps/tauri/commit/d2710e9d2e8fd93975ef6494512370faa8cb3b7e)([#6944](https://www.github.com/tauri-apps/tauri/pull/6944)) Unpin `time`, `ignore`, and `winnow` crate versions. Developers now have to pin crates if needed themselves. A list of crates that need pinning to adhere to Tauri's MSRV will be visible in Tauri's GitHub workflow: https://github.com/tauri-apps/tauri/blob/dev/.github/workflows/test-core.yml#L85.\n\n### Bug Fixes\n\n- [`b41b57eb`](https://www.github.com/tauri-apps/tauri/commit/b41b57ebb27befd366db5befaafb6043c18fdfef)([#7105](https://www.github.com/tauri-apps/tauri/pull/7105)) Fix panics when registering an invalid global shortcuts or checking it is registered and return proper errors instead.\n\n### What's Changed\n\n- [`076e1a81`](https://www.github.com/tauri-apps/tauri/commit/076e1a81a50468e3dfb34ae9ca7e77c5e1758daa)([#7119](https://www.github.com/tauri-apps/tauri/pull/7119)) Use `u32` instead of `u64` for js event listener ids\n\n## \\[0.13.0]\n\n- Added the `additional_browser_args` option when creating a window.\n  - [3dc38b15](https://www.github.com/tauri-apps/tauri/commit/3dc38b150ea8c59c8ba67fd586f921016928f47c) feat(core): expose additional_browser_args to window config (fix: [#5757](https://www.github.com/tauri-apps/tauri/pull/5757)) ([#5799](https://www.github.com/tauri-apps/tauri/pull/5799)) on 2022-12-14\n- Added the `content_protected` option when creating a window and `Window::set_content_protected` to change it at runtime.\n  - [4ab5545b](https://www.github.com/tauri-apps/tauri/commit/4ab5545b7a831c549f3c65e74de487ede3ab7ce5) feat: add content protection api, closes [#5132](https://www.github.com/tauri-apps/tauri/pull/5132) ([#5513](https://www.github.com/tauri-apps/tauri/pull/5513)) on 2022-12-13\n- Added `Builder::device_event_filter` and `App::set_device_event_filter` methods.\n  - [73fd60ee](https://www.github.com/tauri-apps/tauri/commit/73fd60eef2b60f5dc84525ef9c315f4d80c4414f) expose set_device_event_filter in tauri ([#5562](https://www.github.com/tauri-apps/tauri/pull/5562)) on 2022-12-13\n- Fixes tray events not being delivered.\n  - [138cb8d7](https://www.github.com/tauri-apps/tauri/commit/138cb8d739b15bccdb388e555c20f17ffe16318c) fix(tauri-runtime-wry): tray event listener not registered ([#6270](https://www.github.com/tauri-apps/tauri/pull/6270)) on 2023-02-14\n- Add `is_minimized()` window method.\n  - [62144ef3](https://www.github.com/tauri-apps/tauri/commit/62144ef3be63b237869e511826edfb938e2c7174) feat: add is_minimized (fix [#3878](https://www.github.com/tauri-apps/tauri/pull/3878)) ([#5618](https://www.github.com/tauri-apps/tauri/pull/5618)) on 2022-12-13\n- Disable cursor mouse events on Linux.\n  - [8c842a54](https://www.github.com/tauri-apps/tauri/commit/8c842a54a6f3dc5327b4d737df7123dcddaa5769) feature: disable mouse event when building windows on Linux, closes [#5913](https://www.github.com/tauri-apps/tauri/pull/5913) ([#6025](https://www.github.com/tauri-apps/tauri/pull/6025)) on 2023-01-16\n- Bump minimum supported Rust version to 1.60.\n  - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31\n- Pin raw-window-handle to 0.5.0 to keep MSRV.\n  - [c46c09f3](https://www.github.com/tauri-apps/tauri/commit/c46c09f31d9f5169ca8a7e62406a9ea170e3a5c5) fix(deps): pin raw-window-handle to 0.5.0 ([#6480](https://www.github.com/tauri-apps/tauri/pull/6480)) on 2023-03-17\n- Add `title` getter on window.\n  - [233e43b0](https://www.github.com/tauri-apps/tauri/commit/233e43b0c34fada1ca025378533a0b76931a6540) feat: add `title` getter on window, closes [#5023](https://www.github.com/tauri-apps/tauri/pull/5023) ([#5515](https://www.github.com/tauri-apps/tauri/pull/5515)) on 2022-12-13\n- Added `TrayHandle::set_tooltip` and `SystemTray::with_tooltip`.\n  - [2265e097](https://www.github.com/tauri-apps/tauri/commit/2265e09718f6ebfeb1d200f11e1e1e069075af6e) feat(windows): implement `with_tooltip` ([#5938](https://www.github.com/tauri-apps/tauri/pull/5938)) on 2023-01-01\n- Added window's `url()` getter.\n  - [d17027e1](https://www.github.com/tauri-apps/tauri/commit/d17027e1a0db3e8c5ae81fc4f472c5918fbce611) feat: expose url method ([#5914](https://www.github.com/tauri-apps/tauri/pull/5914)) on 2022-12-26\n- On Windows, change webview theme based on Window theme for more accurate `prefers-color-scheme` support.\n  - [7a8d570d](https://www.github.com/tauri-apps/tauri/commit/7a8d570db72667367eb24b75ddc5dd07a968f7c0) fix: sync webview theme with window theme on Windows, closes [#5802](https://www.github.com/tauri-apps/tauri/pull/5802) ([#5874](https://www.github.com/tauri-apps/tauri/pull/5874)) on 2022-12-27\n- On Windows, Fix missing `WindowEvent::Focused` in `App::run` callback.\n  - [ff4ea1ea](https://www.github.com/tauri-apps/tauri/commit/ff4ea1eabbf2874b113c6b4698002929bbac737a) fix: dispatch focus event to app.run on Windows, closes [#6460](https://www.github.com/tauri-apps/tauri/pull/6460) ([#6504](https://www.github.com/tauri-apps/tauri/pull/6504)) on 2023-03-31\n- Implement the webview navigation handler.\n  - [3f35b452](https://www.github.com/tauri-apps/tauri/commit/3f35b452637ef1c794a423f1eda62a15d2ddaf42) Expose wry navigation_handler via WindowBuilder closes [#4080](https://www.github.com/tauri-apps/tauri/pull/4080) ([#5686](https://www.github.com/tauri-apps/tauri/pull/5686)) on 2022-12-27\n\n## \\[0.12.3]\n\n- Block remote URLs from accessing the IPC.\n  - [9c0593c33](https://www.github.com/tauri-apps/tauri/commit/9c0593c33af52cd9e00ec784d15f63efebdf039c) feat(core): block remote URLs from accessing the IPC on 2023-04-12\n\n## \\[0.12.2]\n\n- Fix compatibility with older Linux distributions.\n  - [b490308c](https://www.github.com/tauri-apps/tauri/commit/b490308c8897b893292951754607c2253abbc6e1) fix(core): compilation error on older Linux versions, fixes [#5684](https://www.github.com/tauri-apps/tauri/pull/5684) ([#5697](https://www.github.com/tauri-apps/tauri/pull/5697)) on 2022-11-28\n- Update wry to 0.23.\n  - [fdcd7733](https://www.github.com/tauri-apps/tauri/commit/fdcd77338c1a3a7ef8a8ea1907351c5c350ea7ba) chore(deps): update wry to 0.23 on 2022-12-08\n\n## \\[0.12.1]\n\n- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`.\n  - Bumped due to a bump in tauri-utils.\n  - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18\n\n## \\[0.12.0]\n\n- Add `accept_first_mouse` option for macOS windows.\n  - [95f467ad](https://www.github.com/tauri-apps/tauri/commit/95f467add51448319983c54e2f382c7c09fb72d6) feat(core): add window `accept_first_mouse` option, closes [#5347](https://www.github.com/tauri-apps/tauri/pull/5347) ([#5374](https://www.github.com/tauri-apps/tauri/pull/5374)) on 2022-10-17\n- Disable automatic window tabbing on macOS when the `tabbing_identifier` option is not defined, the window is transparent or does not have decorations.\n  - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19\n- Drop the WebContext when the WebView is dropped.\n  - [9d8b3774](https://www.github.com/tauri-apps/tauri/commit/9d8b377481abf975dc37f9050d2ac7b63ce353e9) feat(tauri-runtime-wry): drop the WebContext on WebView drop ([#5240](https://www.github.com/tauri-apps/tauri/pull/5240)) on 2022-10-19\n- Readd the option to create an unfocused window via the `focused` method. The `focus` function has been deprecated.\n  - [4036e15f](https://www.github.com/tauri-apps/tauri/commit/4036e15f5af933bdc0d0913508b5103958afc143) feat(core): reimplement window initial focus flag, closes [#5120](https://www.github.com/tauri-apps/tauri/pull/5120) ([#5338](https://www.github.com/tauri-apps/tauri/pull/5338)) on 2022-10-08\n- Add `hidden_title` option for macOS windows.\n  - [321f3fed](https://www.github.com/tauri-apps/tauri/commit/321f3fed19df40c1223099bce953332b7f00f7a9) feat(macos): `title_bar_style` and `hidden_title` window options, closes [#2663](https://www.github.com/tauri-apps/tauri/pull/2663) ([#3965](https://www.github.com/tauri-apps/tauri/pull/3965)) on 2022-09-30\n- Custom protocol headers are now implemented on Linux when running on webkit2gtk 2.36 or above.\n  - [357480f4](https://www.github.com/tauri-apps/tauri/commit/357480f4ae43aa8da99f7ba61ae2ee51b4552c60) feat(core): custom protocol headers on Linux, closes [#4496](https://www.github.com/tauri-apps/tauri/pull/4496) ([#5421](https://www.github.com/tauri-apps/tauri/pull/5421)) on 2022-10-17\n- Added `Runtime::show()`, `RuntimeHandle::show()`, `Runtime::hide()`, `RuntimeHandle::hide()` for hiding/showing the entire application on macOS.\n  - [39bf895b](https://www.github.com/tauri-apps/tauri/commit/39bf895b73ec6b53f5758815396ba85dda6b9c67) feat(macOS): Add application `show` and `hide` methods ([#3689](https://www.github.com/tauri-apps/tauri/pull/3689)) on 2022-10-03\n- Fix regression in `SystemTray::with_menu_on_left_click`\n  - [f8a3becb](https://www.github.com/tauri-apps/tauri/commit/f8a3becb287942db7f7b551b5db6aeb5a2e939ee) feat(core): add option to disable tray menu on left click, closes [#4584](https://www.github.com/tauri-apps/tauri/pull/4584) ([#4587](https://www.github.com/tauri-apps/tauri/pull/4587)) on 2022-07-05\n  - [7bbf167c](https://www.github.com/tauri-apps/tauri/commit/7bbf167c1c84493ea6e2353f720edafd7daa47e4) Apply Version Updates From Current Changes ([#4560](https://www.github.com/tauri-apps/tauri/pull/4560)) on 2022-07-06\n  - [63011ca8](https://www.github.com/tauri-apps/tauri/commit/63011ca84e7a22c8c0d8bd1c1be6592140f93ff2) fix(macos): fix regression in `with_menu_on_left_click`, closes [#5220](https://www.github.com/tauri-apps/tauri/pull/5220) ([#5235](https://www.github.com/tauri-apps/tauri/pull/5235)) on 2022-09-30\n- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28\n- Added `tabbing_identifier` to the window builder on macOS.\n  - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19\n- Add `title_bar_style` option for macOS windows.\n  - [321f3fed](https://www.github.com/tauri-apps/tauri/commit/321f3fed19df40c1223099bce953332b7f00f7a9) feat(macos): `title_bar_style` and `hidden_title` window options, closes [#2663](https://www.github.com/tauri-apps/tauri/pull/2663) ([#3965](https://www.github.com/tauri-apps/tauri/pull/3965)) on 2022-09-30\n- Fix regression introduce in tauri@1.1 which prevented removing tray icon when the app exits on Windows.\n  - [f756cd5e](https://www.github.com/tauri-apps/tauri/commit/f756cd5e7ecc86f178f8d602eded1e1b6ecb51f3) fix(core): wait for tray cleanup before exiting app, closes [#5244](https://www.github.com/tauri-apps/tauri/pull/5244) ([#5245](https://www.github.com/tauri-apps/tauri/pull/5245)) on 2022-10-04\n- Added methods to set the system tray title on macOS.\n  - [8f1ace77](https://www.github.com/tauri-apps/tauri/commit/8f1ace77956ac3477826ceb059a191e55b3fff93) feat: expose `set_title` for MacOS tray ([#5182](https://www.github.com/tauri-apps/tauri/pull/5182)) on 2022-09-30\n- Added the `user_agent` option when creating a window.\n  - [a6c94119](https://www.github.com/tauri-apps/tauri/commit/a6c94119d8545d509723b147c273ca5edfe3729f) feat(core): expose user_agent to window config ([#5317](https://www.github.com/tauri-apps/tauri/pull/5317)) on 2022-10-02\n\n## \\[0.11.2]\n\n- Block remote URLs from accessing the IPC.\n  - [58ea0b452](https://www.github.com/tauri-apps/tauri/commit/58ea0b45268dbd46cbac0ebb0887353d057ca767) feat(core): block remote URLs from accessing the IPC on 2023-04-12\n\n## \\[0.11.1]\n\n- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs.\n  - Bumped due to a bump in tauri-utils.\n  - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16\n\n## \\[0.11.0]\n\n- Ignore window events with unknown IDs.\n  - [0668dd42](https://www.github.com/tauri-apps/tauri/commit/0668dd42204b163f11aaf31f45106c8551f15942) fix(tauri-runtime-wry): ignore events on unknown windows on 2022-08-29\n- Implement theme APIs for Linux.\n  - [f21cbecd](https://www.github.com/tauri-apps/tauri/commit/f21cbecdeb3571ac4ad971b9a865ff62a131a176) feat(core): implement theme APIs for Linux ([#4808](https://www.github.com/tauri-apps/tauri/pull/4808)) on 2022-08-02\n- Changed `windows` map to be stored in a `RefCell` instead of a `Mutex`.\n  - [64546cb9](https://www.github.com/tauri-apps/tauri/commit/64546cb9cca2fe56cf81cfc4aaf85c4e1d58877c) refactor: use RefCell instead of Mutex for windows map, closes [#4870](https://www.github.com/tauri-apps/tauri/pull/4870) ([#4909](https://www.github.com/tauri-apps/tauri/pull/4909)) on 2022-08-10\n- Added APIs to create a system tray at runtime.\n  - [4d063ae9](https://www.github.com/tauri-apps/tauri/commit/4d063ae9ee9538cd6fa5e01b80070c6edf8eaeb9) feat(core): create system tray at runtime, closes [#2278](https://www.github.com/tauri-apps/tauri/pull/2278) ([#4862](https://www.github.com/tauri-apps/tauri/pull/4862)) on 2022-08-09\n- Update windows to 0.39.0 and webview2-com to 0.19.1.\n  - [e6d9b670](https://www.github.com/tauri-apps/tauri/commit/e6d9b670b0b314ed667b0e164f2c8d27048e678f) refactor: remove unneeded focus code ([#5065](https://www.github.com/tauri-apps/tauri/pull/5065)) on 2022-09-03\n\n## \\[0.10.3]\n\n- Block remote URLs from accessing the IPC.\n  - [fa90214b0](https://www.github.com/tauri-apps/tauri/commit/fa90214b052b1a5d38d54fbf1ca422b4c37cfd1f) feat(core): block remote URLs from accessing the IPC on 2023-04-12\n\n## \\[0.10.2]\n\n- Disable drag-n-drop of tao based on `fileDropEnabled` value.\n  - [a1d569bb](https://www.github.com/tauri-apps/tauri/commit/a1d569bbc9cfdd58258916df594911e1c512a75e) fix(core): disable tao's drag-n-drop based on `fileDropEnabled`, closes [#4580](https://www.github.com/tauri-apps/tauri/pull/4580) ([#4592](https://www.github.com/tauri-apps/tauri/pull/4592)) on 2022-07-05\n- Added option to disable tray menu on left click on macOS.\n  - [f8a3becb](https://www.github.com/tauri-apps/tauri/commit/f8a3becb287942db7f7b551b5db6aeb5a2e939ee) feat(core): add option to disable tray menu on left click, closes [#4584](https://www.github.com/tauri-apps/tauri/pull/4584) ([#4587](https://www.github.com/tauri-apps/tauri/pull/4587)) on 2022-07-05\n\n## \\[0.10.1]\n\n- Fixes a deadlock on the file drop handler.\n  - [23a48007](https://www.github.com/tauri-apps/tauri/commit/23a48007c0df7346fa45c76dfaf9235a157f59ec) fix(tauri-runtime-wry): deadlock on file drop, closes [#4527](https://www.github.com/tauri-apps/tauri/pull/4527) ([#4535](https://www.github.com/tauri-apps/tauri/pull/4535)) on 2022-06-30\n- Send theme value only once on the getter function implementation on macOS.\n  - [63841c10](https://www.github.com/tauri-apps/tauri/commit/63841c10609c3d7337ba6cd68ae126b18987014d) fix(tauri-runtime-wry): do not send theme twice on macOS, closes [#4532](https://www.github.com/tauri-apps/tauri/pull/4532) ([#4540](https://www.github.com/tauri-apps/tauri/pull/4540)) on 2022-06-30\n- Fixes a deadlock when the window focus change on Windows.\n  - [185b0e31](https://www.github.com/tauri-apps/tauri/commit/185b0e314ece9563cd7c83a16466b2b8b9167eb3) fix(tauri-runtime-wry): deadlock when window focus change, closes [#4533](https://www.github.com/tauri-apps/tauri/pull/4533) ([#4539](https://www.github.com/tauri-apps/tauri/pull/4539)) on 2022-06-30\n\n## \\[0.10.0]\n\n- Implement `raw_window_handle::HasRawWindowHandle` on Linux.\n  - [3efbc67f](https://www.github.com/tauri-apps/tauri/commit/3efbc67f7469ce65a2d9ea4ff2b60b51d2a36aa5) feat: implement `raw_window_handle` on Linux ([#4469](https://www.github.com/tauri-apps/tauri/pull/4469)) on 2022-06-26\n- Moved the window and menu event listeners to the window struct.\n  - [46196fe9](https://www.github.com/tauri-apps/tauri/commit/46196fe922f4f1b38057155c6113236cfa4b3597) refactor(tauri-runtime-wry): move window and menu listeners to window ([#4485](https://www.github.com/tauri-apps/tauri/pull/4485)) on 2022-06-27\n- Refactored the `tauri-runtime-wry` plugin interface.\n  - [e39e2999](https://www.github.com/tauri-apps/tauri/commit/e39e2999e0ab1843a8195ba83aea3d6de705c3d8) refactor(tauri-runtime-wry): enhance plugin interface ([#4476](https://www.github.com/tauri-apps/tauri/pull/4476)) on 2022-06-27\n- Removed the `hwnd` and `ns_window` functions from `Dispatch` in favor of `raw_window_handle`.\n  - [3efbc67f](https://www.github.com/tauri-apps/tauri/commit/3efbc67f7469ce65a2d9ea4ff2b60b51d2a36aa5) feat: implement `raw_window_handle` on Linux ([#4469](https://www.github.com/tauri-apps/tauri/pull/4469)) on 2022-06-26\n- The theme API is now implemented on macOS 10.14+.\n  - [6d94ce42](https://www.github.com/tauri-apps/tauri/commit/6d94ce42353204a02fe9c82ed397d349439f75ef) feat(core): theme is now implemented on macOS ([#4380](https://www.github.com/tauri-apps/tauri/pull/4380)) on 2022-06-17\n- Suppress unused variable warning in release builds.\n  - [45981851](https://www.github.com/tauri-apps/tauri/commit/45981851e35119266c1a079e1ff27a39f1fdfaed) chore(lint): unused variable warnings for release builds ([#4411](https://www.github.com/tauri-apps/tauri/pull/4411)) on 2022-06-22\n- Update tao to 0.12 and wry to 0.19.\n  - [f6edc6df](https://www.github.com/tauri-apps/tauri/commit/f6edc6df29b1c45b483fa87c481a3b95730b131b) chore(deps): update tao to 0.12, wry to 0.19, closes [#3220](https://www.github.com/tauri-apps/tauri/pull/3220) ([#4502](https://www.github.com/tauri-apps/tauri/pull/4502)) on 2022-06-28\n- Fixes deadlocks when using window setters in the main thread.\n  - [123f6e69](https://www.github.com/tauri-apps/tauri/commit/123f6e69f60ca6d4b2fd738ca3ff5cf016d8e814) fix(tauri-runtime-wry): release windows lock immediately, closes [#4390](https://www.github.com/tauri-apps/tauri/pull/4390) ([#4392](https://www.github.com/tauri-apps/tauri/pull/4392)) on 2022-06-19\n\n## \\[0.9.0]\n\n- Upgrade to `stable`!\n  - Bumped due to a bump in tauri-utils.\n  - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15\n\n## \\[0.8.1]\n\n- Add `Menu::os_default` which will create a menu filled with default menu items and submenus.\n  - Bumped due to a bump in tauri-runtime.\n  - [4c4acc30](https://www.github.com/tauri-apps/tauri/commit/4c4acc3094218dd9cee0f1ad61810c979e0b41fa) feat: implement `Default` for `Menu`, closes [#2398](https://www.github.com/tauri-apps/tauri/pull/2398) ([#4291](https://www.github.com/tauri-apps/tauri/pull/4291)) on 2022-06-15\n\n## \\[0.8.0]\n\n- Removed `TrayIcon` and renamed `WindowIcon` to `Icon`, a shared type for both icons.\n  - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14\n\n## \\[0.7.0]\n\n- **Breaking change**: Removed the `gtk-tray` and `ayatana-tray` Cargo features.\n  - [6216eb49](https://www.github.com/tauri-apps/tauri/commit/6216eb49e72863bfb6d4c9edb8827b21406ac393) refactor(core): drop `ayatana-tray` and `gtk-tray` Cargo features ([#4247](https://www.github.com/tauri-apps/tauri/pull/4247)) on 2022-06-02\n\n## \\[0.6.0]\n\n- Account the monitor position when centering a window.\n  - [a7a9fde1](https://www.github.com/tauri-apps/tauri/commit/a7a9fde16fb7c35d48d4f97e83ff95b8baf9e090) fix(core): account for monitor position when centering window ([#4166](https://www.github.com/tauri-apps/tauri/pull/4166)) on 2022-05-21\n- Update `windows-rs` to `0.37.0`, which requires Rust 1.61.0+.\n  - [2326be39](https://www.github.com/tauri-apps/tauri/commit/2326be39821890cdd4de76e7029a531424dcb26f) feat(core): update windows-rs to 0.37.0 ([#4199](https://www.github.com/tauri-apps/tauri/pull/4199)) on 2022-05-24\n\n## \\[0.5.2]\n\n- Use the event loop proxy to create a window so it doesn't deadlock on Windows.\n  - [61e37652](https://www.github.com/tauri-apps/tauri/commit/61e37652b931520424d6a93a134e67893703d992) fix(core): deadlock when creating window from IPC handler, closes [#4121](https://www.github.com/tauri-apps/tauri/pull/4121) ([#4123](https://www.github.com/tauri-apps/tauri/pull/4123)) on 2022-05-13\n\n## \\[0.5.1]\n\n- Added the `plugin` method to the `Wry` runtime, allowing extensions to the event loop.\n  - [c8e0e5b9](https://www.github.com/tauri-apps/tauri/commit/c8e0e5b97d542e549b37be08b545515c862af0e5) feat(tauri-runtime-wry): add plugin API ([#4094](https://www.github.com/tauri-apps/tauri/pull/4094)) on 2022-05-10\n- Update wry to 0.16.2 and webkit2gtk to 0.18.0.\n  - [71a553b7](https://www.github.com/tauri-apps/tauri/commit/71a553b715312e2bcceb963c83e42cffca7a63bc) chore(deps): update wry to 0.16.2, webkit2gtk to 0.18.0 ([#4099](https://www.github.com/tauri-apps/tauri/pull/4099)) on 2022-05-10\n\n## \\[0.5.0]\n\n- The file drop event payloads are now percent-decoded.\n  - [a0ecd81a](https://www.github.com/tauri-apps/tauri/commit/a0ecd81a934e1aa8935151a74cad686786054204) fix(core): percent decode file drop payloads, closes [#4034](https://www.github.com/tauri-apps/tauri/pull/4034) ([#4035](https://www.github.com/tauri-apps/tauri/pull/4035)) on 2022-05-03\n- Fixes a crash when using the menu with the inspector window focused on macOS. In this case the `window_id` will be the id of the first app window.\n  - [891eb748](https://www.github.com/tauri-apps/tauri/commit/891eb748cf590895dc3f1666f8dbd6082b21e04e) fix(tauri-runtime-wry): menu even panic on macOS inspector, closes [#3875](https://www.github.com/tauri-apps/tauri/pull/3875) ([#4027](https://www.github.com/tauri-apps/tauri/pull/4027)) on 2022-05-02\n- Fixes a freeze when calling `set_size` in the main thread on Windows.\n  - [8f259f4e](https://www.github.com/tauri-apps/tauri/commit/8f259f4ef89be3da11b57222c8b66af9487ab736) fix(core): use EventLoopProxy to prevent set_size freeze closes [#3990](https://www.github.com/tauri-apps/tauri/pull/3990) ([#4014](https://www.github.com/tauri-apps/tauri/pull/4014)) on 2022-04-30\n- Expose methods to access the underlying native handles of the webview.\n  - [c82b4761](https://www.github.com/tauri-apps/tauri/commit/c82b4761e1660592472dc55308ad69d9efc5855b) feat(core): expose `with_webview` API to access the platform webview ([#4058](https://www.github.com/tauri-apps/tauri/pull/4058)) on 2022-05-04\n\n## \\[0.4.0]\n\n- \\**Breaking change::* Added the `clipboard` Cargo feature.\n  - [24e4ff20](https://www.github.com/tauri-apps/tauri/commit/24e4ff208ee0fe1a4cc5b10667ea0922ac63dfb5) refactor(core): add clipboard Cargo feature, enhancing binary size ([#3957](https://www.github.com/tauri-apps/tauri/pull/3957)) on 2022-04-24\n- Expose Window cursor APIs `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position`.\n  - [c54ddfe9](https://www.github.com/tauri-apps/tauri/commit/c54ddfe9338e7eb90b4d5b02dfde687d432d5bc1) feat: expose window cursor APIs, closes [#3888](https://www.github.com/tauri-apps/tauri/pull/3888) [#3890](https://www.github.com/tauri-apps/tauri/pull/3890) ([#3935](https://www.github.com/tauri-apps/tauri/pull/3935)) on 2022-04-21\n- Fixes a panic when using the `create_tao_window` API.\n  - [320329a9](https://www.github.com/tauri-apps/tauri/commit/320329a9a7d8a249c0fc9dee6db5669057ca8b39) fix(core): insert to webview_id_map on tao window creation, closes [#3883](https://www.github.com/tauri-apps/tauri/pull/3883) ([#3932](https://www.github.com/tauri-apps/tauri/pull/3932)) on 2022-04-21\n- Fixes a panic when a menu event is triggered when all windows are minimized on macOS.\n  - [70ff55c1](https://www.github.com/tauri-apps/tauri/commit/70ff55c1aa69ed59cd2a78d865e1cb398ef2a4ba) fix(core): panic on menu event with minimized windows, closes [#3902](https://www.github.com/tauri-apps/tauri/pull/3902) ([#3918](https://www.github.com/tauri-apps/tauri/pull/3918)) on 2022-04-20\n- Fixes a rendering issue when resizing the window with the devtools open.\n  - [80b714af](https://www.github.com/tauri-apps/tauri/commit/80b714af6b31365b9026bc92f8631b1721950447) fix: rendering issue when resizing with devtools open closes [#3914](https://www.github.com/tauri-apps/tauri/pull/3914) [#3814](https://www.github.com/tauri-apps/tauri/pull/3814) ([#3915](https://www.github.com/tauri-apps/tauri/pull/3915)) on 2022-04-19\n- \\**Breaking change::* Added the `global-shortcut` Cargo feature.\n  - [e11878bc](https://www.github.com/tauri-apps/tauri/commit/e11878bcf7174b261a1fa146fc7d564d12e6312a) refactor(core): add global-shortcut Cargo feature, enhancing binary size ([#3956](https://www.github.com/tauri-apps/tauri/pull/3956)) on 2022-04-24\n- Added `WindowEvent::ThemeChanged(theme)`.\n  - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21\n- Added `theme` getter on `Window`.\n  - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21\n- Added `theme` setter to the WindowBuilder.\n  - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21\n- Create webview immediately when executed in the main thread.\n  - [fa2baba7](https://www.github.com/tauri-apps/tauri/commit/fa2baba76c8f59c81f2a2f7139033a09d14d89da) feat(core): create webview immediately when running in main thread ([#3891](https://www.github.com/tauri-apps/tauri/pull/3891)) on 2022-04-12\n\n## \\[0.3.5]\n\n- Fixes `WindowEvent::Destroyed` not firing.\n  - [169b5035](https://www.github.com/tauri-apps/tauri/commit/169b5035a93e3f33a420d4b2b0f8943e6404e07f) fix(core): actually fire `WindowEvent::Destroyed` ([#3797](https://www.github.com/tauri-apps/tauri/pull/3797)) on 2022-03-28\n\n## \\[0.3.4]\n\n- Added `close_devtools` and `is_devtools_open` APIs to the `Dispatch` trait.\n  - [e05d718a](https://www.github.com/tauri-apps/tauri/commit/e05d718a7b46476d1fe4817c169008080e84f959) feat(core): add hotkey to toggle devtools, closes [#3776](https://www.github.com/tauri-apps/tauri/pull/3776) ([#3791](https://www.github.com/tauri-apps/tauri/pull/3791)) on 2022-03-28\n- Emit `RunEvent::Exit` on `tao::event::Event::LoopDestroyed` instead of after `RunEvent::ExitRequested`.\n  - [3c4ee7c9](https://www.github.com/tauri-apps/tauri/commit/3c4ee7c997fa3ff696bcfd5b8c82fecaca16bf49) refactor(wry): emit `RunEvent::Exit` on `Event::LoopDestroyed` ([#3785](https://www.github.com/tauri-apps/tauri/pull/3785)) on 2022-03-27\n- **Breaking change:** The `MenuItem::About` variant is now associated with a tuple value `(String, AboutMetadata)`.\n  - [5fb74332](https://www.github.com/tauri-apps/tauri/commit/5fb74332ab9210ac062d96b0e9afd1c942ee2911) chore(deps): update wry to 0.14, tao to 0.7 ([#3790](https://www.github.com/tauri-apps/tauri/pull/3790)) on 2022-03-28\n- Support window parenting on macOS\n  - [4e807a53](https://www.github.com/tauri-apps/tauri/commit/4e807a53e2d6d3f3cd5293d90013d5cdded5454e) Support window parenting on macOS, closes [#3751](https://www.github.com/tauri-apps/tauri/pull/3751) ([#3754](https://www.github.com/tauri-apps/tauri/pull/3754)) on 2022-03-23\n- The file drop event is now part of the `WindowEvent` enum instead of a having a dedicated handler.\n  - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13\n- **Breaking change:** Use the dedicated `WindowEvent` enum on `RunEvent`.\n  - [edad9f4f](https://www.github.com/tauri-apps/tauri/commit/edad9f4f55dcc69a06cd9d6d5a5068c94ecb77dd) refactor(core): add `RunEvent::WindowEvent` ([#3793](https://www.github.com/tauri-apps/tauri/pull/3793)) on 2022-03-28\n- Added `create_proxy` to the `Runtime` and `RuntimeHandle` traits.\n  - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15\n- Allow specifying a user event type for the event loop message.\n  - [5d538ec2](https://www.github.com/tauri-apps/tauri/commit/5d538ec27c246274df4ff5b8057ff78b6364a43f) refactor(core): use the event loop proxy to send updater events ([#3687](https://www.github.com/tauri-apps/tauri/pull/3687)) on 2022-03-15\n- Use a random window id instead of `tao::window::WindowId` to not block the thread waiting for the event loop to process the window creation.\n  - [7cd39c70](https://www.github.com/tauri-apps/tauri/commit/7cd39c70c9ecd62cc9b60d0ab93f10ce0a6dd8b4) refactor(core): use random window id to simplify window creation, closes [#3645](https://www.github.com/tauri-apps/tauri/pull/3645) [#3597](https://www.github.com/tauri-apps/tauri/pull/3597) ([#3684](https://www.github.com/tauri-apps/tauri/pull/3684)) on 2022-03-15\n- Update `wry` to `0.14` and `tao` to `0.7`.\n  - [f2d24ef2](https://www.github.com/tauri-apps/tauri/commit/f2d24ef2fbd95ec7d3433ba651964f4aa3b7f48c) chore(deps): update wry ([#1482](https://www.github.com/tauri-apps/tauri/pull/1482)) on 2021-04-14\n  - [e267ebf1](https://www.github.com/tauri-apps/tauri/commit/e267ebf1f1009b99829e0a7d71519925f5792f9f) Apply Version Updates From Current Changes ([#1486](https://www.github.com/tauri-apps/tauri/pull/1486)) on 2021-04-14\n  - [5fb74332](https://www.github.com/tauri-apps/tauri/commit/5fb74332ab9210ac062d96b0e9afd1c942ee2911) chore(deps): update wry to 0.14, tao to 0.7 ([#3790](https://www.github.com/tauri-apps/tauri/pull/3790)) on 2022-03-28\n- Added the `WindowEvent::FileDrop` variant.\n  - [07d1584c](https://www.github.com/tauri-apps/tauri/commit/07d1584cf06ea326aa45d8044bee1b77ecba5006) feat(core): add `WindowEvent::FileDrop`, closes [#3664](https://www.github.com/tauri-apps/tauri/pull/3664) ([#3686](https://www.github.com/tauri-apps/tauri/pull/3686)) on 2022-03-13\n\n## \\[0.3.3]\n\n- Fixes a deadlock on the `Focused` event when the window is not visible.\n  - [c08cc6d5](https://www.github.com/tauri-apps/tauri/commit/c08cc6d50041ec887d3070c41bb2c793dbac5155) fix(core): deadlock on focus events with invisible window,[#3534](https://www.github.com/tauri-apps/tauri/pull/3534) ([#3622](https://www.github.com/tauri-apps/tauri/pull/3622)) on 2022-03-06\n- **Breaking change:** Move `ico` and `png` parsing behind `icon-ico` and `icon-png` Cargo features.\n  - [8c935872](https://www.github.com/tauri-apps/tauri/commit/8c9358725a17dcc2acaf4d10c3f654afdff586b0) refactor(core): move `png` and `ico` behind Cargo features ([#3588](https://www.github.com/tauri-apps/tauri/pull/3588)) on 2022-03-05\n- Print a warning to stderr if the window transparency has been set to true but `macos-private-api` is not enabled.\n  - [080755b5](https://www.github.com/tauri-apps/tauri/commit/080755b5377a3c0a17adf1d03e63555350422f0a) feat(core): warn if private APIs are not enabled, closes [#3481](https://www.github.com/tauri-apps/tauri/pull/3481) ([#3511](https://www.github.com/tauri-apps/tauri/pull/3511)) on 2022-02-19\n\n## \\[0.3.2]\n\n- Fix requirements for `RuntimeHandle`, `ClipboardManager`, `GlobalShortcutHandle` and `TrayHandle`.\n  - Bumped due to a bump in tauri-runtime.\n  - [84895a9c](https://www.github.com/tauri-apps/tauri/commit/84895a9cd270fc743e236d0f4d4cd6210b24a30f) fix(runtime): trait requirements ([#3489](https://www.github.com/tauri-apps/tauri/pull/3489)) on 2022-02-17\n\n## \\[0.3.1]\n\n- Change default value for the `freezePrototype` configuration to `false`.\n  - Bumped due to a bump in tauri-utils.\n  - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12\n\n## \\[0.3.0]\n\n- Fix `window.center` panic when window size is bigger than screen size.\n  - [76ce9f61](https://www.github.com/tauri-apps/tauri/commit/76ce9f61dd3c5bdd589c7557543894e1f770dd16) fix(core): fix `window.center` panic when window size > screen, closes [#2978](https://www.github.com/tauri-apps/tauri/pull/2978) ([#3002](https://www.github.com/tauri-apps/tauri/pull/3002)) on 2021-12-09\n- Enable non-session cookie persistence on Linux.\n  - [d7c02a30](https://www.github.com/tauri-apps/tauri/commit/d7c02a30a56de79100804969138b379e703f0e07) feat(core): persist non-session cookies on Linux ([#3052](https://www.github.com/tauri-apps/tauri/pull/3052)) on 2021-12-09\n- Fixes a deadlock when creating a window from a menu event handler.\n  - [9c82006b](https://www.github.com/tauri-apps/tauri/commit/9c82006b2fe166d20510183e36cee099bf96e8d9) fix(core): deadlock when creating window from menu handler, closes [#3110](https://www.github.com/tauri-apps/tauri/pull/3110) ([#3126](https://www.github.com/tauri-apps/tauri/pull/3126)) on 2021-12-28\n- Fixes `WindowEvent::Focus` and `WindowEvent::Blur` events not firing.\n  - [3b33d67a](https://www.github.com/tauri-apps/tauri/commit/3b33d67aa4f48dcf4e32b3b8a5f45e83808efc2d) fix: re-adding focus/blur events for linux and macos (fix [#2485](https://www.github.com/tauri-apps/tauri/pull/2485)) ([#2489](https://www.github.com/tauri-apps/tauri/pull/2489)) on 2021-08-24\n- Use webview's inner_size instead of window's value to get the correct size on macOS.\n  - [4c0c780e](https://www.github.com/tauri-apps/tauri/commit/4c0c780e00d8851be38cb1c22f636d9e4ed34a23) fix(core): window's inner_size usage, closes [#2187](https://www.github.com/tauri-apps/tauri/pull/2187) ([#2690](https://www.github.com/tauri-apps/tauri/pull/2690)) on 2021-09-29\n- Reimplement `remove_system_tray` on Windows to drop the `SystemTray` to run its cleanup code.\n  - [a03b8554](https://www.github.com/tauri-apps/tauri/commit/a03b85545a4b0b61a598a43eabe96e03565dcaf0) fix(core): tray not closing on Windows ([#3351](https://www.github.com/tauri-apps/tauri/pull/3351)) on 2022-02-07\n- Replace `WindowBuilder`'s `has_menu` with `get_menu`.\n  - [ac37b56e](https://www.github.com/tauri-apps/tauri/commit/ac37b56ef43c9e97039967a5fd99f0d2dccb5b5a) fix(core): menu id map not reflecting the current window menu ([#2726](https://www.github.com/tauri-apps/tauri/pull/2726)) on 2021-10-08\n- Fix empty header from CORS on Linux.\n  - [b48487e6](https://www.github.com/tauri-apps/tauri/commit/b48487e6a7b33f5a352e542fae21a2efd53ce295) Fix empty header from CORS on Linux, closes [#2327](https://www.github.com/tauri-apps/tauri/pull/2327) ([#2762](https://www.github.com/tauri-apps/tauri/pull/2762)) on 2021-10-18\n- The `run_return` API is now available on Linux.\n  - [8483fde9](https://www.github.com/tauri-apps/tauri/commit/8483fde975aac8833d2ce426e42fb40aeaeecba9) feat(core): expose `run_return` on Linux ([#3352](https://www.github.com/tauri-apps/tauri/pull/3352)) on 2022-02-07\n- Allow window, global shortcut and clipboard APIs to be called on the main thread.\n  - [2812c446](https://www.github.com/tauri-apps/tauri/commit/2812c4464b93a365ab955935d05b5cea8cb03aab) feat(core): window, shortcut and clipboard API calls on main thread ([#2659](https://www.github.com/tauri-apps/tauri/pull/2659)) on 2021-09-26\n  - [d24fd8d1](https://www.github.com/tauri-apps/tauri/commit/d24fd8d10242da3da143a971d976b42ec4de6079) feat(tauri-runtime-wry): allow window creation and closing on the main thread ([#2668](https://www.github.com/tauri-apps/tauri/pull/2668)) on 2021-09-27\n- Change event loop callbacks definition to allow callers to move in mutable values.\n  - [bdbf905e](https://www.github.com/tauri-apps/tauri/commit/bdbf905e5d802b58693d2bd27582ce4269faf79c) Transformed event-loop callback to FnMut to allow mutable values ([#2667](https://www.github.com/tauri-apps/tauri/pull/2667)) on 2021-09-27\n- **Breaking change:** Add `macos-private-api` feature flag, enabled via `tauri.conf.json > tauri > macOSPrivateApi`.\n  - [6ac21b3c](https://www.github.com/tauri-apps/tauri/commit/6ac21b3cef7f14358df38cc69ea3d277011accaf) feat: add private api feature flag ([#7](https://www.github.com/tauri-apps/tauri/pull/7)) on 2022-01-09\n- Refactor `create_tao_window` API to return `Weak<Window>` instead of `Arc<Window>`.\n  - [c1494b35](https://www.github.com/tauri-apps/tauri/commit/c1494b353233c6a9552d7ace962fdf8d5b1f199a) refactor: return Weak<Window> on create_tao_window on 2021-08-31\n- Added `any_thread` constructor on the `Runtime` trait (only possible on Linux and Windows).\n  - [af44bf81](https://www.github.com/tauri-apps/tauri/commit/af44bf8168310cf77fbe102a53e7c433f11641a3) feat(core): allow app run on any thread on Linux & Windows, closes [#3172](https://www.github.com/tauri-apps/tauri/pull/3172) ([#3353](https://www.github.com/tauri-apps/tauri/pull/3353)) on 2022-02-07\n- Added `run_on_main_thread` API on `RuntimeHandle`.\n  - [53fdfe52](https://www.github.com/tauri-apps/tauri/commit/53fdfe52bb30d52653c72ca9f42506c3863dcf4a) feat(core): expose `run_on_main_thread` API ([#2711](https://www.github.com/tauri-apps/tauri/pull/2711)) on 2021-10-04\n- **Breaking change:** Renamed the `RPC` interface to `IPC`.\n  - [3420aa50](https://www.github.com/tauri-apps/tauri/commit/3420aa5031b3274a95c6c5fa0f8683ca13213396) refactor: IPC handler \\[TRI-019] ([#9](https://www.github.com/tauri-apps/tauri/pull/9)) on 2022-01-09\n- Added `open_devtools` to the `Dispatcher` trait.\n  - [55aa22de](https://www.github.com/tauri-apps/tauri/commit/55aa22de80c3de873e29bcffcb5b2fe236a637a6) feat(core): add `Window#open_devtools` API, closes [#1213](https://www.github.com/tauri-apps/tauri/pull/1213) ([#3350](https://www.github.com/tauri-apps/tauri/pull/3350)) on 2022-02-07\n- The minimum Rust version is now `1.56`.\n  - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22\n- Replace all of the `winapi` crate references with the `windows` crate, and replace `webview2` and `webview2-sys` with `webview2-com` and `webview2-com-sys` built with the `windows` crate. This goes along with updates to the TAO and WRY `next` branches.\n  - [bb00d5bd](https://www.github.com/tauri-apps/tauri/commit/bb00d5bd6c9dfcb6bdd0d308dadb70e6c6aafe5c) Replace winapi with windows crate and use webview2-com instead of webview2 ([#2615](https://www.github.com/tauri-apps/tauri/pull/2615)) on 2021-09-24\n- Update the `windows` crate to 0.25.0, which comes with pre-built libraries. WRY and Tao can both reference the same types directly from the `windows` crate instead of sharing bindings in `webview2-com-sys`.\n  - [34be6cf3](https://www.github.com/tauri-apps/tauri/commit/34be6cf37a98ee7cbd66623ebddae08e5a6520fd) Update webview2-com and windows crates ([#2875](https://www.github.com/tauri-apps/tauri/pull/2875)) on 2021-11-11\n- This is a temporary fix of null pointer crash on `get_content` of web resource request.\n  We will switch it back once upstream is updated.\n  - [84f6e3e8](https://www.github.com/tauri-apps/tauri/commit/84f6e3e84a34b01b7fa04f5c4719acb921ef4263) Switch to next branch of wry ([#2574](https://www.github.com/tauri-apps/tauri/pull/2574)) on 2021-09-10\n- Update wry to 0.13.\n  - [343ea3e2](https://www.github.com/tauri-apps/tauri/commit/343ea3e2e8d51bac63ab651289295c26fcc841d8) Update wry to 0.13 ([#3336](https://www.github.com/tauri-apps/tauri/pull/3336)) on 2022-02-06\n\n## \\[0.2.1]\n\n- Migrate to latest custom protocol allowing `Partial content` streaming and Header parsing.\n  - [539e4489](https://www.github.com/tauri-apps/tauri/commit/539e4489e0bac7029d86917e9982ea49e02fe489) refactor: custom protocol ([#2503](https://www.github.com/tauri-apps/tauri/pull/2503)) on 2021-08-23\n\n## \\[0.2.0]\n\n- Fix blur/focus events being incorrect on Windows.\n  - [d832d575](https://www.github.com/tauri-apps/tauri/commit/d832d575d9b03a0ff78accabe4631cc638c08c3b) fix(windows): use webview events on windows ([#2277](https://www.github.com/tauri-apps/tauri/pull/2277)) on 2021-07-23\n\n- Add `ExitRequested` event that allows preventing the app from exiting when all windows are closed, and an `AppHandle.exit()` function to exit the app manually.\n  - [892c63a0](https://www.github.com/tauri-apps/tauri/commit/892c63a0538f8d62680dce5848657128ad6b7af3) feat([#2287](https://www.github.com/tauri-apps/tauri/pull/2287)): Add `ExitRequested` event to let users prevent app from exiting ([#2293](https://www.github.com/tauri-apps/tauri/pull/2293)) on 2021-08-09\n\n- Update gtk and its related libraries to v0.14. This also remove requirements of `clang` as build dependency.\n  - [63ad3039](https://www.github.com/tauri-apps/tauri/commit/63ad303903bbee7c9a7382413b342e2a05d3ea75) chore(linux): bump gtk to v0.14 ([#2361](https://www.github.com/tauri-apps/tauri/pull/2361)) on 2021-08-07\n\n- Implement `Debug` on public API structs and enums.\n  - [fa9341ba](https://www.github.com/tauri-apps/tauri/commit/fa9341ba18ba227735341530900714dba0f27291) feat(core): implement `Debug` on public API structs/enums, closes [#2292](https://www.github.com/tauri-apps/tauri/pull/2292) ([#2387](https://www.github.com/tauri-apps/tauri/pull/2387)) on 2021-08-11\n\n- Fix the error \"cannot find type MenuHash in this scope\"\n  - [226414d1](https://www.github.com/tauri-apps/tauri/commit/226414d1a588c8bc2b540a71fcd84c318319d6af) \"cannot find type `MenuHash` in this scope\" ([#2240](https://www.github.com/tauri-apps/tauri/pull/2240)) on 2021-07-20\n\n- Panic when a dispatcher getter method (`Window`, `GlobalShortcutHandle`, `ClipboardManager` and `MenuHandle` APIs) is called on the main thread.\n  - [50ffdc06](https://www.github.com/tauri-apps/tauri/commit/50ffdc06fbde56aba32b4291fd130104935d1408) feat(core): panic when a dispatcher getter is used on the main thread ([#2455](https://www.github.com/tauri-apps/tauri/pull/2455)) on 2021-08-16\n\n- Remove menu feature flag since there's no package dependency need to be installed on any platform anymore.\n  - [f81ebddf](https://www.github.com/tauri-apps/tauri/commit/f81ebddfcc1aea0d4989706aef43538e8ea98bea) feat: remove menu feature flag ([#2415](https://www.github.com/tauri-apps/tauri/pull/2415)) on 2021-08-13\n\n- Adds `Resumed` and `MainEventsCleared` variants to the `RunEvent` enum.\n  - [6be3f433](https://www.github.com/tauri-apps/tauri/commit/6be3f4339168651fe4e003b09f7d181fd12cd5a8) feat(core): add `Resumed` and `MainEventsCleared` events, closes [#2127](https://www.github.com/tauri-apps/tauri/pull/2127) ([#2439](https://www.github.com/tauri-apps/tauri/pull/2439)) on 2021-08-15\n\n- Adds `set_activation_policy` API to the `Runtime` trait (macOS only).\n  - [4a031add](https://www.github.com/tauri-apps/tauri/commit/4a031add69014a1f3823f4ea19b172a2557f6794) feat(core): expose `set_activation_policy`, closes [#2258](https://www.github.com/tauri-apps/tauri/pull/2258) ([#2420](https://www.github.com/tauri-apps/tauri/pull/2420)) on 2021-08-13\n\n- Allow creation of empty Window with `create_tao_window()` and management with `send_tao_window_event()` on the AppHandler.\n  - [88080855](https://www.github.com/tauri-apps/tauri/commit/8808085541a629b8e22b612a06cef01cf9b3722e) feat(window): Allow creation of Window without `wry` ([#2321](https://www.github.com/tauri-apps/tauri/pull/2321)) on 2021-07-29\n  - [15566cfd](https://www.github.com/tauri-apps/tauri/commit/15566cfd64f5072fa4980a6ce5b33259958e9021) feat(core): add API to send wry window message to the event loop ([#2339](https://www.github.com/tauri-apps/tauri/pull/2339)) on 2021-08-02\n\n- - Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color.\n\n- Images you mark as template images should consist of only black and clear colors. You can use the alpha channel in the image to adjust the opacity of black content, however.\n\n- [426a6b49](https://www.github.com/tauri-apps/tauri/commit/426a6b49962de8faf061db2e820ac10fcbb300d6) feat(macOS): Implement tray icon template ([#2322](https://www.github.com/tauri-apps/tauri/pull/2322)) on 2021-07-29\n\n- Add `Event::Ready` on the `run()` callback. Triggered once when the event loop is ready.\n  - [28c6b7ad](https://www.github.com/tauri-apps/tauri/commit/28c6b7adfe98e701b158e936eafb7541ddc700e0) feat: add `Event::Ready` ([#2433](https://www.github.com/tauri-apps/tauri/pull/2433)) on 2021-08-15\n\n- Add webdriver support to Tauri.\n  - [be76fb1d](https://www.github.com/tauri-apps/tauri/commit/be76fb1dfe73a1605cc2ad246418579f4c2e1999) WebDriver support ([#1972](https://www.github.com/tauri-apps/tauri/pull/1972)) on 2021-06-23\n  - [b4426eda](https://www.github.com/tauri-apps/tauri/commit/b4426eda9e64fcdd25a2d72e548b8b0fbfa09619) Revert \"WebDriver support ([#1972](https://www.github.com/tauri-apps/tauri/pull/1972))\" on 2021-06-23\n  - [4b2aa356](https://www.github.com/tauri-apps/tauri/commit/4b2aa35684632ed2afd7dec4ad848df5704868e4) Add back WebDriver support ([#2324](https://www.github.com/tauri-apps/tauri/pull/2324)) on 2021-08-01\n\n## \\[0.1.4]\n\n- Allow preventing window close when the user requests it.\n  - [8157a68a](https://www.github.com/tauri-apps/tauri/commit/8157a68af1d94de1b90a14aa44139bb123b3436b) feat(core): allow listening to event loop events & prevent window close ([#2131](https://www.github.com/tauri-apps/tauri/pull/2131)) on 2021-07-06\n- Fixes SVG loading on custom protocol.\n  - [e663bdd5](https://www.github.com/tauri-apps/tauri/commit/e663bdd5938830ab4eba961e69c3985191b499dd) fix(core): svg mime type ([#2129](https://www.github.com/tauri-apps/tauri/pull/2129)) on 2021-06-30\n- Fixes `center` and `focus` not being allowed in `tauri.conf.json > tauri > windows` and ignored in `WindowBuilderWrapper`.\n  - [bc2c331d](https://www.github.com/tauri-apps/tauri/commit/bc2c331dec3dec44c79e659b082b5fb6b65cc5ea) fix: center and focus not being allowed in config ([#2199](https://www.github.com/tauri-apps/tauri/pull/2199)) on 2021-07-12\n- Expose `gtk_window` getter.\n  - [e0a8e09c](https://www.github.com/tauri-apps/tauri/commit/e0a8e09cab6799eeb9ec524b5f7780d1e5a84299) feat(core): expose `gtk_window`, closes [#2083](https://www.github.com/tauri-apps/tauri/pull/2083) ([#2141](https://www.github.com/tauri-apps/tauri/pull/2141)) on 2021-07-02\n- Remove a few locks requirement in tauri-runtime-wry\n  - [6569c2bf](https://www.github.com/tauri-apps/tauri/commit/6569c2bf5caf24b009cad1e2cffba25418d6bb68) refactor(wry): remove a few locks requirements ([#2137](https://www.github.com/tauri-apps/tauri/pull/2137)) on 2021-07-02\n- Fix macOS high CPU usage.\n  - [a280ee90](https://www.github.com/tauri-apps/tauri/commit/a280ee90af0749ce18d6d0b00939b06473717bc9) Fix high cpu usage on mac, fix [#2074](https://www.github.com/tauri-apps/tauri/pull/2074) ([#2125](https://www.github.com/tauri-apps/tauri/pull/2125)) on 2021-06-30\n- Bump `wry` 0.11 and fix focus integration to make it compatible with tao 0.4.\n  - [f0a8db62](https://www.github.com/tauri-apps/tauri/commit/f0a8db62e445dbbc5770e7addf0390ce3844c1ea) core(deps): bump `wry` to `0.11` ([#2210](https://www.github.com/tauri-apps/tauri/pull/2210)) on 2021-07-15\n- `Params` has been removed, along with all the associated types on it. Functions that previously accepted those\n  associated types now accept strings instead. Type that used a generic parameter `Params` now use `Runtime` instead. If\n  you use the `wry` feature, then types with a `Runtime` generic parameter should default to `Wry`, letting you omit the\n  explicit type and let the compiler infer it instead.\n\n`tauri`:\n\n- See `Params` note\n- If you were using `Params` inside a function parameter or definition, all references to it have been replaced with a\n  simple runtime that defaults to `Wry`. If you are not using a custom runtime, just remove `Params` from the definition\n  of functions/items that previously took it. If you are using a custom runtime, you *may* need to pass the runtime type\n  to these functions.\n- If you were using custom types for `Params` (uncommon and if you don't understand you probably were not using it), all\n  methods that were previously taking the custom type now takes an `Into<String>` or a `&str`. The types were already\n  required to be string-able, so just make sure to convert it into a string before passing it in if this breaking change\n  affects you.\n\n`tauri-macros`:\n\n- (internal) Added private `default_runtime` proc macro to allow us to give item definitions a custom runtime only when\n  the specified feature is enabled.\n\n`tauri-runtime`:\n\n- See `Params` note\n- Removed `Params`, `MenuId`, `Tag`, `TagRef`.\n- Added `menu::{MenuHash, MenuId, MenuIdRef}` as type aliases for the internal type that menu types now use.\n  - All previous menu items that had a `MenuId` generic now use the underlying `MenuId` type without a generic.\n- `Runtime`, `RuntimeHandle`, and `Dispatch` have no more generic parameter on `create_window(...)` and instead use the\n  `Runtime` type directly\n- `Runtime::system_tray` has no more `MenuId` generic and uses the string based `SystemTray` type directly.\n- (internal) `CustomMenuItem::id_value()` is now hashed on creation and exposed as the `id` field with type `MenuHash`.\n\n`tauri-runtime-wry`:\n\n- See `Params` note\n- update menu and runtime related types to the ones changed in `tauri-runtime`.\n\n`tauri-utils`:\n\n- `Assets::get` signature has changed to take a `&AssetKey` instead of `impl Into<AssetKey>` to become trait object\n  safe.\n- [fd8fab50](https://www.github.com/tauri-apps/tauri/commit/fd8fab507c8fa1b113b841af14c6693eb3955f6b) refactor(core): remove `Params` and replace with strings ([#2191](https://www.github.com/tauri-apps/tauri/pull/2191)) on 2021-07-15\n\n## \\[0.1.3]\n\n- `Window` is now `Send + Sync` on Windows.\n  - [fe32afcc](https://www.github.com/tauri-apps/tauri/commit/fe32afcc933920d6282ae1d63b041b182278a031) fix(core): `Window` must be `Send + Sync` on Windows, closes [#2078](https://www.github.com/tauri-apps/tauri/pull/2078) ([#2093](https://www.github.com/tauri-apps/tauri/pull/2093)) on 2021-06-27\n\n## \\[0.1.2]\n\n- Adds `clipboard` APIs (write and read text).\n  - [285bf64b](https://www.github.com/tauri-apps/tauri/commit/285bf64bf9569efb2df904c69c6df405ff0d62e2) feat(core): add clipboard writeText and readText APIs ([#2035](https://www.github.com/tauri-apps/tauri/pull/2035)) on 2021-06-21\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Fixes window event being emitted to all windows listeners.\n  - [fca97640](https://www.github.com/tauri-apps/tauri/commit/fca976404e6bec373a81332572458c4c44f7bb3a) fix(wry): window event listeners being emitted to all windows ([#2056](https://www.github.com/tauri-apps/tauri/pull/2056)) on 2021-06-23\n- Panic on window getters usage on the main thread when the event loop is not running and document it.\n  - [ab3eb44b](https://www.github.com/tauri-apps/tauri/commit/ab3eb44bac7a3bf73a4985df38ccc2b87a913be7) fix(core): deadlock on window getters, fixes [#1893](https://www.github.com/tauri-apps/tauri/pull/1893) ([#1998](https://www.github.com/tauri-apps/tauri/pull/1998)) on 2021-06-16\n- Adds `focus` API to the WindowBuilder.\n  - [5f351622](https://www.github.com/tauri-apps/tauri/commit/5f351622c7812ad1bb56ddb37364ccaa4124c24b) feat(core): add focus API to the WindowBuilder and WindowOptions, [#1737](https://www.github.com/tauri-apps/tauri/pull/1737) on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds support to PNG icons.\n  - [40b717ed](https://www.github.com/tauri-apps/tauri/commit/40b717edc57288a1393fad0529390e101ab903c1) feat(core): set window icon on Linux, closes [#1922](https://www.github.com/tauri-apps/tauri/pull/1922) ([#1937](https://www.github.com/tauri-apps/tauri/pull/1937)) on 2021-06-01\n- Adds `is_decorated` getter on Window.\n  - [f58a2114](https://www.github.com/tauri-apps/tauri/commit/f58a2114fbfd5307c349f05c88f2e08fd8baa8aa) feat(core): add `is_decorated` Window getter on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `is_resizable` getter on Window.\n  - [1e8af280](https://www.github.com/tauri-apps/tauri/commit/1e8af280c27f381828d6209722b10e889082fa00) feat(core): add `is_resizable` Window getter on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `is_visible` getter on Window.\n  - [36506c96](https://www.github.com/tauri-apps/tauri/commit/36506c967de82bc7ff453d11e6104ecf66d7a588) feat(core): add `is_visible` API on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Removes `image` dependency. For now only `.ico` icons on Windows are supported, and we'll implement other types on demand to optimize bundle size.\n  - [1be37a3f](https://www.github.com/tauri-apps/tauri/commit/1be37a3f30ff789d9396ec9009f9c0dd0bb928a7) refactor(core): remove `image` dependency ([#1859](https://www.github.com/tauri-apps/tauri/pull/1859)) on 2021-05-18\n- The `run_on_main_thread` API now uses WRY's UserEvent, so it wakes the event loop.\n  - [9bf82f0d](https://www.github.com/tauri-apps/tauri/commit/9bf82f0d9261808f58bdb5b5dbd6a255e5dcd333) fix(core): `run_on_main_thread` now wakes the event loop ([#1949](https://www.github.com/tauri-apps/tauri/pull/1949)) on 2021-06-04\n- Adds global shortcut interfaces.\n  - [3280c4aa](https://www.github.com/tauri-apps/tauri/commit/3280c4aa91e50a8ccdd561a8b48a12a4a13ea8d5) refactor(core): global shortcut is now provided by `tao` ([#2031](https://www.github.com/tauri-apps/tauri/pull/2031)) on 2021-06-21\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `request_user_attention` API to the `Dispatcher` trait.\n  - [7dcca6e9](https://www.github.com/tauri-apps/tauri/commit/7dcca6e9281182b11ad3d4a79871f09b30b9b419) feat(core): add `request_user_attention` API, closes [#2023](https://www.github.com/tauri-apps/tauri/pull/2023) ([#2026](https://www.github.com/tauri-apps/tauri/pull/2026)) on 2021-06-20\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `fn run_iteration` (macOS and Windows only) to the Runtime trait.\n  - [8c0d0739](https://www.github.com/tauri-apps/tauri/commit/8c0d0739eebf7286b64a5380e922746411eb52c6) feat(core): add `run_iteration`, `parent_window` and `owner_window` APIs, closes [#1872](https://www.github.com/tauri-apps/tauri/pull/1872) ([#1874](https://www.github.com/tauri-apps/tauri/pull/1874)) on 2021-05-21\n- Adds `show_menu`, `hide_menu` and `is_menu_visible` APIs to the `Dispatcher` trait.\n  - [954460c5](https://www.github.com/tauri-apps/tauri/commit/954460c5205d57444ef4b1412051fbedf3e38676) feat(core): MenuHandle `show`, `hide`, `is_visible` and `toggle` APIs ([#1958](https://www.github.com/tauri-apps/tauri/pull/1958)) on 2021-06-15\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `set_focus` API on Window.\n  - [bb6992f8](https://www.github.com/tauri-apps/tauri/commit/bb6992f888196ca7c87bb2fe74ad2bd8bf393e05) feat(core): add `set_focus` window API, fixes [#1737](https://www.github.com/tauri-apps/tauri/pull/1737) on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `set_skip_taskbar` API on Window.\n  - [e06aa277](https://www.github.com/tauri-apps/tauri/commit/e06aa277384450cfef617c0e57b0d5d403bb1e7f) feat(core): add `set_skip_taskbar` API on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Update `wry` to v0.10.0 and replace the removed `dispatch_script` and `evaluate_script` methods with the new `evaluate_script` method in `handle_event_loop`.\n  - [cca8115d](https://www.github.com/tauri-apps/tauri/commit/cca8115d9c813d13efb30a38445d5bda009a7f97) refactor: update wry, simplify script eval ([#1965](https://www.github.com/tauri-apps/tauri/pull/1965)) on 2021-06-16\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `skip_taskbar` API to the WindowBuilder.\n  - [5525b03a](https://www.github.com/tauri-apps/tauri/commit/5525b03a78a2232c650043fbd9894ce1553cad41) feat(core): add `skip_taskbar` API to the WindowBuilder/WindowOptions on 2021-05-30\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- Adds `Window#center` and `WindowBuilder#center` APIs.\n  - [5cba6eb4](https://www.github.com/tauri-apps/tauri/commit/5cba6eb4d28d53f06855d60d4d0eae6b95233ccf) feat(core): add window `center` API, closes [#1822](https://www.github.com/tauri-apps/tauri/pull/1822) ([#1954](https://www.github.com/tauri-apps/tauri/pull/1954)) on 2021-06-05\n- Adds `parent_window` and `owner_window` setters to the `WindowBuilder` (Windows only).\n  - [8c0d0739](https://www.github.com/tauri-apps/tauri/commit/8c0d0739eebf7286b64a5380e922746411eb52c6) feat(core): add `run_iteration`, `parent_window` and `owner_window` APIs, closes [#1872](https://www.github.com/tauri-apps/tauri/pull/1872) ([#1874](https://www.github.com/tauri-apps/tauri/pull/1874)) on 2021-05-21\n- Adds window native handle getter (HWND on Windows).\n  - [abf78c58](https://www.github.com/tauri-apps/tauri/commit/abf78c5860cdc52fbfd2bc5dbca29a864e2da8f9) fix(core): set parent window handle on dialogs, closes [#1876](https://www.github.com/tauri-apps/tauri/pull/1876) ([#1889](https://www.github.com/tauri-apps/tauri/pull/1889)) on 2021-05-21\n\n## \\[0.1.1]\n\n- Fixes `system-tray` feature usage.\n  - [1ab8dd9](https://www.github.com/tauri-apps/tauri/commit/1ab8dd93e670d2a2d070c7a6ec48308a0ab32f1a) fix(core): `system-tray` cargo feature usage, fixes [#1798](https://www.github.com/tauri-apps/tauri/pull/1798) ([#1801](https://www.github.com/tauri-apps/tauri/pull/1801)) on 2021-05-12\n- Fixes webview transparency.\n  - [f5a480f](https://www.github.com/tauri-apps/tauri/commit/f5a480fea34ab3a75751f1ca760a38b3e53da2cc) fix(core): window transparency ([#1800](https://www.github.com/tauri-apps/tauri/pull/1800)) on 2021-05-12\n\n## \\[0.1.0]\n\n- **Breaking:** `Context` fields are now private, and is expected to be created through `Context::new(...)`.\n  All fields previously available through `Context` are now public methods.\n  - [5542359](https://www.github.com/tauri-apps/tauri/commit/55423590ddbf560684dab6a0214acf95aadfa8d2) refactor(core): Context fields now private, Icon used on all platforms ([#1774](https://www.github.com/tauri-apps/tauri/pull/1774)) on 2021-05-11\n- `tauri-runtime-wry` initial release.\n  - [45a7a11](https://www.github.com/tauri-apps/tauri/commit/45a7a111e0cf9d9956d713cc9a99fa7a5313eec7) feat(core): add `tauri-wry` crate ([#1756](https://www.github.com/tauri-apps/tauri/pull/1756)) on 2021-05-09\n"
  },
  {
    "path": "crates/tauri-runtime-wry/Cargo.toml",
    "content": "[package]\nname = \"tauri-runtime-wry\"\nversion = \"2.10.1\"\ndescription = \"Wry bindings to the Tauri runtime\"\nexclude = [\"CHANGELOG.md\", \"/target\"]\nreadme = \"README.md\"\nauthors.workspace = true\nhomepage.workspace = true\nrepository.workspace = true\ncategories.workspace = true\nlicense.workspace = true\nedition.workspace = true\nrust-version.workspace = true\n\n[dependencies]\nwry = { version = \"0.54.0\", default-features = false, features = [\n  \"drag-drop\",\n  \"protocol\",\n  \"os-webview\",\n  \"linux-body\",\n] }\ntao = { version = \"0.34.5\", default-features = false, features = [\"rwh_06\"] }\ntauri-runtime = { version = \"2.10.1\", path = \"../tauri-runtime\" }\ntauri-utils = { version = \"2.8.3\", path = \"../tauri-utils\" }\nraw-window-handle = \"0.6\"\nhttp = \"1\"\nurl = \"2\"\ntracing = { version = \"0.1\", optional = true }\nlog = \"0.4.21\"\n\n[target.\"cfg(windows)\".dependencies]\nwebview2-com = \"0.38\"\nsoftbuffer = { version = \"0.4\", default-features = false }\nonce_cell = \"1.20\"\n\n[target.\"cfg(windows)\".dependencies.windows]\nversion = \"0.61\"\nfeatures = [\"Win32_Foundation\", \"Win32_Graphics_Dwm\"]\n\n[target.\"cfg(any(target_os = \\\"linux\\\", target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"openbsd\\\", target_os = \\\"netbsd\\\"))\".dependencies]\ngtk = { version = \"0.18\", features = [\"v3_24\"] }\nwebkit2gtk = { version = \"=2.0\", features = [\"v2_40\"] }\npercent-encoding = \"2\"\n\n[target.'cfg(target_vendor = \"apple\")'.dependencies]\nobjc2 = \"0.6\"\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\nobjc2-app-kit = { version = \"0.3\", default-features = false, features = [\n  \"block2\",\n  \"NSApplication\",\n  \"NSResponder\",\n  \"NSView\",\n  \"NSWindow\",\n  \"NSScreen\",\n  \"NSGraphics\",\n] }\n\n[target.\"cfg(target_os = \\\"android\\\")\".dependencies]\njni = \"0.21\"\n\n[features]\ndefault = [\"x11\"]\ndevtools = [\"wry/devtools\", \"tauri-runtime/devtools\"]\nx11 = [\"tao/x11\", \"wry/x11\"]\nmacos-private-api = [\n  \"wry/fullscreen\",\n  \"wry/transparent\",\n  \"tauri-runtime/macos-private-api\",\n]\n# TODO: Remove in v3 - wry does not have this feature anymore\nobjc-exception = []\ntracing = [\"dep:tracing\", \"wry/tracing\"]\nmacos-proxy = [\"wry/mac-proxy\"]\nunstable = []\ncommon-controls-v6 = []\n"
  },
  {
    "path": "crates/tauri-runtime-wry/LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/tauri-runtime-wry/LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2017 - Present Tauri Apps Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "crates/tauri-runtime-wry/README.md",
    "content": "# tauri-runtime-wry\n\n <img align=\"right\" src=\"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\" height=\"128\" width=\"128\">\n\n[![status](https://img.shields.io/badge/Status-Beta-green.svg)](https://github.com/tauri-apps/tauri)\n[![Chat Server](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/SpmNs4S)\n\n[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml)\n[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app)\n\n[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)\n[![support](https://img.shields.io/badge/sponsor-Opencollective-blue.svg)](https://opencollective.com/tauri)\n\n| Component         | Version                                                                                                                |\n| ----------------- | ---------------------------------------------------------------------------------------------------------------------- |\n| tauri-runtime-wry | [![](https://img.shields.io/crates/v/tauri-runtime-wry?style=flat-square)](https://crates.io/crates/tauri-runtime-wry) |\n\n## About Tauri\n\nTauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.\n\nTauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.\n\n## This module\n\nThis crate opens up direct systems-level interactions specifically for WRY, such as printing, monitor detection, and other windowing related tasks. `tauri-runtime` implementation for WRY.\nNone of the exposed API of this crate is stable, and it may break semver\ncompatibility in the future. The major version only signifies the intended Tauri version.\n\nTo learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.\n\n## Semver\n\n**tauri** is following [Semantic Versioning 2.0](https://semver.org/).\n\n## Licenses\n\nCode: (c) 2021 - The Tauri Programme within The Commons Conservancy.\n\nMIT or MIT/Apache 2.0 where applicable.\n\nLogo: CC-BY-NC-ND\n\n- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)\n"
  },
  {
    "path": "crates/tauri-runtime-wry/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// creates a cfg alias if `has_feature` is true.\n// `alias` must be a snake case string.\nfn alias(alias: &str, has_feature: bool) {\n  println!(\"cargo:rustc-check-cfg=cfg({alias})\");\n  if has_feature {\n    println!(\"cargo:rustc-cfg={alias}\");\n  }\n}\n\nfn main() {\n  let target_os = std::env::var(\"CARGO_CFG_TARGET_OS\").unwrap();\n  let mobile = target_os == \"ios\" || target_os == \"android\";\n  alias(\"desktop\", !mobile);\n  alias(\"mobile\", mobile);\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/dialog/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#[cfg(windows)]\nmod windows;\n\npub fn error<S: AsRef<str>>(err: S) {\n  #[cfg(windows)]\n  windows::error(err);\n\n  #[cfg(not(windows))]\n  {\n    unimplemented!(\"Error dialog is not implemented for this platform\");\n  }\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/dialog/windows.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse windows::core::{w, HSTRING};\n\nenum Level {\n  Error,\n  #[allow(unused)]\n  Warning,\n  #[allow(unused)]\n  Info,\n}\n\npub fn error<S: AsRef<str>>(err: S) {\n  dialog_inner(err.as_ref(), Level::Error);\n}\n\nfn dialog_inner(err: &str, level: Level) {\n  let title = match level {\n    Level::Warning => w!(\"Warning\"),\n    Level::Error => w!(\"Error\"),\n    Level::Info => w!(\"Info\"),\n  };\n\n  #[cfg(not(feature = \"common-controls-v6\"))]\n  {\n    use windows::Win32::UI::WindowsAndMessaging::*;\n\n    let err = remove_hyperlink(err);\n    let err = HSTRING::from(err);\n\n    unsafe {\n      MessageBoxW(\n        None,\n        &err,\n        title,\n        match level {\n          Level::Warning => MB_ICONWARNING,\n          Level::Error => MB_ICONERROR,\n          Level::Info => MB_ICONINFORMATION,\n        },\n      )\n    };\n  }\n\n  #[cfg(feature = \"common-controls-v6\")]\n  {\n    use windows::core::{HRESULT, PCWSTR};\n    use windows::Win32::Foundation::*;\n    use windows::Win32::UI::Controls::*;\n    use windows::Win32::UI::Shell::*;\n    use windows::Win32::UI::WindowsAndMessaging::*;\n\n    extern \"system\" fn task_dialog_callback(\n      _hwnd: HWND,\n      msg: TASKDIALOG_NOTIFICATIONS,\n      _wparam: WPARAM,\n      lparam: LPARAM,\n      _data: isize,\n    ) -> HRESULT {\n      if msg == TDN_HYPERLINK_CLICKED {\n        let link = PCWSTR(lparam.0 as _);\n        let _ = unsafe { ShellExecuteW(None, None, link, None, None, SW_SHOWNORMAL) };\n      }\n\n      S_OK\n    }\n\n    let err = HSTRING::from(err);\n    let err = PCWSTR(err.as_ptr());\n\n    let task_dialog_config = TASKDIALOGCONFIG {\n      cbSize: std::mem::size_of::<TASKDIALOGCONFIG>() as u32,\n      dwFlags: TDF_ALLOW_DIALOG_CANCELLATION | TDF_ENABLE_HYPERLINKS,\n      pszWindowTitle: title,\n      pszContent: err,\n      Anonymous1: TASKDIALOGCONFIG_0 {\n        pszMainIcon: match level {\n          Level::Warning => TD_WARNING_ICON,\n          Level::Error => TD_ERROR_ICON,\n          Level::Info => TD_INFORMATION_ICON,\n        },\n      },\n      dwCommonButtons: TDCBF_OK_BUTTON,\n      pfCallback: Some(task_dialog_callback),\n      ..Default::default()\n    };\n\n    let _ = unsafe { TaskDialogIndirect(&task_dialog_config, None, None, None) };\n  }\n}\n\n#[cfg(not(feature = \"common-controls-v6\"))]\nfn remove_hyperlink(str: &str) -> String {\n  let mut result = String::new();\n  let mut in_hyperlink = false;\n\n  for c in str.chars() {\n    if c == '<' {\n      in_hyperlink = true;\n    } else if c == '>' {\n      in_hyperlink = false;\n    } else if !in_hyperlink {\n      result.push(c);\n    }\n  }\n\n  result\n}\n\n#[cfg(test)]\n#[cfg(not(feature = \"common-controls-v6\"))]\nmod tests {\n  use super::*;\n\n  #[test]\n  fn test_remove_hyperlink() {\n    let input = \"This is a <A href=\\\"some link\\\">test</A> string.\";\n    let expected = \"This is a test string.\";\n    let result = remove_hyperlink(input);\n    assert_eq!(result, expected);\n  }\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The [`wry`] Tauri [`Runtime`].\n//!\n//! None of the exposed API of this crate is stable, and it may break semver\n//! compatibility in the future. The major version only signifies the intended Tauri version.\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n\nuse self::monitor::MonitorExt;\nuse http::Request;\n#[cfg(target_os = \"macos\")]\nuse objc2::ClassType;\nuse raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle};\n\n#[cfg(windows)]\nuse tauri_runtime::webview::ScrollBarStyle;\nuse tauri_runtime::{\n  dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},\n  monitor::Monitor,\n  webview::{DetachedWebview, DownloadEvent, PendingWebview, WebviewIpcHandler},\n  window::{\n    CursorIcon, DetachedWindow, DetachedWindowWebview, DragDropEvent, PendingWindow, RawWindow,\n    WebviewEvent, WindowBuilder, WindowBuilderBase, WindowEvent, WindowId, WindowSizeConstraints,\n  },\n  Cookie, DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon,\n  ProgressBarState, ProgressBarStatus, Result, RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs,\n  UserAttentionType, UserEvent, WebviewDispatch, WebviewEventId, WindowDispatch, WindowEventId,\n};\n\n#[cfg(target_vendor = \"apple\")]\nuse objc2::rc::Retained;\n#[cfg(target_os = \"macos\")]\nuse tao::platform::macos::{EventLoopWindowTargetExtMacOS, WindowBuilderExtMacOS};\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"netbsd\",\n  target_os = \"openbsd\"\n))]\nuse tao::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};\n#[cfg(windows)]\nuse tao::platform::windows::{WindowBuilderExtWindows, WindowExtWindows};\n#[cfg(windows)]\nuse webview2_com::{ContainsFullScreenElementChangedEventHandler, FocusChangedEventHandler};\n#[cfg(windows)]\nuse windows::Win32::Foundation::HWND;\n#[cfg(target_os = \"ios\")]\nuse wry::WebViewBuilderExtIos;\n#[cfg(target_os = \"macos\")]\nuse wry::WebViewBuilderExtMacos;\n#[cfg(windows)]\nuse wry::WebViewBuilderExtWindows;\n#[cfg(target_vendor = \"apple\")]\nuse wry::{WebViewBuilderExtDarwin, WebViewExtDarwin};\n\nuse tao::{\n  dpi::{\n    LogicalPosition as TaoLogicalPosition, LogicalSize as TaoLogicalSize,\n    PhysicalPosition as TaoPhysicalPosition, PhysicalSize as TaoPhysicalSize,\n    Position as TaoPosition, Size as TaoSize,\n  },\n  event::{Event, StartCause, WindowEvent as TaoWindowEvent},\n  event_loop::{\n    ControlFlow, DeviceEventFilter as TaoDeviceEventFilter, EventLoop, EventLoopBuilder,\n    EventLoopProxy as TaoEventLoopProxy, EventLoopWindowTarget,\n  },\n  monitor::MonitorHandle,\n  window::{\n    CursorIcon as TaoCursorIcon, Fullscreen, Icon as TaoWindowIcon,\n    ProgressBarState as TaoProgressBarState, ProgressState as TaoProgressState, Theme as TaoTheme,\n    UserAttentionType as TaoUserAttentionType,\n  },\n};\n#[cfg(desktop)]\nuse tauri_utils::config::PreventOverflowConfig;\n#[cfg(target_os = \"macos\")]\nuse tauri_utils::TitleBarStyle;\nuse tauri_utils::{\n  config::{Color, WindowConfig},\n  Theme,\n};\nuse url::Url;\n#[cfg(windows)]\nuse wry::ScrollBarStyle as WryScrollBarStyle;\nuse wry::{\n  DragDropEvent as WryDragDropEvent, ProxyConfig, ProxyEndpoint, WebContext as WryWebContext,\n  WebView, WebViewBuilder,\n};\n\npub use tao;\npub use tao::window::{Window, WindowBuilder as TaoWindowBuilder, WindowId as TaoWindowId};\npub use wry;\npub use wry::webview_version;\n\n#[cfg(windows)]\nuse wry::WebViewExtWindows;\n#[cfg(target_os = \"android\")]\nuse wry::{\n  prelude::{dispatch, find_class},\n  WebViewBuilderExtAndroid, WebViewExtAndroid,\n};\n#[cfg(not(any(\n  target_os = \"windows\",\n  target_os = \"macos\",\n  target_os = \"ios\",\n  target_os = \"android\"\n)))]\nuse wry::{WebViewBuilderExtUnix, WebViewExtUnix};\n\n#[cfg(target_os = \"ios\")]\npub use tao::platform::ios::WindowExtIOS;\n#[cfg(target_os = \"macos\")]\npub use tao::platform::macos::{\n  ActivationPolicy as TaoActivationPolicy, EventLoopExtMacOS, WindowExtMacOS,\n};\n#[cfg(target_os = \"macos\")]\nuse tauri_runtime::ActivationPolicy;\n\nuse std::{\n  cell::RefCell,\n  collections::{\n    hash_map::Entry::{Occupied, Vacant},\n    BTreeMap, HashMap, HashSet,\n  },\n  fmt,\n  ops::Deref,\n  path::PathBuf,\n  rc::Rc,\n  sync::{\n    atomic::{AtomicBool, AtomicU32, Ordering},\n    mpsc::{channel, Sender},\n    Arc, Mutex, Weak,\n  },\n  thread::{current as current_thread, ThreadId},\n};\n\npub type WebviewId = u32;\ntype IpcHandler = dyn Fn(Request<String>) + 'static;\n\n#[cfg(not(debug_assertions))]\nmod dialog;\nmod monitor;\n#[cfg(any(\n  windows,\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"netbsd\",\n  target_os = \"openbsd\"\n))]\nmod undecorated_resizing;\nmod util;\nmod webview;\nmod window;\n\npub use webview::Webview;\nuse window::WindowExt as _;\n\n#[derive(Debug)]\npub struct WebContext {\n  pub inner: WryWebContext,\n  pub referenced_by_webviews: HashSet<String>,\n  // on Linux the custom protocols are associated with the context\n  // and you cannot register a URI scheme more than once\n  pub registered_custom_protocols: HashSet<String>,\n}\n\npub type WebContextStore = Arc<Mutex<HashMap<Option<PathBuf>, WebContext>>>;\n// window\npub type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;\npub type WindowEventListeners = Arc<Mutex<HashMap<WindowEventId, WindowEventHandler>>>;\npub type WebviewEventHandler = Box<dyn Fn(&WebviewEvent) + Send>;\npub type WebviewEventListeners = Arc<Mutex<HashMap<WebviewEventId, WebviewEventHandler>>>;\n\n#[derive(Debug, Clone, Default)]\npub struct WindowIdStore(Arc<Mutex<HashMap<TaoWindowId, WindowId>>>);\n\nimpl WindowIdStore {\n  pub fn insert(&self, w: TaoWindowId, id: WindowId) {\n    self.0.lock().unwrap().insert(w, id);\n  }\n\n  pub fn get(&self, w: &TaoWindowId) -> Option<WindowId> {\n    self.0.lock().unwrap().get(w).copied()\n  }\n}\n\n#[macro_export]\nmacro_rules! getter {\n  ($self: ident, $rx: expr, $message: expr) => {{\n    $crate::send_user_message(&$self.context, $message)?;\n    $rx\n      .recv()\n      .map_err(|_| $crate::Error::FailedToReceiveMessage)\n  }};\n}\n\nmacro_rules! window_getter {\n  ($self: ident, $message: expr) => {{\n    let (tx, rx) = channel();\n    getter!($self, rx, Message::Window($self.window_id, $message(tx)))\n  }};\n}\n\nmacro_rules! event_loop_window_getter {\n  ($self: ident, $message: expr) => {{\n    let (tx, rx) = channel();\n    getter!($self, rx, Message::EventLoopWindowTarget($message(tx)))\n  }};\n}\n\nmacro_rules! webview_getter {\n  ($self: ident, $message: expr) => {{\n    let (tx, rx) = channel();\n    getter!(\n      $self,\n      rx,\n      Message::Webview(\n        *$self.window_id.lock().unwrap(),\n        $self.webview_id,\n        $message(tx)\n      )\n    )\n  }};\n}\n\npub(crate) fn send_user_message<T: UserEvent>(\n  context: &Context<T>,\n  message: Message<T>,\n) -> Result<()> {\n  if current_thread().id() == context.main_thread_id {\n    handle_user_message(\n      &context.main_thread.window_target,\n      message,\n      UserMessageContext {\n        window_id_map: context.window_id_map.clone(),\n        windows: context.main_thread.windows.clone(),\n      },\n    );\n    Ok(())\n  } else {\n    context\n      .proxy\n      .send_event(message)\n      .map_err(|_| Error::FailedToSendMessage)\n  }\n}\n\n#[derive(Clone)]\npub struct Context<T: UserEvent> {\n  pub window_id_map: WindowIdStore,\n  main_thread_id: ThreadId,\n  pub proxy: TaoEventLoopProxy<Message<T>>,\n  main_thread: DispatcherMainThreadContext<T>,\n  plugins: Arc<Mutex<Vec<Box<dyn Plugin<T> + Send>>>>,\n  next_window_id: Arc<AtomicU32>,\n  next_webview_id: Arc<AtomicU32>,\n  next_window_event_id: Arc<AtomicU32>,\n  next_webview_event_id: Arc<AtomicU32>,\n  webview_runtime_installed: bool,\n}\n\nimpl<T: UserEvent> Context<T> {\n  pub fn run_threaded<R, F>(&self, f: F) -> R\n  where\n    F: FnOnce(Option<&DispatcherMainThreadContext<T>>) -> R,\n  {\n    f(if current_thread().id() == self.main_thread_id {\n      Some(&self.main_thread)\n    } else {\n      None\n    })\n  }\n\n  fn next_window_id(&self) -> WindowId {\n    self.next_window_id.fetch_add(1, Ordering::Relaxed).into()\n  }\n\n  fn next_webview_id(&self) -> WebviewId {\n    self.next_webview_id.fetch_add(1, Ordering::Relaxed)\n  }\n\n  fn next_window_event_id(&self) -> u32 {\n    self.next_window_event_id.fetch_add(1, Ordering::Relaxed)\n  }\n\n  fn next_webview_event_id(&self) -> u32 {\n    self.next_webview_event_id.fetch_add(1, Ordering::Relaxed)\n  }\n}\n\nimpl<T: UserEvent> Context<T> {\n  fn create_window<F: Fn(RawWindow) + Send + 'static>(\n    &self,\n    pending: PendingWindow<T, Wry<T>>,\n    after_window_creation: Option<F>,\n  ) -> Result<DetachedWindow<T, Wry<T>>> {\n    let label = pending.label.clone();\n    let context = self.clone();\n    let window_id = self.next_window_id();\n    let (webview_id, use_https_scheme) = pending\n      .webview\n      .as_ref()\n      .map(|w| {\n        (\n          Some(context.next_webview_id()),\n          w.webview_attributes.use_https_scheme,\n        )\n      })\n      .unwrap_or((None, false));\n\n    send_user_message(\n      self,\n      Message::CreateWindow(\n        window_id,\n        Box::new(move |event_loop| {\n          create_window(\n            window_id,\n            webview_id.unwrap_or_default(),\n            event_loop,\n            &context,\n            pending,\n            after_window_creation,\n          )\n        }),\n      ),\n    )?;\n\n    let dispatcher = WryWindowDispatcher {\n      window_id,\n      context: self.clone(),\n    };\n\n    let detached_webview = webview_id.map(|id| {\n      let webview = DetachedWebview {\n        label: label.clone(),\n        dispatcher: WryWebviewDispatcher {\n          window_id: Arc::new(Mutex::new(window_id)),\n          webview_id: id,\n          context: self.clone(),\n        },\n      };\n      DetachedWindowWebview {\n        webview,\n        use_https_scheme,\n      }\n    });\n\n    Ok(DetachedWindow {\n      id: window_id,\n      label,\n      dispatcher,\n      webview: detached_webview,\n    })\n  }\n\n  fn create_webview(\n    &self,\n    window_id: WindowId,\n    pending: PendingWebview<T, Wry<T>>,\n  ) -> Result<DetachedWebview<T, Wry<T>>> {\n    let label = pending.label.clone();\n    let context = self.clone();\n\n    let webview_id = self.next_webview_id();\n\n    let window_id_wrapper = Arc::new(Mutex::new(window_id));\n    let window_id_wrapper_ = window_id_wrapper.clone();\n\n    send_user_message(\n      self,\n      Message::CreateWebview(\n        window_id,\n        Box::new(move |window, options| {\n          create_webview(\n            WebviewKind::WindowChild,\n            window,\n            window_id_wrapper_,\n            webview_id,\n            &context,\n            pending,\n            options.focused_webview,\n          )\n        }),\n      ),\n    )?;\n\n    let dispatcher = WryWebviewDispatcher {\n      window_id: window_id_wrapper,\n      webview_id,\n      context: self.clone(),\n    };\n\n    Ok(DetachedWebview { label, dispatcher })\n  }\n}\n\n#[cfg(feature = \"tracing\")]\n#[derive(Debug, Clone, Default)]\npub struct ActiveTraceSpanStore(Rc<RefCell<Vec<ActiveTracingSpan>>>);\n\n#[cfg(feature = \"tracing\")]\nimpl ActiveTraceSpanStore {\n  pub fn remove_window_draw(&self) {\n    self\n      .0\n      .borrow_mut()\n      .retain(|t| !matches!(t, ActiveTracingSpan::WindowDraw { id: _, span: _ }));\n  }\n}\n\n#[cfg(feature = \"tracing\")]\n#[derive(Debug)]\npub enum ActiveTracingSpan {\n  WindowDraw {\n    id: TaoWindowId,\n    span: tracing::span::EnteredSpan,\n  },\n}\n\n#[derive(Debug)]\npub struct WindowsStore(pub RefCell<BTreeMap<WindowId, WindowWrapper>>);\n\n// SAFETY: we ensure this type is only used on the main thread.\n#[allow(clippy::non_send_fields_in_send_ty)]\nunsafe impl Send for WindowsStore {}\n\n// SAFETY: we ensure this type is only used on the main thread.\n#[allow(clippy::non_send_fields_in_send_ty)]\nunsafe impl Sync for WindowsStore {}\n\n#[derive(Debug, Clone)]\npub struct DispatcherMainThreadContext<T: UserEvent> {\n  pub window_target: EventLoopWindowTarget<Message<T>>,\n  pub web_context: WebContextStore,\n  // changing this to an Rc will cause frequent app crashes.\n  pub windows: Arc<WindowsStore>,\n  #[cfg(feature = \"tracing\")]\n  pub active_tracing_spans: ActiveTraceSpanStore,\n}\n\n// SAFETY: we ensure this type is only used on the main thread.\n#[allow(clippy::non_send_fields_in_send_ty)]\nunsafe impl<T: UserEvent> Send for DispatcherMainThreadContext<T> {}\n\n// SAFETY: we ensure this type is only used on the main thread.\n#[allow(clippy::non_send_fields_in_send_ty)]\nunsafe impl<T: UserEvent> Sync for DispatcherMainThreadContext<T> {}\n\nimpl<T: UserEvent> fmt::Debug for Context<T> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"Context\")\n      .field(\"main_thread_id\", &self.main_thread_id)\n      .field(\"proxy\", &self.proxy)\n      .field(\"main_thread\", &self.main_thread)\n      .finish()\n  }\n}\n\npub struct DeviceEventFilterWrapper(pub TaoDeviceEventFilter);\n\nimpl From<DeviceEventFilter> for DeviceEventFilterWrapper {\n  fn from(item: DeviceEventFilter) -> Self {\n    match item {\n      DeviceEventFilter::Always => Self(TaoDeviceEventFilter::Always),\n      DeviceEventFilter::Never => Self(TaoDeviceEventFilter::Never),\n      DeviceEventFilter::Unfocused => Self(TaoDeviceEventFilter::Unfocused),\n    }\n  }\n}\n\npub struct RectWrapper(pub wry::Rect);\nimpl From<tauri_runtime::dpi::Rect> for RectWrapper {\n  fn from(value: tauri_runtime::dpi::Rect) -> Self {\n    RectWrapper(wry::Rect {\n      position: value.position,\n      size: value.size,\n    })\n  }\n}\n\n/// Wrapper around a [`tao::window::Icon`] that can be created from an [`Icon`].\npub struct TaoIcon(pub TaoWindowIcon);\n\nimpl TryFrom<Icon<'_>> for TaoIcon {\n  type Error = Error;\n  fn try_from(icon: Icon<'_>) -> std::result::Result<Self, Self::Error> {\n    TaoWindowIcon::from_rgba(icon.rgba.to_vec(), icon.width, icon.height)\n      .map(Self)\n      .map_err(|e| Error::InvalidIcon(Box::new(e)))\n  }\n}\n\npub struct WindowEventWrapper(pub Option<WindowEvent>);\n\nimpl WindowEventWrapper {\n  fn map_from_tao(\n    event: &TaoWindowEvent<'_>,\n    #[allow(unused_variables)] window: &WindowWrapper,\n  ) -> Self {\n    let event = match event {\n      TaoWindowEvent::Resized(size) => WindowEvent::Resized(PhysicalSizeWrapper(*size).into()),\n      TaoWindowEvent::Moved(position) => {\n        WindowEvent::Moved(PhysicalPositionWrapper(*position).into())\n      }\n      TaoWindowEvent::Destroyed => WindowEvent::Destroyed,\n      TaoWindowEvent::ScaleFactorChanged {\n        scale_factor,\n        new_inner_size,\n      } => WindowEvent::ScaleFactorChanged {\n        scale_factor: *scale_factor,\n        new_inner_size: PhysicalSizeWrapper(**new_inner_size).into(),\n      },\n      TaoWindowEvent::Focused(focused) => {\n        #[cfg(not(windows))]\n        return Self(Some(WindowEvent::Focused(*focused)));\n        // on multiwebview mode, if there's no focused webview, it means we're receiving a direct window focus change\n        // (without receiving a webview focus, such as when clicking the taskbar app icon or using Alt + Tab)\n        // in this case we must send the focus change event here\n        #[cfg(windows)]\n        if window.has_children.load(Ordering::Relaxed) {\n          const FOCUSED_WEBVIEW_MARKER: &str = \"__tauriWindow?\";\n          let mut focused_webview = window.focused_webview.lock().unwrap();\n          // when we focus a webview and the window was previously focused, we get a blur event here\n          // so on blur we should only send events if the current focus is owned by the window\n          if !*focused\n            && focused_webview\n              .as_deref()\n              .is_some_and(|w| w != FOCUSED_WEBVIEW_MARKER)\n          {\n            return Self(None);\n          }\n\n          // reset focused_webview on blur, or set to a dummy value on focus\n          // (to prevent double focus event when we click a webview after focusing a window)\n          *focused_webview = (*focused).then(|| FOCUSED_WEBVIEW_MARKER.to_string());\n\n          return Self(Some(WindowEvent::Focused(*focused)));\n        } else {\n          // when not on multiwebview mode, we handle focus change events on the webview (add_GotFocus and add_LostFocus)\n          return Self(None);\n        }\n      }\n      TaoWindowEvent::ThemeChanged(theme) => WindowEvent::ThemeChanged(map_theme(theme)),\n      _ => return Self(None),\n    };\n    Self(Some(event))\n  }\n\n  fn parse(window: &WindowWrapper, event: &TaoWindowEvent<'_>) -> Self {\n    match event {\n      // resized event from tao doesn't include a reliable size on macOS\n      // because wry replaces the NSView\n      TaoWindowEvent::Resized(_) => {\n        if let Some(w) = &window.inner {\n          let size = inner_size(\n            w,\n            &window.webviews,\n            window.has_children.load(Ordering::Relaxed),\n          );\n          Self(Some(WindowEvent::Resized(PhysicalSizeWrapper(size).into())))\n        } else {\n          Self(None)\n        }\n      }\n      e => Self::map_from_tao(e, window),\n    }\n  }\n}\n\npub fn map_theme(theme: &TaoTheme) -> Theme {\n  match theme {\n    TaoTheme::Light => Theme::Light,\n    TaoTheme::Dark => Theme::Dark,\n    _ => Theme::Light,\n  }\n}\n\n#[cfg(target_os = \"macos\")]\nfn tao_activation_policy(activation_policy: ActivationPolicy) -> TaoActivationPolicy {\n  match activation_policy {\n    ActivationPolicy::Regular => TaoActivationPolicy::Regular,\n    ActivationPolicy::Accessory => TaoActivationPolicy::Accessory,\n    ActivationPolicy::Prohibited => TaoActivationPolicy::Prohibited,\n    _ => unimplemented!(),\n  }\n}\n\npub struct MonitorHandleWrapper(pub MonitorHandle);\n\nimpl From<MonitorHandleWrapper> for Monitor {\n  fn from(monitor: MonitorHandleWrapper) -> Monitor {\n    Self {\n      name: monitor.0.name(),\n      position: PhysicalPositionWrapper(monitor.0.position()).into(),\n      size: PhysicalSizeWrapper(monitor.0.size()).into(),\n      work_area: monitor.0.work_area(),\n      scale_factor: monitor.0.scale_factor(),\n    }\n  }\n}\n\npub struct PhysicalPositionWrapper<T>(pub TaoPhysicalPosition<T>);\n\nimpl<T> From<PhysicalPositionWrapper<T>> for PhysicalPosition<T> {\n  fn from(position: PhysicalPositionWrapper<T>) -> Self {\n    Self {\n      x: position.0.x,\n      y: position.0.y,\n    }\n  }\n}\n\nimpl<T> From<PhysicalPosition<T>> for PhysicalPositionWrapper<T> {\n  fn from(position: PhysicalPosition<T>) -> Self {\n    Self(TaoPhysicalPosition {\n      x: position.x,\n      y: position.y,\n    })\n  }\n}\n\nstruct LogicalPositionWrapper<T>(TaoLogicalPosition<T>);\n\nimpl<T> From<LogicalPosition<T>> for LogicalPositionWrapper<T> {\n  fn from(position: LogicalPosition<T>) -> Self {\n    Self(TaoLogicalPosition {\n      x: position.x,\n      y: position.y,\n    })\n  }\n}\n\npub struct PhysicalSizeWrapper<T>(pub TaoPhysicalSize<T>);\n\nimpl<T> From<PhysicalSizeWrapper<T>> for PhysicalSize<T> {\n  fn from(size: PhysicalSizeWrapper<T>) -> Self {\n    Self {\n      width: size.0.width,\n      height: size.0.height,\n    }\n  }\n}\n\nimpl<T> From<PhysicalSize<T>> for PhysicalSizeWrapper<T> {\n  fn from(size: PhysicalSize<T>) -> Self {\n    Self(TaoPhysicalSize {\n      width: size.width,\n      height: size.height,\n    })\n  }\n}\n\nstruct LogicalSizeWrapper<T>(TaoLogicalSize<T>);\n\nimpl<T> From<LogicalSize<T>> for LogicalSizeWrapper<T> {\n  fn from(size: LogicalSize<T>) -> Self {\n    Self(TaoLogicalSize {\n      width: size.width,\n      height: size.height,\n    })\n  }\n}\n\npub struct SizeWrapper(pub TaoSize);\n\nimpl From<Size> for SizeWrapper {\n  fn from(size: Size) -> Self {\n    match size {\n      Size::Logical(s) => Self(TaoSize::Logical(LogicalSizeWrapper::from(s).0)),\n      Size::Physical(s) => Self(TaoSize::Physical(PhysicalSizeWrapper::from(s).0)),\n    }\n  }\n}\n\npub struct PositionWrapper(pub TaoPosition);\n\nimpl From<Position> for PositionWrapper {\n  fn from(position: Position) -> Self {\n    match position {\n      Position::Logical(s) => Self(TaoPosition::Logical(LogicalPositionWrapper::from(s).0)),\n      Position::Physical(s) => Self(TaoPosition::Physical(PhysicalPositionWrapper::from(s).0)),\n    }\n  }\n}\n\n#[derive(Debug, Clone)]\npub struct UserAttentionTypeWrapper(pub TaoUserAttentionType);\n\nimpl From<UserAttentionType> for UserAttentionTypeWrapper {\n  fn from(request_type: UserAttentionType) -> Self {\n    let o = match request_type {\n      UserAttentionType::Critical => TaoUserAttentionType::Critical,\n      UserAttentionType::Informational => TaoUserAttentionType::Informational,\n    };\n    Self(o)\n  }\n}\n\n#[derive(Debug)]\npub struct CursorIconWrapper(pub TaoCursorIcon);\n\nimpl From<CursorIcon> for CursorIconWrapper {\n  fn from(icon: CursorIcon) -> Self {\n    use CursorIcon::*;\n    let i = match icon {\n      Default => TaoCursorIcon::Default,\n      Crosshair => TaoCursorIcon::Crosshair,\n      Hand => TaoCursorIcon::Hand,\n      Arrow => TaoCursorIcon::Arrow,\n      Move => TaoCursorIcon::Move,\n      Text => TaoCursorIcon::Text,\n      Wait => TaoCursorIcon::Wait,\n      Help => TaoCursorIcon::Help,\n      Progress => TaoCursorIcon::Progress,\n      NotAllowed => TaoCursorIcon::NotAllowed,\n      ContextMenu => TaoCursorIcon::ContextMenu,\n      Cell => TaoCursorIcon::Cell,\n      VerticalText => TaoCursorIcon::VerticalText,\n      Alias => TaoCursorIcon::Alias,\n      Copy => TaoCursorIcon::Copy,\n      NoDrop => TaoCursorIcon::NoDrop,\n      Grab => TaoCursorIcon::Grab,\n      Grabbing => TaoCursorIcon::Grabbing,\n      AllScroll => TaoCursorIcon::AllScroll,\n      ZoomIn => TaoCursorIcon::ZoomIn,\n      ZoomOut => TaoCursorIcon::ZoomOut,\n      EResize => TaoCursorIcon::EResize,\n      NResize => TaoCursorIcon::NResize,\n      NeResize => TaoCursorIcon::NeResize,\n      NwResize => TaoCursorIcon::NwResize,\n      SResize => TaoCursorIcon::SResize,\n      SeResize => TaoCursorIcon::SeResize,\n      SwResize => TaoCursorIcon::SwResize,\n      WResize => TaoCursorIcon::WResize,\n      EwResize => TaoCursorIcon::EwResize,\n      NsResize => TaoCursorIcon::NsResize,\n      NeswResize => TaoCursorIcon::NeswResize,\n      NwseResize => TaoCursorIcon::NwseResize,\n      ColResize => TaoCursorIcon::ColResize,\n      RowResize => TaoCursorIcon::RowResize,\n      _ => TaoCursorIcon::Default,\n    };\n    Self(i)\n  }\n}\n\npub struct ProgressStateWrapper(pub TaoProgressState);\n\nimpl From<ProgressBarStatus> for ProgressStateWrapper {\n  fn from(status: ProgressBarStatus) -> Self {\n    let state = match status {\n      ProgressBarStatus::None => TaoProgressState::None,\n      ProgressBarStatus::Normal => TaoProgressState::Normal,\n      ProgressBarStatus::Indeterminate => TaoProgressState::Indeterminate,\n      ProgressBarStatus::Paused => TaoProgressState::Paused,\n      ProgressBarStatus::Error => TaoProgressState::Error,\n    };\n    Self(state)\n  }\n}\n\npub struct ProgressBarStateWrapper(pub TaoProgressBarState);\n\nimpl From<ProgressBarState> for ProgressBarStateWrapper {\n  fn from(progress_state: ProgressBarState) -> Self {\n    Self(TaoProgressBarState {\n      progress: progress_state.progress,\n      state: progress_state\n        .status\n        .map(|state| ProgressStateWrapper::from(state).0),\n      desktop_filename: progress_state.desktop_filename,\n    })\n  }\n}\n\n#[derive(Clone, Default)]\npub struct WindowBuilderWrapper {\n  inner: TaoWindowBuilder,\n  center: bool,\n  prevent_overflow: Option<Size>,\n  #[cfg(target_os = \"macos\")]\n  tabbing_identifier: Option<String>,\n}\n\nimpl std::fmt::Debug for WindowBuilderWrapper {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    let mut s = f.debug_struct(\"WindowBuilderWrapper\");\n    s.field(\"inner\", &self.inner)\n      .field(\"center\", &self.center)\n      .field(\"prevent_overflow\", &self.prevent_overflow);\n    #[cfg(target_os = \"macos\")]\n    {\n      s.field(\"tabbing_identifier\", &self.tabbing_identifier);\n    }\n    s.finish()\n  }\n}\n\n// SAFETY: this type is `Send` since `menu_items` are read only here\n#[allow(clippy::non_send_fields_in_send_ty)]\nunsafe impl Send for WindowBuilderWrapper {}\n\nimpl WindowBuilderBase for WindowBuilderWrapper {}\nimpl WindowBuilder for WindowBuilderWrapper {\n  fn new() -> Self {\n    #[allow(unused_mut)]\n    let mut builder = Self::default().focused(true);\n\n    #[cfg(target_os = \"macos\")]\n    {\n      // TODO: find a proper way to prevent webview being pushed out of the window.\n      // Workround for issue: https://github.com/tauri-apps/tauri/issues/10225\n      // The window requires `NSFullSizeContentViewWindowMask` flag to prevent devtools\n      // pushing the content view out of the window.\n      // By setting the default style to `TitleBarStyle::Visible` should fix the issue for most of the users.\n      builder = builder.title_bar_style(TitleBarStyle::Visible);\n    }\n\n    builder = builder.title(\"Tauri App\");\n\n    #[cfg(windows)]\n    {\n      builder = builder.window_classname(\"Tauri Window\");\n    }\n\n    builder\n  }\n\n  fn with_config(config: &WindowConfig) -> Self {\n    let mut window = WindowBuilderWrapper::new();\n\n    #[cfg(target_os = \"macos\")]\n    {\n      window = window\n        .hidden_title(config.hidden_title)\n        .title_bar_style(config.title_bar_style);\n      if let Some(identifier) = &config.tabbing_identifier {\n        window = window.tabbing_identifier(identifier);\n      }\n      if let Some(position) = &config.traffic_light_position {\n        window = window.traffic_light_position(tauri_runtime::dpi::LogicalPosition::new(\n          position.x, position.y,\n        ));\n      }\n    }\n\n    #[cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\"))]\n    {\n      window = window.transparent(config.transparent);\n    }\n    #[cfg(all(\n      target_os = \"macos\",\n      not(feature = \"macos-private-api\"),\n      debug_assertions\n    ))]\n    if config.transparent {\n      eprintln!(\n        \"The window is set to be transparent but the `macos-private-api` is not enabled.\n        This can be enabled via the `tauri.macOSPrivateApi` configuration property <https://v2.tauri.app/reference/config/#macosprivateapi>\n      \");\n    }\n\n    #[cfg(any(\n      target_os = \"linux\",\n      target_os = \"dragonfly\",\n      target_os = \"freebsd\",\n      target_os = \"netbsd\",\n      target_os = \"openbsd\"\n    ))]\n    {\n      // Mouse event is disabled on Linux since sudden event bursts could block event loop.\n      window.inner = window.inner.with_cursor_moved_event(false);\n    }\n\n    #[cfg(desktop)]\n    {\n      window = window\n        .title(config.title.to_string())\n        .inner_size(config.width, config.height)\n        .focused(config.focus)\n        .focusable(config.focusable)\n        .visible(config.visible)\n        .resizable(config.resizable)\n        .fullscreen(config.fullscreen)\n        .decorations(config.decorations)\n        .maximized(config.maximized)\n        .always_on_bottom(config.always_on_bottom)\n        .always_on_top(config.always_on_top)\n        .visible_on_all_workspaces(config.visible_on_all_workspaces)\n        .content_protected(config.content_protected)\n        .skip_taskbar(config.skip_taskbar)\n        .theme(config.theme)\n        .closable(config.closable)\n        .maximizable(config.maximizable)\n        .minimizable(config.minimizable)\n        .shadow(config.shadow);\n\n      let mut constraints = WindowSizeConstraints::default();\n\n      if let Some(min_width) = config.min_width {\n        constraints.min_width = Some(tao::dpi::LogicalUnit::new(min_width).into());\n      }\n      if let Some(min_height) = config.min_height {\n        constraints.min_height = Some(tao::dpi::LogicalUnit::new(min_height).into());\n      }\n      if let Some(max_width) = config.max_width {\n        constraints.max_width = Some(tao::dpi::LogicalUnit::new(max_width).into());\n      }\n      if let Some(max_height) = config.max_height {\n        constraints.max_height = Some(tao::dpi::LogicalUnit::new(max_height).into());\n      }\n      if let Some(color) = config.background_color {\n        window = window.background_color(color);\n      }\n      window = window.inner_size_constraints(constraints);\n\n      if let (Some(x), Some(y)) = (config.x, config.y) {\n        window = window.position(x, y);\n      }\n\n      if config.center {\n        window = window.center();\n      }\n\n      if let Some(window_classname) = &config.window_classname {\n        window = window.window_classname(window_classname);\n      }\n\n      if let Some(prevent_overflow) = &config.prevent_overflow {\n        window = match prevent_overflow {\n          PreventOverflowConfig::Enable(true) => window.prevent_overflow(),\n          PreventOverflowConfig::Margin(margin) => window\n            .prevent_overflow_with_margin(TaoPhysicalSize::new(margin.width, margin.height).into()),\n          _ => window,\n        };\n      }\n    }\n\n    window\n  }\n\n  fn center(mut self) -> Self {\n    self.center = true;\n    self\n  }\n\n  fn position(mut self, x: f64, y: f64) -> Self {\n    self.inner = self.inner.with_position(TaoLogicalPosition::new(x, y));\n    self\n  }\n\n  fn inner_size(mut self, width: f64, height: f64) -> Self {\n    self.inner = self\n      .inner\n      .with_inner_size(TaoLogicalSize::new(width, height));\n    self\n  }\n\n  fn min_inner_size(mut self, min_width: f64, min_height: f64) -> Self {\n    self.inner = self\n      .inner\n      .with_min_inner_size(TaoLogicalSize::new(min_width, min_height));\n    self\n  }\n\n  fn max_inner_size(mut self, max_width: f64, max_height: f64) -> Self {\n    self.inner = self\n      .inner\n      .with_max_inner_size(TaoLogicalSize::new(max_width, max_height));\n    self\n  }\n\n  fn inner_size_constraints(mut self, constraints: WindowSizeConstraints) -> Self {\n    self.inner.window.inner_size_constraints = tao::window::WindowSizeConstraints {\n      min_width: constraints.min_width,\n      min_height: constraints.min_height,\n      max_width: constraints.max_width,\n      max_height: constraints.max_height,\n    };\n    self\n  }\n\n  /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size) on creation\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  fn prevent_overflow(mut self) -> Self {\n    self\n      .prevent_overflow\n      .replace(PhysicalSize::new(0, 0).into());\n    self\n  }\n\n  /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size)\n  /// on creation with a margin\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  fn prevent_overflow_with_margin(mut self, margin: Size) -> Self {\n    self.prevent_overflow.replace(margin);\n    self\n  }\n\n  fn resizable(mut self, resizable: bool) -> Self {\n    self.inner = self.inner.with_resizable(resizable);\n    self\n  }\n\n  fn maximizable(mut self, maximizable: bool) -> Self {\n    self.inner = self.inner.with_maximizable(maximizable);\n    self\n  }\n\n  fn minimizable(mut self, minimizable: bool) -> Self {\n    self.inner = self.inner.with_minimizable(minimizable);\n    self\n  }\n\n  fn closable(mut self, closable: bool) -> Self {\n    self.inner = self.inner.with_closable(closable);\n    self\n  }\n\n  fn title<S: Into<String>>(mut self, title: S) -> Self {\n    self.inner = self.inner.with_title(title.into());\n    self\n  }\n\n  fn fullscreen(mut self, fullscreen: bool) -> Self {\n    self.inner = if fullscreen {\n      self\n        .inner\n        .with_fullscreen(Some(Fullscreen::Borderless(None)))\n    } else {\n      self.inner.with_fullscreen(None)\n    };\n    self\n  }\n\n  fn focused(mut self, focused: bool) -> Self {\n    self.inner = self.inner.with_focused(focused);\n    self\n  }\n\n  fn focusable(mut self, focusable: bool) -> Self {\n    self.inner = self.inner.with_focusable(focusable);\n    self\n  }\n\n  fn maximized(mut self, maximized: bool) -> Self {\n    self.inner = self.inner.with_maximized(maximized);\n    self\n  }\n\n  fn visible(mut self, visible: bool) -> Self {\n    self.inner = self.inner.with_visible(visible);\n    self\n  }\n\n  #[cfg(any(not(target_os = \"macos\"), feature = \"macos-private-api\"))]\n  fn transparent(mut self, transparent: bool) -> Self {\n    self.inner = self.inner.with_transparent(transparent);\n    self\n  }\n\n  fn decorations(mut self, decorations: bool) -> Self {\n    self.inner = self.inner.with_decorations(decorations);\n    self\n  }\n\n  fn always_on_bottom(mut self, always_on_bottom: bool) -> Self {\n    self.inner = self.inner.with_always_on_bottom(always_on_bottom);\n    self\n  }\n\n  fn always_on_top(mut self, always_on_top: bool) -> Self {\n    self.inner = self.inner.with_always_on_top(always_on_top);\n    self\n  }\n\n  fn visible_on_all_workspaces(mut self, visible_on_all_workspaces: bool) -> Self {\n    self.inner = self\n      .inner\n      .with_visible_on_all_workspaces(visible_on_all_workspaces);\n    self\n  }\n\n  fn content_protected(mut self, protected: bool) -> Self {\n    self.inner = self.inner.with_content_protection(protected);\n    self\n  }\n\n  fn shadow(#[allow(unused_mut)] mut self, _enable: bool) -> Self {\n    #[cfg(windows)]\n    {\n      self.inner = self.inner.with_undecorated_shadow(_enable);\n    }\n    #[cfg(target_os = \"macos\")]\n    {\n      self.inner = self.inner.with_has_shadow(_enable);\n    }\n    self\n  }\n\n  #[cfg(windows)]\n  fn owner(mut self, owner: HWND) -> Self {\n    self.inner = self.inner.with_owner_window(owner.0 as _);\n    self\n  }\n\n  #[cfg(windows)]\n  fn parent(mut self, parent: HWND) -> Self {\n    self.inner = self.inner.with_parent_window(parent.0 as _);\n    self\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn parent(mut self, parent: *mut std::ffi::c_void) -> Self {\n    self.inner = self.inner.with_parent_window(parent);\n    self\n  }\n\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn transient_for(mut self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self {\n    self.inner = self.inner.with_transient_for(parent);\n    self\n  }\n\n  #[cfg(windows)]\n  fn drag_and_drop(mut self, enabled: bool) -> Self {\n    self.inner = self.inner.with_drag_and_drop(enabled);\n    self\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn title_bar_style(mut self, style: TitleBarStyle) -> Self {\n    match style {\n      TitleBarStyle::Visible => {\n        self.inner = self.inner.with_titlebar_transparent(false);\n        // Fixes rendering issue when resizing window with devtools open (https://github.com/tauri-apps/tauri/issues/3914)\n        self.inner = self.inner.with_fullsize_content_view(true);\n      }\n      TitleBarStyle::Transparent => {\n        self.inner = self.inner.with_titlebar_transparent(true);\n        self.inner = self.inner.with_fullsize_content_view(false);\n      }\n      TitleBarStyle::Overlay => {\n        self.inner = self.inner.with_titlebar_transparent(true);\n        self.inner = self.inner.with_fullsize_content_view(true);\n      }\n      unknown => {\n        #[cfg(feature = \"tracing\")]\n        tracing::warn!(\"unknown title bar style applied: {unknown}\");\n\n        #[cfg(not(feature = \"tracing\"))]\n        eprintln!(\"unknown title bar style applied: {unknown}\");\n      }\n    }\n    self\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn traffic_light_position<P: Into<Position>>(mut self, position: P) -> Self {\n    self.inner = self.inner.with_traffic_light_inset(position.into());\n    self\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn hidden_title(mut self, hidden: bool) -> Self {\n    self.inner = self.inner.with_title_hidden(hidden);\n    self\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn tabbing_identifier(mut self, identifier: &str) -> Self {\n    self.inner = self.inner.with_tabbing_identifier(identifier);\n    self.tabbing_identifier.replace(identifier.into());\n    self\n  }\n\n  fn icon(mut self, icon: Icon) -> Result<Self> {\n    self.inner = self\n      .inner\n      .with_window_icon(Some(TaoIcon::try_from(icon)?.0));\n    Ok(self)\n  }\n\n  fn background_color(mut self, color: Color) -> Self {\n    self.inner = self.inner.with_background_color(color.into());\n    self\n  }\n\n  #[cfg(any(\n    windows,\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn skip_taskbar(mut self, skip: bool) -> Self {\n    self.inner = self.inner.with_skip_taskbar(skip);\n    self\n  }\n\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\", target_os = \"android\"))]\n  fn skip_taskbar(self, _skip: bool) -> Self {\n    self\n  }\n\n  fn theme(mut self, theme: Option<Theme>) -> Self {\n    self.inner = self.inner.with_theme(if let Some(t) = theme {\n      match t {\n        Theme::Dark => Some(TaoTheme::Dark),\n        _ => Some(TaoTheme::Light),\n      }\n    } else {\n      None\n    });\n\n    self\n  }\n\n  fn has_icon(&self) -> bool {\n    self.inner.window.window_icon.is_some()\n  }\n\n  fn get_theme(&self) -> Option<Theme> {\n    self.inner.window.preferred_theme.map(|theme| match theme {\n      TaoTheme::Dark => Theme::Dark,\n      _ => Theme::Light,\n    })\n  }\n\n  #[cfg(windows)]\n  fn window_classname<S: Into<String>>(mut self, window_classname: S) -> Self {\n    self.inner = self.inner.with_window_classname(window_classname);\n    self\n  }\n  #[cfg(not(windows))]\n  fn window_classname<S: Into<String>>(self, _window_classname: S) -> Self {\n    self\n  }\n}\n\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"netbsd\",\n  target_os = \"openbsd\"\n))]\npub struct GtkWindow(pub gtk::ApplicationWindow);\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"netbsd\",\n  target_os = \"openbsd\"\n))]\n#[allow(clippy::non_send_fields_in_send_ty)]\nunsafe impl Send for GtkWindow {}\n\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"netbsd\",\n  target_os = \"openbsd\"\n))]\npub struct GtkBox(pub gtk::Box);\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"netbsd\",\n  target_os = \"openbsd\"\n))]\n#[allow(clippy::non_send_fields_in_send_ty)]\nunsafe impl Send for GtkBox {}\n\npub struct SendRawWindowHandle(pub raw_window_handle::RawWindowHandle);\nunsafe impl Send for SendRawWindowHandle {}\n\npub enum ApplicationMessage {\n  #[cfg(target_os = \"macos\")]\n  Show,\n  #[cfg(target_os = \"macos\")]\n  Hide,\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  FetchDataStoreIdentifiers(Box<dyn FnOnce(Vec<[u8; 16]>) + Send + 'static>),\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  RemoveDataStore([u8; 16], Box<dyn FnOnce(Result<()>) + Send + 'static>),\n}\n\npub enum WindowMessage {\n  AddEventListener(WindowEventId, Box<dyn Fn(&WindowEvent) + Send>),\n  // Getters\n  ScaleFactor(Sender<f64>),\n  InnerPosition(Sender<Result<PhysicalPosition<i32>>>),\n  OuterPosition(Sender<Result<PhysicalPosition<i32>>>),\n  InnerSize(Sender<PhysicalSize<u32>>),\n  OuterSize(Sender<PhysicalSize<u32>>),\n  IsFullscreen(Sender<bool>),\n  IsMinimized(Sender<bool>),\n  IsMaximized(Sender<bool>),\n  IsFocused(Sender<bool>),\n  IsDecorated(Sender<bool>),\n  IsResizable(Sender<bool>),\n  IsMaximizable(Sender<bool>),\n  IsMinimizable(Sender<bool>),\n  IsClosable(Sender<bool>),\n  IsVisible(Sender<bool>),\n  Title(Sender<String>),\n  CurrentMonitor(Sender<Option<MonitorHandle>>),\n  PrimaryMonitor(Sender<Option<MonitorHandle>>),\n  MonitorFromPoint(Sender<Option<MonitorHandle>>, (f64, f64)),\n  AvailableMonitors(Sender<Vec<MonitorHandle>>),\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  GtkWindow(Sender<GtkWindow>),\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  GtkBox(Sender<GtkBox>),\n  RawWindowHandle(Sender<std::result::Result<SendRawWindowHandle, raw_window_handle::HandleError>>),\n  Theme(Sender<Theme>),\n  IsEnabled(Sender<bool>),\n  IsAlwaysOnTop(Sender<bool>),\n  // Setters\n  Center,\n  RequestUserAttention(Option<UserAttentionTypeWrapper>),\n  SetEnabled(bool),\n  SetResizable(bool),\n  SetMaximizable(bool),\n  SetMinimizable(bool),\n  SetClosable(bool),\n  SetTitle(String),\n  Maximize,\n  Unmaximize,\n  Minimize,\n  Unminimize,\n  Show,\n  Hide,\n  Close,\n  Destroy,\n  SetDecorations(bool),\n  SetShadow(bool),\n  SetAlwaysOnBottom(bool),\n  SetAlwaysOnTop(bool),\n  SetVisibleOnAllWorkspaces(bool),\n  SetContentProtected(bool),\n  SetSize(Size),\n  SetMinSize(Option<Size>),\n  SetMaxSize(Option<Size>),\n  SetSizeConstraints(WindowSizeConstraints),\n  SetPosition(Position),\n  SetFullscreen(bool),\n  #[cfg(target_os = \"macos\")]\n  SetSimpleFullscreen(bool),\n  SetFocus,\n  SetFocusable(bool),\n  SetIcon(TaoWindowIcon),\n  SetSkipTaskbar(bool),\n  SetCursorGrab(bool),\n  SetCursorVisible(bool),\n  SetCursorIcon(CursorIcon),\n  SetCursorPosition(Position),\n  SetIgnoreCursorEvents(bool),\n  SetBadgeCount(Option<i64>, Option<String>),\n  SetBadgeLabel(Option<String>),\n  SetOverlayIcon(Option<TaoIcon>),\n  SetProgressBar(ProgressBarState),\n  SetTitleBarStyle(tauri_utils::TitleBarStyle),\n  SetTrafficLightPosition(Position),\n  SetTheme(Option<Theme>),\n  SetBackgroundColor(Option<Color>),\n  DragWindow,\n  ResizeDragWindow(tauri_runtime::ResizeDirection),\n  RequestRedraw,\n}\n\n#[derive(Debug, Clone)]\npub enum SynthesizedWindowEvent {\n  Focused(bool),\n  DragDrop(DragDropEvent),\n}\n\nimpl From<SynthesizedWindowEvent> for WindowEventWrapper {\n  fn from(event: SynthesizedWindowEvent) -> Self {\n    let event = match event {\n      SynthesizedWindowEvent::Focused(focused) => WindowEvent::Focused(focused),\n      SynthesizedWindowEvent::DragDrop(event) => WindowEvent::DragDrop(event),\n    };\n    Self(Some(event))\n  }\n}\n\npub enum WebviewMessage {\n  AddEventListener(WebviewEventId, Box<dyn Fn(&WebviewEvent) + Send>),\n  #[cfg(not(all(feature = \"tracing\", not(target_os = \"android\"))))]\n  EvaluateScript(String),\n  #[cfg(all(feature = \"tracing\", not(target_os = \"android\")))]\n  EvaluateScript(String, Sender<()>, tracing::Span),\n  CookiesForUrl(Url, Sender<Result<Vec<tauri_runtime::Cookie<'static>>>>),\n  Cookies(Sender<Result<Vec<tauri_runtime::Cookie<'static>>>>),\n  SetCookie(tauri_runtime::Cookie<'static>),\n  DeleteCookie(tauri_runtime::Cookie<'static>),\n  WebviewEvent(WebviewEvent),\n  SynthesizedWindowEvent(SynthesizedWindowEvent),\n  Navigate(Url),\n  Reload,\n  Print,\n  Close,\n  Show,\n  Hide,\n  SetPosition(Position),\n  SetSize(Size),\n  SetBounds(tauri_runtime::dpi::Rect),\n  SetFocus,\n  Reparent(WindowId, Sender<Result<()>>),\n  SetAutoResize(bool),\n  SetZoom(f64),\n  SetBackgroundColor(Option<Color>),\n  ClearAllBrowsingData,\n  // Getters\n  Url(Sender<Result<String>>),\n  Bounds(Sender<Result<tauri_runtime::dpi::Rect>>),\n  Position(Sender<Result<PhysicalPosition<i32>>>),\n  Size(Sender<Result<PhysicalSize<u32>>>),\n  WithWebview(Box<dyn FnOnce(Webview) + Send>),\n  // Devtools\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  OpenDevTools,\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  CloseDevTools,\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  IsDevToolsOpen(Sender<bool>),\n}\n\npub enum EventLoopWindowTargetMessage {\n  CursorPosition(Sender<Result<PhysicalPosition<f64>>>),\n  SetTheme(Option<Theme>),\n  SetDeviceEventFilter(DeviceEventFilter),\n}\n\npub type CreateWindowClosure<T> =\n  Box<dyn FnOnce(&EventLoopWindowTarget<Message<T>>) -> Result<WindowWrapper> + Send>;\n\npub type CreateWebviewClosure =\n  Box<dyn FnOnce(&Window, CreateWebviewOptions) -> Result<WebviewWrapper> + Send>;\n\npub struct CreateWebviewOptions {\n  pub focused_webview: Arc<Mutex<Option<String>>>,\n}\n\npub enum Message<T: 'static> {\n  Task(Box<dyn FnOnce() + Send>),\n  #[cfg(target_os = \"macos\")]\n  SetActivationPolicy(ActivationPolicy),\n  #[cfg(target_os = \"macos\")]\n  SetDockVisibility(bool),\n  RequestExit(i32),\n  Application(ApplicationMessage),\n  Window(WindowId, WindowMessage),\n  Webview(WindowId, WebviewId, WebviewMessage),\n  EventLoopWindowTarget(EventLoopWindowTargetMessage),\n  CreateWebview(WindowId, CreateWebviewClosure),\n  CreateWindow(WindowId, CreateWindowClosure<T>),\n  CreateRawWindow(\n    WindowId,\n    Box<dyn FnOnce() -> (String, TaoWindowBuilder) + Send>,\n    Sender<Result<Weak<Window>>>,\n  ),\n  UserEvent(T),\n}\n\nimpl<T: UserEvent> Clone for Message<T> {\n  fn clone(&self) -> Self {\n    match self {\n      Self::UserEvent(t) => Self::UserEvent(t.clone()),\n      _ => unimplemented!(),\n    }\n  }\n}\n\n/// The Tauri [`WebviewDispatch`] for [`Wry`].\n#[derive(Debug, Clone)]\npub struct WryWebviewDispatcher<T: UserEvent> {\n  window_id: Arc<Mutex<WindowId>>,\n  webview_id: WebviewId,\n  context: Context<T>,\n}\n\nimpl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {\n  type Runtime = Wry<T>;\n\n  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {\n    send_user_message(&self.context, Message::Task(Box::new(f)))\n  }\n\n  fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) -> WindowEventId {\n    let id = self.context.next_webview_event_id();\n    let _ = self.context.proxy.send_event(Message::Webview(\n      *self.window_id.lock().unwrap(),\n      self.webview_id,\n      WebviewMessage::AddEventListener(id, Box::new(f)),\n    ));\n    id\n  }\n\n  fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::WithWebview(Box::new(move |webview| f(Box::new(webview)))),\n      ),\n    )\n  }\n\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  fn open_devtools(&self) {\n    let _ = send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::OpenDevTools,\n      ),\n    );\n  }\n\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  fn close_devtools(&self) {\n    let _ = send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::CloseDevTools,\n      ),\n    );\n  }\n\n  /// Gets the devtools window's current open state.\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  fn is_devtools_open(&self) -> Result<bool> {\n    webview_getter!(self, WebviewMessage::IsDevToolsOpen)\n  }\n\n  // Getters\n\n  fn url(&self) -> Result<String> {\n    webview_getter!(self, WebviewMessage::Url)?\n  }\n\n  fn bounds(&self) -> Result<tauri_runtime::dpi::Rect> {\n    webview_getter!(self, WebviewMessage::Bounds)?\n  }\n\n  fn position(&self) -> Result<PhysicalPosition<i32>> {\n    webview_getter!(self, WebviewMessage::Position)?\n  }\n\n  fn size(&self) -> Result<PhysicalSize<u32>> {\n    webview_getter!(self, WebviewMessage::Size)?\n  }\n\n  // Setters\n\n  fn navigate(&self, url: Url) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::Navigate(url),\n      ),\n    )\n  }\n\n  fn reload(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::Reload,\n      ),\n    )\n  }\n\n  fn print(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::Print,\n      ),\n    )\n  }\n\n  fn close(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::Close,\n      ),\n    )\n  }\n\n  fn set_bounds(&self, bounds: tauri_runtime::dpi::Rect) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::SetBounds(bounds),\n      ),\n    )\n  }\n\n  fn set_size(&self, size: Size) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::SetSize(size),\n      ),\n    )\n  }\n\n  fn set_position(&self, position: Position) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::SetPosition(position),\n      ),\n    )\n  }\n\n  fn set_focus(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::SetFocus,\n      ),\n    )\n  }\n\n  fn reparent(&self, window_id: WindowId) -> Result<()> {\n    let mut current_window_id = self.window_id.lock().unwrap();\n    let (tx, rx) = channel();\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *current_window_id,\n        self.webview_id,\n        WebviewMessage::Reparent(window_id, tx),\n      ),\n    )?;\n\n    rx.recv().unwrap()?;\n\n    *current_window_id = window_id;\n    Ok(())\n  }\n\n  fn cookies_for_url(&self, url: Url) -> Result<Vec<Cookie<'static>>> {\n    let current_window_id = self.window_id.lock().unwrap();\n    let (tx, rx) = channel();\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *current_window_id,\n        self.webview_id,\n        WebviewMessage::CookiesForUrl(url, tx),\n      ),\n    )?;\n\n    rx.recv().unwrap()\n  }\n\n  fn cookies(&self) -> Result<Vec<Cookie<'static>>> {\n    webview_getter!(self, WebviewMessage::Cookies)?\n  }\n\n  fn set_cookie(&self, cookie: Cookie<'_>) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::SetCookie(cookie.into_owned()),\n      ),\n    )?;\n    Ok(())\n  }\n\n  fn delete_cookie(&self, cookie: Cookie<'_>) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::DeleteCookie(cookie.into_owned()),\n      ),\n    )?;\n    Ok(())\n  }\n\n  fn set_auto_resize(&self, auto_resize: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::SetAutoResize(auto_resize),\n      ),\n    )\n  }\n\n  #[cfg(all(feature = \"tracing\", not(target_os = \"android\")))]\n  fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {\n    // use a channel so the EvaluateScript task uses the current span as parent\n    let (tx, rx) = channel();\n    getter!(\n      self,\n      rx,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::EvaluateScript(script.into(), tx, tracing::Span::current()),\n      )\n    )\n  }\n\n  #[cfg(not(all(feature = \"tracing\", not(target_os = \"android\"))))]\n  fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::EvaluateScript(script.into()),\n      ),\n    )\n  }\n\n  fn set_zoom(&self, scale_factor: f64) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::SetZoom(scale_factor),\n      ),\n    )\n  }\n\n  fn clear_all_browsing_data(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::ClearAllBrowsingData,\n      ),\n    )\n  }\n\n  fn hide(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::Hide,\n      ),\n    )\n  }\n\n  fn show(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::Show,\n      ),\n    )\n  }\n\n  fn set_background_color(&self, color: Option<Color>) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Webview(\n        *self.window_id.lock().unwrap(),\n        self.webview_id,\n        WebviewMessage::SetBackgroundColor(color),\n      ),\n    )\n  }\n}\n\n/// The Tauri [`WindowDispatch`] for [`Wry`].\n#[derive(Debug, Clone)]\npub struct WryWindowDispatcher<T: UserEvent> {\n  window_id: WindowId,\n  context: Context<T>,\n}\n\n// SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`.\n#[allow(clippy::non_send_fields_in_send_ty)]\nunsafe impl<T: UserEvent> Sync for WryWindowDispatcher<T> {}\n\nfn get_raw_window_handle<T: UserEvent>(\n  dispatcher: &WryWindowDispatcher<T>,\n) -> Result<std::result::Result<SendRawWindowHandle, raw_window_handle::HandleError>> {\n  window_getter!(dispatcher, WindowMessage::RawWindowHandle)\n}\n\nimpl<T: UserEvent> WindowDispatch<T> for WryWindowDispatcher<T> {\n  type Runtime = Wry<T>;\n  type WindowBuilder = WindowBuilderWrapper;\n\n  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {\n    send_user_message(&self.context, Message::Task(Box::new(f)))\n  }\n\n  fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> WindowEventId {\n    let id = self.context.next_window_event_id();\n    let _ = self.context.proxy.send_event(Message::Window(\n      self.window_id,\n      WindowMessage::AddEventListener(id, Box::new(f)),\n    ));\n    id\n  }\n\n  // Getters\n\n  fn scale_factor(&self) -> Result<f64> {\n    window_getter!(self, WindowMessage::ScaleFactor)\n  }\n\n  fn inner_position(&self) -> Result<PhysicalPosition<i32>> {\n    window_getter!(self, WindowMessage::InnerPosition)?\n  }\n\n  fn outer_position(&self) -> Result<PhysicalPosition<i32>> {\n    window_getter!(self, WindowMessage::OuterPosition)?\n  }\n\n  fn inner_size(&self) -> Result<PhysicalSize<u32>> {\n    window_getter!(self, WindowMessage::InnerSize)\n  }\n\n  fn outer_size(&self) -> Result<PhysicalSize<u32>> {\n    window_getter!(self, WindowMessage::OuterSize)\n  }\n\n  fn is_fullscreen(&self) -> Result<bool> {\n    window_getter!(self, WindowMessage::IsFullscreen)\n  }\n\n  fn is_minimized(&self) -> Result<bool> {\n    window_getter!(self, WindowMessage::IsMinimized)\n  }\n\n  fn is_maximized(&self) -> Result<bool> {\n    window_getter!(self, WindowMessage::IsMaximized)\n  }\n\n  fn is_focused(&self) -> Result<bool> {\n    window_getter!(self, WindowMessage::IsFocused)\n  }\n\n  /// Gets the window's current decoration state.\n  fn is_decorated(&self) -> Result<bool> {\n    window_getter!(self, WindowMessage::IsDecorated)\n  }\n\n  /// Gets the window's current resizable state.\n  fn is_resizable(&self) -> Result<bool> {\n    window_getter!(self, WindowMessage::IsResizable)\n  }\n\n  /// Gets the current native window's maximize button state\n  fn is_maximizable(&self) -> Result<bool> {\n    window_getter!(self, WindowMessage::IsMaximizable)\n  }\n\n  /// Gets the current native window's minimize button state\n  fn is_minimizable(&self) -> Result<bool> {\n    window_getter!(self, WindowMessage::IsMinimizable)\n  }\n\n  /// Gets the current native window's close button state\n  fn is_closable(&self) -> Result<bool> {\n    window_getter!(self, WindowMessage::IsClosable)\n  }\n\n  fn is_visible(&self) -> Result<bool> {\n    window_getter!(self, WindowMessage::IsVisible)\n  }\n\n  fn title(&self) -> Result<String> {\n    window_getter!(self, WindowMessage::Title)\n  }\n\n  fn current_monitor(&self) -> Result<Option<Monitor>> {\n    Ok(window_getter!(self, WindowMessage::CurrentMonitor)?.map(|m| MonitorHandleWrapper(m).into()))\n  }\n\n  fn primary_monitor(&self) -> Result<Option<Monitor>> {\n    Ok(window_getter!(self, WindowMessage::PrimaryMonitor)?.map(|m| MonitorHandleWrapper(m).into()))\n  }\n\n  fn monitor_from_point(&self, x: f64, y: f64) -> Result<Option<Monitor>> {\n    let (tx, rx) = channel();\n\n    let _ = send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::MonitorFromPoint(tx, (x, y))),\n    );\n\n    Ok(\n      rx.recv()\n        .map_err(|_| crate::Error::FailedToReceiveMessage)?\n        .map(|m| MonitorHandleWrapper(m).into()),\n    )\n  }\n\n  fn available_monitors(&self) -> Result<Vec<Monitor>> {\n    Ok(\n      window_getter!(self, WindowMessage::AvailableMonitors)?\n        .into_iter()\n        .map(|m| MonitorHandleWrapper(m).into())\n        .collect(),\n    )\n  }\n\n  fn theme(&self) -> Result<Theme> {\n    window_getter!(self, WindowMessage::Theme)\n  }\n\n  fn is_enabled(&self) -> Result<bool> {\n    window_getter!(self, WindowMessage::IsEnabled)\n  }\n\n  fn is_always_on_top(&self) -> Result<bool> {\n    window_getter!(self, WindowMessage::IsAlwaysOnTop)\n  }\n\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn gtk_window(&self) -> Result<gtk::ApplicationWindow> {\n    window_getter!(self, WindowMessage::GtkWindow).map(|w| w.0)\n  }\n\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn default_vbox(&self) -> Result<gtk::Box> {\n    window_getter!(self, WindowMessage::GtkBox).map(|w| w.0)\n  }\n\n  fn window_handle(\n    &self,\n  ) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {\n    get_raw_window_handle(self)\n      .map_err(|_| raw_window_handle::HandleError::Unavailable)\n      .and_then(|r| r.map(|h| unsafe { raw_window_handle::WindowHandle::borrow_raw(h.0) }))\n  }\n\n  // Setters\n\n  fn center(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::Center),\n    )\n  }\n\n  fn request_user_attention(&self, request_type: Option<UserAttentionType>) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(\n        self.window_id,\n        WindowMessage::RequestUserAttention(request_type.map(Into::into)),\n      ),\n    )\n  }\n\n  // Creates a window by dispatching a message to the event loop.\n  // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.\n  fn create_window<F: Fn(RawWindow) + Send + 'static>(\n    &mut self,\n    pending: PendingWindow<T, Self::Runtime>,\n    after_window_creation: Option<F>,\n  ) -> Result<DetachedWindow<T, Self::Runtime>> {\n    self.context.create_window(pending, after_window_creation)\n  }\n\n  // Creates a webview by dispatching a message to the event loop.\n  // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.\n  fn create_webview(\n    &mut self,\n    pending: PendingWebview<T, Self::Runtime>,\n  ) -> Result<DetachedWebview<T, Self::Runtime>> {\n    self.context.create_webview(self.window_id, pending)\n  }\n\n  fn set_resizable(&self, resizable: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetResizable(resizable)),\n    )\n  }\n\n  fn set_enabled(&self, enabled: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetEnabled(enabled)),\n    )\n  }\n\n  fn set_maximizable(&self, maximizable: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetMaximizable(maximizable)),\n    )\n  }\n\n  fn set_minimizable(&self, minimizable: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetMinimizable(minimizable)),\n    )\n  }\n\n  fn set_closable(&self, closable: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetClosable(closable)),\n    )\n  }\n\n  fn set_title<S: Into<String>>(&self, title: S) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetTitle(title.into())),\n    )\n  }\n\n  fn maximize(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::Maximize),\n    )\n  }\n\n  fn unmaximize(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::Unmaximize),\n    )\n  }\n\n  fn minimize(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::Minimize),\n    )\n  }\n\n  fn unminimize(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::Unminimize),\n    )\n  }\n\n  fn show(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::Show),\n    )\n  }\n\n  fn hide(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::Hide),\n    )\n  }\n\n  fn close(&self) -> Result<()> {\n    // NOTE: close cannot use the `send_user_message` function because it accesses the event loop callback\n    self\n      .context\n      .proxy\n      .send_event(Message::Window(self.window_id, WindowMessage::Close))\n      .map_err(|_| Error::FailedToSendMessage)\n  }\n\n  fn destroy(&self) -> Result<()> {\n    // NOTE: destroy cannot use the `send_user_message` function because it accesses the event loop callback\n    self\n      .context\n      .proxy\n      .send_event(Message::Window(self.window_id, WindowMessage::Destroy))\n      .map_err(|_| Error::FailedToSendMessage)\n  }\n\n  fn set_decorations(&self, decorations: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetDecorations(decorations)),\n    )\n  }\n\n  fn set_shadow(&self, enable: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetShadow(enable)),\n    )\n  }\n\n  fn set_always_on_bottom(&self, always_on_bottom: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(\n        self.window_id,\n        WindowMessage::SetAlwaysOnBottom(always_on_bottom),\n      ),\n    )\n  }\n\n  fn set_always_on_top(&self, always_on_top: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetAlwaysOnTop(always_on_top)),\n    )\n  }\n\n  fn set_visible_on_all_workspaces(&self, visible_on_all_workspaces: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(\n        self.window_id,\n        WindowMessage::SetVisibleOnAllWorkspaces(visible_on_all_workspaces),\n      ),\n    )\n  }\n\n  fn set_content_protected(&self, protected: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(\n        self.window_id,\n        WindowMessage::SetContentProtected(protected),\n      ),\n    )\n  }\n\n  fn set_size(&self, size: Size) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetSize(size)),\n    )\n  }\n\n  fn set_min_size(&self, size: Option<Size>) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetMinSize(size)),\n    )\n  }\n\n  fn set_max_size(&self, size: Option<Size>) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetMaxSize(size)),\n    )\n  }\n\n  fn set_size_constraints(&self, constraints: WindowSizeConstraints) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(\n        self.window_id,\n        WindowMessage::SetSizeConstraints(constraints),\n      ),\n    )\n  }\n\n  fn set_position(&self, position: Position) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetPosition(position)),\n    )\n  }\n\n  fn set_fullscreen(&self, fullscreen: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetFullscreen(fullscreen)),\n    )\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn set_simple_fullscreen(&self, enable: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetSimpleFullscreen(enable)),\n    )\n  }\n\n  fn set_focus(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetFocus),\n    )\n  }\n\n  fn set_focusable(&self, focusable: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetFocusable(focusable)),\n    )\n  }\n\n  fn set_icon(&self, icon: Icon) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(\n        self.window_id,\n        WindowMessage::SetIcon(TaoIcon::try_from(icon)?.0),\n      ),\n    )\n  }\n\n  fn set_skip_taskbar(&self, skip: bool) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetSkipTaskbar(skip)),\n    )\n  }\n\n  fn set_cursor_grab(&self, grab: bool) -> crate::Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetCursorGrab(grab)),\n    )\n  }\n\n  fn set_cursor_visible(&self, visible: bool) -> crate::Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetCursorVisible(visible)),\n    )\n  }\n\n  fn set_cursor_icon(&self, icon: CursorIcon) -> crate::Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetCursorIcon(icon)),\n    )\n  }\n\n  fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(\n        self.window_id,\n        WindowMessage::SetCursorPosition(position.into()),\n      ),\n    )\n  }\n\n  fn set_ignore_cursor_events(&self, ignore: bool) -> crate::Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetIgnoreCursorEvents(ignore)),\n    )\n  }\n\n  fn start_dragging(&self) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::DragWindow),\n    )\n  }\n\n  fn start_resize_dragging(&self, direction: tauri_runtime::ResizeDirection) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::ResizeDragWindow(direction)),\n    )\n  }\n\n  fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(\n        self.window_id,\n        WindowMessage::SetBadgeCount(count, desktop_filename),\n      ),\n    )\n  }\n\n  fn set_badge_label(&self, label: Option<String>) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetBadgeLabel(label)),\n    )\n  }\n\n  fn set_overlay_icon(&self, icon: Option<Icon>) -> Result<()> {\n    let icon: Result<Option<TaoIcon>> = icon.map_or(Ok(None), |x| Ok(Some(TaoIcon::try_from(x)?)));\n\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetOverlayIcon(icon?)),\n    )\n  }\n\n  fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(\n        self.window_id,\n        WindowMessage::SetProgressBar(progress_state),\n      ),\n    )\n  }\n\n  fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetTitleBarStyle(style)),\n    )\n  }\n\n  fn set_traffic_light_position(&self, position: Position) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(\n        self.window_id,\n        WindowMessage::SetTrafficLightPosition(position),\n      ),\n    )\n  }\n\n  fn set_theme(&self, theme: Option<Theme>) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetTheme(theme)),\n    )\n  }\n\n  fn set_background_color(&self, color: Option<Color>) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Window(self.window_id, WindowMessage::SetBackgroundColor(color)),\n    )\n  }\n}\n\n#[derive(Clone)]\npub struct WebviewWrapper {\n  label: String,\n  id: WebviewId,\n  inner: Rc<WebView>,\n  context_store: WebContextStore,\n  webview_event_listeners: WebviewEventListeners,\n  // the key of the WebContext if it's not shared\n  context_key: Option<PathBuf>,\n  bounds: Arc<Mutex<Option<WebviewBounds>>>,\n}\n\nimpl Deref for WebviewWrapper {\n  type Target = WebView;\n\n  #[inline(always)]\n  fn deref(&self) -> &Self::Target {\n    &self.inner\n  }\n}\n\nimpl Drop for WebviewWrapper {\n  fn drop(&mut self) {\n    if Rc::get_mut(&mut self.inner).is_some() {\n      let mut context_store = self.context_store.lock().unwrap();\n\n      if let Some(web_context) = context_store.get_mut(&self.context_key) {\n        web_context.referenced_by_webviews.remove(&self.label);\n\n        // https://github.com/tauri-apps/tauri/issues/14626\n        // Because WebKit does not close its network process even when no webviews are running,\n        // we need to ensure to re-use the existing process on Linux by keeping the WebContext\n        // alive for the lifetime of the app.\n        // WebKit on macOS handles this itself.\n        #[cfg(not(any(\n          target_os = \"linux\",\n          target_os = \"dragonfly\",\n          target_os = \"freebsd\",\n          target_os = \"netbsd\",\n          target_os = \"openbsd\"\n        )))]\n        if web_context.referenced_by_webviews.is_empty() {\n          context_store.remove(&self.context_key);\n        }\n      }\n    }\n  }\n}\n\npub struct WindowWrapper {\n  label: String,\n  inner: Option<Arc<Window>>,\n  // whether this window has child webviews\n  // or it's just a container for a single webview\n  has_children: AtomicBool,\n  webviews: Vec<WebviewWrapper>,\n  window_event_listeners: WindowEventListeners,\n  #[cfg(windows)]\n  background_color: Option<tao::window::RGBA>,\n  #[cfg(windows)]\n  is_window_transparent: bool,\n  #[cfg(windows)]\n  surface: Option<softbuffer::Surface<Arc<Window>, Arc<Window>>>,\n  focused_webview: Arc<Mutex<Option<String>>>,\n}\n\nimpl WindowWrapper {\n  pub fn label(&self) -> &str {\n    &self.label\n  }\n}\n\nimpl fmt::Debug for WindowWrapper {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"WindowWrapper\")\n      .field(\"label\", &self.label)\n      .field(\"inner\", &self.inner)\n      .finish()\n  }\n}\n\n#[derive(Debug, Clone)]\npub struct EventProxy<T: UserEvent>(TaoEventLoopProxy<Message<T>>);\n\n#[cfg(target_os = \"ios\")]\n#[allow(clippy::non_send_fields_in_send_ty)]\nunsafe impl<T: UserEvent> Sync for EventProxy<T> {}\n\nimpl<T: UserEvent> EventLoopProxy<T> for EventProxy<T> {\n  fn send_event(&self, event: T) -> Result<()> {\n    self\n      .0\n      .send_event(Message::UserEvent(event))\n      .map_err(|_| Error::EventLoopClosed)\n  }\n}\n\npub trait PluginBuilder<T: UserEvent> {\n  type Plugin: Plugin<T>;\n  fn build(self, context: Context<T>) -> Self::Plugin;\n}\n\npub trait Plugin<T: UserEvent> {\n  fn on_event(\n    &mut self,\n    event: &Event<Message<T>>,\n    event_loop: &EventLoopWindowTarget<Message<T>>,\n    proxy: &TaoEventLoopProxy<Message<T>>,\n    control_flow: &mut ControlFlow,\n    context: EventLoopIterationContext<'_, T>,\n    web_context: &WebContextStore,\n  ) -> bool;\n}\n\n/// A Tauri [`Runtime`] wrapper around wry.\npub struct Wry<T: UserEvent> {\n  context: Context<T>,\n  event_loop: EventLoop<Message<T>>,\n}\n\nimpl<T: UserEvent> fmt::Debug for Wry<T> {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"Wry\")\n      .field(\"main_thread_id\", &self.context.main_thread_id)\n      .field(\"event_loop\", &self.event_loop)\n      .field(\"windows\", &self.context.main_thread.windows)\n      .field(\"web_context\", &self.context.main_thread.web_context)\n      .finish()\n  }\n}\n\n/// A handle to the Wry runtime.\n#[derive(Debug, Clone)]\npub struct WryHandle<T: UserEvent> {\n  context: Context<T>,\n}\n\n// SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`.\n#[allow(clippy::non_send_fields_in_send_ty)]\nunsafe impl<T: UserEvent> Sync for WryHandle<T> {}\n\nimpl<T: UserEvent> WryHandle<T> {\n  /// Creates a new tao window using a callback, and returns its window id.\n  pub fn create_tao_window<F: FnOnce() -> (String, TaoWindowBuilder) + Send + 'static>(\n    &self,\n    f: F,\n  ) -> Result<Weak<Window>> {\n    let id = self.context.next_window_id();\n    let (tx, rx) = channel();\n    send_user_message(&self.context, Message::CreateRawWindow(id, Box::new(f), tx))?;\n    rx.recv().unwrap()\n  }\n\n  /// Gets the [`WebviewId'] associated with the given [`WindowId`].\n  pub fn window_id(&self, window_id: TaoWindowId) -> WindowId {\n    *self\n      .context\n      .window_id_map\n      .0\n      .lock()\n      .unwrap()\n      .get(&window_id)\n      .unwrap()\n  }\n\n  /// Send a message to the event loop.\n  pub fn send_event(&self, message: Message<T>) -> Result<()> {\n    self\n      .context\n      .proxy\n      .send_event(message)\n      .map_err(|_| Error::FailedToSendMessage)?;\n    Ok(())\n  }\n\n  pub fn plugin<P: PluginBuilder<T> + 'static>(&mut self, plugin: P)\n  where\n    <P as PluginBuilder<T>>::Plugin: Send,\n  {\n    self\n      .context\n      .plugins\n      .lock()\n      .unwrap()\n      .push(Box::new(plugin.build(self.context.clone())));\n  }\n}\n\nimpl<T: UserEvent> RuntimeHandle<T> for WryHandle<T> {\n  type Runtime = Wry<T>;\n\n  fn create_proxy(&self) -> EventProxy<T> {\n    EventProxy(self.context.proxy.clone())\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn set_activation_policy(&self, activation_policy: ActivationPolicy) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::SetActivationPolicy(activation_policy),\n    )\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn set_dock_visibility(&self, visible: bool) -> Result<()> {\n    send_user_message(&self.context, Message::SetDockVisibility(visible))\n  }\n\n  fn request_exit(&self, code: i32) -> Result<()> {\n    // NOTE: request_exit cannot use the `send_user_message` function because it accesses the event loop callback\n    self\n      .context\n      .proxy\n      .send_event(Message::RequestExit(code))\n      .map_err(|_| Error::FailedToSendMessage)\n  }\n\n  // Creates a window by dispatching a message to the event loop.\n  // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.\n  fn create_window<F: Fn(RawWindow) + Send + 'static>(\n    &self,\n    pending: PendingWindow<T, Self::Runtime>,\n    after_window_creation: Option<F>,\n  ) -> Result<DetachedWindow<T, Self::Runtime>> {\n    self.context.create_window(pending, after_window_creation)\n  }\n\n  // Creates a webview by dispatching a message to the event loop.\n  // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.\n  fn create_webview(\n    &self,\n    window_id: WindowId,\n    pending: PendingWebview<T, Self::Runtime>,\n  ) -> Result<DetachedWebview<T, Self::Runtime>> {\n    self.context.create_webview(window_id, pending)\n  }\n\n  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {\n    send_user_message(&self.context, Message::Task(Box::new(f)))\n  }\n\n  fn display_handle(\n    &self,\n  ) -> std::result::Result<DisplayHandle<'_>, raw_window_handle::HandleError> {\n    self.context.main_thread.window_target.display_handle()\n  }\n\n  fn primary_monitor(&self) -> Option<Monitor> {\n    self\n      .context\n      .main_thread\n      .window_target\n      .primary_monitor()\n      .map(|m| MonitorHandleWrapper(m).into())\n  }\n\n  fn monitor_from_point(&self, x: f64, y: f64) -> Option<Monitor> {\n    self\n      .context\n      .main_thread\n      .window_target\n      .monitor_from_point(x, y)\n      .map(|m| MonitorHandleWrapper(m).into())\n  }\n\n  fn available_monitors(&self) -> Vec<Monitor> {\n    self\n      .context\n      .main_thread\n      .window_target\n      .available_monitors()\n      .map(|m| MonitorHandleWrapper(m).into())\n      .collect()\n  }\n\n  fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {\n    event_loop_window_getter!(self, EventLoopWindowTargetMessage::CursorPosition)?\n      .map(PhysicalPositionWrapper)\n      .map(Into::into)\n      .map_err(|_| Error::FailedToGetCursorPosition)\n  }\n\n  fn set_theme(&self, theme: Option<Theme>) {\n    let _ = send_user_message(\n      &self.context,\n      Message::EventLoopWindowTarget(EventLoopWindowTargetMessage::SetTheme(theme)),\n    );\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn show(&self) -> tauri_runtime::Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Application(ApplicationMessage::Show),\n    )\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn hide(&self) -> tauri_runtime::Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Application(ApplicationMessage::Hide),\n    )\n  }\n\n  fn set_device_event_filter(&self, filter: DeviceEventFilter) {\n    let _ = send_user_message(\n      &self.context,\n      Message::EventLoopWindowTarget(EventLoopWindowTargetMessage::SetDeviceEventFilter(filter)),\n    );\n  }\n\n  #[cfg(target_os = \"android\")]\n  fn find_class<'a>(\n    &self,\n    env: &mut jni::JNIEnv<'a>,\n    activity: &jni::objects::JObject<'_>,\n    name: impl Into<String>,\n  ) -> std::result::Result<jni::objects::JClass<'a>, jni::errors::Error> {\n    find_class(env, activity, name.into())\n  }\n\n  #[cfg(target_os = \"android\")]\n  fn run_on_android_context<F>(&self, f: F)\n  where\n    F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject, &jni::objects::JObject) + Send + 'static,\n  {\n    dispatch(f)\n  }\n\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  fn fetch_data_store_identifiers<F: FnOnce(Vec<[u8; 16]>) + Send + 'static>(\n    &self,\n    cb: F,\n  ) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Application(ApplicationMessage::FetchDataStoreIdentifiers(Box::new(cb))),\n    )\n  }\n\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  fn remove_data_store<F: FnOnce(Result<()>) + Send + 'static>(\n    &self,\n    uuid: [u8; 16],\n    cb: F,\n  ) -> Result<()> {\n    send_user_message(\n      &self.context,\n      Message::Application(ApplicationMessage::RemoveDataStore(uuid, Box::new(cb))),\n    )\n  }\n}\n\nimpl<T: UserEvent> Wry<T> {\n  fn init_with_builder(\n    mut event_loop_builder: EventLoopBuilder<Message<T>>,\n    #[allow(unused_variables)] args: RuntimeInitArgs,\n  ) -> Result<Self> {\n    #[cfg(windows)]\n    if let Some(hook) = args.msg_hook {\n      use tao::platform::windows::EventLoopBuilderExtWindows;\n      event_loop_builder.with_msg_hook(hook);\n    }\n\n    #[cfg(any(\n      target_os = \"linux\",\n      target_os = \"dragonfly\",\n      target_os = \"freebsd\",\n      target_os = \"netbsd\",\n      target_os = \"openbsd\"\n    ))]\n    if let Some(app_id) = args.app_id {\n      use tao::platform::unix::EventLoopBuilderExtUnix;\n      event_loop_builder.with_app_id(app_id);\n    }\n    Self::init(event_loop_builder.build())\n  }\n\n  fn init(event_loop: EventLoop<Message<T>>) -> Result<Self> {\n    let main_thread_id = current_thread().id();\n    let web_context = WebContextStore::default();\n\n    let windows = Arc::new(WindowsStore(RefCell::new(BTreeMap::default())));\n    let window_id_map = WindowIdStore::default();\n\n    let context = Context {\n      window_id_map,\n      main_thread_id,\n      proxy: event_loop.create_proxy(),\n      main_thread: DispatcherMainThreadContext {\n        window_target: event_loop.deref().clone(),\n        web_context,\n        windows,\n        #[cfg(feature = \"tracing\")]\n        active_tracing_spans: Default::default(),\n      },\n      plugins: Default::default(),\n      next_window_id: Default::default(),\n      next_webview_id: Default::default(),\n      next_window_event_id: Default::default(),\n      next_webview_event_id: Default::default(),\n      webview_runtime_installed: wry::webview_version().is_ok(),\n    };\n\n    Ok(Self {\n      context,\n      event_loop,\n    })\n  }\n}\n\nimpl<T: UserEvent> Runtime<T> for Wry<T> {\n  type WindowDispatcher = WryWindowDispatcher<T>;\n  type WebviewDispatcher = WryWebviewDispatcher<T>;\n  type Handle = WryHandle<T>;\n\n  type EventLoopProxy = EventProxy<T>;\n\n  fn new(args: RuntimeInitArgs) -> Result<Self> {\n    Self::init_with_builder(EventLoopBuilder::<Message<T>>::with_user_event(), args)\n  }\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  fn new_any_thread(args: RuntimeInitArgs) -> Result<Self> {\n    use tao::platform::unix::EventLoopBuilderExtUnix;\n    let mut event_loop_builder = EventLoopBuilder::<Message<T>>::with_user_event();\n    event_loop_builder.with_any_thread(true);\n    Self::init_with_builder(event_loop_builder, args)\n  }\n\n  #[cfg(windows)]\n  fn new_any_thread(args: RuntimeInitArgs) -> Result<Self> {\n    use tao::platform::windows::EventLoopBuilderExtWindows;\n    let mut event_loop_builder = EventLoopBuilder::<Message<T>>::with_user_event();\n    event_loop_builder.with_any_thread(true);\n    Self::init_with_builder(event_loop_builder, args)\n  }\n\n  fn create_proxy(&self) -> EventProxy<T> {\n    EventProxy(self.event_loop.create_proxy())\n  }\n\n  fn handle(&self) -> Self::Handle {\n    WryHandle {\n      context: self.context.clone(),\n    }\n  }\n\n  fn create_window<F: Fn(RawWindow) + Send + 'static>(\n    &self,\n    pending: PendingWindow<T, Self>,\n    after_window_creation: Option<F>,\n  ) -> Result<DetachedWindow<T, Self>> {\n    let label = pending.label.clone();\n    let window_id = self.context.next_window_id();\n    let (webview_id, use_https_scheme) = pending\n      .webview\n      .as_ref()\n      .map(|w| {\n        (\n          Some(self.context.next_webview_id()),\n          w.webview_attributes.use_https_scheme,\n        )\n      })\n      .unwrap_or((None, false));\n\n    let window = create_window(\n      window_id,\n      webview_id.unwrap_or_default(),\n      &self.event_loop,\n      &self.context,\n      pending,\n      after_window_creation,\n    )?;\n\n    let dispatcher = WryWindowDispatcher {\n      window_id,\n      context: self.context.clone(),\n    };\n\n    self\n      .context\n      .main_thread\n      .windows\n      .0\n      .borrow_mut()\n      .insert(window_id, window);\n\n    let detached_webview = webview_id.map(|id| {\n      let webview = DetachedWebview {\n        label: label.clone(),\n        dispatcher: WryWebviewDispatcher {\n          window_id: Arc::new(Mutex::new(window_id)),\n          webview_id: id,\n          context: self.context.clone(),\n        },\n      };\n      DetachedWindowWebview {\n        webview,\n        use_https_scheme,\n      }\n    });\n\n    Ok(DetachedWindow {\n      id: window_id,\n      label,\n      dispatcher,\n      webview: detached_webview,\n    })\n  }\n\n  fn create_webview(\n    &self,\n    window_id: WindowId,\n    pending: PendingWebview<T, Self>,\n  ) -> Result<DetachedWebview<T, Self>> {\n    let label = pending.label.clone();\n\n    let window = self\n      .context\n      .main_thread\n      .windows\n      .0\n      .borrow()\n      .get(&window_id)\n      .map(|w| (w.inner.clone(), w.focused_webview.clone()));\n    if let Some((Some(window), focused_webview)) = window {\n      let window_id_wrapper = Arc::new(Mutex::new(window_id));\n\n      let webview_id = self.context.next_webview_id();\n\n      let webview = create_webview(\n        WebviewKind::WindowChild,\n        &window,\n        window_id_wrapper.clone(),\n        webview_id,\n        &self.context,\n        pending,\n        focused_webview,\n      )?;\n\n      #[allow(unknown_lints, clippy::manual_inspect)]\n      self\n        .context\n        .main_thread\n        .windows\n        .0\n        .borrow_mut()\n        .get_mut(&window_id)\n        .map(|w| {\n          w.webviews.push(webview);\n          w.has_children.store(true, Ordering::Relaxed);\n          w\n        });\n\n      let dispatcher = WryWebviewDispatcher {\n        window_id: window_id_wrapper,\n        webview_id,\n        context: self.context.clone(),\n      };\n\n      Ok(DetachedWebview { label, dispatcher })\n    } else {\n      Err(Error::WindowNotFound)\n    }\n  }\n\n  fn primary_monitor(&self) -> Option<Monitor> {\n    self\n      .context\n      .main_thread\n      .window_target\n      .primary_monitor()\n      .map(|m| MonitorHandleWrapper(m).into())\n  }\n\n  fn monitor_from_point(&self, x: f64, y: f64) -> Option<Monitor> {\n    self\n      .context\n      .main_thread\n      .window_target\n      .monitor_from_point(x, y)\n      .map(|m| MonitorHandleWrapper(m).into())\n  }\n\n  fn available_monitors(&self) -> Vec<Monitor> {\n    self\n      .context\n      .main_thread\n      .window_target\n      .available_monitors()\n      .map(|m| MonitorHandleWrapper(m).into())\n      .collect()\n  }\n\n  fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {\n    self\n      .context\n      .main_thread\n      .window_target\n      .cursor_position()\n      .map(PhysicalPositionWrapper)\n      .map(Into::into)\n      .map_err(|_| Error::FailedToGetCursorPosition)\n  }\n\n  fn set_theme(&self, theme: Option<Theme>) {\n    self.event_loop.set_theme(to_tao_theme(theme));\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {\n    self\n      .event_loop\n      .set_activation_policy(tao_activation_policy(activation_policy));\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn set_dock_visibility(&mut self, visible: bool) {\n    self.event_loop.set_dock_visibility(visible);\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn show(&self) {\n    self.event_loop.show_application();\n  }\n\n  #[cfg(target_os = \"macos\")]\n  fn hide(&self) {\n    self.event_loop.hide_application();\n  }\n\n  fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {\n    self\n      .event_loop\n      .set_device_event_filter(DeviceEventFilterWrapper::from(filter).0);\n  }\n\n  #[cfg(desktop)]\n  fn run_iteration<F: FnMut(RunEvent<T>) + 'static>(&mut self, mut callback: F) {\n    use tao::platform::run_return::EventLoopExtRunReturn;\n    let windows = self.context.main_thread.windows.clone();\n    let window_id_map = self.context.window_id_map.clone();\n    let web_context = &self.context.main_thread.web_context;\n    let plugins = self.context.plugins.clone();\n\n    #[cfg(feature = \"tracing\")]\n    let active_tracing_spans = self.context.main_thread.active_tracing_spans.clone();\n\n    let proxy = self.event_loop.create_proxy();\n\n    self\n      .event_loop\n      .run_return(|event, event_loop, control_flow| {\n        *control_flow = ControlFlow::Wait;\n        if let Event::MainEventsCleared = &event {\n          *control_flow = ControlFlow::Exit;\n        }\n\n        for p in plugins.lock().unwrap().iter_mut() {\n          let prevent_default = p.on_event(\n            &event,\n            event_loop,\n            &proxy,\n            control_flow,\n            EventLoopIterationContext {\n              callback: &mut callback,\n              window_id_map: window_id_map.clone(),\n              windows: windows.clone(),\n              #[cfg(feature = \"tracing\")]\n              active_tracing_spans: active_tracing_spans.clone(),\n            },\n            web_context,\n          );\n          if prevent_default {\n            return;\n          }\n        }\n\n        handle_event_loop(\n          event,\n          event_loop,\n          control_flow,\n          EventLoopIterationContext {\n            callback: &mut callback,\n            windows: windows.clone(),\n            window_id_map: window_id_map.clone(),\n            #[cfg(feature = \"tracing\")]\n            active_tracing_spans: active_tracing_spans.clone(),\n          },\n        );\n      });\n  }\n\n  fn run<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) {\n    let event_handler = make_event_handler(&self, callback);\n\n    self.event_loop.run(event_handler)\n  }\n\n  #[cfg(not(target_os = \"ios\"))]\n  fn run_return<F: FnMut(RunEvent<T>) + 'static>(mut self, callback: F) -> i32 {\n    use tao::platform::run_return::EventLoopExtRunReturn;\n\n    let event_handler = make_event_handler(&self, callback);\n\n    self.event_loop.run_return(event_handler)\n  }\n\n  #[cfg(target_os = \"ios\")]\n  fn run_return<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) -> i32 {\n    self.run(callback);\n    0\n  }\n}\n\nfn make_event_handler<T, F>(\n  runtime: &Wry<T>,\n  mut callback: F,\n) -> impl FnMut(Event<'_, Message<T>>, &EventLoopWindowTarget<Message<T>>, &mut ControlFlow)\nwhere\n  T: UserEvent,\n  F: FnMut(RunEvent<T>) + 'static,\n{\n  let windows = runtime.context.main_thread.windows.clone();\n  let window_id_map = runtime.context.window_id_map.clone();\n  let web_context = runtime.context.main_thread.web_context.clone();\n  let plugins = runtime.context.plugins.clone();\n\n  #[cfg(feature = \"tracing\")]\n  let active_tracing_spans = runtime.context.main_thread.active_tracing_spans.clone();\n  let proxy = runtime.event_loop.create_proxy();\n\n  move |event, event_loop, control_flow| {\n    for p in plugins.lock().unwrap().iter_mut() {\n      let prevent_default = p.on_event(\n        &event,\n        event_loop,\n        &proxy,\n        control_flow,\n        EventLoopIterationContext {\n          callback: &mut callback,\n          window_id_map: window_id_map.clone(),\n          windows: windows.clone(),\n          #[cfg(feature = \"tracing\")]\n          active_tracing_spans: active_tracing_spans.clone(),\n        },\n        &web_context,\n      );\n      if prevent_default {\n        return;\n      }\n    }\n    handle_event_loop(\n      event,\n      event_loop,\n      control_flow,\n      EventLoopIterationContext {\n        callback: &mut callback,\n        window_id_map: window_id_map.clone(),\n        windows: windows.clone(),\n        #[cfg(feature = \"tracing\")]\n        active_tracing_spans: active_tracing_spans.clone(),\n      },\n    );\n  }\n}\n\npub struct EventLoopIterationContext<'a, T: UserEvent> {\n  pub callback: &'a mut (dyn FnMut(RunEvent<T>) + 'static),\n  pub window_id_map: WindowIdStore,\n  pub windows: Arc<WindowsStore>,\n  #[cfg(feature = \"tracing\")]\n  pub active_tracing_spans: ActiveTraceSpanStore,\n}\n\nstruct UserMessageContext {\n  windows: Arc<WindowsStore>,\n  window_id_map: WindowIdStore,\n}\n\nfn handle_user_message<T: UserEvent>(\n  event_loop: &EventLoopWindowTarget<Message<T>>,\n  message: Message<T>,\n  context: UserMessageContext,\n) {\n  let UserMessageContext {\n    window_id_map,\n    windows,\n  } = context;\n  match message {\n    Message::Task(task) => task(),\n    #[cfg(target_os = \"macos\")]\n    Message::SetActivationPolicy(activation_policy) => {\n      event_loop.set_activation_policy_at_runtime(tao_activation_policy(activation_policy))\n    }\n    #[cfg(target_os = \"macos\")]\n    Message::SetDockVisibility(visible) => event_loop.set_dock_visibility(visible),\n    Message::RequestExit(_code) => panic!(\"cannot handle RequestExit on the main thread\"),\n    Message::Application(application_message) => match application_message {\n      #[cfg(target_os = \"macos\")]\n      ApplicationMessage::Show => {\n        event_loop.show_application();\n      }\n      #[cfg(target_os = \"macos\")]\n      ApplicationMessage::Hide => {\n        event_loop.hide_application();\n      }\n      #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n      ApplicationMessage::FetchDataStoreIdentifiers(cb) => {\n        if let Err(e) = WebView::fetch_data_store_identifiers(cb) {\n          // this shouldn't ever happen because we're running on the main thread\n          // but let's be safe and warn here\n          log::error!(\"failed to fetch data store identifiers: {e}\");\n        }\n      }\n      #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n      ApplicationMessage::RemoveDataStore(uuid, cb) => {\n        WebView::remove_data_store(&uuid, move |res| {\n          cb(res.map_err(|_| Error::FailedToRemoveDataStore))\n        })\n      }\n    },\n    Message::Window(id, window_message) => {\n      let w = windows.0.borrow().get(&id).map(|w| {\n        (\n          w.inner.clone(),\n          w.webviews.clone(),\n          w.has_children.load(Ordering::Relaxed),\n          w.window_event_listeners.clone(),\n        )\n      });\n      if let Some((Some(window), webviews, has_children, window_event_listeners)) = w {\n        match window_message {\n          WindowMessage::AddEventListener(id, listener) => {\n            window_event_listeners.lock().unwrap().insert(id, listener);\n          }\n\n          // Getters\n          WindowMessage::ScaleFactor(tx) => tx.send(window.scale_factor()).unwrap(),\n          WindowMessage::InnerPosition(tx) => tx\n            .send(\n              window\n                .inner_position()\n                .map(|p| PhysicalPositionWrapper(p).into())\n                .map_err(|_| Error::FailedToSendMessage),\n            )\n            .unwrap(),\n          WindowMessage::OuterPosition(tx) => tx\n            .send(\n              window\n                .outer_position()\n                .map(|p| PhysicalPositionWrapper(p).into())\n                .map_err(|_| Error::FailedToSendMessage),\n            )\n            .unwrap(),\n          WindowMessage::InnerSize(tx) => tx\n            .send(PhysicalSizeWrapper(inner_size(&window, &webviews, has_children)).into())\n            .unwrap(),\n          WindowMessage::OuterSize(tx) => tx\n            .send(PhysicalSizeWrapper(window.outer_size()).into())\n            .unwrap(),\n          WindowMessage::IsFullscreen(tx) => tx.send(window.fullscreen().is_some()).unwrap(),\n          WindowMessage::IsMinimized(tx) => tx.send(window.is_minimized()).unwrap(),\n          WindowMessage::IsMaximized(tx) => tx.send(window.is_maximized()).unwrap(),\n          WindowMessage::IsFocused(tx) => tx.send(window.is_focused()).unwrap(),\n          WindowMessage::IsDecorated(tx) => tx.send(window.is_decorated()).unwrap(),\n          WindowMessage::IsResizable(tx) => tx.send(window.is_resizable()).unwrap(),\n          WindowMessage::IsMaximizable(tx) => tx.send(window.is_maximizable()).unwrap(),\n          WindowMessage::IsMinimizable(tx) => tx.send(window.is_minimizable()).unwrap(),\n          WindowMessage::IsClosable(tx) => tx.send(window.is_closable()).unwrap(),\n          WindowMessage::IsVisible(tx) => tx.send(window.is_visible()).unwrap(),\n          WindowMessage::Title(tx) => tx.send(window.title()).unwrap(),\n          WindowMessage::CurrentMonitor(tx) => tx.send(window.current_monitor()).unwrap(),\n          WindowMessage::PrimaryMonitor(tx) => tx.send(window.primary_monitor()).unwrap(),\n          WindowMessage::MonitorFromPoint(tx, (x, y)) => {\n            tx.send(window.monitor_from_point(x, y)).unwrap()\n          }\n          WindowMessage::AvailableMonitors(tx) => {\n            tx.send(window.available_monitors().collect()).unwrap()\n          }\n          #[cfg(any(\n            target_os = \"linux\",\n            target_os = \"dragonfly\",\n            target_os = \"freebsd\",\n            target_os = \"netbsd\",\n            target_os = \"openbsd\"\n          ))]\n          WindowMessage::GtkWindow(tx) => tx.send(GtkWindow(window.gtk_window().clone())).unwrap(),\n          #[cfg(any(\n            target_os = \"linux\",\n            target_os = \"dragonfly\",\n            target_os = \"freebsd\",\n            target_os = \"netbsd\",\n            target_os = \"openbsd\"\n          ))]\n          WindowMessage::GtkBox(tx) => tx\n            .send(GtkBox(window.default_vbox().unwrap().clone()))\n            .unwrap(),\n          WindowMessage::RawWindowHandle(tx) => tx\n            .send(\n              window\n                .window_handle()\n                .map(|h| SendRawWindowHandle(h.as_raw())),\n            )\n            .unwrap(),\n          WindowMessage::Theme(tx) => {\n            tx.send(map_theme(&window.theme())).unwrap();\n          }\n          WindowMessage::IsEnabled(tx) => tx.send(window.is_enabled()).unwrap(),\n          WindowMessage::IsAlwaysOnTop(tx) => tx.send(window.is_always_on_top()).unwrap(),\n          // Setters\n          WindowMessage::Center => window.center(),\n          WindowMessage::RequestUserAttention(request_type) => {\n            window.request_user_attention(request_type.map(|r| r.0));\n          }\n          WindowMessage::SetResizable(resizable) => {\n            window.set_resizable(resizable);\n            #[cfg(windows)]\n            if !resizable {\n              undecorated_resizing::detach_resize_handler(window.hwnd());\n            } else if !window.is_decorated() {\n              undecorated_resizing::attach_resize_handler(\n                window.hwnd(),\n                window.has_undecorated_shadow(),\n              );\n            }\n          }\n          WindowMessage::SetMaximizable(maximizable) => window.set_maximizable(maximizable),\n          WindowMessage::SetMinimizable(minimizable) => window.set_minimizable(minimizable),\n          WindowMessage::SetClosable(closable) => window.set_closable(closable),\n          WindowMessage::SetTitle(title) => window.set_title(&title),\n          WindowMessage::Maximize => window.set_maximized(true),\n          WindowMessage::Unmaximize => window.set_maximized(false),\n          WindowMessage::Minimize => window.set_minimized(true),\n          WindowMessage::Unminimize => window.set_minimized(false),\n          WindowMessage::SetEnabled(enabled) => window.set_enabled(enabled),\n          WindowMessage::Show => window.set_visible(true),\n          WindowMessage::Hide => window.set_visible(false),\n          WindowMessage::Close => {\n            panic!(\"cannot handle `WindowMessage::Close` on the main thread\")\n          }\n          WindowMessage::Destroy => {\n            panic!(\"cannot handle `WindowMessage::Destroy` on the main thread\")\n          }\n          WindowMessage::SetDecorations(decorations) => {\n            window.set_decorations(decorations);\n            #[cfg(windows)]\n            if decorations {\n              undecorated_resizing::detach_resize_handler(window.hwnd());\n            } else if window.is_resizable() {\n              undecorated_resizing::attach_resize_handler(\n                window.hwnd(),\n                window.has_undecorated_shadow(),\n              );\n            }\n          }\n          WindowMessage::SetShadow(_enable) => {\n            #[cfg(windows)]\n            {\n              window.set_undecorated_shadow(_enable);\n              undecorated_resizing::update_drag_hwnd_rgn_for_undecorated(window.hwnd(), _enable);\n            }\n            #[cfg(target_os = \"macos\")]\n            window.set_has_shadow(_enable);\n          }\n          WindowMessage::SetAlwaysOnBottom(always_on_bottom) => {\n            window.set_always_on_bottom(always_on_bottom)\n          }\n          WindowMessage::SetAlwaysOnTop(always_on_top) => window.set_always_on_top(always_on_top),\n          WindowMessage::SetVisibleOnAllWorkspaces(visible_on_all_workspaces) => {\n            window.set_visible_on_all_workspaces(visible_on_all_workspaces)\n          }\n          WindowMessage::SetContentProtected(protected) => window.set_content_protection(protected),\n          WindowMessage::SetSize(size) => {\n            window.set_inner_size(SizeWrapper::from(size).0);\n          }\n          WindowMessage::SetMinSize(size) => {\n            window.set_min_inner_size(size.map(|s| SizeWrapper::from(s).0));\n          }\n          WindowMessage::SetMaxSize(size) => {\n            window.set_max_inner_size(size.map(|s| SizeWrapper::from(s).0));\n          }\n          WindowMessage::SetSizeConstraints(constraints) => {\n            window.set_inner_size_constraints(tao::window::WindowSizeConstraints {\n              min_width: constraints.min_width,\n              min_height: constraints.min_height,\n              max_width: constraints.max_width,\n              max_height: constraints.max_height,\n            });\n          }\n          WindowMessage::SetPosition(position) => {\n            window.set_outer_position(PositionWrapper::from(position).0)\n          }\n          WindowMessage::SetFullscreen(fullscreen) => {\n            if fullscreen {\n              window.set_fullscreen(Some(Fullscreen::Borderless(None)))\n            } else {\n              window.set_fullscreen(None)\n            }\n          }\n\n          #[cfg(target_os = \"macos\")]\n          WindowMessage::SetSimpleFullscreen(enable) => {\n            window.set_simple_fullscreen(enable);\n          }\n\n          WindowMessage::SetFocus => {\n            window.set_focus();\n          }\n          WindowMessage::SetFocusable(focusable) => {\n            window.set_focusable(focusable);\n          }\n          WindowMessage::SetIcon(icon) => {\n            window.set_window_icon(Some(icon));\n          }\n          #[allow(unused_variables)]\n          WindowMessage::SetSkipTaskbar(skip) => {\n            #[cfg(any(\n              windows,\n              target_os = \"linux\",\n              target_os = \"dragonfly\",\n              target_os = \"freebsd\",\n              target_os = \"netbsd\",\n              target_os = \"openbsd\"\n            ))]\n            let _ = window.set_skip_taskbar(skip);\n          }\n          WindowMessage::SetCursorGrab(grab) => {\n            let _ = window.set_cursor_grab(grab);\n          }\n          WindowMessage::SetCursorVisible(visible) => {\n            window.set_cursor_visible(visible);\n          }\n          WindowMessage::SetCursorIcon(icon) => {\n            window.set_cursor_icon(CursorIconWrapper::from(icon).0);\n          }\n          WindowMessage::SetCursorPosition(position) => {\n            let _ = window.set_cursor_position(PositionWrapper::from(position).0);\n          }\n          WindowMessage::SetIgnoreCursorEvents(ignore) => {\n            let _ = window.set_ignore_cursor_events(ignore);\n          }\n          WindowMessage::DragWindow => {\n            let _ = window.drag_window();\n          }\n          WindowMessage::ResizeDragWindow(direction) => {\n            let _ = window.drag_resize_window(match direction {\n              tauri_runtime::ResizeDirection::East => tao::window::ResizeDirection::East,\n              tauri_runtime::ResizeDirection::North => tao::window::ResizeDirection::North,\n              tauri_runtime::ResizeDirection::NorthEast => tao::window::ResizeDirection::NorthEast,\n              tauri_runtime::ResizeDirection::NorthWest => tao::window::ResizeDirection::NorthWest,\n              tauri_runtime::ResizeDirection::South => tao::window::ResizeDirection::South,\n              tauri_runtime::ResizeDirection::SouthEast => tao::window::ResizeDirection::SouthEast,\n              tauri_runtime::ResizeDirection::SouthWest => tao::window::ResizeDirection::SouthWest,\n              tauri_runtime::ResizeDirection::West => tao::window::ResizeDirection::West,\n            });\n          }\n          WindowMessage::RequestRedraw => {\n            window.request_redraw();\n          }\n          WindowMessage::SetBadgeCount(_count, _desktop_filename) => {\n            #[cfg(target_os = \"ios\")]\n            window.set_badge_count(\n              _count.map_or(0, |x| x.clamp(i32::MIN as i64, i32::MAX as i64) as i32),\n            );\n\n            #[cfg(target_os = \"macos\")]\n            window.set_badge_label(_count.map(|x| x.to_string()));\n\n            #[cfg(any(\n              target_os = \"linux\",\n              target_os = \"dragonfly\",\n              target_os = \"freebsd\",\n              target_os = \"netbsd\",\n              target_os = \"openbsd\"\n            ))]\n            window.set_badge_count(_count, _desktop_filename);\n          }\n          WindowMessage::SetBadgeLabel(_label) => {\n            #[cfg(target_os = \"macos\")]\n            window.set_badge_label(_label);\n          }\n          WindowMessage::SetOverlayIcon(_icon) => {\n            #[cfg(windows)]\n            window.set_overlay_icon(_icon.map(|x| x.0).as_ref());\n          }\n          WindowMessage::SetProgressBar(progress_state) => {\n            window.set_progress_bar(ProgressBarStateWrapper::from(progress_state).0);\n          }\n          WindowMessage::SetTitleBarStyle(_style) => {\n            #[cfg(target_os = \"macos\")]\n            match _style {\n              TitleBarStyle::Visible => {\n                window.set_titlebar_transparent(false);\n                window.set_fullsize_content_view(true);\n              }\n              TitleBarStyle::Transparent => {\n                window.set_titlebar_transparent(true);\n                window.set_fullsize_content_view(false);\n              }\n              TitleBarStyle::Overlay => {\n                window.set_titlebar_transparent(true);\n                window.set_fullsize_content_view(true);\n              }\n              unknown => {\n                #[cfg(feature = \"tracing\")]\n                tracing::warn!(\"unknown title bar style applied: {unknown}\");\n\n                #[cfg(not(feature = \"tracing\"))]\n                eprintln!(\"unknown title bar style applied: {unknown}\");\n              }\n            };\n          }\n          WindowMessage::SetTrafficLightPosition(_position) => {\n            #[cfg(target_os = \"macos\")]\n            window.set_traffic_light_inset(_position);\n          }\n          WindowMessage::SetTheme(theme) => {\n            window.set_theme(to_tao_theme(theme));\n          }\n          WindowMessage::SetBackgroundColor(color) => {\n            window.set_background_color(color.map(Into::into))\n          }\n        }\n      }\n    }\n    Message::Webview(window_id, webview_id, webview_message) => {\n      #[cfg(any(\n        target_os = \"macos\",\n        windows,\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n      ))]\n      if let WebviewMessage::Reparent(new_parent_window_id, tx) = webview_message {\n        let webview_handle = windows.0.borrow_mut().get_mut(&window_id).and_then(|w| {\n          w.webviews\n            .iter()\n            .position(|w| w.id == webview_id)\n            .map(|webview_index| w.webviews.remove(webview_index))\n        });\n\n        if let Some(webview) = webview_handle {\n          if let Some((Some(new_parent_window), new_parent_window_webviews)) = windows\n            .0\n            .borrow_mut()\n            .get_mut(&new_parent_window_id)\n            .map(|w| (w.inner.clone(), &mut w.webviews))\n          {\n            #[cfg(target_os = \"macos\")]\n            let reparent_result = {\n              use wry::WebViewExtMacOS;\n              webview.inner.reparent(new_parent_window.ns_window() as _)\n            };\n            #[cfg(windows)]\n            let reparent_result = { webview.inner.reparent(new_parent_window.hwnd()) };\n\n            #[cfg(any(\n              target_os = \"linux\",\n              target_os = \"dragonfly\",\n              target_os = \"freebsd\",\n              target_os = \"netbsd\",\n              target_os = \"openbsd\"\n            ))]\n            let reparent_result = {\n              if let Some(container) = new_parent_window.default_vbox() {\n                webview.inner.reparent(container)\n              } else {\n                Err(wry::Error::MessageSender)\n              }\n            };\n\n            match reparent_result {\n              Ok(_) => {\n                new_parent_window_webviews.push(webview);\n                tx.send(Ok(())).unwrap();\n              }\n              Err(e) => {\n                log::error!(\"failed to reparent webview: {e}\");\n                tx.send(Err(Error::FailedToSendMessage)).unwrap();\n              }\n            }\n          }\n        } else {\n          tx.send(Err(Error::FailedToSendMessage)).unwrap();\n        }\n\n        return;\n      }\n\n      let webview_handle = windows.0.borrow().get(&window_id).map(|w| {\n        (\n          w.inner.clone(),\n          w.webviews.iter().find(|w| w.id == webview_id).cloned(),\n        )\n      });\n      if let Some((Some(window), Some(webview))) = webview_handle {\n        match webview_message {\n          WebviewMessage::WebviewEvent(_) => { /* already handled */ }\n          WebviewMessage::SynthesizedWindowEvent(_) => { /* already handled */ }\n          WebviewMessage::Reparent(_window_id, _tx) => { /* already handled */ }\n          WebviewMessage::AddEventListener(id, listener) => {\n            webview\n              .webview_event_listeners\n              .lock()\n              .unwrap()\n              .insert(id, listener);\n          }\n\n          #[cfg(all(feature = \"tracing\", not(target_os = \"android\")))]\n          WebviewMessage::EvaluateScript(script, tx, span) => {\n            let _span = span.entered();\n            if let Err(e) = webview.evaluate_script(&script) {\n              log::error!(\"{e}\");\n            }\n            tx.send(()).unwrap();\n          }\n          #[cfg(not(all(feature = \"tracing\", not(target_os = \"android\"))))]\n          WebviewMessage::EvaluateScript(script) => {\n            if let Err(e) = webview.evaluate_script(&script) {\n              log::error!(\"{e}\");\n            }\n          }\n          WebviewMessage::Navigate(url) => {\n            if let Err(e) = webview.load_url(url.as_str()) {\n              log::error!(\"failed to navigate to url {}: {}\", url, e);\n            }\n          }\n          WebviewMessage::Reload => {\n            if let Err(e) = webview.reload() {\n              log::error!(\"failed to reload: {e}\");\n            }\n          }\n          WebviewMessage::Show => {\n            if let Err(e) = webview.set_visible(true) {\n              log::error!(\"failed to change webview visibility: {e}\");\n            }\n          }\n          WebviewMessage::Hide => {\n            if let Err(e) = webview.set_visible(false) {\n              log::error!(\"failed to change webview visibility: {e}\");\n            }\n          }\n          WebviewMessage::Print => {\n            let _ = webview.print();\n          }\n          WebviewMessage::Close => {\n            #[allow(unknown_lints, clippy::manual_inspect)]\n            windows.0.borrow_mut().get_mut(&window_id).map(|window| {\n              if let Some(i) = window.webviews.iter().position(|w| w.id == webview.id) {\n                window.webviews.remove(i);\n              }\n              window\n            });\n          }\n          WebviewMessage::SetBounds(bounds) => {\n            let bounds: RectWrapper = bounds.into();\n            let bounds = bounds.0;\n\n            if let Some(b) = &mut *webview.bounds.lock().unwrap() {\n              let scale_factor = window.scale_factor();\n              let size = bounds.size.to_logical::<f32>(scale_factor);\n              let position = bounds.position.to_logical::<f32>(scale_factor);\n              let window_size = window.inner_size().to_logical::<f32>(scale_factor);\n              b.width_rate = size.width / window_size.width;\n              b.height_rate = size.height / window_size.height;\n              b.x_rate = position.x / window_size.width;\n              b.y_rate = position.y / window_size.height;\n            }\n\n            if let Err(e) = webview.set_bounds(bounds) {\n              log::error!(\"failed to set webview size: {e}\");\n            }\n          }\n          WebviewMessage::SetSize(size) => match webview.bounds() {\n            Ok(mut bounds) => {\n              bounds.size = size;\n\n              let scale_factor = window.scale_factor();\n              let size = size.to_logical::<f32>(scale_factor);\n\n              if let Some(b) = &mut *webview.bounds.lock().unwrap() {\n                let window_size = window.inner_size().to_logical::<f32>(scale_factor);\n                b.width_rate = size.width / window_size.width;\n                b.height_rate = size.height / window_size.height;\n              }\n\n              if let Err(e) = webview.set_bounds(bounds) {\n                log::error!(\"failed to set webview size: {e}\");\n              }\n            }\n            Err(e) => {\n              log::error!(\"failed to get webview bounds: {e}\");\n            }\n          },\n          WebviewMessage::SetPosition(position) => match webview.bounds() {\n            Ok(mut bounds) => {\n              bounds.position = position;\n\n              let scale_factor = window.scale_factor();\n              let position = position.to_logical::<f32>(scale_factor);\n\n              if let Some(b) = &mut *webview.bounds.lock().unwrap() {\n                let window_size = window.inner_size().to_logical::<f32>(scale_factor);\n                b.x_rate = position.x / window_size.width;\n                b.y_rate = position.y / window_size.height;\n              }\n\n              if let Err(e) = webview.set_bounds(bounds) {\n                log::error!(\"failed to set webview position: {e}\");\n              }\n            }\n            Err(e) => {\n              log::error!(\"failed to get webview bounds: {e}\");\n            }\n          },\n          WebviewMessage::SetZoom(scale_factor) => {\n            if let Err(e) = webview.zoom(scale_factor) {\n              log::error!(\"failed to set webview zoom: {e}\");\n            }\n          }\n          WebviewMessage::SetBackgroundColor(color) => {\n            if let Err(e) =\n              webview.set_background_color(color.map(Into::into).unwrap_or((255, 255, 255, 255)))\n            {\n              log::error!(\"failed to set webview background color: {e}\");\n            }\n          }\n          WebviewMessage::ClearAllBrowsingData => {\n            if let Err(e) = webview.clear_all_browsing_data() {\n              log::error!(\"failed to clear webview browsing data: {e}\");\n            }\n          }\n          // Getters\n          WebviewMessage::Url(tx) => {\n            tx.send(\n              webview\n                .url()\n                .map(|u| u.parse().expect(\"invalid webview URL\"))\n                .map_err(|_| Error::FailedToSendMessage),\n            )\n            .unwrap();\n          }\n\n          WebviewMessage::Cookies(tx) => {\n            tx.send(webview.cookies().map_err(|_| Error::FailedToSendMessage))\n              .unwrap();\n          }\n\n          WebviewMessage::SetCookie(cookie) => {\n            if let Err(e) = webview.set_cookie(&cookie) {\n              log::error!(\"failed to set webview cookie: {e}\");\n            }\n          }\n\n          WebviewMessage::DeleteCookie(cookie) => {\n            if let Err(e) = webview.delete_cookie(&cookie) {\n              log::error!(\"failed to delete webview cookie: {e}\");\n            }\n          }\n\n          WebviewMessage::CookiesForUrl(url, tx) => {\n            let webview_cookies = webview\n              .cookies_for_url(url.as_str())\n              .map_err(|_| Error::FailedToSendMessage);\n            tx.send(webview_cookies).unwrap();\n          }\n\n          WebviewMessage::Bounds(tx) => {\n            tx.send(\n              webview\n                .bounds()\n                .map(|bounds| tauri_runtime::dpi::Rect {\n                  size: bounds.size,\n                  position: bounds.position,\n                })\n                .map_err(|_| Error::FailedToSendMessage),\n            )\n            .unwrap();\n          }\n          WebviewMessage::Position(tx) => {\n            tx.send(\n              webview\n                .bounds()\n                .map(|bounds| bounds.position.to_physical(window.scale_factor()))\n                .map_err(|_| Error::FailedToSendMessage),\n            )\n            .unwrap();\n          }\n          WebviewMessage::Size(tx) => {\n            tx.send(\n              webview\n                .bounds()\n                .map(|bounds| bounds.size.to_physical(window.scale_factor()))\n                .map_err(|_| Error::FailedToSendMessage),\n            )\n            .unwrap();\n          }\n          WebviewMessage::SetFocus => {\n            if let Err(e) = webview.focus() {\n              log::error!(\"failed to focus webview: {e}\");\n            }\n          }\n          WebviewMessage::SetAutoResize(auto_resize) => match webview.bounds() {\n            Ok(bounds) => {\n              let scale_factor = window.scale_factor();\n              let window_size = window.inner_size().to_logical::<f32>(scale_factor);\n              *webview.bounds.lock().unwrap() = if auto_resize {\n                let size = bounds.size.to_logical::<f32>(scale_factor);\n                let position = bounds.position.to_logical::<f32>(scale_factor);\n                Some(WebviewBounds {\n                  x_rate: position.x / window_size.width,\n                  y_rate: position.y / window_size.height,\n                  width_rate: size.width / window_size.width,\n                  height_rate: size.height / window_size.height,\n                })\n              } else {\n                None\n              };\n            }\n            Err(e) => {\n              log::error!(\"failed to get webview bounds: {e}\");\n            }\n          },\n          WebviewMessage::WithWebview(f) => {\n            #[cfg(any(\n              target_os = \"linux\",\n              target_os = \"dragonfly\",\n              target_os = \"freebsd\",\n              target_os = \"netbsd\",\n              target_os = \"openbsd\"\n            ))]\n            {\n              f(webview.webview());\n            }\n            #[cfg(target_os = \"macos\")]\n            {\n              use wry::WebViewExtMacOS;\n              f(Webview {\n                webview: Retained::into_raw(webview.webview()) as *mut objc2::runtime::AnyObject\n                  as *mut std::ffi::c_void,\n                manager: Retained::into_raw(webview.manager()) as *mut objc2::runtime::AnyObject\n                  as *mut std::ffi::c_void,\n                ns_window: Retained::into_raw(webview.ns_window()) as *mut objc2::runtime::AnyObject\n                  as *mut std::ffi::c_void,\n              });\n            }\n            #[cfg(target_os = \"ios\")]\n            {\n              use wry::WebViewExtIOS;\n\n              f(Webview {\n                webview: Retained::into_raw(webview.inner.webview())\n                  as *mut objc2::runtime::AnyObject\n                  as *mut std::ffi::c_void,\n                manager: Retained::into_raw(webview.inner.manager())\n                  as *mut objc2::runtime::AnyObject\n                  as *mut std::ffi::c_void,\n                view_controller: window.ui_view_controller(),\n              });\n            }\n            #[cfg(windows)]\n            {\n              f(Webview {\n                controller: webview.controller(),\n                environment: webview.environment(),\n              });\n            }\n            #[cfg(target_os = \"android\")]\n            {\n              f(webview.handle())\n            }\n          }\n          #[cfg(any(debug_assertions, feature = \"devtools\"))]\n          WebviewMessage::OpenDevTools => {\n            webview.open_devtools();\n          }\n          #[cfg(any(debug_assertions, feature = \"devtools\"))]\n          WebviewMessage::CloseDevTools => {\n            webview.close_devtools();\n          }\n          #[cfg(any(debug_assertions, feature = \"devtools\"))]\n          WebviewMessage::IsDevToolsOpen(tx) => {\n            tx.send(webview.is_devtools_open()).unwrap();\n          }\n        }\n      }\n    }\n    Message::CreateWebview(window_id, handler) => {\n      let window = windows\n        .0\n        .borrow()\n        .get(&window_id)\n        .map(|w| (w.inner.clone(), w.focused_webview.clone()));\n      if let Some((Some(window), focused_webview)) = window {\n        match handler(&window, CreateWebviewOptions { focused_webview }) {\n          Ok(webview) => {\n            #[allow(unknown_lints, clippy::manual_inspect)]\n            windows.0.borrow_mut().get_mut(&window_id).map(|w| {\n              w.webviews.push(webview);\n              w.has_children.store(true, Ordering::Relaxed);\n              w\n            });\n          }\n          Err(e) => {\n            log::error!(\"{e}\");\n          }\n        }\n      }\n    }\n    Message::CreateWindow(window_id, handler) => match handler(event_loop) {\n      // wait for borrow_mut to be available - on Windows we might poll for the window to be inserted\n      Ok(webview) => loop {\n        if let Ok(mut windows) = windows.0.try_borrow_mut() {\n          windows.insert(window_id, webview);\n          break;\n        }\n      },\n      Err(e) => {\n        log::error!(\"{e}\");\n      }\n    },\n    Message::CreateRawWindow(window_id, handler, sender) => {\n      let (label, builder) = handler();\n\n      #[cfg(windows)]\n      let background_color = builder.window.background_color;\n      #[cfg(windows)]\n      let is_window_transparent = builder.window.transparent;\n\n      if let Ok(window) = builder.build(event_loop) {\n        window_id_map.insert(window.id(), window_id);\n\n        let window = Arc::new(window);\n\n        #[cfg(windows)]\n        let surface = if is_window_transparent {\n          if let Ok(context) = softbuffer::Context::new(window.clone()) {\n            if let Ok(mut surface) = softbuffer::Surface::new(&context, window.clone()) {\n              window.draw_surface(&mut surface, background_color);\n              Some(surface)\n            } else {\n              None\n            }\n          } else {\n            None\n          }\n        } else {\n          None\n        };\n\n        windows.0.borrow_mut().insert(\n          window_id,\n          WindowWrapper {\n            label,\n            has_children: AtomicBool::new(false),\n            inner: Some(window.clone()),\n            window_event_listeners: Default::default(),\n            webviews: Vec::new(),\n            #[cfg(windows)]\n            background_color,\n            #[cfg(windows)]\n            is_window_transparent,\n            #[cfg(windows)]\n            surface,\n            focused_webview: Default::default(),\n          },\n        );\n        sender.send(Ok(Arc::downgrade(&window))).unwrap();\n      } else {\n        sender.send(Err(Error::CreateWindow)).unwrap();\n      }\n    }\n\n    Message::UserEvent(_) => (),\n    Message::EventLoopWindowTarget(message) => match message {\n      EventLoopWindowTargetMessage::CursorPosition(sender) => {\n        let pos = event_loop\n          .cursor_position()\n          .map_err(|_| Error::FailedToSendMessage);\n        sender.send(pos).unwrap();\n      }\n      EventLoopWindowTargetMessage::SetTheme(theme) => {\n        event_loop.set_theme(to_tao_theme(theme));\n      }\n      EventLoopWindowTargetMessage::SetDeviceEventFilter(filter) => {\n        event_loop.set_device_event_filter(DeviceEventFilterWrapper::from(filter).0);\n      }\n    },\n  }\n}\n\nfn handle_event_loop<T: UserEvent>(\n  event: Event<'_, Message<T>>,\n  event_loop: &EventLoopWindowTarget<Message<T>>,\n  control_flow: &mut ControlFlow,\n  context: EventLoopIterationContext<'_, T>,\n) {\n  let EventLoopIterationContext {\n    callback,\n    window_id_map,\n    windows,\n    #[cfg(feature = \"tracing\")]\n    active_tracing_spans,\n  } = context;\n  if *control_flow != ControlFlow::Exit {\n    *control_flow = ControlFlow::Wait;\n  }\n\n  match event {\n    Event::NewEvents(StartCause::Init) => {\n      callback(RunEvent::Ready);\n    }\n\n    Event::NewEvents(StartCause::Poll) => {\n      callback(RunEvent::Resumed);\n    }\n\n    Event::MainEventsCleared => {\n      callback(RunEvent::MainEventsCleared);\n    }\n\n    Event::LoopDestroyed => {\n      callback(RunEvent::Exit);\n    }\n\n    #[cfg(windows)]\n    Event::RedrawRequested(id) => {\n      if let Some(window_id) = window_id_map.get(&id) {\n        let mut windows_ref = windows.0.borrow_mut();\n        if let Some(window) = windows_ref.get_mut(&window_id) {\n          if window.is_window_transparent {\n            let background_color = window.background_color;\n            if let Some(surface) = &mut window.surface {\n              if let Some(window) = &window.inner {\n                window.draw_surface(surface, background_color);\n              }\n            }\n          }\n        }\n      }\n    }\n\n    #[cfg(feature = \"tracing\")]\n    Event::RedrawEventsCleared => {\n      active_tracing_spans.remove_window_draw();\n    }\n\n    Event::UserEvent(Message::Webview(\n      window_id,\n      webview_id,\n      WebviewMessage::WebviewEvent(event),\n    )) => {\n      let windows_ref = windows.0.borrow();\n      if let Some(window) = windows_ref.get(&window_id) {\n        if let Some(webview) = window.webviews.iter().find(|w| w.id == webview_id) {\n          let label = webview.label.clone();\n          let webview_event_listeners = webview.webview_event_listeners.clone();\n\n          drop(windows_ref);\n\n          callback(RunEvent::WebviewEvent {\n            label,\n            event: event.clone(),\n          });\n          let listeners = webview_event_listeners.lock().unwrap();\n          let handlers = listeners.values();\n          for handler in handlers {\n            handler(&event);\n          }\n        }\n      }\n    }\n\n    Event::UserEvent(Message::Webview(\n      window_id,\n      _webview_id,\n      WebviewMessage::SynthesizedWindowEvent(event),\n    )) => {\n      if let Some(event) = WindowEventWrapper::from(event).0 {\n        let windows_ref = windows.0.borrow();\n        let window = windows_ref.get(&window_id);\n        if let Some(window) = window {\n          let label = window.label.clone();\n          let window_event_listeners = window.window_event_listeners.clone();\n\n          drop(windows_ref);\n\n          callback(RunEvent::WindowEvent {\n            label,\n            event: event.clone(),\n          });\n\n          let listeners = window_event_listeners.lock().unwrap();\n          let handlers = listeners.values();\n          for handler in handlers {\n            handler(&event);\n          }\n        }\n      }\n    }\n\n    Event::WindowEvent {\n      event, window_id, ..\n    } => {\n      if let Some(window_id) = window_id_map.get(&window_id) {\n        {\n          let windows_ref = windows.0.borrow();\n          if let Some(window) = windows_ref.get(&window_id) {\n            if let Some(event) = WindowEventWrapper::parse(window, &event).0 {\n              let label = window.label.clone();\n              let window_event_listeners = window.window_event_listeners.clone();\n\n              drop(windows_ref);\n\n              callback(RunEvent::WindowEvent {\n                label,\n                event: event.clone(),\n              });\n              let listeners = window_event_listeners.lock().unwrap();\n              let handlers = listeners.values();\n              for handler in handlers {\n                handler(&event);\n              }\n            }\n          }\n        }\n\n        match event {\n          #[cfg(windows)]\n          TaoWindowEvent::ThemeChanged(theme) => {\n            if let Some(window) = windows.0.borrow().get(&window_id) {\n              for webview in &window.webviews {\n                let theme = match theme {\n                  TaoTheme::Dark => wry::Theme::Dark,\n                  TaoTheme::Light => wry::Theme::Light,\n                  _ => wry::Theme::Light,\n                };\n                if let Err(e) = webview.set_theme(theme) {\n                  log::error!(\"failed to set theme: {e}\");\n                }\n              }\n            }\n          }\n          TaoWindowEvent::CloseRequested => {\n            on_close_requested(callback, window_id, windows);\n          }\n          TaoWindowEvent::Destroyed => {\n            let removed = windows.0.borrow_mut().remove(&window_id).is_some();\n            if removed {\n              let is_empty = windows.0.borrow().is_empty();\n              if is_empty {\n                let (tx, rx) = channel();\n                callback(RunEvent::ExitRequested { code: None, tx });\n\n                let recv = rx.try_recv();\n                let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));\n\n                if !should_prevent {\n                  *control_flow = ControlFlow::Exit;\n                }\n              }\n            }\n          }\n          TaoWindowEvent::Resized(size) => {\n            if let Some((Some(window), webviews)) = windows\n              .0\n              .borrow()\n              .get(&window_id)\n              .map(|w| (w.inner.clone(), w.webviews.clone()))\n            {\n              let size = size.to_logical::<f32>(window.scale_factor());\n              for webview in webviews {\n                if let Some(b) = &*webview.bounds.lock().unwrap() {\n                  if let Err(e) = webview.set_bounds(wry::Rect {\n                    position: LogicalPosition::new(size.width * b.x_rate, size.height * b.y_rate)\n                      .into(),\n                    size: LogicalSize::new(size.width * b.width_rate, size.height * b.height_rate)\n                      .into(),\n                  }) {\n                    log::error!(\"failed to autoresize webview: {e}\");\n                  }\n                }\n              }\n            }\n          }\n          _ => {}\n        }\n      }\n    }\n    Event::UserEvent(message) => match message {\n      Message::RequestExit(code) => {\n        let (tx, rx) = channel();\n        callback(RunEvent::ExitRequested {\n          code: Some(code),\n          tx,\n        });\n\n        let recv = rx.try_recv();\n        let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));\n\n        if !should_prevent {\n          *control_flow = ControlFlow::Exit;\n        }\n      }\n      Message::Window(id, WindowMessage::Close) => {\n        on_close_requested(callback, id, windows);\n      }\n      Message::Window(id, WindowMessage::Destroy) => {\n        on_window_close(id, windows);\n      }\n      Message::UserEvent(t) => callback(RunEvent::UserEvent(t)),\n      message => {\n        handle_user_message(\n          event_loop,\n          message,\n          UserMessageContext {\n            window_id_map,\n            windows,\n          },\n        );\n      }\n    },\n    #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n    Event::Opened { urls } => {\n      callback(RunEvent::Opened { urls });\n    }\n    #[cfg(target_os = \"macos\")]\n    Event::Reopen {\n      has_visible_windows,\n      ..\n    } => callback(RunEvent::Reopen {\n      has_visible_windows,\n    }),\n    _ => (),\n  }\n}\n\nfn on_close_requested<'a, T: UserEvent>(\n  callback: &'a mut (dyn FnMut(RunEvent<T>) + 'static),\n  window_id: WindowId,\n  windows: Arc<WindowsStore>,\n) {\n  let (tx, rx) = channel();\n  let windows_ref = windows.0.borrow();\n  if let Some(w) = windows_ref.get(&window_id) {\n    let label = w.label.clone();\n    let window_event_listeners = w.window_event_listeners.clone();\n\n    drop(windows_ref);\n\n    let listeners = window_event_listeners.lock().unwrap();\n    let handlers = listeners.values();\n    for handler in handlers {\n      handler(&WindowEvent::CloseRequested {\n        signal_tx: tx.clone(),\n      });\n    }\n    callback(RunEvent::WindowEvent {\n      label,\n      event: WindowEvent::CloseRequested { signal_tx: tx },\n    });\n    if let Ok(true) = rx.try_recv() {\n    } else {\n      on_window_close(window_id, windows);\n    }\n  }\n}\n\nfn on_window_close(window_id: WindowId, windows: Arc<WindowsStore>) {\n  if let Some(window_wrapper) = windows.0.borrow_mut().get_mut(&window_id) {\n    window_wrapper.inner = None;\n    #[cfg(windows)]\n    window_wrapper.surface.take();\n  }\n}\n\nfn parse_proxy_url(url: &Url) -> Result<ProxyConfig> {\n  let host = url.host().map(|h| h.to_string()).unwrap_or_default();\n  let port = url.port().map(|p| p.to_string()).unwrap_or_default();\n\n  if url.scheme() == \"http\" {\n    let config = ProxyConfig::Http(ProxyEndpoint { host, port });\n\n    Ok(config)\n  } else if url.scheme() == \"socks5\" {\n    let config = ProxyConfig::Socks5(ProxyEndpoint { host, port });\n\n    Ok(config)\n  } else {\n    Err(Error::InvalidProxyUrl)\n  }\n}\n\nfn create_window<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(\n  window_id: WindowId,\n  webview_id: u32,\n  event_loop: &EventLoopWindowTarget<Message<T>>,\n  context: &Context<T>,\n  pending: PendingWindow<T, Wry<T>>,\n  after_window_creation: Option<F>,\n) -> Result<WindowWrapper> {\n  #[allow(unused_mut)]\n  let PendingWindow {\n    mut window_builder,\n    label,\n    webview,\n  } = pending;\n\n  #[cfg(feature = \"tracing\")]\n  let _webview_create_span = tracing::debug_span!(\"wry::webview::create\").entered();\n  #[cfg(feature = \"tracing\")]\n  let window_draw_span = tracing::debug_span!(\"wry::window::draw\").entered();\n  #[cfg(feature = \"tracing\")]\n  let window_create_span =\n    tracing::debug_span!(parent: &window_draw_span, \"wry::window::create\").entered();\n\n  let window_event_listeners = WindowEventListeners::default();\n\n  #[cfg(windows)]\n  let background_color = window_builder.inner.window.background_color;\n  #[cfg(windows)]\n  let is_window_transparent = window_builder.inner.window.transparent;\n\n  #[cfg(target_os = \"macos\")]\n  {\n    if window_builder.tabbing_identifier.is_none()\n      || window_builder.inner.window.transparent\n      || !window_builder.inner.window.decorations\n    {\n      window_builder.inner = window_builder.inner.with_automatic_window_tabbing(false);\n    }\n  }\n\n  #[cfg(desktop)]\n  if window_builder.prevent_overflow.is_some() || window_builder.center {\n    let monitor = if let Some(window_position) = &window_builder.inner.window.position {\n      event_loop.available_monitors().find(|m| {\n        let monitor_pos = m.position();\n        let monitor_size = m.size();\n\n        // type annotations required for 32bit targets.\n        let window_position = window_position.to_physical::<i32>(m.scale_factor());\n\n        monitor_pos.x <= window_position.x\n          && window_position.x < monitor_pos.x + monitor_size.width as i32\n          && monitor_pos.y <= window_position.y\n          && window_position.y < monitor_pos.y + monitor_size.height as i32\n      })\n    } else {\n      event_loop.primary_monitor()\n    };\n    if let Some(monitor) = monitor {\n      let scale_factor = monitor.scale_factor();\n      let desired_size = window_builder\n        .inner\n        .window\n        .inner_size\n        .unwrap_or_else(|| TaoPhysicalSize::new(800, 600).into());\n      let mut inner_size = window_builder\n        .inner\n        .window\n        .inner_size_constraints\n        .clamp(desired_size, scale_factor)\n        .to_physical::<u32>(scale_factor);\n      let mut window_size = inner_size;\n      #[allow(unused_mut)]\n      // Left and right window shadow counts as part of the window on Windows\n      // We need to include it when calculating positions, but not size\n      let mut shadow_width = 0;\n      #[cfg(windows)]\n      if window_builder.inner.window.decorations {\n        use windows::Win32::UI::WindowsAndMessaging::{AdjustWindowRect, WS_OVERLAPPEDWINDOW};\n        let mut rect = windows::Win32::Foundation::RECT::default();\n        let result = unsafe { AdjustWindowRect(&mut rect, WS_OVERLAPPEDWINDOW, false) };\n        if result.is_ok() {\n          shadow_width = (rect.right - rect.left) as u32;\n          // rect.bottom is made out of shadow, and we don't care about it\n          window_size.height += -rect.top as u32;\n        }\n      }\n\n      if let Some(margin) = window_builder.prevent_overflow {\n        let work_area = monitor.work_area();\n        let margin = margin.to_physical::<u32>(scale_factor);\n        let constraint = PhysicalSize::new(\n          work_area.size.width - margin.width,\n          work_area.size.height - margin.height,\n        );\n        if window_size.width > constraint.width || window_size.height > constraint.height {\n          if window_size.width > constraint.width {\n            inner_size.width = inner_size\n              .width\n              .saturating_sub(window_size.width - constraint.width);\n            window_size.width = constraint.width;\n          }\n          if window_size.height > constraint.height {\n            inner_size.height = inner_size\n              .height\n              .saturating_sub(window_size.height - constraint.height);\n            window_size.height = constraint.height;\n          }\n          window_builder.inner.window.inner_size = Some(inner_size.into());\n        }\n      }\n\n      if window_builder.center {\n        window_size.width += shadow_width;\n        let position = window::calculate_window_center_position(window_size, monitor);\n        let logical_position = position.to_logical::<f64>(scale_factor);\n        window_builder = window_builder.position(logical_position.x, logical_position.y);\n      }\n    }\n  };\n\n  let window = window_builder\n    .inner\n    .build(event_loop)\n    .map_err(|_| Error::CreateWindow)?;\n\n  #[cfg(feature = \"tracing\")]\n  {\n    drop(window_create_span);\n\n    context\n      .main_thread\n      .active_tracing_spans\n      .0\n      .borrow_mut()\n      .push(ActiveTracingSpan::WindowDraw {\n        id: window.id(),\n        span: window_draw_span,\n      });\n  }\n\n  context.window_id_map.insert(window.id(), window_id);\n\n  if let Some(handler) = after_window_creation {\n    let raw = RawWindow {\n      #[cfg(windows)]\n      hwnd: window.hwnd(),\n      #[cfg(any(\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n      ))]\n      gtk_window: window.gtk_window(),\n      #[cfg(any(\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n      ))]\n      default_vbox: window.default_vbox(),\n      _marker: &std::marker::PhantomData,\n    };\n    handler(raw);\n  }\n\n  let mut webviews = Vec::new();\n\n  let focused_webview = Arc::new(Mutex::new(None));\n\n  if let Some(webview) = webview {\n    webviews.push(create_webview(\n      #[cfg(feature = \"unstable\")]\n      WebviewKind::WindowChild,\n      #[cfg(not(feature = \"unstable\"))]\n      WebviewKind::WindowContent,\n      &window,\n      Arc::new(Mutex::new(window_id)),\n      webview_id,\n      context,\n      webview,\n      focused_webview.clone(),\n    )?);\n  }\n\n  let window = Arc::new(window);\n\n  #[cfg(windows)]\n  let surface = if is_window_transparent {\n    if let Ok(context) = softbuffer::Context::new(window.clone()) {\n      if let Ok(mut surface) = softbuffer::Surface::new(&context, window.clone()) {\n        window.draw_surface(&mut surface, background_color);\n        Some(surface)\n      } else {\n        None\n      }\n    } else {\n      None\n    }\n  } else {\n    None\n  };\n\n  Ok(WindowWrapper {\n    label,\n    has_children: AtomicBool::new(false),\n    inner: Some(window),\n    webviews,\n    window_event_listeners,\n    #[cfg(windows)]\n    background_color,\n    #[cfg(windows)]\n    is_window_transparent,\n    #[cfg(windows)]\n    surface,\n    focused_webview,\n  })\n}\n\n/// the kind of the webview\n#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]\nenum WebviewKind {\n  // webview is the entire window content\n  WindowContent,\n  // webview is a child of the window, which can contain other webviews too\n  WindowChild,\n}\n\n#[derive(Debug, Clone)]\nstruct WebviewBounds {\n  x_rate: f32,\n  y_rate: f32,\n  width_rate: f32,\n  height_rate: f32,\n}\n\nfn create_webview<T: UserEvent>(\n  kind: WebviewKind,\n  window: &Window,\n  window_id: Arc<Mutex<WindowId>>,\n  id: WebviewId,\n  context: &Context<T>,\n  pending: PendingWebview<T, Wry<T>>,\n  #[allow(unused_variables)] focused_webview: Arc<Mutex<Option<String>>>,\n) -> Result<WebviewWrapper> {\n  if !context.webview_runtime_installed {\n    #[cfg(all(not(debug_assertions), windows))]\n    dialog::error(\n      r#\"Could not find the WebView2 Runtime.\n\nMake sure it is installed or download it from <A href=\"https://developer.microsoft.com/en-us/microsoft-edge/webview2\">https://developer.microsoft.com/en-us/microsoft-edge/webview2</A>\n\nYou may have it installed on another user account, but it is not available for this one.\n\"#,\n    );\n\n    if cfg!(target_os = \"macos\") {\n      log::warn!(\"WebKit webview runtime not found, attempting to create webview anyway.\");\n    } else {\n      return Err(Error::WebviewRuntimeNotInstalled);\n    }\n  }\n\n  #[allow(unused_mut)]\n  let PendingWebview {\n    webview_attributes,\n    uri_scheme_protocols,\n    label,\n    ipc_handler,\n    url,\n    ..\n  } = pending;\n\n  let mut web_context = context\n    .main_thread\n    .web_context\n    .lock()\n    .expect(\"poisoned WebContext store\");\n  let is_first_context = web_context.is_empty();\n  // the context must be stored on the HashMap because it must outlive the WebView on macOS\n  let automation_enabled = std::env::var(\"TAURI_WEBVIEW_AUTOMATION\").as_deref() == Ok(\"true\");\n  let web_context_key = webview_attributes.data_directory;\n  let entry = web_context.entry(web_context_key.clone());\n  let web_context = match entry {\n    Occupied(occupied) => {\n      let occupied = occupied.into_mut();\n      occupied.referenced_by_webviews.insert(label.clone());\n      occupied\n    }\n    Vacant(vacant) => {\n      let mut web_context = WryWebContext::new(web_context_key.clone());\n      web_context.set_allows_automation(if automation_enabled {\n        is_first_context\n      } else {\n        false\n      });\n      vacant.insert(WebContext {\n        inner: web_context,\n        referenced_by_webviews: [label.clone()].into(),\n        registered_custom_protocols: HashSet::new(),\n      })\n    }\n  };\n\n  let mut webview_builder = WebViewBuilder::new_with_web_context(&mut web_context.inner)\n    .with_id(&label)\n    .with_focused(webview_attributes.focus)\n    .with_transparent(webview_attributes.transparent)\n    .with_accept_first_mouse(webview_attributes.accept_first_mouse)\n    .with_incognito(webview_attributes.incognito)\n    .with_clipboard(webview_attributes.clipboard)\n    .with_hotkeys_zoom(webview_attributes.zoom_hotkeys_enabled);\n\n  if url != \"about:blank\" {\n    webview_builder = webview_builder.with_url(&url);\n  }\n\n  #[cfg(target_os = \"macos\")]\n  if let Some(webview_configuration) = webview_attributes.webview_configuration {\n    webview_builder = webview_builder.with_webview_configuration(webview_configuration);\n  }\n\n  #[cfg(any(target_os = \"windows\", target_os = \"android\"))]\n  {\n    webview_builder = webview_builder.with_https_scheme(webview_attributes.use_https_scheme);\n  }\n\n  if let Some(background_throttling) = webview_attributes.background_throttling {\n    webview_builder = webview_builder.with_background_throttling(match background_throttling {\n      tauri_utils::config::BackgroundThrottlingPolicy::Disabled => {\n        wry::BackgroundThrottlingPolicy::Disabled\n      }\n      tauri_utils::config::BackgroundThrottlingPolicy::Suspend => {\n        wry::BackgroundThrottlingPolicy::Suspend\n      }\n      tauri_utils::config::BackgroundThrottlingPolicy::Throttle => {\n        wry::BackgroundThrottlingPolicy::Throttle\n      }\n    });\n  }\n\n  if webview_attributes.javascript_disabled {\n    webview_builder = webview_builder.with_javascript_disabled();\n  }\n\n  if let Some(color) = webview_attributes.background_color {\n    webview_builder = webview_builder.with_background_color(color.into());\n  }\n\n  if webview_attributes.drag_drop_handler_enabled {\n    let proxy = context.proxy.clone();\n    let window_id_ = window_id.clone();\n    webview_builder = webview_builder.with_drag_drop_handler(move |event| {\n      let event = match event {\n        WryDragDropEvent::Enter {\n          paths,\n          position: (x, y),\n        } => DragDropEvent::Enter {\n          paths,\n          position: PhysicalPosition::new(x as _, y as _),\n        },\n        WryDragDropEvent::Over { position: (x, y) } => DragDropEvent::Over {\n          position: PhysicalPosition::new(x as _, y as _),\n        },\n        WryDragDropEvent::Drop {\n          paths,\n          position: (x, y),\n        } => DragDropEvent::Drop {\n          paths,\n          position: PhysicalPosition::new(x as _, y as _),\n        },\n        WryDragDropEvent::Leave => DragDropEvent::Leave,\n        _ => unimplemented!(),\n      };\n\n      let message = if kind == WebviewKind::WindowContent {\n        WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::DragDrop(event))\n      } else {\n        WebviewMessage::WebviewEvent(WebviewEvent::DragDrop(event))\n      };\n\n      let _ = proxy.send_event(Message::Webview(*window_id_.lock().unwrap(), id, message));\n      true\n    });\n  }\n\n  if let Some(navigation_handler) = pending.navigation_handler {\n    webview_builder = webview_builder.with_navigation_handler(move |url| {\n      url\n        .parse()\n        .map(|url| navigation_handler(&url))\n        .unwrap_or(true)\n    });\n  }\n\n  if let Some(new_window_handler) = pending.new_window_handler {\n    #[cfg(desktop)]\n    let context = context.clone();\n    webview_builder = webview_builder.with_new_window_req_handler(move |url, features| {\n      url\n        .parse()\n        .map(|url| {\n          let response = new_window_handler(\n            url,\n            tauri_runtime::webview::NewWindowFeatures::new(\n              features.size,\n              features.position,\n              tauri_runtime::webview::NewWindowOpener {\n                #[cfg(desktop)]\n                webview: features.opener.webview,\n                #[cfg(windows)]\n                environment: features.opener.environment,\n                #[cfg(target_os = \"macos\")]\n                target_configuration: features.opener.target_configuration,\n              },\n            ),\n          );\n          match response {\n            tauri_runtime::webview::NewWindowResponse::Allow => wry::NewWindowResponse::Allow,\n            #[cfg(desktop)]\n            tauri_runtime::webview::NewWindowResponse::Create { window_id } => {\n              let windows = &context.main_thread.windows.0;\n              let webview = loop {\n                if let Some(webview) = windows.try_borrow().ok().and_then(|windows| {\n                  windows\n                    .get(&window_id)\n                    .map(|window| window.webviews.first().unwrap().clone())\n                }) {\n                  break webview;\n                } else {\n                  // on Windows the window is created async so we should wait for it to be available\n                  std::thread::sleep(std::time::Duration::from_millis(50));\n                  continue;\n                };\n              };\n\n              #[cfg(desktop)]\n              wry::NewWindowResponse::Create {\n                #[cfg(target_os = \"macos\")]\n                webview: wry::WebViewExtMacOS::webview(&*webview).as_super().into(),\n                #[cfg(any(\n                  target_os = \"linux\",\n                  target_os = \"dragonfly\",\n                  target_os = \"freebsd\",\n                  target_os = \"netbsd\",\n                  target_os = \"openbsd\",\n                ))]\n                webview: webview.webview(),\n                #[cfg(windows)]\n                webview: webview.webview(),\n              }\n            }\n            tauri_runtime::webview::NewWindowResponse::Deny => wry::NewWindowResponse::Deny,\n          }\n        })\n        .unwrap_or(wry::NewWindowResponse::Deny)\n    });\n  }\n\n  if let Some(document_title_changed_handler) = pending.document_title_changed_handler {\n    webview_builder =\n      webview_builder.with_document_title_changed_handler(document_title_changed_handler)\n  }\n\n  let webview_bounds = if let Some(bounds) = webview_attributes.bounds {\n    let bounds: RectWrapper = bounds.into();\n    let bounds = bounds.0;\n\n    let scale_factor = window.scale_factor();\n    let position = bounds.position.to_logical::<f32>(scale_factor);\n    let size = bounds.size.to_logical::<f32>(scale_factor);\n\n    webview_builder = webview_builder.with_bounds(bounds);\n\n    let window_size = window.inner_size().to_logical::<f32>(scale_factor);\n\n    if webview_attributes.auto_resize {\n      Some(WebviewBounds {\n        x_rate: position.x / window_size.width,\n        y_rate: position.y / window_size.height,\n        width_rate: size.width / window_size.width,\n        height_rate: size.height / window_size.height,\n      })\n    } else {\n      None\n    }\n  } else {\n    #[cfg(feature = \"unstable\")]\n    {\n      webview_builder = webview_builder.with_bounds(wry::Rect {\n        position: LogicalPosition::new(0, 0).into(),\n        size: window.inner_size().into(),\n      });\n      Some(WebviewBounds {\n        x_rate: 0.,\n        y_rate: 0.,\n        width_rate: 1.,\n        height_rate: 1.,\n      })\n    }\n    #[cfg(not(feature = \"unstable\"))]\n    None\n  };\n\n  if let Some(download_handler) = pending.download_handler {\n    let download_handler_ = download_handler.clone();\n    webview_builder = webview_builder.with_download_started_handler(move |url, path| {\n      if let Ok(url) = url.parse() {\n        download_handler_(DownloadEvent::Requested {\n          url,\n          destination: path,\n        })\n      } else {\n        false\n      }\n    });\n    webview_builder = webview_builder.with_download_completed_handler(move |url, path, success| {\n      if let Ok(url) = url.parse() {\n        download_handler(DownloadEvent::Finished { url, path, success });\n      }\n    });\n  }\n\n  if let Some(page_load_handler) = pending.on_page_load_handler {\n    webview_builder = webview_builder.with_on_page_load_handler(move |event, url| {\n      let _ = url.parse().map(|url| {\n        page_load_handler(\n          url,\n          match event {\n            wry::PageLoadEvent::Started => tauri_runtime::webview::PageLoadEvent::Started,\n            wry::PageLoadEvent::Finished => tauri_runtime::webview::PageLoadEvent::Finished,\n          },\n        )\n      });\n    });\n  }\n\n  if let Some(user_agent) = webview_attributes.user_agent {\n    webview_builder = webview_builder.with_user_agent(&user_agent);\n  }\n\n  if let Some(proxy_url) = webview_attributes.proxy_url {\n    let config = parse_proxy_url(&proxy_url)?;\n\n    webview_builder = webview_builder.with_proxy_config(config);\n  }\n\n  #[cfg(windows)]\n  {\n    if let Some(additional_browser_args) = webview_attributes.additional_browser_args {\n      webview_builder = webview_builder.with_additional_browser_args(&additional_browser_args);\n    }\n\n    if let Some(environment) = webview_attributes.environment {\n      webview_builder = webview_builder.with_environment(environment);\n    }\n\n    webview_builder = webview_builder.with_theme(match window.theme() {\n      TaoTheme::Dark => wry::Theme::Dark,\n      TaoTheme::Light => wry::Theme::Light,\n      _ => wry::Theme::Light,\n    });\n\n    webview_builder =\n      webview_builder.with_scroll_bar_style(match webview_attributes.scroll_bar_style {\n        ScrollBarStyle::Default => WryScrollBarStyle::Default,\n        ScrollBarStyle::FluentOverlay => WryScrollBarStyle::FluentOverlay,\n        _ => unreachable!(),\n      });\n  }\n\n  #[cfg(windows)]\n  {\n    webview_builder = webview_builder\n      .with_browser_extensions_enabled(webview_attributes.browser_extensions_enabled);\n  }\n\n  #[cfg(any(\n    windows,\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  {\n    if let Some(path) = &webview_attributes.extensions_path {\n      webview_builder = webview_builder.with_extensions_path(path);\n    }\n  }\n\n  #[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n  ))]\n  {\n    if let Some(related_view) = webview_attributes.related_view {\n      webview_builder = webview_builder.with_related_view(related_view);\n    }\n  }\n\n  #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n  {\n    if let Some(data_store_identifier) = &webview_attributes.data_store_identifier {\n      webview_builder = webview_builder.with_data_store_identifier(*data_store_identifier);\n    }\n\n    webview_builder =\n      webview_builder.with_allow_link_preview(webview_attributes.allow_link_preview);\n  }\n\n  #[cfg(target_os = \"ios\")]\n  {\n    if let Some(input_accessory_view_builder) = webview_attributes.input_accessory_view_builder {\n      webview_builder = webview_builder\n        .with_input_accessory_view_builder(move |webview| input_accessory_view_builder.0(webview));\n    }\n  }\n\n  #[cfg(target_os = \"macos\")]\n  {\n    if let Some(position) = &webview_attributes.traffic_light_position {\n      webview_builder = webview_builder.with_traffic_light_inset(*position);\n    }\n  }\n\n  webview_builder = webview_builder.with_ipc_handler(create_ipc_handler(\n    kind,\n    window_id.clone(),\n    id,\n    context.clone(),\n    label.clone(),\n    ipc_handler,\n  ));\n\n  for script in webview_attributes.initialization_scripts {\n    webview_builder = webview_builder\n      .with_initialization_script_for_main_only(script.script, script.for_main_frame_only);\n  }\n\n  for (scheme, protocol) in uri_scheme_protocols {\n    // on Linux the custom protocols are associated with the web context\n    // and you cannot register a scheme more than once\n    #[cfg(any(\n      target_os = \"linux\",\n      target_os = \"dragonfly\",\n      target_os = \"freebsd\",\n      target_os = \"netbsd\",\n      target_os = \"openbsd\"\n    ))]\n    {\n      if web_context.registered_custom_protocols.contains(&scheme) {\n        continue;\n      }\n\n      web_context\n        .registered_custom_protocols\n        .insert(scheme.clone());\n    }\n\n    webview_builder = webview_builder.with_asynchronous_custom_protocol(\n      scheme,\n      move |webview_id, request, responder| {\n        protocol(\n          webview_id,\n          request,\n          Box::new(move |response| responder.respond(response)),\n        )\n      },\n    );\n  }\n\n  #[cfg(any(debug_assertions, feature = \"devtools\"))]\n  {\n    webview_builder = webview_builder.with_devtools(webview_attributes.devtools.unwrap_or(true));\n  }\n\n  #[cfg(target_os = \"android\")]\n  {\n    if let Some(on_webview_created) = pending.on_webview_created {\n      webview_builder = webview_builder.on_webview_created(move |ctx| {\n        on_webview_created(tauri_runtime::webview::CreationContext {\n          env: ctx.env,\n          activity: ctx.activity,\n          webview: ctx.webview,\n        })\n      });\n    }\n  }\n\n  let webview = match kind {\n    #[cfg(not(any(\n      target_os = \"windows\",\n      target_os = \"macos\",\n      target_os = \"ios\",\n      target_os = \"android\"\n    )))]\n    WebviewKind::WindowChild => {\n      // only way to account for menu bar height, and also works for multiwebviews :)\n      let vbox = window.default_vbox().unwrap();\n      webview_builder.build_gtk(vbox)\n    }\n    #[cfg(any(\n      target_os = \"windows\",\n      target_os = \"macos\",\n      target_os = \"ios\",\n      target_os = \"android\"\n    ))]\n    WebviewKind::WindowChild => webview_builder.build_as_child(&window),\n    WebviewKind::WindowContent => {\n      #[cfg(any(\n        target_os = \"windows\",\n        target_os = \"macos\",\n        target_os = \"ios\",\n        target_os = \"android\"\n      ))]\n      let builder = webview_builder.build(&window);\n      #[cfg(not(any(\n        target_os = \"windows\",\n        target_os = \"macos\",\n        target_os = \"ios\",\n        target_os = \"android\"\n      )))]\n      let builder = {\n        let vbox = window.default_vbox().unwrap();\n        webview_builder.build_gtk(vbox)\n      };\n      builder\n    }\n  }\n  .map_err(|e| Error::CreateWebview(Box::new(e)))?;\n\n  if kind == WebviewKind::WindowContent {\n    #[cfg(any(\n      target_os = \"linux\",\n      target_os = \"dragonfly\",\n      target_os = \"freebsd\",\n      target_os = \"netbsd\",\n      target_os = \"openbsd\"\n    ))]\n    undecorated_resizing::attach_resize_handler(&webview);\n    #[cfg(windows)]\n    if window.is_resizable() && !window.is_decorated() {\n      undecorated_resizing::attach_resize_handler(window.hwnd(), window.has_undecorated_shadow());\n    }\n  }\n\n  #[cfg(windows)]\n  {\n    let controller = webview.controller();\n    let proxy_clone = context.proxy.clone();\n    let window_id_ = window_id.clone();\n    let mut token = 0;\n    unsafe {\n      let label_ = label.clone();\n      let focused_webview_ = focused_webview.clone();\n      controller.add_GotFocus(\n        &FocusChangedEventHandler::create(Box::new(move |_, _| {\n          let mut focused_webview = focused_webview_.lock().unwrap();\n          // when using multiwebview mode, we should check if the focus change is actually a \"webview focus change\"\n          // instead of a window focus change (here we're patching window events, so we only care about the actual window changing focus)\n          let already_focused = focused_webview.is_some();\n          focused_webview.replace(label_.clone());\n\n          if !already_focused {\n            let _ = proxy_clone.send_event(Message::Webview(\n              *window_id_.lock().unwrap(),\n              id,\n              WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(true)),\n            ));\n          }\n          Ok(())\n        })),\n        &mut token,\n      )\n    }\n    .unwrap();\n    unsafe {\n      let label_ = label.clone();\n      let window_id_ = window_id.clone();\n      let proxy_clone = context.proxy.clone();\n      controller.add_LostFocus(\n        &FocusChangedEventHandler::create(Box::new(move |_, _| {\n          let mut focused_webview = focused_webview.lock().unwrap();\n          // when using multiwebview mode, we should handle webview focus changes\n          // so we check is the currently focused webview matches this webview's\n          // (in this case, it means we lost the window focus)\n          //\n          // on multiwebview mode if we change focus to a different webview\n          // we get the gotFocus event of the other webview before the lostFocus\n          // so this check makes sense\n          let lost_window_focus = focused_webview.as_ref().map_or(true, |w| w == &label_);\n\n          if lost_window_focus {\n            // only reset when we lost window focus - otherwise some other webview is focused\n            *focused_webview = None;\n            let _ = proxy_clone.send_event(Message::Webview(\n              *window_id_.lock().unwrap(),\n              id,\n              WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(false)),\n            ));\n          }\n          Ok(())\n        })),\n        &mut token,\n      )\n    }\n    .unwrap();\n\n    if let Ok(webview) = unsafe { controller.CoreWebView2() } {\n      let proxy_clone = context.proxy.clone();\n      unsafe {\n        let _ = webview.add_ContainsFullScreenElementChanged(\n          &ContainsFullScreenElementChangedEventHandler::create(Box::new(move |sender, _| {\n            let mut contains_fullscreen_element = windows::core::BOOL::default();\n            sender\n              .ok_or_else(windows::core::Error::empty)?\n              .ContainsFullScreenElement(&mut contains_fullscreen_element)?;\n            let _ = proxy_clone.send_event(Message::Window(\n              *window_id.lock().unwrap(),\n              WindowMessage::SetFullscreen(contains_fullscreen_element.as_bool()),\n            ));\n            Ok(())\n          })),\n          &mut token,\n        );\n      }\n    }\n  }\n\n  Ok(WebviewWrapper {\n    label,\n    id,\n    inner: Rc::new(webview),\n    context_store: context.main_thread.web_context.clone(),\n    webview_event_listeners: Default::default(),\n    context_key: if automation_enabled {\n      None\n    } else {\n      web_context_key\n    },\n    bounds: Arc::new(Mutex::new(webview_bounds)),\n  })\n}\n\n/// Create a wry ipc handler from a tauri ipc handler.\nfn create_ipc_handler<T: UserEvent>(\n  _kind: WebviewKind,\n  window_id: Arc<Mutex<WindowId>>,\n  webview_id: WebviewId,\n  context: Context<T>,\n  label: String,\n  ipc_handler: Option<WebviewIpcHandler<T, Wry<T>>>,\n) -> Box<IpcHandler> {\n  Box::new(move |request| {\n    if let Some(handler) = &ipc_handler {\n      handler(\n        DetachedWebview {\n          label: label.clone(),\n          dispatcher: WryWebviewDispatcher {\n            window_id: window_id.clone(),\n            webview_id,\n            context: context.clone(),\n          },\n        },\n        request,\n      );\n    }\n  })\n}\n\n#[cfg(target_os = \"macos\")]\nfn inner_size(\n  window: &Window,\n  webviews: &[WebviewWrapper],\n  has_children: bool,\n) -> TaoPhysicalSize<u32> {\n  if !has_children && !webviews.is_empty() {\n    use wry::WebViewExtMacOS;\n    let webview = webviews.first().unwrap();\n    let view = unsafe { Retained::cast_unchecked::<objc2_app_kit::NSView>(webview.webview()) };\n    let view_frame = view.frame();\n    let logical: TaoLogicalSize<f64> = (view_frame.size.width, view_frame.size.height).into();\n    return logical.to_physical(window.scale_factor());\n  }\n\n  window.inner_size()\n}\n\n#[cfg(not(target_os = \"macos\"))]\n#[allow(unused_variables)]\nfn inner_size(\n  window: &Window,\n  webviews: &[WebviewWrapper],\n  has_children: bool,\n) -> TaoPhysicalSize<u32> {\n  window.inner_size()\n}\n\nfn to_tao_theme(theme: Option<Theme>) -> Option<TaoTheme> {\n  match theme {\n    Some(Theme::Light) => Some(TaoTheme::Light),\n    Some(Theme::Dark) => Some(TaoTheme::Dark),\n    _ => None,\n  }\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/monitor/linux.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse gtk::prelude::MonitorExt;\nuse tao::platform::unix::MonitorHandleExtUnix;\nuse tauri_runtime::dpi::{LogicalPosition, LogicalSize, PhysicalRect};\n\nimpl super::MonitorExt for tao::monitor::MonitorHandle {\n  fn work_area(&self) -> PhysicalRect<i32, u32> {\n    let rect = self.gdk_monitor().workarea();\n    let scale_factor = self.scale_factor();\n    PhysicalRect {\n      size: LogicalSize::new(rect.width() as u32, rect.height() as u32).to_physical(scale_factor),\n      position: LogicalPosition::new(rect.x(), rect.y()).to_physical(scale_factor),\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/monitor/macos.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse tauri_runtime::dpi::{LogicalSize, PhysicalRect};\n\nimpl super::MonitorExt for tao::monitor::MonitorHandle {\n  fn work_area(&self) -> PhysicalRect<i32, u32> {\n    use objc2_app_kit::NSScreen;\n    use tao::platform::macos::MonitorHandleExtMacOS;\n    if let Some(ns_screen) = self.ns_screen() {\n      let ns_screen: &NSScreen = unsafe { &*ns_screen.cast() };\n      let screen_frame = ns_screen.frame();\n      let visible_frame = ns_screen.visibleFrame();\n\n      let scale_factor = self.scale_factor();\n\n      let mut position = self.position().to_logical::<f64>(scale_factor);\n\n      position.x += visible_frame.origin.x - screen_frame.origin.x;\n\n      PhysicalRect {\n        size: LogicalSize::new(visible_frame.size.width, visible_frame.size.height)\n          .to_physical(scale_factor),\n        position: position.to_physical(scale_factor),\n      }\n    } else {\n      PhysicalRect {\n        size: self.size(),\n        position: self.position(),\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/monitor/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse tauri_runtime::dpi::PhysicalRect;\n\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"netbsd\",\n  target_os = \"openbsd\"\n))]\nmod linux;\n#[cfg(target_os = \"macos\")]\nmod macos;\n#[cfg(windows)]\nmod windows;\n\npub trait MonitorExt {\n  /// Get the work area of this monitor\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Android / iOS**: Unsupported.\n  fn work_area(&self) -> PhysicalRect<i32, u32>;\n}\n\n#[cfg(mobile)]\nimpl MonitorExt for tao::monitor::MonitorHandle {\n  fn work_area(&self) -> PhysicalRect<i32, u32> {\n    PhysicalRect {\n      size: self.size(),\n      position: self.position(),\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/monitor/windows.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse tao::dpi::{PhysicalPosition, PhysicalSize};\nuse tauri_runtime::dpi::PhysicalRect;\n\nimpl super::MonitorExt for tao::monitor::MonitorHandle {\n  fn work_area(&self) -> PhysicalRect<i32, u32> {\n    use tao::platform::windows::MonitorHandleExtWindows;\n    use windows::Win32::Graphics::Gdi::{GetMonitorInfoW, HMONITOR, MONITORINFO};\n    let mut monitor_info = MONITORINFO {\n      cbSize: std::mem::size_of::<MONITORINFO>() as u32,\n      ..Default::default()\n    };\n    let status = unsafe { GetMonitorInfoW(HMONITOR(self.hmonitor() as _), &mut monitor_info) };\n    if status.as_bool() {\n      PhysicalRect {\n        size: PhysicalSize::new(\n          (monitor_info.rcWork.right - monitor_info.rcWork.left) as u32,\n          (monitor_info.rcWork.bottom - monitor_info.rcWork.top) as u32,\n        ),\n        position: PhysicalPosition::new(monitor_info.rcWork.left, monitor_info.rcWork.top),\n      }\n    } else {\n      PhysicalRect {\n        size: self.size(),\n        position: self.position(),\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/undecorated_resizing.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg(any(\n  windows,\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"netbsd\",\n  target_os = \"openbsd\"\n))]\n\nconst CLIENT: isize = 0b0000;\nconst LEFT: isize = 0b0001;\nconst RIGHT: isize = 0b0010;\nconst TOP: isize = 0b0100;\nconst BOTTOM: isize = 0b1000;\nconst TOPLEFT: isize = TOP | LEFT;\nconst TOPRIGHT: isize = TOP | RIGHT;\nconst BOTTOMLEFT: isize = BOTTOM | LEFT;\nconst BOTTOMRIGHT: isize = BOTTOM | RIGHT;\n\n#[cfg(not(windows))]\npub use self::gtk::*;\n#[cfg(windows)]\npub use self::windows::*;\n\n#[cfg(windows)]\ntype WindowPositions = i32;\n#[cfg(not(windows))]\ntype WindowPositions = f64;\n\n#[derive(Debug, PartialEq, Eq, Clone, Copy)]\nenum HitTestResult {\n  Client,\n  Left,\n  Right,\n  Top,\n  Bottom,\n  TopLeft,\n  TopRight,\n  BottomLeft,\n  BottomRight,\n  NoWhere,\n}\n\n#[allow(clippy::too_many_arguments)]\nfn hit_test(\n  left: WindowPositions,\n  top: WindowPositions,\n  right: WindowPositions,\n  bottom: WindowPositions,\n  cx: WindowPositions,\n  cy: WindowPositions,\n  border_x: WindowPositions,\n  border_y: WindowPositions,\n) -> HitTestResult {\n  #[rustfmt::skip]\n  let result = (LEFT * (cx < left + border_x) as isize)\n             | (RIGHT * (cx >= right - border_x) as isize)\n             | (TOP * (cy < top + border_y) as isize)\n             | (BOTTOM * (cy >= bottom - border_y) as isize);\n\n  match result {\n    CLIENT => HitTestResult::Client,\n    LEFT => HitTestResult::Left,\n    RIGHT => HitTestResult::Right,\n    TOP => HitTestResult::Top,\n    BOTTOM => HitTestResult::Bottom,\n    TOPLEFT => HitTestResult::TopLeft,\n    TOPRIGHT => HitTestResult::TopRight,\n    BOTTOMLEFT => HitTestResult::BottomLeft,\n    BOTTOMRIGHT => HitTestResult::BottomRight,\n    _ => HitTestResult::NoWhere,\n  }\n}\n\n#[cfg(windows)]\nmod windows {\n  use crate::util;\n\n  use super::{hit_test, HitTestResult};\n\n  use windows::core::*;\n  use windows::Win32::System::LibraryLoader::*;\n  use windows::Win32::UI::WindowsAndMessaging::*;\n  use windows::Win32::{Foundation::*, UI::Shell::SetWindowSubclass};\n  use windows::Win32::{Graphics::Gdi::*, UI::Shell::DefSubclassProc};\n\n  impl HitTestResult {\n    fn to_win32(self) -> i32 {\n      match self {\n        HitTestResult::Left => HTLEFT as _,\n        HitTestResult::Right => HTRIGHT as _,\n        HitTestResult::Top => HTTOP as _,\n        HitTestResult::Bottom => HTBOTTOM as _,\n        HitTestResult::TopLeft => HTTOPLEFT as _,\n        HitTestResult::TopRight => HTTOPRIGHT as _,\n        HitTestResult::BottomLeft => HTBOTTOMLEFT as _,\n        HitTestResult::BottomRight => HTBOTTOMRIGHT as _,\n        _ => HTTRANSPARENT,\n      }\n    }\n  }\n\n  const CLASS_NAME: PCWSTR = w!(\"TAURI_DRAG_RESIZE_BORDERS\");\n  const WINDOW_NAME: PCWSTR = w!(\"TAURI_DRAG_RESIZE_WINDOW\");\n\n  const WM_UPDATE_UNDECORATED_SHADOWS: u32 = WM_USER + 100;\n\n  struct UndecoratedResizingData {\n    child: HWND,\n    has_undecorated_shadows: bool,\n  }\n\n  pub fn attach_resize_handler(hwnd: isize, has_undecorated_shadows: bool) {\n    let parent = HWND(hwnd as _);\n\n    // return early if we already attached\n    if unsafe { FindWindowExW(Some(parent), None, CLASS_NAME, WINDOW_NAME) }.is_ok() {\n      return;\n    }\n\n    let class = WNDCLASSEXW {\n      cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,\n      style: WNDCLASS_STYLES::default(),\n      lpfnWndProc: Some(drag_resize_window_proc),\n      cbClsExtra: 0,\n      cbWndExtra: 0,\n      hInstance: unsafe { HINSTANCE(GetModuleHandleW(PCWSTR::null()).unwrap_or_default().0) },\n      hIcon: HICON::default(),\n      hCursor: HCURSOR::default(),\n      hbrBackground: HBRUSH::default(),\n      lpszMenuName: PCWSTR::null(),\n      lpszClassName: CLASS_NAME,\n      hIconSm: HICON::default(),\n    };\n\n    unsafe { RegisterClassExW(&class) };\n\n    let mut rect = RECT::default();\n    unsafe { GetClientRect(parent, &mut rect).unwrap() };\n    let width = rect.right - rect.left;\n    let height = rect.bottom - rect.top;\n\n    let data = UndecoratedResizingData {\n      child: HWND::default(),\n      has_undecorated_shadows,\n    };\n\n    let Ok(drag_window) = (unsafe {\n      CreateWindowExW(\n        WINDOW_EX_STYLE::default(),\n        CLASS_NAME,\n        WINDOW_NAME,\n        WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,\n        0,\n        0,\n        width,\n        height,\n        Some(parent),\n        None,\n        GetModuleHandleW(None).ok().map(|h| HINSTANCE(h.0)),\n        Some(Box::into_raw(Box::new(data)) as _),\n      )\n    }) else {\n      return;\n    };\n\n    unsafe {\n      set_drag_hwnd_rgn(drag_window, width, height, has_undecorated_shadows);\n\n      let _ = SetWindowPos(\n        drag_window,\n        Some(HWND_TOP),\n        0,\n        0,\n        0,\n        0,\n        SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE,\n      );\n\n      let data = UndecoratedResizingData {\n        child: drag_window,\n        has_undecorated_shadows,\n      };\n\n      let _ = SetWindowSubclass(\n        parent,\n        Some(subclass_parent),\n        (WM_USER + 1) as _,\n        Box::into_raw(Box::new(data)) as _,\n      );\n    }\n  }\n\n  unsafe extern \"system\" fn subclass_parent(\n    parent: HWND,\n    msg: u32,\n    wparam: WPARAM,\n    lparam: LPARAM,\n    _: usize,\n    data: usize,\n  ) -> LRESULT {\n    match msg {\n      WM_SIZE => {\n        let data = data as *mut UndecoratedResizingData;\n        let data = &*data;\n        let child = data.child;\n        let has_undecorated_shadows = data.has_undecorated_shadows;\n\n        // when parent is maximized, remove the undecorated window drag resize region\n        if is_maximized(parent).unwrap_or(false) {\n          let _ = SetWindowPos(\n            child,\n            Some(HWND_TOP),\n            0,\n            0,\n            0,\n            0,\n            SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE,\n          );\n        } else {\n          // otherwise updat the cutout region\n          let mut rect = RECT::default();\n          if GetClientRect(parent, &mut rect).is_ok() {\n            let width = rect.right - rect.left;\n            let height = rect.bottom - rect.top;\n\n            let _ = SetWindowPos(\n              child,\n              Some(HWND_TOP),\n              0,\n              0,\n              width,\n              height,\n              SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE,\n            );\n\n            set_drag_hwnd_rgn(child, width, height, has_undecorated_shadows);\n          }\n        }\n      }\n\n      WM_UPDATE_UNDECORATED_SHADOWS => {\n        let data = data as *mut UndecoratedResizingData;\n        let data = &mut *data;\n        data.has_undecorated_shadows = wparam.0 != 0;\n      }\n\n      WM_NCDESTROY => {\n        let data = data as *mut UndecoratedResizingData;\n        drop(Box::from_raw(data));\n      }\n\n      _ => {}\n    }\n\n    DefSubclassProc(parent, msg, wparam, lparam)\n  }\n\n  unsafe extern \"system\" fn drag_resize_window_proc(\n    child: HWND,\n    msg: u32,\n    wparam: WPARAM,\n    lparam: LPARAM,\n  ) -> LRESULT {\n    match msg {\n      WM_CREATE => {\n        let data = lparam.0 as *mut CREATESTRUCTW;\n        let data = (*data).lpCreateParams as *mut UndecoratedResizingData;\n        (*data).child = child;\n        SetWindowLongPtrW(child, GWLP_USERDATA, data as _);\n      }\n\n      WM_NCHITTEST => {\n        let data = GetWindowLongPtrW(child, GWLP_USERDATA);\n        let data = &*(data as *mut UndecoratedResizingData);\n\n        let Ok(parent) = GetParent(child) else {\n          return DefWindowProcW(child, msg, wparam, lparam);\n        };\n        let style = GetWindowLongPtrW(parent, GWL_STYLE);\n        let style = WINDOW_STYLE(style as u32);\n\n        let is_resizable = (style & WS_SIZEBOX).0 != 0;\n        if !is_resizable {\n          return DefWindowProcW(child, msg, wparam, lparam);\n        }\n\n        // if the window has undecorated shadows,\n        // it should always be the top border,\n        // ensured by the cutout drag window\n        if data.has_undecorated_shadows {\n          return LRESULT(HTTOP as _);\n        }\n\n        let mut rect = RECT::default();\n        if GetWindowRect(child, &mut rect).is_err() {\n          return DefWindowProcW(child, msg, wparam, lparam);\n        }\n\n        let (cx, cy) = (GET_X_LPARAM(lparam) as i32, GET_Y_LPARAM(lparam) as i32);\n\n        let dpi = unsafe { util::hwnd_dpi(child) };\n        let border_x = util::get_system_metrics_for_dpi(SM_CXFRAME, dpi);\n        let border_y = util::get_system_metrics_for_dpi(SM_CYFRAME, dpi);\n\n        let res = hit_test(\n          rect.left,\n          rect.top,\n          rect.right,\n          rect.bottom,\n          cx,\n          cy,\n          border_x,\n          border_y,\n        );\n\n        return LRESULT(res.to_win32() as _);\n      }\n\n      WM_NCLBUTTONDOWN => {\n        let data = GetWindowLongPtrW(child, GWLP_USERDATA);\n        let data = &*(data as *mut UndecoratedResizingData);\n\n        let Ok(parent) = GetParent(child) else {\n          return DefWindowProcW(child, msg, wparam, lparam);\n        };\n        let style = GetWindowLongPtrW(parent, GWL_STYLE);\n        let style = WINDOW_STYLE(style as u32);\n\n        let is_resizable = (style & WS_SIZEBOX).0 != 0;\n        if !is_resizable {\n          return DefWindowProcW(child, msg, wparam, lparam);\n        }\n\n        let (cx, cy) = (GET_X_LPARAM(lparam) as i32, GET_Y_LPARAM(lparam) as i32);\n\n        // if the window has undecorated shadows,\n        // it should always be the top border,\n        // ensured by the cutout drag window\n        let res = if data.has_undecorated_shadows {\n          HitTestResult::Top\n        } else {\n          let mut rect = RECT::default();\n          if GetWindowRect(child, &mut rect).is_err() {\n            return DefWindowProcW(child, msg, wparam, lparam);\n          }\n\n          let dpi = unsafe { util::hwnd_dpi(child) };\n          let border_x = util::get_system_metrics_for_dpi(SM_CXFRAME, dpi);\n          let border_y = util::get_system_metrics_for_dpi(SM_CYFRAME, dpi);\n\n          hit_test(\n            rect.left,\n            rect.top,\n            rect.right,\n            rect.bottom,\n            cx,\n            cy,\n            border_x,\n            border_y,\n          )\n        };\n\n        if res != HitTestResult::NoWhere {\n          let points = POINTS {\n            x: cx as i16,\n            y: cy as i16,\n          };\n\n          let _ = PostMessageW(\n            Some(parent),\n            WM_NCLBUTTONDOWN,\n            WPARAM(res.to_win32() as _),\n            LPARAM(&points as *const _ as _),\n          );\n        }\n\n        return LRESULT(0);\n      }\n\n      WM_UPDATE_UNDECORATED_SHADOWS => {\n        let data = GetWindowLongPtrW(child, GWLP_USERDATA);\n        let data = &mut *(data as *mut UndecoratedResizingData);\n        data.has_undecorated_shadows = wparam.0 != 0;\n      }\n\n      WM_NCDESTROY => {\n        let data = GetWindowLongPtrW(child, GWLP_USERDATA);\n        let data = data as *mut UndecoratedResizingData;\n        drop(Box::from_raw(data));\n      }\n\n      _ => {}\n    }\n\n    DefWindowProcW(child, msg, wparam, lparam)\n  }\n\n  pub fn detach_resize_handler(hwnd: isize) {\n    let hwnd = HWND(hwnd as _);\n\n    let Ok(child) = (unsafe { FindWindowExW(Some(hwnd), None, CLASS_NAME, WINDOW_NAME) }) else {\n      return;\n    };\n\n    let _ = unsafe { DestroyWindow(child) };\n  }\n\n  unsafe fn set_drag_hwnd_rgn(hwnd: HWND, width: i32, height: i32, only_top: bool) {\n    // The window used for drag resizing an undecorated window\n    // is a child HWND that covers the whole window\n    // and so we need create a cut out in the middle for the parent and other child\n    // windows like the webview can receive mouse events.\n\n    let dpi = unsafe { util::hwnd_dpi(hwnd) };\n    let border_x = util::get_system_metrics_for_dpi(SM_CXFRAME, dpi);\n    let border_y = util::get_system_metrics_for_dpi(SM_CYFRAME, dpi);\n\n    // hrgn1 must be mutable to call .free() later\n    let mut hrgn1 = CreateRectRgn(0, 0, width, height);\n\n    let x1 = if only_top { 0 } else { border_x };\n    let y1 = border_y;\n    let x2 = if only_top { width } else { width - border_x };\n    let y2 = if only_top { height } else { height - border_y };\n\n    // Wrap hrgn2 in Owned so it is automatically freed when going out of scope\n    let hrgn2 = Owned::new(CreateRectRgn(x1, y1, x2, y2));\n\n    CombineRgn(Some(hrgn1), Some(hrgn1), Some(*hrgn2), RGN_DIFF);\n\n    // Try to set the window region\n    if SetWindowRgn(hwnd, Some(hrgn1), true) == 0 {\n      // If it fails, we must free hrgn1 manually\n      hrgn1.free();\n    }\n  }\n\n  pub fn update_drag_hwnd_rgn_for_undecorated(hwnd: isize, has_undecorated_shadows: bool) {\n    let hwnd = HWND(hwnd as _);\n\n    let mut rect = RECT::default();\n    let Ok(_) = (unsafe { GetClientRect(hwnd, &mut rect) }) else {\n      return;\n    };\n\n    let width = rect.right - rect.left;\n    let height = rect.bottom - rect.top;\n\n    let Ok(child) = (unsafe { FindWindowExW(Some(hwnd), None, CLASS_NAME, WINDOW_NAME) }) else {\n      return;\n    };\n\n    unsafe { set_drag_hwnd_rgn(child, width, height, has_undecorated_shadows) };\n\n    unsafe {\n      SendMessageW(\n        hwnd,\n        WM_UPDATE_UNDECORATED_SHADOWS,\n        None,\n        Some(LPARAM(has_undecorated_shadows as _)),\n      )\n    };\n    unsafe {\n      SendMessageW(\n        child,\n        WM_UPDATE_UNDECORATED_SHADOWS,\n        None,\n        Some(LPARAM(has_undecorated_shadows as _)),\n      )\n    };\n  }\n\n  fn is_maximized(window: HWND) -> windows::core::Result<bool> {\n    let mut placement = WINDOWPLACEMENT {\n      length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,\n      ..WINDOWPLACEMENT::default()\n    };\n    unsafe { GetWindowPlacement(window, &mut placement)? };\n    Ok(placement.showCmd == SW_MAXIMIZE.0 as u32)\n  }\n\n  /// Implementation of the `GET_X_LPARAM` macro.\n  #[allow(non_snake_case)]\n  #[inline]\n  fn GET_X_LPARAM(lparam: LPARAM) -> i16 {\n    ((lparam.0 as usize) & 0xFFFF) as u16 as i16\n  }\n\n  /// Implementation of the `GET_Y_LPARAM` macro.\n  #[allow(non_snake_case)]\n  #[inline]\n  fn GET_Y_LPARAM(lparam: LPARAM) -> i16 {\n    (((lparam.0 as usize) & 0xFFFF_0000) >> 16) as u16 as i16\n  }\n}\n\n#[cfg(not(windows))]\nmod gtk {\n  use super::{hit_test, HitTestResult};\n\n  const BORDERLESS_RESIZE_INSET: i32 = 5;\n\n  impl HitTestResult {\n    fn to_gtk_edge(self) -> gtk::gdk::WindowEdge {\n      match self {\n        HitTestResult::Client | HitTestResult::NoWhere => gtk::gdk::WindowEdge::__Unknown(0),\n        HitTestResult::Left => gtk::gdk::WindowEdge::West,\n        HitTestResult::Right => gtk::gdk::WindowEdge::East,\n        HitTestResult::Top => gtk::gdk::WindowEdge::North,\n        HitTestResult::Bottom => gtk::gdk::WindowEdge::South,\n        HitTestResult::TopLeft => gtk::gdk::WindowEdge::NorthWest,\n        HitTestResult::TopRight => gtk::gdk::WindowEdge::NorthEast,\n        HitTestResult::BottomLeft => gtk::gdk::WindowEdge::SouthWest,\n        HitTestResult::BottomRight => gtk::gdk::WindowEdge::SouthEast,\n      }\n    }\n  }\n\n  pub fn attach_resize_handler(webview: &wry::WebView) {\n    use gtk::{\n      gdk::{prelude::*, WindowEdge},\n      glib::Propagation,\n      prelude::*,\n    };\n    use wry::WebViewExtUnix;\n\n    let webview = webview.webview();\n\n    webview.add_events(\n      gtk::gdk::EventMask::BUTTON1_MOTION_MASK\n        | gtk::gdk::EventMask::BUTTON_PRESS_MASK\n        | gtk::gdk::EventMask::TOUCH_MASK,\n    );\n\n    webview.connect_button_press_event(\n      move |webview: &webkit2gtk::WebView, event: &gtk::gdk::EventButton| {\n        if event.button() == 1 {\n          // This one should be GtkBox\n          if let Some(window) = webview.parent().and_then(|w| w.parent()) {\n            // Safe to unwrap unless this is not from tao\n            let window: gtk::Window = window.downcast().unwrap();\n            if !window.is_decorated() && window.is_resizable() && !window.is_maximized() {\n              if let Some(window) = window.window() {\n                let (root_x, root_y) = event.root();\n                let (window_x, window_y) = window.position();\n                let (client_x, client_y) = (root_x - window_x as f64, root_y - window_y as f64);\n                let border = window.scale_factor() * BORDERLESS_RESIZE_INSET;\n                let edge = hit_test(\n                  0.0,\n                  0.0,\n                  window.width() as f64,\n                  window.height() as f64,\n                  client_x,\n                  client_y,\n                  border as _,\n                  border as _,\n                )\n                .to_gtk_edge();\n\n                // we ignore the `__Unknown` variant so the webview receives the click correctly if it is not on the edges.\n                match edge {\n                  WindowEdge::__Unknown(_) => (),\n                  _ => {\n                    window.begin_resize_drag(edge, 1, root_x as i32, root_y as i32, event.time())\n                  }\n                }\n              }\n            }\n          }\n        }\n\n        Propagation::Proceed\n      },\n    );\n\n    webview.connect_touch_event(\n      move |webview: &webkit2gtk::WebView, event: &gtk::gdk::Event| {\n        // This one should be GtkBox\n        if let Some(window) = webview.parent().and_then(|w| w.parent()) {\n          // Safe to unwrap unless this is not from tao\n          let window: gtk::Window = window.downcast().unwrap();\n          if !window.is_decorated() && window.is_resizable() && !window.is_maximized() {\n            if let Some(window) = window.window() {\n              if let Some((root_x, root_y)) = event.root_coords() {\n                if let Some(device) = event.device() {\n                  let (window_x, window_y) = window.position();\n                  let (client_x, client_y) = (root_x - window_x as f64, root_y - window_y as f64);\n                  let border = window.scale_factor() * BORDERLESS_RESIZE_INSET;\n                  let edge = hit_test(\n                    0.0,\n                    0.0,\n                    window.width() as f64,\n                    window.height() as f64,\n                    client_x,\n                    client_y,\n                    border as _,\n                    border as _,\n                  )\n                  .to_gtk_edge();\n\n                  // we ignore the `__Unknown` variant so the window receives the click correctly if it is not on the edges.\n                  match edge {\n                    WindowEdge::__Unknown(_) => (),\n                    _ => window.begin_resize_drag_for_device(\n                      edge,\n                      &device,\n                      0,\n                      root_x as i32,\n                      root_y as i32,\n                      event.time(),\n                    ),\n                  }\n                }\n              }\n            }\n          }\n        }\n\n        Propagation::Proceed\n      },\n    );\n  }\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/util.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#[cfg_attr(not(windows), allow(unused_imports))]\npub use imp::*;\n\n#[cfg(not(windows))]\nmod imp {}\n\n#[cfg(windows)]\nmod imp {\n  use std::{iter::once, os::windows::ffi::OsStrExt};\n\n  use once_cell::sync::Lazy;\n  use windows::{\n    core::{HRESULT, PCSTR, PCWSTR},\n    Win32::{\n      Foundation::*,\n      Graphics::Gdi::*,\n      System::LibraryLoader::{GetProcAddress, LoadLibraryW},\n      UI::{HiDpi::*, WindowsAndMessaging::*},\n    },\n  };\n\n  pub fn encode_wide(string: impl AsRef<std::ffi::OsStr>) -> Vec<u16> {\n    string.as_ref().encode_wide().chain(once(0)).collect()\n  }\n\n  // Helper function to dynamically load function pointer.\n  // `library` and `function` must be zero-terminated.\n  pub(super) fn get_function_impl(library: &str, function: &str) -> FARPROC {\n    let library = encode_wide(library);\n    assert_eq!(function.chars().last(), Some('\\0'));\n\n    // Library names we will use are ASCII so we can use the A version to avoid string conversion.\n    let module = unsafe { LoadLibraryW(PCWSTR::from_raw(library.as_ptr())) }.unwrap_or_default();\n    if module.is_invalid() {\n      return None;\n    }\n\n    unsafe { GetProcAddress(module, PCSTR::from_raw(function.as_ptr())) }\n  }\n\n  macro_rules! get_function {\n    ($lib:expr, $func:ident) => {\n      $crate::util::get_function_impl($lib, concat!(stringify!($func), '\\0'))\n        .map(|f| unsafe { std::mem::transmute::<_, $func>(f) })\n    };\n  }\n\n  type GetDpiForWindow = unsafe extern \"system\" fn(hwnd: HWND) -> u32;\n  type GetDpiForMonitor = unsafe extern \"system\" fn(\n    hmonitor: HMONITOR,\n    dpi_type: MONITOR_DPI_TYPE,\n    dpi_x: *mut u32,\n    dpi_y: *mut u32,\n  ) -> HRESULT;\n  type GetSystemMetricsForDpi =\n    unsafe extern \"system\" fn(nindex: SYSTEM_METRICS_INDEX, dpi: u32) -> i32;\n\n  static GET_DPI_FOR_WINDOW: Lazy<Option<GetDpiForWindow>> =\n    Lazy::new(|| get_function!(\"user32.dll\", GetDpiForWindow));\n  static GET_DPI_FOR_MONITOR: Lazy<Option<GetDpiForMonitor>> =\n    Lazy::new(|| get_function!(\"shcore.dll\", GetDpiForMonitor));\n  static GET_SYSTEM_METRICS_FOR_DPI: Lazy<Option<GetSystemMetricsForDpi>> =\n    Lazy::new(|| get_function!(\"user32.dll\", GetSystemMetricsForDpi));\n\n  #[allow(non_snake_case)]\n  pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {\n    let hdc = GetDC(Some(hwnd));\n    if hdc.is_invalid() {\n      return USER_DEFAULT_SCREEN_DPI;\n    }\n    if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {\n      // We are on Windows 10 Anniversary Update (1607) or later.\n      match GetDpiForWindow(hwnd) {\n        0 => USER_DEFAULT_SCREEN_DPI, // 0 is returned if hwnd is invalid\n        dpi => dpi,\n      }\n    } else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {\n      // We are on Windows 8.1 or later.\n      let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);\n      if monitor.is_invalid() {\n        return USER_DEFAULT_SCREEN_DPI;\n      }\n\n      let mut dpi_x = 0;\n      let mut dpi_y = 0;\n      if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y).is_ok() {\n        dpi_x\n      } else {\n        USER_DEFAULT_SCREEN_DPI\n      }\n    } else {\n      // We are on Vista or later.\n      if IsProcessDPIAware().as_bool() {\n        // If the process is DPI aware, then scaling must be handled by the application using\n        // this DPI value.\n        GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32\n      } else {\n        // If the process is DPI unaware, then scaling is performed by the OS; we thus return\n        // 96 (scale factor 1.0) to prevent the window from being re-scaled by both the\n        // application and the WM.\n        USER_DEFAULT_SCREEN_DPI\n      }\n    }\n  }\n\n  #[allow(non_snake_case)]\n  pub unsafe fn get_system_metrics_for_dpi(nindex: SYSTEM_METRICS_INDEX, dpi: u32) -> i32 {\n    if let Some(GetSystemMetricsForDpi) = *GET_SYSTEM_METRICS_FOR_DPI {\n      GetSystemMetricsForDpi(nindex, dpi)\n    } else {\n      GetSystemMetrics(nindex)\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/webview.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"netbsd\",\n  target_os = \"openbsd\"\n))]\nmod imp {\n  pub type Webview = webkit2gtk::WebView;\n}\n\n#[cfg(target_vendor = \"apple\")]\nmod imp {\n  use std::ffi::c_void;\n\n  pub struct Webview {\n    pub webview: *mut c_void,\n    pub manager: *mut c_void,\n    #[cfg(target_os = \"macos\")]\n    pub ns_window: *mut c_void,\n    #[cfg(target_os = \"ios\")]\n    pub view_controller: *mut c_void,\n  }\n}\n\n#[cfg(windows)]\nmod imp {\n  use webview2_com::Microsoft::Web::WebView2::Win32::{\n    ICoreWebView2Controller, ICoreWebView2Environment,\n  };\n  pub struct Webview {\n    pub controller: ICoreWebView2Controller,\n    pub environment: ICoreWebView2Environment,\n  }\n}\n\n#[cfg(target_os = \"android\")]\nmod imp {\n  use wry::JniHandle;\n  pub type Webview = JniHandle;\n}\n\npub use imp::*;\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/window/linux.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse gtk::prelude::*;\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"netbsd\",\n  target_os = \"openbsd\"\n))]\nuse tao::platform::unix::WindowExtUnix;\n\nimpl super::WindowExt for tao::window::Window {\n  fn set_enabled(&self, enabled: bool) {\n    self.gtk_window().set_sensitive(enabled);\n  }\n\n  fn is_enabled(&self) -> bool {\n    self.gtk_window().is_sensitive()\n  }\n\n  fn center(&self) {\n    if let Some(monitor) = self.current_monitor() {\n      let window_size = self.outer_size();\n      let new_pos = super::calculate_window_center_position(window_size, monitor);\n      self.set_outer_position(new_pos);\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/window/macos.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse objc2::MainThreadMarker;\nuse objc2_app_kit::{NSBackingStoreType, NSWindow, NSWindowStyleMask};\nuse tao::platform::macos::WindowExtMacOS;\n\nimpl super::WindowExt for tao::window::Window {\n  // based on electron implementation\n  // https://github.com/electron/electron/blob/15db63e26df3e3d59ce6281f030624f746518511/shell/browser/native_window_mac.mm#L474\n  fn set_enabled(&self, enabled: bool) {\n    let ns_window: &NSWindow = unsafe { &*self.ns_window().cast() };\n    if !enabled {\n      let frame = ns_window.frame();\n      let mtm = MainThreadMarker::new()\n        .expect(\"`Window::set_enabled` can only be called on the main thread\");\n      let sheet = unsafe {\n        NSWindow::initWithContentRect_styleMask_backing_defer(\n          mtm.alloc(),\n          frame,\n          NSWindowStyleMask::Titled,\n          NSBackingStoreType::Buffered,\n          false,\n        )\n      };\n      unsafe { sheet.setAlphaValue(0.5) };\n      unsafe { ns_window.beginSheet_completionHandler(&sheet, None) };\n    } else if let Some(attached) = unsafe { ns_window.attachedSheet() } {\n      unsafe { ns_window.endSheet(&attached) };\n    }\n  }\n\n  fn is_enabled(&self) -> bool {\n    let ns_window: &NSWindow = unsafe { &*self.ns_window().cast() };\n    unsafe { ns_window.attachedSheet() }.is_none()\n  }\n\n  fn center(&self) {\n    let ns_window: &NSWindow = unsafe { &*self.ns_window().cast() };\n    ns_window.center();\n  }\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/window/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#[cfg(any(\n  target_os = \"linux\",\n  target_os = \"dragonfly\",\n  target_os = \"freebsd\",\n  target_os = \"netbsd\",\n  target_os = \"openbsd\"\n))]\nmod linux;\n#[cfg(target_os = \"macos\")]\nmod macos;\n#[cfg(windows)]\nmod windows;\n\nuse crate::monitor::MonitorExt;\n\npub trait WindowExt {\n  /// Enable or disable the window\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Android / iOS**: Unsupported.\n  fn set_enabled(&self, enabled: bool);\n\n  /// Whether the window is enabled or disabled.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Android / iOS**: Unsupported, always returns `true`.\n  fn is_enabled(&self) -> bool;\n\n  /// Center the window\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Android / iOS**: Unsupported.\n  fn center(&self) {}\n\n  /// Clears the window surface. i.e make it transparent.\n  #[cfg(windows)]\n  fn draw_surface(\n    &self,\n    surface: &mut softbuffer::Surface<\n      std::sync::Arc<tao::window::Window>,\n      std::sync::Arc<tao::window::Window>,\n    >,\n    background_color: Option<tao::window::RGBA>,\n  );\n}\n\n#[cfg(mobile)]\nimpl WindowExt for tao::window::Window {\n  fn set_enabled(&self, _: bool) {}\n  fn is_enabled(&self) -> bool {\n    true\n  }\n}\n\npub fn calculate_window_center_position(\n  window_size: tao::dpi::PhysicalSize<u32>,\n  target_monitor: tao::monitor::MonitorHandle,\n) -> tao::dpi::PhysicalPosition<i32> {\n  let work_area = target_monitor.work_area();\n\n  tao::dpi::PhysicalPosition::new(\n    (work_area.size.width as i32 - window_size.width as i32) / 2 + work_area.position.x,\n    (work_area.size.height as i32 - window_size.height as i32) / 2 + work_area.position.y,\n  )\n}\n"
  },
  {
    "path": "crates/tauri-runtime-wry/src/window/windows.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse windows::Win32::{\n  Foundation::{HWND, RECT},\n  Graphics::Dwm::{DwmGetWindowAttribute, DWMWA_EXTENDED_FRAME_BOUNDS},\n  UI::Input::KeyboardAndMouse::{EnableWindow, IsWindowEnabled},\n};\n\nuse tao::platform::windows::WindowExtWindows;\n\nimpl super::WindowExt for tao::window::Window {\n  fn set_enabled(&self, enabled: bool) {\n    let _ = unsafe { EnableWindow(HWND(self.hwnd() as _), enabled) };\n  }\n\n  fn is_enabled(&self) -> bool {\n    unsafe { IsWindowEnabled(HWND(self.hwnd() as _)) }.as_bool()\n  }\n\n  fn center(&self) {\n    if let Some(monitor) = self.current_monitor() {\n      let mut window_size = self.outer_size();\n\n      if self.is_decorated() {\n        let mut rect = RECT::default();\n        let result = unsafe {\n          DwmGetWindowAttribute(\n            HWND(self.hwnd() as _),\n            DWMWA_EXTENDED_FRAME_BOUNDS,\n            &mut rect as *mut _ as *mut _,\n            std::mem::size_of::<RECT>() as u32,\n          )\n        };\n        if result.is_ok() {\n          window_size.height = (rect.bottom - rect.top) as u32;\n        }\n      }\n\n      let new_pos = super::calculate_window_center_position(window_size, monitor);\n      self.set_outer_position(new_pos);\n    }\n  }\n\n  fn draw_surface(\n    &self,\n    surface: &mut softbuffer::Surface<\n      std::sync::Arc<tao::window::Window>,\n      std::sync::Arc<tao::window::Window>,\n    >,\n    background_color: Option<tao::window::RGBA>,\n  ) {\n    let size = self.inner_size();\n    if let (Some(width), Some(height)) = (\n      std::num::NonZeroU32::new(size.width),\n      std::num::NonZeroU32::new(size.height),\n    ) {\n      surface.resize(width, height).unwrap();\n      let mut buffer = surface.buffer_mut().unwrap();\n      let color = background_color\n        .map(|(r, g, b, _)| (b as u32) | ((g as u32) << 8) | ((r as u32) << 16))\n        .unwrap_or(0);\n      buffer.fill(color);\n      let _ = buffer.present();\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-schema-generator/Cargo.toml",
    "content": "[package]\nname = \"tauri-schema-generator\"\nversion = \"0.0.0\"\nedition = \"2021\"\npublish = false\n\n[build-dependencies]\ntauri-utils = { features = [\"schema\"], path = \"../tauri-utils\" }\nschemars = { version = \"0.8.21\", features = [\"url\", \"preserve_order\"] }\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\nurl = { version = \"2\", features = [\"serde\"] }\n"
  },
  {
    "path": "crates/tauri-schema-generator/README.md",
    "content": "# tauri-schema-generator\n\nThis crate is part of the tauri monorepo workspace and its sole purpose is to generate JSON schema for various tauri config files.\n"
  },
  {
    "path": "crates/tauri-schema-generator/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{error::Error, path::PathBuf};\n\nuse serde::Deserialize;\nuse tauri_utils::{\n  acl::{capability::Capability, Permission, Scopes},\n  config::Config,\n  write_if_changed,\n};\n\nmacro_rules! schema {\n  ($name:literal, $path:ty) => {\n    (concat!($name, \".schema.json\"), schemars::schema_for!($path))\n  };\n}\n\n#[derive(Deserialize)]\npub struct VersionMetadata {\n  tauri: String,\n}\n\npub fn main() -> Result<(), Box<dyn Error>> {\n  let schemas = [\n    schema!(\"capability\", Capability),\n    schema!(\"permission\", Permission),\n    schema!(\"scope\", Scopes),\n  ];\n\n  let out = PathBuf::from(std::env::var(\"CARGO_MANIFEST_DIR\")?);\n\n  let schemas_dir = out.join(\"schemas\");\n  std::fs::create_dir_all(&schemas_dir)?;\n\n  for (filename, schema) in schemas {\n    let schema = serde_json::to_string_pretty(&schema)?;\n    write_if_changed(schemas_dir.join(filename), &schema)?;\n  }\n\n  // write config schema file\n  {\n    let metadata = include_str!(\"../tauri-cli/metadata-v2.json\");\n    let tauri_ver = serde_json::from_str::<VersionMetadata>(metadata)?.tauri;\n\n    // set id for generated schema\n    let (filename, mut config_schema) = schema!(\"config\", Config);\n    let schema_metadata = config_schema.schema.metadata.as_mut().unwrap();\n    schema_metadata.id = Some(format!(\"https://schema.tauri.app/config/{tauri_ver}\"));\n\n    let config_schema = serde_json::to_string_pretty(&config_schema)?;\n    write_if_changed(schemas_dir.join(filename), &config_schema)?;\n    write_if_changed(out.join(\"../tauri-cli/config.schema.json\"), config_schema)?;\n  }\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-schema-generator/schemas/capability.schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"title\": \"Capability\",\n  \"description\": \"A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\\n\\n It controls application windows' and webviews' fine grained access\\n to the Tauri core, application, or plugin commands.\\n If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\\n\\n This can be done to create groups of windows, based on their required system access, which can reduce\\n impact of frontend vulnerabilities in less privileged windows.\\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\\n A Window can have none, one, or multiple associated capabilities.\\n\\n ## Example\\n\\n ```json\\n {\\n   \\\"identifier\\\": \\\"main-user-files-write\\\",\\n   \\\"description\\\": \\\"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\\\",\\n   \\\"windows\\\": [\\n     \\\"main\\\"\\n   ],\\n   \\\"permissions\\\": [\\n     \\\"core:default\\\",\\n     \\\"dialog:open\\\",\\n     {\\n       \\\"identifier\\\": \\\"fs:allow-write-text-file\\\",\\n       \\\"allow\\\": [{ \\\"path\\\": \\\"$HOME/test.txt\\\" }]\\n     },\\n   ],\\n   \\\"platforms\\\": [\\\"macOS\\\",\\\"windows\\\"]\\n }\\n ```\",\n  \"type\": \"object\",\n  \"required\": [\n    \"identifier\",\n    \"permissions\"\n  ],\n  \"properties\": {\n    \"identifier\": {\n      \"description\": \"Identifier of the capability.\\n\\n ## Example\\n\\n `main-user-files-write`\",\n      \"type\": \"string\"\n    },\n    \"description\": {\n      \"description\": \"Description of what the capability is intended to allow on associated windows.\\n\\n It should contain a description of what the grouped permissions should allow.\\n\\n ## Example\\n\\n This capability allows the `main` window access to `filesystem` write related\\n commands and `dialog` commands to enable programmatic access to files selected by the user.\",\n      \"default\": \"\",\n      \"type\": \"string\"\n    },\n    \"remote\": {\n      \"description\": \"Configure remote URLs that can use the capability permissions.\\n\\n This setting is optional and defaults to not being set, as our\\n default use case is that the content is served from our local application.\\n\\n :::caution\\n Make sure you understand the security implications of providing remote\\n sources with local system access.\\n :::\\n\\n ## Example\\n\\n ```json\\n {\\n   \\\"urls\\\": [\\\"https://*.mydomain.dev\\\"]\\n }\\n ```\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/CapabilityRemote\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"local\": {\n      \"description\": \"Whether this capability is enabled for local app URLs or not. Defaults to `true`.\",\n      \"default\": true,\n      \"type\": \"boolean\"\n    },\n    \"windows\": {\n      \"description\": \"List of windows that are affected by this capability. Can be a glob pattern.\\n\\n If a window label matches any of the patterns in this list,\\n the capability will be enabled on all the webviews of that window,\\n regardless of the value of [`Self::webviews`].\\n\\n On multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`]\\n for a fine grained access control.\\n\\n ## Example\\n\\n `[\\\"main\\\"]`\",\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"webviews\": {\n      \"description\": \"List of webviews that are affected by this capability. Can be a glob pattern.\\n\\n The capability will be enabled on all the webviews\\n whose label matches any of the patterns in this list,\\n regardless of whether the webview's window label matches a pattern in [`Self::windows`].\\n\\n ## Example\\n\\n `[\\\"sub-webview-one\\\", \\\"sub-webview-two\\\"]`\",\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"permissions\": {\n      \"description\": \"List of permissions attached to this capability.\\n\\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\\n For commands directly implemented in the application itself only `${permission-name}`\\n is required.\\n\\n ## Example\\n\\n ```json\\n [\\n   \\\"core:default\\\",\\n   \\\"shell:allow-open\\\",\\n   \\\"dialog:open\\\",\\n   {\\n     \\\"identifier\\\": \\\"fs:allow-write-text-file\\\",\\n     \\\"allow\\\": [{ \\\"path\\\": \\\"$HOME/test.txt\\\" }]\\n   }\\n ]\\n ```\",\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/definitions/PermissionEntry\"\n      },\n      \"uniqueItems\": true\n    },\n    \"platforms\": {\n      \"description\": \"Limit which target platforms this capability applies to.\\n\\n By default all platforms are targeted.\\n\\n ## Example\\n\\n `[\\\"macOS\\\",\\\"windows\\\"]`\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/definitions/Target\"\n      }\n    }\n  },\n  \"definitions\": {\n    \"CapabilityRemote\": {\n      \"description\": \"Configuration for remote URLs that are associated with the capability.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"urls\"\n      ],\n      \"properties\": {\n        \"urls\": {\n          \"description\": \"Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\\n\\n ## Examples\\n\\n - \\\"https://*.mydomain.dev\\\": allows subdomains of mydomain.dev\\n - \\\"https://mydomain.dev/api/*\\\": allows any subpath of mydomain.dev/api\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"PermissionEntry\": {\n      \"description\": \"An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`]\\n or an object that references a permission and extends its scope.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Reference a permission or permission set by identifier.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Identifier\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Reference a permission or permission set by identifier and extends its scope.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"identifier\"\n          ],\n          \"properties\": {\n            \"identifier\": {\n              \"description\": \"Identifier of the permission or permission set.\",\n              \"allOf\": [\n                {\n                  \"$ref\": \"#/definitions/Identifier\"\n                }\n              ]\n            },\n            \"allow\": {\n              \"description\": \"Data that defines what is allowed by the scope.\",\n              \"type\": [\n                \"array\",\n                \"null\"\n              ],\n              \"items\": {\n                \"$ref\": \"#/definitions/Value\"\n              }\n            },\n            \"deny\": {\n              \"description\": \"Data that defines what is denied by the scope. This should be prioritized by validation logic.\",\n              \"type\": [\n                \"array\",\n                \"null\"\n              ],\n              \"items\": {\n                \"$ref\": \"#/definitions/Value\"\n              }\n            }\n          }\n        }\n      ]\n    },\n    \"Identifier\": {\n      \"type\": \"string\"\n    },\n    \"Value\": {\n      \"description\": \"All supported ACL values.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents a null JSON value.\",\n          \"type\": \"null\"\n        },\n        {\n          \"description\": \"Represents a [`bool`].\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Represents a valid ACL [`Number`].\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Number\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Represents a [`String`].\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Represents a list of other [`Value`]s.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        },\n        {\n          \"description\": \"Represents a map of [`String`] keys to [`Value`]s.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        }\n      ]\n    },\n    \"Number\": {\n      \"description\": \"A valid ACL number.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents an [`i64`].\",\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        {\n          \"description\": \"Represents a [`f64`].\",\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      ]\n    },\n    \"Target\": {\n      \"description\": \"Platform target.\",\n      \"oneOf\": [\n        {\n          \"description\": \"MacOS.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"macOS\"\n          ]\n        },\n        {\n          \"description\": \"Windows.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"windows\"\n          ]\n        },\n        {\n          \"description\": \"Linux.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"linux\"\n          ]\n        },\n        {\n          \"description\": \"Android.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"android\"\n          ]\n        },\n        {\n          \"description\": \"iOS.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"iOS\"\n          ]\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "crates/tauri-schema-generator/schemas/config.schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"$id\": \"https://schema.tauri.app/config/2.10.3\",\n  \"title\": \"Config\",\n  \"description\": \"The Tauri configuration object.\\n It is read from a file where you can define your frontend assets,\\n configure the bundler and define a tray icon.\\n\\n The configuration file is generated by the\\n [`tauri init`](https://v2.tauri.app/reference/cli/#init) command that lives in\\n your Tauri application source directory (src-tauri).\\n\\n Once generated, you may modify it at will to customize your Tauri application.\\n\\n ## File Formats\\n\\n By default, the configuration is defined as a JSON file named `tauri.conf.json`.\\n\\n Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.\\n The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.\\n The TOML file name is `Tauri.toml`.\\n\\n ## Platform-Specific Configuration\\n\\n In addition to the default configuration file, Tauri can\\n read a platform-specific configuration from `tauri.linux.conf.json`,\\n `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`\\n (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),\\n which gets merged with the main configuration object.\\n\\n ## Configuration Structure\\n\\n The configuration is composed of the following objects:\\n\\n - [`app`](#appconfig): The Tauri configuration\\n - [`build`](#buildconfig): The build configuration\\n - [`bundle`](#bundleconfig): The bundle configurations\\n - [`plugins`](#pluginconfig): The plugins configuration\\n\\n Example tauri.config.json file:\\n\\n ```json\\n {\\n   \\\"productName\\\": \\\"tauri-app\\\",\\n   \\\"version\\\": \\\"0.1.0\\\",\\n   \\\"build\\\": {\\n     \\\"beforeBuildCommand\\\": \\\"\\\",\\n     \\\"beforeDevCommand\\\": \\\"\\\",\\n     \\\"devUrl\\\": \\\"http://localhost:3000\\\",\\n     \\\"frontendDist\\\": \\\"../dist\\\"\\n   },\\n   \\\"app\\\": {\\n     \\\"security\\\": {\\n       \\\"csp\\\": null\\n     },\\n     \\\"windows\\\": [\\n       {\\n         \\\"fullscreen\\\": false,\\n         \\\"height\\\": 600,\\n         \\\"resizable\\\": true,\\n         \\\"title\\\": \\\"Tauri App\\\",\\n         \\\"width\\\": 800\\n       }\\n     ]\\n   },\\n   \\\"bundle\\\": {},\\n   \\\"plugins\\\": {}\\n }\\n ```\",\n  \"type\": \"object\",\n  \"required\": [\n    \"identifier\"\n  ],\n  \"properties\": {\n    \"$schema\": {\n      \"description\": \"The JSON schema for the Tauri config.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"productName\": {\n      \"description\": \"App name.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ],\n      \"pattern\": \"^[^/\\\\:*?\\\"<>|]+$\"\n    },\n    \"mainBinaryName\": {\n      \"description\": \"Overrides app's main binary filename.\\n\\n By default, Tauri uses the output binary from `cargo`, by setting this, we will rename that binary in `tauri-cli`'s\\n `tauri build` command, and target `tauri bundle` to it\\n\\n If possible, change the [`package name`] or set the [`name field`] instead,\\n and if that's not enough and you're using nightly, consider using the [`different-binary-name`] feature instead\\n\\n Note: this config should not include the binary extension (e.g. `.exe`), we'll add that for you\\n\\n [`package name`]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field\\n [`name field`]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-name-field\\n [`different-binary-name`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#different-binary-name\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"version\": {\n      \"description\": \"App version. It is a semver version number or a path to a `package.json` file containing the `version` field.\\n\\n If removed the version number from `Cargo.toml` is used.\\n It's recommended to manage the app versioning in the Tauri config.\\n\\n ## Platform-specific\\n\\n - **macOS**: Translates to the bundle's CFBundleShortVersionString property and is used as the default CFBundleVersion.\\n    You can set an specific bundle version using [`bundle > macOS > bundleVersion`](MacConfig::bundle_version).\\n - **iOS**: Translates to the bundle's CFBundleShortVersionString property and is used as the default CFBundleVersion.\\n    You can set an specific bundle version using [`bundle > iOS > bundleVersion`](IosConfig::bundle_version).\\n    The `tauri ios build` CLI command has a `--build-number <number>` option that lets you append a build number to the app version.\\n - **Android**: By default version 1.0 is used. You can set a version code using [`bundle > android > versionCode`](AndroidConfig::version_code).\\n\\n By default version 1.0 is used on Android.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"identifier\": {\n      \"description\": \"The application identifier in reverse domain name notation (e.g. `com.tauri.example`).\\n This string must be unique across applications since it is used in system configurations like\\n the bundle ID and path to the webview data directory.\\n This string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-),\\n and periods (.).\",\n      \"type\": \"string\"\n    },\n    \"app\": {\n      \"description\": \"The App configuration.\",\n      \"default\": {\n        \"enableGTKAppId\": false,\n        \"macOSPrivateApi\": false,\n        \"security\": {\n          \"assetProtocol\": {\n            \"enable\": false,\n            \"scope\": []\n          },\n          \"capabilities\": [],\n          \"dangerousDisableAssetCspModification\": false,\n          \"freezePrototype\": false,\n          \"pattern\": {\n            \"use\": \"brownfield\"\n          }\n        },\n        \"windows\": [],\n        \"withGlobalTauri\": false\n      },\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/AppConfig\"\n        }\n      ]\n    },\n    \"build\": {\n      \"description\": \"The build configuration.\",\n      \"default\": {\n        \"additionalWatchFolders\": [],\n        \"removeUnusedCommands\": false\n      },\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/BuildConfig\"\n        }\n      ]\n    },\n    \"bundle\": {\n      \"description\": \"The bundler configuration.\",\n      \"default\": {\n        \"active\": false,\n        \"android\": {\n          \"autoIncrementVersionCode\": false,\n          \"minSdkVersion\": 24\n        },\n        \"createUpdaterArtifacts\": false,\n        \"iOS\": {\n          \"minimumSystemVersion\": \"14.0\"\n        },\n        \"icon\": [],\n        \"linux\": {\n          \"appimage\": {\n            \"bundleMediaFramework\": false,\n            \"files\": {}\n          },\n          \"deb\": {\n            \"files\": {}\n          },\n          \"rpm\": {\n            \"epoch\": 0,\n            \"files\": {},\n            \"release\": \"1\"\n          }\n        },\n        \"macOS\": {\n          \"dmg\": {\n            \"appPosition\": {\n              \"x\": 180,\n              \"y\": 170\n            },\n            \"applicationFolderPosition\": {\n              \"x\": 480,\n              \"y\": 170\n            },\n            \"windowSize\": {\n              \"height\": 400,\n              \"width\": 660\n            }\n          },\n          \"files\": {},\n          \"hardenedRuntime\": true,\n          \"minimumSystemVersion\": \"10.13\"\n        },\n        \"targets\": \"all\",\n        \"useLocalToolsDir\": false,\n        \"windows\": {\n          \"allowDowngrades\": true,\n          \"certificateThumbprint\": null,\n          \"digestAlgorithm\": null,\n          \"nsis\": null,\n          \"signCommand\": null,\n          \"timestampUrl\": null,\n          \"tsp\": false,\n          \"webviewInstallMode\": {\n            \"silent\": true,\n            \"type\": \"downloadBootstrapper\"\n          },\n          \"wix\": null\n        }\n      },\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/BundleConfig\"\n        }\n      ]\n    },\n    \"plugins\": {\n      \"description\": \"The plugins config.\",\n      \"default\": {},\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/PluginConfig\"\n        }\n      ]\n    }\n  },\n  \"additionalProperties\": false,\n  \"definitions\": {\n    \"AppConfig\": {\n      \"description\": \"The App configuration object.\\n\\n See more: <https://v2.tauri.app/reference/config/#appconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"windows\": {\n          \"description\": \"The app windows configuration.\\n\\n ## Example:\\n\\n To create a window at app startup\\n\\n ```json\\n {\\n   \\\"app\\\": {\\n     \\\"windows\\\": [\\n       { \\\"width\\\": 800, \\\"height\\\": 600 }\\n     ]\\n   }\\n }\\n ```\\n\\n If not specified, the window's label (its identifier) defaults to \\\"main\\\",\\n you can use this label to get the window through\\n `app.get_webview_window` in Rust or `WebviewWindow.getByLabel` in JavaScript\\n\\n When working with multiple windows, each window will need an unique label\\n\\n ```json\\n {\\n   \\\"app\\\": {\\n     \\\"windows\\\": [\\n       { \\\"label\\\": \\\"main\\\", \\\"width\\\": 800, \\\"height\\\": 600 },\\n       { \\\"label\\\": \\\"secondary\\\", \\\"width\\\": 800, \\\"height\\\": 600 }\\n     ]\\n   }\\n }\\n ```\\n\\n You can also set `create` to false and use this config through the Rust APIs\\n\\n ```json\\n {\\n   \\\"app\\\": {\\n     \\\"windows\\\": [\\n       { \\\"create\\\": false, \\\"width\\\": 800, \\\"height\\\": 600 }\\n     ]\\n   }\\n }\\n ```\\n\\n and use it like this\\n\\n ```rust\\n tauri::Builder::default()\\n   .setup(|app| {\\n     tauri::WebviewWindowBuilder::from_config(app.handle(), &app.config().app.windows[0])?.build()?;\\n     Ok(())\\n   });\\n ```\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/WindowConfig\"\n          }\n        },\n        \"security\": {\n          \"description\": \"Security configuration.\",\n          \"default\": {\n            \"assetProtocol\": {\n              \"enable\": false,\n              \"scope\": []\n            },\n            \"capabilities\": [],\n            \"dangerousDisableAssetCspModification\": false,\n            \"freezePrototype\": false,\n            \"pattern\": {\n              \"use\": \"brownfield\"\n            }\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/SecurityConfig\"\n            }\n          ]\n        },\n        \"trayIcon\": {\n          \"description\": \"Configuration for app tray icon.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/TrayIconConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"macOSPrivateApi\": {\n          \"description\": \"MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"withGlobalTauri\": {\n          \"description\": \"Whether we should inject the Tauri API on `window.__TAURI__` or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"enableGTKAppId\": {\n          \"description\": \"If set to true \\\"identifier\\\" will be set as GTK app ID (on systems that use GTK).\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WindowConfig\": {\n      \"description\": \"The window configuration object.\\n\\n See more: <https://v2.tauri.app/reference/config/#windowconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"label\": {\n          \"description\": \"The window identifier. It must be alphanumeric.\",\n          \"default\": \"main\",\n          \"type\": \"string\"\n        },\n        \"create\": {\n          \"description\": \"Whether Tauri should create this window at app startup or not.\\n\\n When this is set to `false` you must manually grab the config object via `app.config().app.windows`\\n and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).\\n\\n ## Example:\\n\\n ```rust\\n tauri::Builder::default()\\n   .setup(|app| {\\n     tauri::WebviewWindowBuilder::from_config(app.handle(), &app.config().app.windows[0])?.build()?;\\n     Ok(())\\n   });\\n ```\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"url\": {\n          \"description\": \"The window webview URL.\",\n          \"default\": \"index.html\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WebviewUrl\"\n            }\n          ]\n        },\n        \"userAgent\": {\n          \"description\": \"The user agent for the webview\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dragDropEnabled\": {\n          \"description\": \"Whether the drag and drop is enabled or not on the webview. By default it is enabled.\\n\\n Disabling it is required to use HTML5 drag and drop on the frontend on Windows.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"center\": {\n          \"description\": \"Whether or not the window starts centered or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"x\": {\n          \"description\": \"The horizontal position of the window's top left corner in logical pixels\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"y\": {\n          \"description\": \"The vertical position of the window's top left corner in logical pixels\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"width\": {\n          \"description\": \"The window width in logical pixels.\",\n          \"default\": 800.0,\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"height\": {\n          \"description\": \"The window height in logical pixels.\",\n          \"default\": 600.0,\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"minWidth\": {\n          \"description\": \"The min window width in logical pixels.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"minHeight\": {\n          \"description\": \"The min window height in logical pixels.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"maxWidth\": {\n          \"description\": \"The max window width in logical pixels.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"maxHeight\": {\n          \"description\": \"The max window height in logical pixels.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"preventOverflow\": {\n          \"description\": \"Whether or not to prevent the window from overflowing the workarea\\n\\n ## Platform-specific\\n\\n - **iOS / Android:** Unsupported.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/PreventOverflowConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"resizable\": {\n          \"description\": \"Whether the window is resizable or not. When resizable is set to false, native window's maximize button is automatically disabled.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"maximizable\": {\n          \"description\": \"Whether the window's native maximize button is enabled or not.\\n If resizable is set to false, this setting is ignored.\\n\\n ## Platform-specific\\n\\n - **macOS:** Disables the \\\"zoom\\\" button in the window titlebar, which is also used to enter fullscreen mode.\\n - **Linux / iOS / Android:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"minimizable\": {\n          \"description\": \"Whether the window's native minimize button is enabled or not.\\n\\n ## Platform-specific\\n\\n - **Linux / iOS / Android:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"closable\": {\n          \"description\": \"Whether the window's native close button is enabled or not.\\n\\n ## Platform-specific\\n\\n - **Linux:** \\\"GTK+ will do its best to convince the window manager not to show a close button.\\n   Depending on the system, this function may not have any effect when called on a window that is already visible\\\"\\n - **iOS / Android:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"title\": {\n          \"description\": \"The window title.\",\n          \"default\": \"Tauri App\",\n          \"type\": \"string\"\n        },\n        \"fullscreen\": {\n          \"description\": \"Whether the window starts as fullscreen or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"focus\": {\n          \"description\": \"Whether the window will be initially focused or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"focusable\": {\n          \"description\": \"Whether the window will be focusable or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"transparent\": {\n          \"description\": \"Whether the window is transparent or not.\\n\\n Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri > macOSPrivateApi`.\\n WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"maximized\": {\n          \"description\": \"Whether the window is maximized or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"visible\": {\n          \"description\": \"Whether the window is visible or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"decorations\": {\n          \"description\": \"Whether the window should have borders and bars.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"alwaysOnBottom\": {\n          \"description\": \"Whether the window should always be below other windows.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"alwaysOnTop\": {\n          \"description\": \"Whether the window should always be on top of other windows.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"visibleOnAllWorkspaces\": {\n          \"description\": \"Whether the window should be visible on all workspaces or virtual desktops.\\n\\n ## Platform-specific\\n\\n - **Windows / iOS / Android:** Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"contentProtected\": {\n          \"description\": \"Prevents the window contents from being captured by other apps.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"skipTaskbar\": {\n          \"description\": \"If `true`, hides the window icon from the taskbar on Windows and Linux.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"windowClassname\": {\n          \"description\": \"The name of the window class created on Windows to create the window. **Windows only**.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"theme\": {\n          \"description\": \"The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Theme\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"titleBarStyle\": {\n          \"description\": \"The style of the macOS title bar.\",\n          \"default\": \"Visible\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/TitleBarStyle\"\n            }\n          ]\n        },\n        \"trafficLightPosition\": {\n          \"description\": \"The position of the window controls on macOS.\\n\\n Requires titleBarStyle: Overlay and decorations: true.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/LogicalPosition\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"hiddenTitle\": {\n          \"description\": \"If `true`, sets the window title to be hidden on macOS.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"acceptFirstMouse\": {\n          \"description\": \"Whether clicking an inactive window also clicks through to the webview on macOS.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"tabbingIdentifier\": {\n          \"description\": \"Defines the window [tabbing identifier] for macOS.\\n\\n Windows with matching tabbing identifiers will be grouped together.\\n If the tabbing identifier is not set, automatic tabbing will be disabled.\\n\\n [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"additionalBrowserArgs\": {\n          \"description\": \"Defines additional browser arguments on Windows. By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`\\n so if you use this method, you also need to disable these components by yourself if you want.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"shadow\": {\n          \"description\": \"Whether or not the window has shadow.\\n\\n ## Platform-specific\\n\\n - **Windows:**\\n   - `false` has no effect on decorated window, shadow are always ON.\\n   - `true` will make undecorated window have a 1px white border,\\n and on Windows 11, it will have a rounded corners.\\n - **Linux:** Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"windowEffects\": {\n          \"description\": \"Window effects.\\n\\n Requires the window to be transparent.\\n\\n ## Platform-specific:\\n\\n - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>\\n - **Linux**: Unsupported\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowEffectsConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"incognito\": {\n          \"description\": \"Whether or not the webview should be launched in incognito  mode.\\n\\n ## Platform-specific:\\n\\n - **Android**: Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"parent\": {\n          \"description\": \"Sets the window associated with this label to be the parent of the window to be created.\\n\\n ## Platform-specific\\n\\n - **Windows**: This sets the passed parent as an owner window to the window to be created.\\n   From [MSDN owned windows docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows):\\n     - An owned window is always above its owner in the z-order.\\n     - The system automatically destroys an owned window when its owner is destroyed.\\n     - An owned window is hidden when its owner is minimized.\\n - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\\n - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"proxyUrl\": {\n          \"description\": \"The proxy URL for the WebView for all network requests.\\n\\n Must be either a `http://` or a `socks5://` URL.\\n\\n ## Platform-specific\\n\\n - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"format\": \"uri\"\n        },\n        \"zoomHotkeysEnabled\": {\n          \"description\": \"Whether page zooming by hotkeys is enabled\\n\\n ## Platform-specific:\\n\\n - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.\\n - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,\\n 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission\\n\\n - **Android / iOS**: Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"browserExtensionsEnabled\": {\n          \"description\": \"Whether browser extensions can be installed for the webview process\\n\\n ## Platform-specific:\\n\\n - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)\\n - **MacOS / Linux / iOS / Android** - Unsupported.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"useHttpsScheme\": {\n          \"description\": \"Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.\\n\\n ## Note\\n\\n Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.\\n\\n ## Warning\\n\\n Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"devtools\": {\n          \"description\": \"Enable web inspector which is usually called browser devtools. Enabled by default.\\n\\n This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.\\n\\n ## Platform-specific\\n\\n - macOS: This will call private functions on **macOS**.\\n - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.\\n - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"backgroundColor\": {\n          \"description\": \"Set the window and webview background color.\\n\\n ## Platform-specific:\\n\\n - **Windows**: alpha channel is ignored for the window layer.\\n - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.\\n - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored for the webview layer.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Color\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"backgroundThrottling\": {\n          \"description\": \"Change the default background throttling behaviour.\\n\\n By default, browsers use a suspend policy that will throttle timers and even unload\\n the whole tab (view) to free resources after roughly 5 minutes when a view became\\n minimized or hidden. This will pause all tasks until the documents visibility state\\n changes back from hidden to visible by bringing the view back to the foreground.\\n\\n ## Platform-specific\\n\\n - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.\\n - **iOS**: Supported since version 17.0+.\\n - **macOS**: Supported since version 14.0+.\\n\\n see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BackgroundThrottlingPolicy\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"javascriptDisabled\": {\n          \"description\": \"Whether we should disable JavaScript code execution on the webview or not.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"allowLinkPreview\": {\n          \"description\": \"on macOS and iOS there is a link preview on long pressing links, this is enabled by default.\\n see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"disableInputAccessoryView\": {\n          \"description\": \"Allows disabling the input accessory view on iOS.\\n\\n The accessory view is the view that appears above the keyboard when a text input element is focused.\\n It usually displays a view with \\\"Done\\\", \\\"Next\\\" buttons.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"dataDirectory\": {\n          \"description\": \"Set a custom path for the webview's data directory (localStorage, cache, etc.) **relative to [`appDataDir()`]/${label}**.\\n\\n To set absolute paths, use [`WebviewWindowBuilder::data_directory`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.data_directory)\\n\\n #### Platform-specific:\\n\\n - **Windows**: WebViews with different values for settings like `additionalBrowserArgs`, `browserExtensionsEnabled` or `scrollBarStyle` must have different data directories.\\n - **macOS / iOS**: Unsupported, use `dataStoreIdentifier` instead.\\n - **Android**: Unsupported.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dataStoreIdentifier\": {\n          \"description\": \"Initialize the WebView with a custom data store identifier. This can be seen as a replacement for `dataDirectory` which is unavailable in WKWebView.\\n See https://developer.apple.com/documentation/webkit/wkwebsitedatastore/init(foridentifier:)?language=objc\\n\\n The array must contain 16 u8 numbers.\\n\\n #### Platform-specific:\\n\\n - **iOS**: Supported since version 17.0+.\\n - **macOS**: Supported since version 14.0+.\\n - **Windows / Linux / Android**: Unsupported.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"integer\",\n            \"format\": \"uint8\",\n            \"minimum\": 0.0\n          },\n          \"maxItems\": 16,\n          \"minItems\": 16\n        },\n        \"scrollBarStyle\": {\n          \"description\": \"Specifies the native scrollbar style to use with the webview.\\n CSS styles that modify the scrollbar are applied on top of the native appearance configured here.\\n\\n Defaults to `default`, which is the browser default.\\n\\n ## Platform-specific\\n\\n - **Windows**:\\n   - `fluentOverlay` requires WebView2 Runtime version 125.0.2535.41 or higher,\\n     and does nothing on older versions.\\n   - This option must be given the same value for all webviews that target the same data directory.\\n - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.\",\n          \"default\": \"default\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/ScrollBarStyle\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WebviewUrl\": {\n      \"description\": \"An URL to open on a Tauri webview window.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An external URL. Must use either the `http` or `https` schemes.\",\n          \"type\": \"string\",\n          \"format\": \"uri\"\n        },\n        {\n          \"description\": \"The path portion of an app URL.\\n For instance, to load `tauri://localhost/users/john`,\\n you can simply provide `users/john` in this configuration.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A custom protocol url, for example, `doom://index.html`\",\n          \"type\": \"string\",\n          \"format\": \"uri\"\n        }\n      ]\n    },\n    \"PreventOverflowConfig\": {\n      \"description\": \"Prevent overflow with a margin\",\n      \"anyOf\": [\n        {\n          \"description\": \"Enable prevent overflow or not\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Enable prevent overflow with a margin\\n so that the window's size + this margin won't overflow the workarea\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/PreventOverflowMargin\"\n            }\n          ]\n        }\n      ]\n    },\n    \"PreventOverflowMargin\": {\n      \"description\": \"Enable prevent overflow with a margin\\n so that the window's size + this margin won't overflow the workarea\",\n      \"type\": \"object\",\n      \"required\": [\n        \"height\",\n        \"width\"\n      ],\n      \"properties\": {\n        \"width\": {\n          \"description\": \"Horizontal margin in physical pixels\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"height\": {\n          \"description\": \"Vertical margin in physical pixels\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Theme\": {\n      \"description\": \"System theme.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Light theme.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Light\"\n          ]\n        },\n        {\n          \"description\": \"Dark theme.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Dark\"\n          ]\n        }\n      ]\n    },\n    \"TitleBarStyle\": {\n      \"description\": \"How the window title bar should be displayed on macOS.\",\n      \"oneOf\": [\n        {\n          \"description\": \"A normal title bar.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Visible\"\n          ]\n        },\n        {\n          \"description\": \"Makes the title bar transparent, so the window background color is shown instead.\\n\\n Useful if you don't need to have actual HTML under the title bar. This lets you avoid the caveats of using `TitleBarStyle::Overlay`. Will be more useful when Tauri lets you set a custom window background color.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Transparent\"\n          ]\n        },\n        {\n          \"description\": \"Shows the title bar as a transparent overlay over the window's content.\\n\\n Keep in mind:\\n - The height of the title bar is different on different OS versions, which can lead to window the controls and title not being where you don't expect.\\n - You need to define a custom drag region to make your window draggable, however due to a limitation you can't drag the window when it's not in focus <https://github.com/tauri-apps/tauri/issues/4316>.\\n - The color of the window title depends on the system theme.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Overlay\"\n          ]\n        }\n      ]\n    },\n    \"LogicalPosition\": {\n      \"description\": \"Position coordinates struct.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"x\",\n        \"y\"\n      ],\n      \"properties\": {\n        \"x\": {\n          \"description\": \"X coordinate.\",\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"y\": {\n          \"description\": \"Y coordinate.\",\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WindowEffectsConfig\": {\n      \"description\": \"The window effects configuration object\",\n      \"type\": \"object\",\n      \"required\": [\n        \"effects\"\n      ],\n      \"properties\": {\n        \"effects\": {\n          \"description\": \"List of Window effects to apply to the Window.\\n Conflicting effects will apply the first one and ignore the rest.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/WindowEffect\"\n          }\n        },\n        \"state\": {\n          \"description\": \"Window effect state **macOS Only**\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowEffectState\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"radius\": {\n          \"description\": \"Window effect corner radius **macOS Only**\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"double\"\n        },\n        \"color\": {\n          \"description\": \"Window effect color. Affects [`WindowEffect::Blur`] and [`WindowEffect::Acrylic`] only\\n on Windows 10 v1903+. Doesn't have any effect on Windows 7 or Windows 11.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Color\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WindowEffect\": {\n      \"description\": \"Platform-specific window effects\",\n      \"oneOf\": [\n        {\n          \"description\": \"A default material appropriate for the view's effectiveAppearance. **macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"appearanceBased\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"light\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"dark\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"mediumLight\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14-**\",\n          \"deprecated\": true,\n          \"type\": \"string\",\n          \"enum\": [\n            \"ultraDark\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.10+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"titlebar\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.10+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"selection\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.11+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"menu\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.11+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"popover\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.11+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"sidebar\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"headerView\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"sheet\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"windowBackground\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"hudWindow\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"fullScreenUI\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tooltip\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"contentBackground\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"underWindowBackground\"\n          ]\n        },\n        {\n          \"description\": \"**macOS 10.14+**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"underPageBackground\"\n          ]\n        },\n        {\n          \"description\": \"Mica effect that matches the system dark preference **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"mica\"\n          ]\n        },\n        {\n          \"description\": \"Mica effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"micaDark\"\n          ]\n        },\n        {\n          \"description\": \"Mica effect with light mode **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"micaLight\"\n          ]\n        },\n        {\n          \"description\": \"Tabbed effect that matches the system dark preference **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tabbed\"\n          ]\n        },\n        {\n          \"description\": \"Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tabbedDark\"\n          ]\n        },\n        {\n          \"description\": \"Tabbed effect with light mode **Windows 11 Only**\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"tabbedLight\"\n          ]\n        },\n        {\n          \"description\": \"**Windows 7/10/11(22H1) Only**\\n\\n ## Notes\\n\\n This effect has bad performance when resizing/dragging the window on Windows 11 build 22621.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"blur\"\n          ]\n        },\n        {\n          \"description\": \"**Windows 10/11 Only**\\n\\n ## Notes\\n\\n This effect has bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"acrylic\"\n          ]\n        }\n      ]\n    },\n    \"WindowEffectState\": {\n      \"description\": \"Window effect state **macOS only**\\n\\n <https://developer.apple.com/documentation/appkit/nsvisualeffectview/state>\",\n      \"oneOf\": [\n        {\n          \"description\": \"Make window effect state follow the window's active state\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"followsWindowActiveState\"\n          ]\n        },\n        {\n          \"description\": \"Make window effect state always active\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"active\"\n          ]\n        },\n        {\n          \"description\": \"Make window effect state always inactive\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"inactive\"\n          ]\n        }\n      ]\n    },\n    \"Color\": {\n      \"anyOf\": [\n        {\n          \"description\": \"Color hex string, for example: #fff, #ffffff, or #ffffffff.\",\n          \"type\": \"string\",\n          \"pattern\": \"^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$\"\n        },\n        {\n          \"description\": \"Array of RGB colors. Each value has minimum of 0 and maximum of 255.\",\n          \"type\": \"array\",\n          \"items\": [\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            }\n          ],\n          \"maxItems\": 3,\n          \"minItems\": 3\n        },\n        {\n          \"description\": \"Array of RGBA colors. Each value has minimum of 0 and maximum of 255.\",\n          \"type\": \"array\",\n          \"items\": [\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            }\n          ],\n          \"maxItems\": 4,\n          \"minItems\": 4\n        },\n        {\n          \"description\": \"Object of red, green, blue, alpha color values. Each value has minimum of 0 and maximum of 255.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"blue\",\n            \"green\",\n            \"red\"\n          ],\n          \"properties\": {\n            \"red\": {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            \"green\": {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            \"blue\": {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            },\n            \"alpha\": {\n              \"default\": 255,\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"minimum\": 0.0\n            }\n          }\n        }\n      ]\n    },\n    \"BackgroundThrottlingPolicy\": {\n      \"description\": \"Background throttling policy.\",\n      \"oneOf\": [\n        {\n          \"description\": \"A policy where background throttling is disabled\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"disabled\"\n          ]\n        },\n        {\n          \"description\": \"A policy where a web view that's not in a window fully suspends tasks. This is usually the default behavior in case no policy is set.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"suspend\"\n          ]\n        },\n        {\n          \"description\": \"A policy where a web view that's not in a window limits processing, but does not fully suspend tasks.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"throttle\"\n          ]\n        }\n      ]\n    },\n    \"ScrollBarStyle\": {\n      \"description\": \"The scrollbar style to use in the webview.\\n\\n ## Platform-specific\\n\\n - **Windows**: This option must be given the same value for all webviews that target the same data directory.\",\n      \"oneOf\": [\n        {\n          \"description\": \"The scrollbar style to use in the webview.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"default\"\n          ]\n        },\n        {\n          \"description\": \"Fluent UI style overlay scrollbars. **Windows Only**\\n\\n Requires WebView2 Runtime version 125.0.2535.41 or higher, does nothing on older versions,\\n see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/?tabs=dotnetcsharp#10253541\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"fluentOverlay\"\n          ]\n        }\n      ]\n    },\n    \"SecurityConfig\": {\n      \"description\": \"Security configuration.\\n\\n See more: <https://v2.tauri.app/reference/config/#securityconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"csp\": {\n          \"description\": \"The Content Security Policy that will be injected on all HTML files on the built application.\\n If [`dev_csp`](#SecurityConfig.devCsp) is not specified, this value is also injected on dev.\\n\\n This is a really important part of the configuration since it helps you ensure your WebView is secured.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Csp\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"devCsp\": {\n          \"description\": \"The Content Security Policy that will be injected on all HTML files on development.\\n\\n This is a really important part of the configuration since it helps you ensure your WebView is secured.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Csp\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"freezePrototype\": {\n          \"description\": \"Freeze the `Object.prototype` when using the custom protocol.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"dangerousDisableAssetCspModification\": {\n          \"description\": \"Disables the Tauri-injected CSP sources.\\n\\n At compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy\\n to only allow loading of your own scripts and styles by injecting nonce and hash sources.\\n This stricts your CSP, which may introduce issues when using along with other flexing sources.\\n\\n This configuration option allows both a boolean and a list of strings as value.\\n A boolean instructs Tauri to disable the injection for all CSP injections,\\n and a list of strings indicates the CSP directives that Tauri cannot inject.\\n\\n **WARNING:** Only disable this if you know what you are doing and have properly configured the CSP.\\n Your application might be vulnerable to XSS attacks without this Tauri protection.\",\n          \"default\": false,\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/DisabledCspModificationKind\"\n            }\n          ]\n        },\n        \"assetProtocol\": {\n          \"description\": \"Custom protocol config.\",\n          \"default\": {\n            \"enable\": false,\n            \"scope\": []\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/AssetProtocolConfig\"\n            }\n          ]\n        },\n        \"pattern\": {\n          \"description\": \"The pattern to use.\",\n          \"default\": {\n            \"use\": \"brownfield\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/PatternKind\"\n            }\n          ]\n        },\n        \"capabilities\": {\n          \"description\": \"List of capabilities that are enabled on the application.\\n\\n By default (not set or empty list), all capability files from `./capabilities/` are included,\\n by setting values in this entry, you have fine grained control over which capabilities are included\\n\\n You can either reference a capability file defined in `./capabilities/` with its identifier or inline a [`Capability`]\\n\\n ### Example\\n\\n ```json\\n {\\n   \\\"app\\\": {\\n     \\\"capabilities\\\": [\\n       \\\"main-window\\\",\\n       {\\n         \\\"identifier\\\": \\\"drag-window\\\",\\n         \\\"permissions\\\": [\\\"core:window:allow-start-dragging\\\"]\\n       }\\n     ]\\n   }\\n }\\n ```\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/CapabilityEntry\"\n          }\n        },\n        \"headers\": {\n          \"description\": \"The headers, which are added to every http response from tauri to the web view\\n This doesn't include IPC Messages and error responses\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Csp\": {\n      \"description\": \"A Content-Security-Policy definition.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\",\n      \"anyOf\": [\n        {\n          \"description\": \"The entire CSP policy in a single text string.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An object mapping a directive with its sources values as a list of strings.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/CspDirectiveSources\"\n          }\n        }\n      ]\n    },\n    \"CspDirectiveSources\": {\n      \"description\": \"A Content-Security-Policy directive source list.\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources>.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An inline list of CSP sources. Same as [`Self::List`], but concatenated with a space separator.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A list of CSP sources. The collection will be concatenated with a space separator for the CSP string.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"DisabledCspModificationKind\": {\n      \"description\": \"The possible values for the `dangerous_disable_asset_csp_modification` config option.\",\n      \"anyOf\": [\n        {\n          \"description\": \"If `true`, disables all CSP modification.\\n `false` is the default value and it configures Tauri to control the CSP.\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Disables the given list of CSP directives modifications.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"AssetProtocolConfig\": {\n      \"description\": \"Config for the asset custom protocol.\\n\\n See more: <https://v2.tauri.app/reference/config/#assetprotocolconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"scope\": {\n          \"description\": \"The access scope for the asset protocol.\",\n          \"default\": [],\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/FsScope\"\n            }\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Enables the asset protocol.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"FsScope\": {\n      \"description\": \"Protocol scope definition.\\n It is a list of glob patterns that restrict the API access from the webview.\\n\\n Each pattern can start with a variable that resolves to a system base directory.\\n The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,\\n `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,\\n `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$TEMP`,\\n `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A list of paths that are allowed by this scope.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A complete scope configuration.\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"allow\": {\n              \"description\": \"A list of paths that are allowed by this scope.\",\n              \"default\": [],\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            },\n            \"deny\": {\n              \"description\": \"A list of paths that are not allowed by this scope.\\n This gets precedence over the [`Self::Scope::allow`] list.\",\n              \"default\": [],\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            },\n            \"requireLiteralLeadingDot\": {\n              \"description\": \"Whether or not paths that contain components that start with a `.`\\n will require that `.` appears literally in the pattern; `*`, `?`, `**`,\\n or `[...]` will not match. This is useful because such files are\\n conventionally considered hidden on Unix systems and it might be\\n desirable to skip them when listing files.\\n\\n Defaults to `true` on Unix systems and `false` on Windows\",\n              \"type\": [\n                \"boolean\",\n                \"null\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"PatternKind\": {\n      \"description\": \"The application pattern.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Brownfield pattern.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"use\"\n          ],\n          \"properties\": {\n            \"use\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"brownfield\"\n              ]\n            }\n          }\n        },\n        {\n          \"description\": \"Isolation pattern. Recommended for security purposes.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"options\",\n            \"use\"\n          ],\n          \"properties\": {\n            \"use\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"isolation\"\n              ]\n            },\n            \"options\": {\n              \"type\": \"object\",\n              \"required\": [\n                \"dir\"\n              ],\n              \"properties\": {\n                \"dir\": {\n                  \"description\": \"The dir containing the index.html file that contains the secure isolation application.\",\n                  \"type\": \"string\"\n                }\n              }\n            }\n          }\n        }\n      ]\n    },\n    \"CapabilityEntry\": {\n      \"description\": \"A capability entry which can be either an inlined capability or a reference to a capability defined on its own file.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An inlined capability.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Capability\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Reference to a capability identifier.\",\n          \"type\": \"string\"\n        }\n      ]\n    },\n    \"Capability\": {\n      \"description\": \"A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\\n\\n It controls application windows' and webviews' fine grained access\\n to the Tauri core, application, or plugin commands.\\n If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\\n\\n This can be done to create groups of windows, based on their required system access, which can reduce\\n impact of frontend vulnerabilities in less privileged windows.\\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\\n A Window can have none, one, or multiple associated capabilities.\\n\\n ## Example\\n\\n ```json\\n {\\n   \\\"identifier\\\": \\\"main-user-files-write\\\",\\n   \\\"description\\\": \\\"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\\\",\\n   \\\"windows\\\": [\\n     \\\"main\\\"\\n   ],\\n   \\\"permissions\\\": [\\n     \\\"core:default\\\",\\n     \\\"dialog:open\\\",\\n     {\\n       \\\"identifier\\\": \\\"fs:allow-write-text-file\\\",\\n       \\\"allow\\\": [{ \\\"path\\\": \\\"$HOME/test.txt\\\" }]\\n     },\\n   ],\\n   \\\"platforms\\\": [\\\"macOS\\\",\\\"windows\\\"]\\n }\\n ```\",\n      \"type\": \"object\",\n      \"required\": [\n        \"identifier\",\n        \"permissions\"\n      ],\n      \"properties\": {\n        \"identifier\": {\n          \"description\": \"Identifier of the capability.\\n\\n ## Example\\n\\n `main-user-files-write`\",\n          \"type\": \"string\"\n        },\n        \"description\": {\n          \"description\": \"Description of what the capability is intended to allow on associated windows.\\n\\n It should contain a description of what the grouped permissions should allow.\\n\\n ## Example\\n\\n This capability allows the `main` window access to `filesystem` write related\\n commands and `dialog` commands to enable programmatic access to files selected by the user.\",\n          \"default\": \"\",\n          \"type\": \"string\"\n        },\n        \"remote\": {\n          \"description\": \"Configure remote URLs that can use the capability permissions.\\n\\n This setting is optional and defaults to not being set, as our\\n default use case is that the content is served from our local application.\\n\\n :::caution\\n Make sure you understand the security implications of providing remote\\n sources with local system access.\\n :::\\n\\n ## Example\\n\\n ```json\\n {\\n   \\\"urls\\\": [\\\"https://*.mydomain.dev\\\"]\\n }\\n ```\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/CapabilityRemote\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"local\": {\n          \"description\": \"Whether this capability is enabled for local app URLs or not. Defaults to `true`.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"windows\": {\n          \"description\": \"List of windows that are affected by this capability. Can be a glob pattern.\\n\\n If a window label matches any of the patterns in this list,\\n the capability will be enabled on all the webviews of that window,\\n regardless of the value of [`Self::webviews`].\\n\\n On multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`]\\n for a fine grained access control.\\n\\n ## Example\\n\\n `[\\\"main\\\"]`\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"webviews\": {\n          \"description\": \"List of webviews that are affected by this capability. Can be a glob pattern.\\n\\n The capability will be enabled on all the webviews\\n whose label matches any of the patterns in this list,\\n regardless of whether the webview's window label matches a pattern in [`Self::windows`].\\n\\n ## Example\\n\\n `[\\\"sub-webview-one\\\", \\\"sub-webview-two\\\"]`\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"permissions\": {\n          \"description\": \"List of permissions attached to this capability.\\n\\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\\n For commands directly implemented in the application itself only `${permission-name}`\\n is required.\\n\\n ## Example\\n\\n ```json\\n [\\n   \\\"core:default\\\",\\n   \\\"shell:allow-open\\\",\\n   \\\"dialog:open\\\",\\n   {\\n     \\\"identifier\\\": \\\"fs:allow-write-text-file\\\",\\n     \\\"allow\\\": [{ \\\"path\\\": \\\"$HOME/test.txt\\\" }]\\n   }\\n ]\\n ```\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/PermissionEntry\"\n          },\n          \"uniqueItems\": true\n        },\n        \"platforms\": {\n          \"description\": \"Limit which target platforms this capability applies to.\\n\\n By default all platforms are targeted.\\n\\n ## Example\\n\\n `[\\\"macOS\\\",\\\"windows\\\"]`\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/definitions/Target\"\n          }\n        }\n      }\n    },\n    \"CapabilityRemote\": {\n      \"description\": \"Configuration for remote URLs that are associated with the capability.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"urls\"\n      ],\n      \"properties\": {\n        \"urls\": {\n          \"description\": \"Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\\n\\n ## Examples\\n\\n - \\\"https://*.mydomain.dev\\\": allows subdomains of mydomain.dev\\n - \\\"https://mydomain.dev/api/*\\\": allows any subpath of mydomain.dev/api\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"PermissionEntry\": {\n      \"description\": \"An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`]\\n or an object that references a permission and extends its scope.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Reference a permission or permission set by identifier.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Identifier\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Reference a permission or permission set by identifier and extends its scope.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"identifier\"\n          ],\n          \"properties\": {\n            \"identifier\": {\n              \"description\": \"Identifier of the permission or permission set.\",\n              \"allOf\": [\n                {\n                  \"$ref\": \"#/definitions/Identifier\"\n                }\n              ]\n            },\n            \"allow\": {\n              \"description\": \"Data that defines what is allowed by the scope.\",\n              \"type\": [\n                \"array\",\n                \"null\"\n              ],\n              \"items\": {\n                \"$ref\": \"#/definitions/Value\"\n              }\n            },\n            \"deny\": {\n              \"description\": \"Data that defines what is denied by the scope. This should be prioritized by validation logic.\",\n              \"type\": [\n                \"array\",\n                \"null\"\n              ],\n              \"items\": {\n                \"$ref\": \"#/definitions/Value\"\n              }\n            }\n          }\n        }\n      ]\n    },\n    \"Identifier\": {\n      \"type\": \"string\"\n    },\n    \"Value\": {\n      \"description\": \"All supported ACL values.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents a null JSON value.\",\n          \"type\": \"null\"\n        },\n        {\n          \"description\": \"Represents a [`bool`].\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Represents a valid ACL [`Number`].\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Number\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Represents a [`String`].\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Represents a list of other [`Value`]s.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        },\n        {\n          \"description\": \"Represents a map of [`String`] keys to [`Value`]s.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        }\n      ]\n    },\n    \"Number\": {\n      \"description\": \"A valid ACL number.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents an [`i64`].\",\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        {\n          \"description\": \"Represents a [`f64`].\",\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      ]\n    },\n    \"Target\": {\n      \"description\": \"Platform target.\",\n      \"oneOf\": [\n        {\n          \"description\": \"MacOS.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"macOS\"\n          ]\n        },\n        {\n          \"description\": \"Windows.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"windows\"\n          ]\n        },\n        {\n          \"description\": \"Linux.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"linux\"\n          ]\n        },\n        {\n          \"description\": \"Android.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"android\"\n          ]\n        },\n        {\n          \"description\": \"iOS.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"iOS\"\n          ]\n        }\n      ]\n    },\n    \"HeaderConfig\": {\n      \"description\": \"A struct, where the keys are some specific http header names.\\n\\n If the values to those keys are defined, then they will be send as part of a response message.\\n This does not include error messages and ipc messages\\n\\n ## Example configuration\\n ```javascript\\n {\\n  //..\\n   app:{\\n     //..\\n     security: {\\n       headers: {\\n         \\\"Cross-Origin-Opener-Policy\\\": \\\"same-origin\\\",\\n         \\\"Cross-Origin-Embedder-Policy\\\": \\\"require-corp\\\",\\n         \\\"Timing-Allow-Origin\\\": [\\n           \\\"https://developer.mozilla.org\\\",\\n           \\\"https://example.com\\\",\\n         ],\\n         \\\"Access-Control-Expose-Headers\\\": \\\"Tauri-Custom-Header\\\",\\n         \\\"Tauri-Custom-Header\\\": {\\n           \\\"key1\\\": \\\"'value1' 'value2'\\\",\\n           \\\"key2\\\": \\\"'value3'\\\"\\n         }\\n       },\\n       csp: \\\"default-src 'self'; connect-src ipc: http://ipc.localhost\\\",\\n     }\\n     //..\\n   }\\n  //..\\n }\\n ```\\n In this example `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` are set to allow for the use of [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).\\n The result is, that those headers are then set on every response sent via the `get_response` function in crates/tauri/src/protocol/tauri.rs.\\n The Content-Security-Policy header is defined separately, because it is also handled separately.\\n\\n For the helloworld example, this config translates into those response headers:\\n ```http\\n access-control-allow-origin:  http://tauri.localhost\\n access-control-expose-headers: Tauri-Custom-Header\\n content-security-policy: default-src 'self'; connect-src ipc: http://ipc.localhost; script-src 'self' 'sha256-Wjjrs6qinmnr+tOry8x8PPwI77eGpUFR3EEGZktjJNs='\\n content-type: text/html\\n cross-origin-embedder-policy: require-corp\\n cross-origin-opener-policy: same-origin\\n tauri-custom-header: key1 'value1' 'value2'; key2 'value3'\\n timing-allow-origin: https://developer.mozilla.org, https://example.com\\n ```\\n Since the resulting header values are always 'string-like'. So depending on the what data type the HeaderSource is, they need to be converted.\\n  - `String`(JS/Rust): stay the same for the resulting header value\\n  - `Array`(JS)/`Vec\\\\<String\\\\>`(Rust): Item are joined by \\\", \\\" for the resulting header value\\n  - `Object`(JS)/ `Hashmap\\\\<String,String\\\\>`(Rust): Items are composed from: key + space + value. Item are then joined by \\\"; \\\" for the resulting header value\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"Access-Control-Allow-Credentials\": {\n          \"description\": \"The Access-Control-Allow-Credentials response header tells browsers whether the\\n server allows cross-origin HTTP requests to include credentials.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Access-Control-Allow-Headers\": {\n          \"description\": \"The Access-Control-Allow-Headers response header is used in response\\n to a preflight request which includes the Access-Control-Request-Headers\\n to indicate which HTTP headers can be used during the actual request.\\n\\n This header is required if the request has an Access-Control-Request-Headers header.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Access-Control-Allow-Methods\": {\n          \"description\": \"The Access-Control-Allow-Methods response header specifies one or more methods\\n allowed when accessing a resource in response to a preflight request.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Access-Control-Expose-Headers\": {\n          \"description\": \"The Access-Control-Expose-Headers response header allows a server to indicate\\n which response headers should be made available to scripts running in the browser,\\n in response to a cross-origin request.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Access-Control-Max-Age\": {\n          \"description\": \"The Access-Control-Max-Age response header indicates how long the results of a\\n preflight request (that is the information contained in the\\n Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can\\n be cached.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Cross-Origin-Embedder-Policy\": {\n          \"description\": \"The HTTP Cross-Origin-Embedder-Policy (COEP) response header configures embedding\\n cross-origin resources into the document.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Cross-Origin-Opener-Policy\": {\n          \"description\": \"The HTTP Cross-Origin-Opener-Policy (COOP) response header allows you to ensure a\\n top-level document does not share a browsing context group with cross-origin documents.\\n COOP will process-isolate your document and potential attackers can't access your global\\n object if they were to open it in a popup, preventing a set of cross-origin attacks dubbed XS-Leaks.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Cross-Origin-Resource-Policy\": {\n          \"description\": \"The HTTP Cross-Origin-Resource-Policy response header conveys a desire that the\\n browser blocks no-cors cross-origin/cross-site requests to the given resource.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Permissions-Policy\": {\n          \"description\": \"The HTTP Permissions-Policy header provides a mechanism to allow and deny the\\n use of browser features in a document or within any \\\\<iframe\\\\> elements in the document.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Service-Worker-Allowed\": {\n          \"description\": \"The HTTP Service-Worker-Allowed response header is used to broaden the path restriction for a\\n service worker's default scope.\\n\\n By default, the scope for a service worker registration is the directory where the service\\n worker script is located. For example, if the script `sw.js` is located in `/js/sw.js`,\\n it can only control URLs under `/js/` by default. Servers can use the `Service-Worker-Allowed`\\n header to allow a service worker to control URLs outside of its own directory.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Service-Worker-Allowed>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Timing-Allow-Origin\": {\n          \"description\": \"The Timing-Allow-Origin response header specifies origins that are allowed to see values\\n of attributes retrieved via features of the Resource Timing API, which would otherwise be\\n reported as zero due to cross-origin restrictions.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"X-Content-Type-Options\": {\n          \"description\": \"The X-Content-Type-Options response HTTP header is a marker used by the server to indicate\\n that the MIME types advertised in the Content-Type headers should be followed and not be\\n changed. The header allows you to avoid MIME type sniffing by saying that the MIME types\\n are deliberately configured.\\n\\n See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"Tauri-Custom-Header\": {\n          \"description\": \"A custom header field Tauri-Custom-Header, don't use it.\\n Remember to set Access-Control-Expose-Headers accordingly\\n\\n **NOT INTENDED FOR PRODUCTION USE**\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HeaderSource\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"HeaderSource\": {\n      \"description\": \"definition of a header source\\n\\n The header value to a header name\",\n      \"anyOf\": [\n        {\n          \"description\": \"string version of the header Value\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"list version of the header value. Item are joined by \\\",\\\" for the real header value\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"(Rust struct | Json | JavaScript Object) equivalent of the header value. Items are composed from: key + space + value. Item are then joined by \\\";\\\" for the real header value\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"TrayIconConfig\": {\n      \"description\": \"Configuration for application tray icon.\\n\\n See more: <https://v2.tauri.app/reference/config/#trayiconconfig>\",\n      \"type\": \"object\",\n      \"required\": [\n        \"iconPath\"\n      ],\n      \"properties\": {\n        \"id\": {\n          \"description\": \"Set an id for this tray icon so you can reference it later, defaults to `main`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"iconPath\": {\n          \"description\": \"Path to the default icon to use for the tray icon.\\n\\n Note: this stores the image in raw pixels to the final binary,\\n so keep the icon size (width and height) small\\n or else it's going to bloat your final executable\",\n          \"type\": \"string\"\n        },\n        \"iconAsTemplate\": {\n          \"description\": \"A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"menuOnLeftClick\": {\n          \"description\": \"A Boolean value that determines whether the menu should appear when the tray icon receives a left click.\\n\\n ## Platform-specific:\\n\\n - **Linux**: Unsupported.\",\n          \"default\": true,\n          \"deprecated\": true,\n          \"type\": \"boolean\"\n        },\n        \"showMenuOnLeftClick\": {\n          \"description\": \"A Boolean value that determines whether the menu should appear when the tray icon receives a left click.\\n\\n ## Platform-specific:\\n\\n - **Linux**: Unsupported.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"title\": {\n          \"description\": \"Title for MacOS tray\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tooltip\": {\n          \"description\": \"Tray icon tooltip on Windows and macOS\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"BuildConfig\": {\n      \"description\": \"The Build configuration object.\\n\\n See more: <https://v2.tauri.app/reference/config/#buildconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"runner\": {\n          \"description\": \"The binary used to build and run the application.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/RunnerConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"devUrl\": {\n          \"description\": \"The URL to load in development.\\n\\n This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.\\n Most modern JavaScript bundlers like [Vite](https://vite.dev/guide/) provides a way to start a dev server by default.\\n\\n If you don't have a dev server or don't want to use one, ignore this option and use [`frontendDist`](BuildConfig::frontend_dist)\\n and point to a web assets directory, and Tauri CLI will run its built-in dev server and provide a simple hot-reload experience.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"format\": \"uri\"\n        },\n        \"frontendDist\": {\n          \"description\": \"The path to the application assets (usually the `dist` folder of your javascript bundler)\\n or a URL that could be either a custom protocol registered in the tauri app (for example: `myprotocol://`)\\n or a remote URL (for example: `https://site.com/app`).\\n\\n When a path relative to the configuration file is provided,\\n it is read recursively and all files are embedded in the application binary.\\n Tauri then looks for an `index.html` and serves it as the default entry point for your application.\\n\\n You can also provide a list of paths to be embedded, which allows granular control over what files are added to the binary.\\n In this case, all files are added to the root and you must reference it that way in your HTML files.\\n\\n When a URL is provided, the application won't have bundled assets\\n and the application will load that URL by default.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/FrontendDist\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"beforeDevCommand\": {\n          \"description\": \"A shell command to run before `tauri dev` kicks in.\\n\\n The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BeforeDevCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"beforeBuildCommand\": {\n          \"description\": \"A shell command to run before `tauri build` kicks in.\\n\\n The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HookCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"beforeBundleCommand\": {\n          \"description\": \"A shell command to run before the bundling phase in `tauri build` kicks in.\\n\\n The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/HookCommand\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"features\": {\n          \"description\": \"Features passed to `cargo` commands.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"removeUnusedCommands\": {\n          \"description\": \"Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\\n and they'll try to get all the allowed commands and remove the rest\\n\\n Note:\\n   - This won't be accounting for dynamically added ACLs when you use features from the `dynamic-acl` (currently enabled by default) feature flag, so make sure to check it when using this\\n   - This feature requires tauri-plugin 2.1 and tauri 2.4\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"additionalWatchFolders\": {\n          \"description\": \"Additional paths to watch for changes when running `tauri dev`.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"RunnerConfig\": {\n      \"description\": \"The runner configuration.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A string specifying the binary to run.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An object with advanced configuration options.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"cmd\"\n          ],\n          \"properties\": {\n            \"cmd\": {\n              \"description\": \"The binary to run.\",\n              \"type\": \"string\"\n            },\n            \"cwd\": {\n              \"description\": \"The current working directory to run the command from.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            },\n            \"args\": {\n              \"description\": \"Arguments to pass to the command.\",\n              \"type\": [\n                \"array\",\n                \"null\"\n              ],\n              \"items\": {\n                \"type\": \"string\"\n              }\n            }\n          }\n        }\n      ]\n    },\n    \"FrontendDist\": {\n      \"description\": \"Defines the URL or assets to embed in the application.\",\n      \"anyOf\": [\n        {\n          \"description\": \"An external URL that should be used as the default application URL. No assets are embedded in the app in this case.\",\n          \"type\": \"string\",\n          \"format\": \"uri\"\n        },\n        {\n          \"description\": \"Path to a directory containing the frontend dist assets.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An array of files to embed in the app.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"BeforeDevCommand\": {\n      \"description\": \"Describes the shell command to run before `tauri dev`.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Run the given script with the default options.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Run the given script with custom options.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"script\"\n          ],\n          \"properties\": {\n            \"script\": {\n              \"description\": \"The script to execute.\",\n              \"type\": \"string\"\n            },\n            \"cwd\": {\n              \"description\": \"The current working directory.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            },\n            \"wait\": {\n              \"description\": \"Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`.\",\n              \"default\": false,\n              \"type\": \"boolean\"\n            }\n          }\n        }\n      ]\n    },\n    \"HookCommand\": {\n      \"description\": \"Describes a shell command to be executed when a CLI hook is triggered.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Run the given script with the default options.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Run the given script with custom options.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"script\"\n          ],\n          \"properties\": {\n            \"script\": {\n              \"description\": \"The script to execute.\",\n              \"type\": \"string\"\n            },\n            \"cwd\": {\n              \"description\": \"The current working directory.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"BundleConfig\": {\n      \"description\": \"Configuration for tauri-bundler.\\n\\n See more: <https://v2.tauri.app/reference/config/#bundleconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"active\": {\n          \"description\": \"Whether Tauri should bundle your application or just output the executable.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"targets\": {\n          \"description\": \"The bundle targets, currently supports [\\\"deb\\\", \\\"rpm\\\", \\\"appimage\\\", \\\"nsis\\\", \\\"msi\\\", \\\"app\\\", \\\"dmg\\\"] or \\\"all\\\".\",\n          \"default\": \"all\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleTarget\"\n            }\n          ]\n        },\n        \"createUpdaterArtifacts\": {\n          \"description\": \"Produce updaters and their signatures or not\",\n          \"default\": false,\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Updater\"\n            }\n          ]\n        },\n        \"publisher\": {\n          \"description\": \"The application's publisher. Defaults to the second element in the identifier string.\\n\\n Currently maps to the Manufacturer property of the Windows Installer\\n and the Maintainer field of debian packages if the Cargo.toml does not have the authors field.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"homepage\": {\n          \"description\": \"A url to the home page of your application. If unset, will\\n fallback to `homepage` defined in `Cargo.toml`.\\n\\n Supported bundle targets: `deb`, `rpm`, `nsis` and `msi`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"icon\": {\n          \"description\": \"The app's icons\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"resources\": {\n          \"description\": \"App resources to bundle.\\n Each resource is a path to a file or directory.\\n Glob patterns are supported.\\n\\n ## Examples\\n\\n To include a list of files:\\n\\n ```json\\n {\\n   \\\"bundle\\\": {\\n     \\\"resources\\\": [\\n       \\\"./path/to/some-file.txt\\\",\\n       \\\"/absolute/path/to/textfile.txt\\\",\\n       \\\"../relative/path/to/jsonfile.json\\\",\\n       \\\"some-folder/\\\",\\n       \\\"resources/**/*.md\\\"\\n     ]\\n   }\\n }\\n ```\\n\\n The bundled files will be in `$RESOURCES/` with the original directory structure preserved,\\n for example: `./path/to/some-file.txt` -> `$RESOURCE/path/to/some-file.txt`\\n\\n To fine control where the files will get copied to, use a map instead\\n\\n ```json\\n {\\n   \\\"bundle\\\": {\\n     \\\"resources\\\": {\\n       \\\"/absolute/path/to/textfile.txt\\\": \\\"resources/textfile.txt\\\",\\n       \\\"relative/path/to/jsonfile.json\\\": \\\"resources/jsonfile.json\\\",\\n       \\\"resources/\\\": \\\"\\\",\\n       \\\"docs/**/*md\\\": \\\"website-docs/\\\"\\n     }\\n   }\\n }\\n ```\\n\\n Note that when using glob pattern in this case, the original directory structure is not preserved,\\n everything gets copied to the target directory directly\\n\\n See more: <https://v2.tauri.app/develop/resources/>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleResources\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"copyright\": {\n          \"description\": \"A copyright string associated with your application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"license\": {\n          \"description\": \"The package's license identifier to be included in the appropriate bundles.\\n If not set, defaults to the license from the Cargo.toml file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"licenseFile\": {\n          \"description\": \"The path to the license file to be included in the appropriate bundles.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"category\": {\n          \"description\": \"The application kind.\\n\\n Should be one of the following:\\n Business, DeveloperTool, Education, Entertainment, Finance, Game, ActionGame, AdventureGame, ArcadeGame, BoardGame, CardGame, CasinoGame, DiceGame, EducationalGame, FamilyGame, KidsGame, MusicGame, PuzzleGame, RacingGame, RolePlayingGame, SimulationGame, SportsGame, StrategyGame, TriviaGame, WordGame, GraphicsAndDesign, HealthcareAndFitness, Lifestyle, Medical, Music, News, Photography, Productivity, Reference, SocialNetworking, Sports, Travel, Utility, Video, Weather.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fileAssociations\": {\n          \"description\": \"File types to associate with the application.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/definitions/FileAssociation\"\n          }\n        },\n        \"shortDescription\": {\n          \"description\": \"A short description of your application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"longDescription\": {\n          \"description\": \"A longer, multi-line description of the application.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"useLocalToolsDir\": {\n          \"description\": \"Whether to use the project's `target` directory, for caching build tools (e.g., Wix and NSIS) when building this application. Defaults to `false`.\\n\\n If true, tools will be cached in `target/.tauri/`.\\n If false, tools will be cached in the current user's platform-specific cache directory.\\n\\n An example where it can be appropriate to set this to `true` is when building this application as a Windows System user (e.g., AWS EC2 workloads),\\n because the Window system's app data directory is restricted.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"externalBin\": {\n          \"description\": \"A list of—either absolute or relative—paths to binaries to embed with your application.\\n\\n Note that Tauri will look for system-specific binaries following the pattern \\\"binary-name{-target-triple}{.system-extension}\\\".\\n\\n E.g. for the external binary \\\"my-binary\\\", Tauri looks for:\\n\\n - \\\"my-binary-x86_64-pc-windows-msvc.exe\\\" for Windows\\n - \\\"my-binary-x86_64-apple-darwin\\\" for macOS\\n - \\\"my-binary-x86_64-unknown-linux-gnu\\\" for Linux\\n\\n so don't forget to provide binaries for all targeted platforms.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"windows\": {\n          \"description\": \"Configuration for the Windows bundles.\",\n          \"default\": {\n            \"allowDowngrades\": true,\n            \"certificateThumbprint\": null,\n            \"digestAlgorithm\": null,\n            \"nsis\": null,\n            \"signCommand\": null,\n            \"timestampUrl\": null,\n            \"tsp\": false,\n            \"webviewInstallMode\": {\n              \"silent\": true,\n              \"type\": \"downloadBootstrapper\"\n            },\n            \"wix\": null\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WindowsConfig\"\n            }\n          ]\n        },\n        \"linux\": {\n          \"description\": \"Configuration for the Linux bundles.\",\n          \"default\": {\n            \"appimage\": {\n              \"bundleMediaFramework\": false,\n              \"files\": {}\n            },\n            \"deb\": {\n              \"files\": {}\n            },\n            \"rpm\": {\n              \"epoch\": 0,\n              \"files\": {},\n              \"release\": \"1\"\n            }\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/LinuxConfig\"\n            }\n          ]\n        },\n        \"macOS\": {\n          \"description\": \"Configuration for the macOS bundles.\",\n          \"default\": {\n            \"dmg\": {\n              \"appPosition\": {\n                \"x\": 180,\n                \"y\": 170\n              },\n              \"applicationFolderPosition\": {\n                \"x\": 480,\n                \"y\": 170\n              },\n              \"windowSize\": {\n                \"height\": 400,\n                \"width\": 660\n              }\n            },\n            \"files\": {},\n            \"hardenedRuntime\": true,\n            \"minimumSystemVersion\": \"10.13\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/MacConfig\"\n            }\n          ]\n        },\n        \"iOS\": {\n          \"description\": \"iOS configuration.\",\n          \"default\": {\n            \"minimumSystemVersion\": \"14.0\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/IosConfig\"\n            }\n          ]\n        },\n        \"android\": {\n          \"description\": \"Android configuration.\",\n          \"default\": {\n            \"autoIncrementVersionCode\": false,\n            \"minSdkVersion\": 24\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/AndroidConfig\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"BundleTarget\": {\n      \"description\": \"Targets to bundle. Each value is case insensitive.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Bundle all targets.\",\n          \"const\": \"all\"\n        },\n        {\n          \"description\": \"A list of bundle targets.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/BundleType\"\n          }\n        },\n        {\n          \"description\": \"A single bundle target.\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleType\"\n            }\n          ]\n        }\n      ]\n    },\n    \"BundleType\": {\n      \"description\": \"A bundle referenced by tauri-bundler.\",\n      \"oneOf\": [\n        {\n          \"description\": \"The debian bundle (.deb).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"deb\"\n          ]\n        },\n        {\n          \"description\": \"The RPM bundle (.rpm).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"rpm\"\n          ]\n        },\n        {\n          \"description\": \"The AppImage bundle (.appimage).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"appimage\"\n          ]\n        },\n        {\n          \"description\": \"The Microsoft Installer bundle (.msi).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"msi\"\n          ]\n        },\n        {\n          \"description\": \"The NSIS bundle (.exe).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"nsis\"\n          ]\n        },\n        {\n          \"description\": \"The macOS application bundle (.app).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"app\"\n          ]\n        },\n        {\n          \"description\": \"The Apple Disk Image bundle (.dmg).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"dmg\"\n          ]\n        }\n      ]\n    },\n    \"Updater\": {\n      \"description\": \"Updater type\",\n      \"anyOf\": [\n        {\n          \"description\": \"Generates legacy zipped v1 compatible updaters\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/V1Compatible\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Produce updaters and their signatures or not\",\n          \"type\": \"boolean\"\n        }\n      ]\n    },\n    \"V1Compatible\": {\n      \"description\": \"Generates legacy zipped v1 compatible updaters\",\n      \"oneOf\": [\n        {\n          \"description\": \"Generates legacy zipped v1 compatible updaters\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"v1Compatible\"\n          ]\n        }\n      ]\n    },\n    \"BundleResources\": {\n      \"description\": \"Definition for bundle resources.\\n Can be either a list of paths to include or a map of source to target paths.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A list of paths to include.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A map of source to target paths.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        }\n      ]\n    },\n    \"FileAssociation\": {\n      \"description\": \"File association\",\n      \"type\": \"object\",\n      \"required\": [\n        \"ext\"\n      ],\n      \"properties\": {\n        \"ext\": {\n          \"description\": \"File extensions to associate with this app. e.g. 'png'\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/AssociationExt\"\n          }\n        },\n        \"contentTypes\": {\n          \"description\": \"Declare support to a file with the given content type. Maps to `LSItemContentTypes` on macOS.\\n\\n This allows supporting any file format declared by another application that conforms to this type.\\n Declaration of new types can be done with [`Self::exported_type`] and linking to certain content types are done via [`ExportedFileAssociation::conforms_to`].\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"name\": {\n          \"description\": \"The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"description\": {\n          \"description\": \"The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"role\": {\n          \"description\": \"The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.\",\n          \"default\": \"Editor\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleTypeRole\"\n            }\n          ]\n        },\n        \"mimeType\": {\n          \"description\": \"The mime-type e.g. 'image/png' or 'text/plain'. Linux-only.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"rank\": {\n          \"description\": \"The ranking of this app among apps that declare themselves as editors or viewers of the given file type.  Maps to `LSHandlerRank` on macOS.\",\n          \"default\": \"Default\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/HandlerRank\"\n            }\n          ]\n        },\n        \"exportedType\": {\n          \"description\": \"The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.\\n\\n You should define this if the associated file is a custom file type defined by your application.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/ExportedFileAssociation\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"AssociationExt\": {\n      \"description\": \"An extension for a [`FileAssociation`].\\n\\n A leading `.` is automatically stripped.\",\n      \"type\": \"string\"\n    },\n    \"BundleTypeRole\": {\n      \"description\": \"macOS-only. Corresponds to CFBundleTypeRole\",\n      \"oneOf\": [\n        {\n          \"description\": \"CFBundleTypeRole.Editor. Files can be read and edited.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Editor\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.Viewer. Files can be read.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Viewer\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.Shell\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Shell\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.QLGenerator\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"QLGenerator\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.None\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"None\"\n          ]\n        }\n      ]\n    },\n    \"HandlerRank\": {\n      \"description\": \"Corresponds to LSHandlerRank\",\n      \"oneOf\": [\n        {\n          \"description\": \"LSHandlerRank.Default. This app is an opener of files of this type; this value is also used if no rank is specified.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Default\"\n          ]\n        },\n        {\n          \"description\": \"LSHandlerRank.Owner. This app is the primary creator of files of this type.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Owner\"\n          ]\n        },\n        {\n          \"description\": \"LSHandlerRank.Alternate. This app is a secondary viewer of files of this type.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"Alternate\"\n          ]\n        },\n        {\n          \"description\": \"LSHandlerRank.None. This app is never selected to open files of this type, but it accepts drops of files of this type.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"None\"\n          ]\n        }\n      ]\n    },\n    \"ExportedFileAssociation\": {\n      \"description\": \"The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"identifier\"\n      ],\n      \"properties\": {\n        \"identifier\": {\n          \"description\": \"The unique identifier for the exported type. Maps to `UTTypeIdentifier`.\",\n          \"type\": \"string\"\n        },\n        \"conformsTo\": {\n          \"description\": \"The types that this type conforms to. Maps to `UTTypeConformsTo`.\\n\\n Examples are `public.data`, `public.image`, `public.json` and `public.database`.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WindowsConfig\": {\n      \"description\": \"Windows bundler configuration.\\n\\n See more: <https://v2.tauri.app/reference/config/#windowsconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"digestAlgorithm\": {\n          \"description\": \"Specifies the file digest algorithm to use for creating file signatures.\\n Required for code signing. SHA-256 is recommended.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"certificateThumbprint\": {\n          \"description\": \"Specifies the SHA1 hash of the signing certificate.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"timestampUrl\": {\n          \"description\": \"Server to use during timestamping.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tsp\": {\n          \"description\": \"Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may\\n use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"webviewInstallMode\": {\n          \"description\": \"The installation mode for the Webview2 runtime.\",\n          \"default\": {\n            \"silent\": true,\n            \"type\": \"downloadBootstrapper\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WebviewInstallMode\"\n            }\n          ]\n        },\n        \"allowDowngrades\": {\n          \"description\": \"Validates a second app installation, blocking the user from installing an older version if set to `false`.\\n\\n For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\\n\\n The default value of this flag is `true`.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"wix\": {\n          \"description\": \"Configuration for the MSI generated with WiX.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/WixConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"nsis\": {\n          \"description\": \"Configuration for the installer generated with NSIS.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/NsisConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"signCommand\": {\n          \"description\": \"Specify a custom command to sign the binaries.\\n This command needs to have a `%1` in args which is just a placeholder for the binary path,\\n which we will detect and replace before calling the command.\\n\\n By Default we use `signtool.exe` which can be found only on Windows so\\n if you are on another platform and want to cross-compile and sign you will\\n need to use another tool like `osslsigncode`.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/CustomSignCommandConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WebviewInstallMode\": {\n      \"description\": \"Install modes for the Webview2 runtime.\\n Note that for the updater bundle [`Self::DownloadBootstrapper`] is used.\\n\\n For more information see <https://v2.tauri.app/distribute/windows-installer/#webview2-installation-options>.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Do not install the Webview2 as part of the Windows Installer.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"skip\"\n              ]\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Download the bootstrapper and run it.\\n Requires an internet connection.\\n Results in a smaller installer size, but is not recommended on Windows 7.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"downloadBootstrapper\"\n              ]\n            },\n            \"silent\": {\n              \"description\": \"Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.\",\n              \"default\": true,\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Embed the bootstrapper and run it.\\n Requires an internet connection.\\n Increases the installer size by around 1.8MB, but offers better support on Windows 7.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"embedBootstrapper\"\n              ]\n            },\n            \"silent\": {\n              \"description\": \"Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.\",\n              \"default\": true,\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Embed the offline installer and run it.\\n Does not require an internet connection.\\n Increases the installer size by around 127MB.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"offlineInstaller\"\n              ]\n            },\n            \"silent\": {\n              \"description\": \"Instructs the installer to run the installer in silent mode. Defaults to `true`.\",\n              \"default\": true,\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Embed a fixed webview2 version and use it at runtime.\\n Increases the installer size by around 180MB.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"path\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"fixedRuntime\"\n              ]\n            },\n            \"path\": {\n              \"description\": \"The path to the fixed runtime to use.\\n\\n The fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section).\\n The `.cab` file must be extracted to a folder and this folder path must be defined on this field.\",\n              \"type\": \"string\"\n            }\n          },\n          \"additionalProperties\": false\n        }\n      ]\n    },\n    \"WixConfig\": {\n      \"description\": \"Configuration for the MSI bundle using WiX.\\n\\n See more: <https://v2.tauri.app/reference/config/#wixconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"version\": {\n          \"description\": \"MSI installer version in the format `major.minor.patch.build` (build is optional).\\n\\n Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.\\n\\n The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\\n The third and fourth fields have a maximum value of 65,535.\\n\\n See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"upgradeCode\": {\n          \"description\": \"A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,\\n otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\\n\\n By default, tauri generates this code by generating a Uuid v5 using the string `<productName>.exe.app.x64` in the DNS namespace.\\n You can use Tauri's CLI to generate and print this code for you, run `tauri inspect wix-upgrade-code`.\\n\\n It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code\\n whenever you want to change your product name.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"format\": \"uuid\"\n        },\n        \"language\": {\n          \"description\": \"The installer languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.\",\n          \"default\": \"en-US\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/WixLanguage\"\n            }\n          ]\n        },\n        \"template\": {\n          \"description\": \"A custom .wxs template to use.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fragmentPaths\": {\n          \"description\": \"A list of paths to .wxs files with WiX fragments to use.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"componentGroupRefs\": {\n          \"description\": \"The ComponentGroup element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"componentRefs\": {\n          \"description\": \"The Component element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"featureGroupRefs\": {\n          \"description\": \"The FeatureGroup element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"featureRefs\": {\n          \"description\": \"The Feature element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"mergeRefs\": {\n          \"description\": \"The Merge element ids you want to reference from the fragments.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"enableElevatedUpdateTask\": {\n          \"description\": \"Create an elevated update task within Windows Task Scheduler.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"bannerPath\": {\n          \"description\": \"Path to a bitmap file to use as the installation user interface banner.\\n This bitmap will appear at the top of all but the first page of the installer.\\n\\n The required dimensions are 493px × 58px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dialogImagePath\": {\n          \"description\": \"Path to a bitmap file to use on the installation user interface dialogs.\\n It is used on the welcome and completion dialogs.\\n\\n The required dimensions are 493px × 312px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fipsCompliant\": {\n          \"description\": \"Enables FIPS compliant algorithms.\\n Can also be enabled via the `TAURI_BUNDLER_WIX_FIPS_COMPLIANT` env var.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WixLanguage\": {\n      \"description\": \"The languages to build using WiX.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A single language to build, without configuration.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"A list of languages to build, without configuration.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A map of languages and its configuration.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/WixLanguageConfig\"\n          }\n        }\n      ]\n    },\n    \"WixLanguageConfig\": {\n      \"description\": \"Configuration for a target language for the WiX build.\\n\\n See more: <https://v2.tauri.app/reference/config/#wixlanguageconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"localePath\": {\n          \"description\": \"The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NsisConfig\": {\n      \"description\": \"Configuration for the Installer bundle using NSIS.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"template\": {\n          \"description\": \"A custom .nsi template to use.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"headerImage\": {\n          \"description\": \"The path to a bitmap file to display on the header of installers pages.\\n\\n The recommended dimensions are 150px x 57px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"sidebarImage\": {\n          \"description\": \"The path to a bitmap file for the Welcome page and the Finish page.\\n\\n The recommended dimensions are 164px x 314px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installerIcon\": {\n          \"description\": \"The path to an icon file used as the installer icon.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installMode\": {\n          \"description\": \"Whether the installation will be for all users or just the current user.\",\n          \"default\": \"currentUser\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/NSISInstallerMode\"\n            }\n          ]\n        },\n        \"languages\": {\n          \"description\": \"A list of installer languages.\\n By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.\\n To allow the user to select the language, set `display_language_selector` to `true`.\\n\\n See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"customLanguageFiles\": {\n          \"description\": \"A key-value pair where the key is the language and the\\n value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.\\n\\n See <https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-bundler/src/bundle/windows/nsis/languages/English.nsh> for an example `.nsh` file.\\n\\n **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array,\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"displayLanguageSelector\": {\n          \"description\": \"Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.\\n By default the OS language is selected, with a fallback to the first language in the `languages` array.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"compression\": {\n          \"description\": \"Set the compression algorithm used to compress files in the installer.\\n\\n See <https://nsis.sourceforge.io/Reference/SetCompressor>\",\n          \"default\": \"lzma\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/NsisCompression\"\n            }\n          ]\n        },\n        \"startMenuFolder\": {\n          \"description\": \"Set the folder name for the start menu shortcut.\\n\\n Use this option if you have multiple apps and wish to group their shortcuts under one folder\\n or if you generally prefer to set your shortcut inside a folder.\\n\\n Examples:\\n - `AwesomePublisher`, shortcut will be placed in `%AppData%\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\AwesomePublisher\\\\<your-app>.lnk`\\n - If unset, shortcut will be placed in `%AppData%\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\<your-app>.lnk`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installerHooks\": {\n          \"description\": \"A path to a `.nsh` file that contains special NSIS macros to be hooked into the\\n main installer.nsi script.\\n\\n Supported hooks are:\\n\\n - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\\n - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\\n - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\\n - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\\n\\n ### Example\\n\\n ```nsh\\n !macro NSIS_HOOK_PREINSTALL\\n   MessageBox MB_OK \\\"PreInstall\\\"\\n !macroend\\n\\n !macro NSIS_HOOK_POSTINSTALL\\n   MessageBox MB_OK \\\"PostInstall\\\"\\n !macroend\\n\\n !macro NSIS_HOOK_PREUNINSTALL\\n   MessageBox MB_OK \\\"PreUnInstall\\\"\\n !macroend\\n\\n !macro NSIS_HOOK_POSTUNINSTALL\\n   MessageBox MB_OK \\\"PostUninstall\\\"\\n !macroend\\n ```\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimumWebview2Version\": {\n          \"description\": \"Try to ensure that the WebView2 version is equal to or newer than this version,\\n if the user's WebView2 is older than this version,\\n the installer will try to trigger a WebView2 update.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NSISInstallerMode\": {\n      \"description\": \"Install Modes for the NSIS installer.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Default mode for the installer.\\n\\n Install the app by default in a directory that doesn't require Administrator access.\\n\\n Installer metadata will be saved under the `HKCU` registry path.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"currentUser\"\n          ]\n        },\n        {\n          \"description\": \"Install the app by default in the `Program Files` folder directory requires Administrator\\n access for the installation.\\n\\n Installer metadata will be saved under the `HKLM` registry path.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"perMachine\"\n          ]\n        },\n        {\n          \"description\": \"Combines both modes and allows the user to choose at install time\\n whether to install for the current user or per machine. Note that this mode\\n will require Administrator access even if the user wants to install it for the current user only.\\n\\n Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"both\"\n          ]\n        }\n      ]\n    },\n    \"NsisCompression\": {\n      \"description\": \"Compression algorithms used in the NSIS installer.\\n\\n See <https://nsis.sourceforge.io/Reference/SetCompressor>\",\n      \"oneOf\": [\n        {\n          \"description\": \"ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"zlib\"\n          ]\n        },\n        {\n          \"description\": \"BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"bzip2\"\n          ]\n        },\n        {\n          \"description\": \"LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"lzma\"\n          ]\n        },\n        {\n          \"description\": \"Disable compression\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"none\"\n          ]\n        }\n      ]\n    },\n    \"CustomSignCommandConfig\": {\n      \"description\": \"Custom Signing Command configuration.\",\n      \"anyOf\": [\n        {\n          \"description\": \"A string notation of the script to execute.\\n\\n \\\"%1\\\" will be replaced with the path to the binary to be signed.\\n\\n This is a simpler notation for the command.\\n Tauri will split the string with `' '` and use the first element as the command name and the rest as arguments.\\n\\n If you need to use whitespace in the command or arguments, use the object notation [`Self::CommandWithOptions`].\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An object notation of the command.\\n\\n This is more complex notation for the command but\\n this allows you to use whitespace in the command and arguments.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"args\",\n            \"cmd\"\n          ],\n          \"properties\": {\n            \"cmd\": {\n              \"description\": \"The command to run to sign the binary.\",\n              \"type\": \"string\"\n            },\n            \"args\": {\n              \"description\": \"The arguments to pass to the command.\\n\\n \\\"%1\\\" will be replaced with the path to the binary to be signed.\",\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            }\n          },\n          \"additionalProperties\": false\n        }\n      ]\n    },\n    \"LinuxConfig\": {\n      \"description\": \"Configuration for Linux bundles.\\n\\n See more: <https://v2.tauri.app/reference/config/#linuxconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"appimage\": {\n          \"description\": \"Configuration for the AppImage bundle.\",\n          \"default\": {\n            \"bundleMediaFramework\": false,\n            \"files\": {}\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/AppImageConfig\"\n            }\n          ]\n        },\n        \"deb\": {\n          \"description\": \"Configuration for the Debian bundle.\",\n          \"default\": {\n            \"files\": {}\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/DebConfig\"\n            }\n          ]\n        },\n        \"rpm\": {\n          \"description\": \"Configuration for the RPM bundle.\",\n          \"default\": {\n            \"epoch\": 0,\n            \"files\": {},\n            \"release\": \"1\"\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/RpmConfig\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"AppImageConfig\": {\n      \"description\": \"Configuration for AppImage bundles.\\n\\n See more: <https://v2.tauri.app/reference/config/#appimageconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bundleMediaFramework\": {\n          \"description\": \"Include additional gstreamer dependencies needed for audio and video playback.\\n This increases the bundle size by ~15-35MB depending on your build system.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"files\": {\n          \"description\": \"The files to include in the Appimage Binary.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"DebConfig\": {\n      \"description\": \"Configuration for Debian (.deb) bundles.\\n\\n See more: <https://v2.tauri.app/reference/config/#debconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"depends\": {\n          \"description\": \"The list of deb dependencies your application relies on.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"recommends\": {\n          \"description\": \"The list of deb dependencies your application recommends.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"provides\": {\n          \"description\": \"The list of dependencies the package provides.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"conflicts\": {\n          \"description\": \"The list of package conflicts.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"replaces\": {\n          \"description\": \"The list of package replaces.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"files\": {\n          \"description\": \"The files to include on the package.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"section\": {\n          \"description\": \"Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"priority\": {\n          \"description\": \"Change the priority of the Debian Package. By default, it is set to `optional`.\\n Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"changelog\": {\n          \"description\": \"Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\\n <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"desktopTemplate\": {\n          \"description\": \"Path to a custom desktop file Handlebars template.\\n\\n Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preInstallScript\": {\n          \"description\": \"Path to script that will be executed before the package is unpacked. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postInstallScript\": {\n          \"description\": \"Path to script that will be executed after the package is unpacked. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preRemoveScript\": {\n          \"description\": \"Path to script that will be executed before the package is removed. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postRemoveScript\": {\n          \"description\": \"Path to script that will be executed after the package is removed. See\\n <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"RpmConfig\": {\n      \"description\": \"Configuration for RPM bundles.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"depends\": {\n          \"description\": \"The list of RPM dependencies your application relies on.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"recommends\": {\n          \"description\": \"The list of RPM dependencies your application recommends.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"provides\": {\n          \"description\": \"The list of RPM dependencies your application provides.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"conflicts\": {\n          \"description\": \"The list of RPM dependencies your application conflicts with. They must not be present\\n in order for the package to be installed.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"obsoletes\": {\n          \"description\": \"The list of RPM dependencies your application supersedes - if this package is installed,\\n packages listed as \\\"obsoletes\\\" will be automatically removed (if they are present).\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"release\": {\n          \"description\": \"The RPM release tag.\",\n          \"default\": \"1\",\n          \"type\": \"string\"\n        },\n        \"epoch\": {\n          \"description\": \"The RPM epoch.\",\n          \"default\": 0,\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"files\": {\n          \"description\": \"The files to include on the package.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"desktopTemplate\": {\n          \"description\": \"Path to a custom desktop file Handlebars template.\\n\\n Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preInstallScript\": {\n          \"description\": \"Path to script that will be executed before the package is unpacked. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postInstallScript\": {\n          \"description\": \"Path to script that will be executed after the package is unpacked. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preRemoveScript\": {\n          \"description\": \"Path to script that will be executed before the package is removed. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"postRemoveScript\": {\n          \"description\": \"Path to script that will be executed after the package is removed. See\\n <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"compression\": {\n          \"description\": \"Compression algorithm and level. Defaults to `Gzip` with level 6.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/RpmCompression\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"RpmCompression\": {\n      \"description\": \"Compression algorithms used when bundling RPM packages.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Gzip compression\",\n          \"type\": \"object\",\n          \"required\": [\n            \"level\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"gzip\"\n              ]\n            },\n            \"level\": {\n              \"description\": \"Gzip compression level\",\n              \"type\": \"integer\",\n              \"format\": \"uint32\",\n              \"minimum\": 0.0\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Zstd compression\",\n          \"type\": \"object\",\n          \"required\": [\n            \"level\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"zstd\"\n              ]\n            },\n            \"level\": {\n              \"description\": \"Zstd compression level\",\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Xz compression\",\n          \"type\": \"object\",\n          \"required\": [\n            \"level\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"xz\"\n              ]\n            },\n            \"level\": {\n              \"description\": \"Xz compression level\",\n              \"type\": \"integer\",\n              \"format\": \"uint32\",\n              \"minimum\": 0.0\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Bzip2 compression\",\n          \"type\": \"object\",\n          \"required\": [\n            \"level\",\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"bzip2\"\n              ]\n            },\n            \"level\": {\n              \"description\": \"Bzip2 compression level\",\n              \"type\": \"integer\",\n              \"format\": \"uint32\",\n              \"minimum\": 0.0\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Disable compression\",\n          \"type\": \"object\",\n          \"required\": [\n            \"type\"\n          ],\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"enum\": [\n                \"none\"\n              ]\n            }\n          },\n          \"additionalProperties\": false\n        }\n      ]\n    },\n    \"MacConfig\": {\n      \"description\": \"Configuration for the macOS bundles.\\n\\n See more: <https://v2.tauri.app/reference/config/#macconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"frameworks\": {\n          \"description\": \"A list of strings indicating any macOS X frameworks that need to be bundled with the application.\\n\\n If a name is used, \\\".framework\\\" must be omitted and it will look for standard install locations. You may also use a path to a specific framework.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"files\": {\n          \"description\": \"The files to include in the application relative to the Contents directory.\",\n          \"default\": {},\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"bundleVersion\": {\n          \"description\": \"The version of the build that identifies an iteration of the bundle.\\n\\n Translates to the bundle's CFBundleVersion property.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"bundleName\": {\n          \"description\": \"The name of the builder that built the bundle.\\n\\n Translates to the bundle's CFBundleName property.\\n\\n If not set, defaults to the package's product name.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimumSystemVersion\": {\n          \"description\": \"A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\\n\\n Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`\\n and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\\n\\n Ignored in `tauri dev`.\\n\\n An empty string is considered an invalid value so the default value is used.\",\n          \"default\": \"10.13\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"exceptionDomain\": {\n          \"description\": \"Allows your application to communicate with the outside world.\\n It should be a lowercase, without port and protocol domain name.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"signingIdentity\": {\n          \"description\": \"Identity to use for code signing.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"hardenedRuntime\": {\n          \"description\": \"Whether the codesign should enable [hardened runtime](https://developer.apple.com/documentation/security/hardened_runtime) (for executables) or not.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"providerShortName\": {\n          \"description\": \"Provider short name for notarization.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"entitlements\": {\n          \"description\": \"Path to the entitlements file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"infoPlist\": {\n          \"description\": \"Path to a Info.plist file to merge with the default Info.plist.\\n\\n Note that Tauri also looks for a `Info.plist` file in the same directory as the Tauri configuration file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dmg\": {\n          \"description\": \"DMG-specific settings.\",\n          \"default\": {\n            \"appPosition\": {\n              \"x\": 180,\n              \"y\": 170\n            },\n            \"applicationFolderPosition\": {\n              \"x\": 480,\n              \"y\": 170\n            },\n            \"windowSize\": {\n              \"height\": 400,\n              \"width\": 660\n            }\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/DmgConfig\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"DmgConfig\": {\n      \"description\": \"Configuration for Apple Disk Image (.dmg) bundles.\\n\\n See more: <https://v2.tauri.app/reference/config/#dmgconfig>\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"background\": {\n          \"description\": \"Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"windowPosition\": {\n          \"description\": \"Position of volume window on screen.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"windowSize\": {\n          \"description\": \"Size of volume window.\",\n          \"default\": {\n            \"height\": 400,\n            \"width\": 660\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Size\"\n            }\n          ]\n        },\n        \"appPosition\": {\n          \"description\": \"Position of app file on window.\",\n          \"default\": {\n            \"x\": 180,\n            \"y\": 170\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            }\n          ]\n        },\n        \"applicationFolderPosition\": {\n          \"description\": \"Position of application folder on window.\",\n          \"default\": {\n            \"x\": 480,\n            \"y\": 170\n          },\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Position\": {\n      \"description\": \"Position coordinates struct.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"x\",\n        \"y\"\n      ],\n      \"properties\": {\n        \"x\": {\n          \"description\": \"X coordinate.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"y\": {\n          \"description\": \"Y coordinate.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Size\": {\n      \"description\": \"Size of the window.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"height\",\n        \"width\"\n      ],\n      \"properties\": {\n        \"width\": {\n          \"description\": \"Width of the window.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"height\": {\n          \"description\": \"Height of the window.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"IosConfig\": {\n      \"description\": \"General configuration for the iOS target.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"template\": {\n          \"description\": \"A custom [XcodeGen] project.yml template to use.\\n\\n [XcodeGen]: <https://github.com/yonaskolb/XcodeGen>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"frameworks\": {\n          \"description\": \"A list of strings indicating any iOS frameworks that need to be bundled with the application.\\n\\n Note that you need to recreate the iOS project for the changes to be applied.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"developmentTeam\": {\n          \"description\": \"The development team. This value is required for iOS development because code signing is enforced.\\n The `APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"bundleVersion\": {\n          \"description\": \"The version of the build that identifies an iteration of the bundle.\\n\\n Translates to the bundle's CFBundleVersion property.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"minimumSystemVersion\": {\n          \"description\": \"A version string indicating the minimum iOS version that the bundled application supports. Defaults to `13.0`.\\n\\n Maps to the IPHONEOS_DEPLOYMENT_TARGET value.\",\n          \"default\": \"14.0\",\n          \"type\": \"string\"\n        },\n        \"infoPlist\": {\n          \"description\": \"Path to a Info.plist file to merge with the default Info.plist.\\n\\n Note that Tauri also looks for a `Info.plist` and `Info.ios.plist` file in the same directory as the Tauri configuration file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"AndroidConfig\": {\n      \"description\": \"General configuration for the Android target.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"minSdkVersion\": {\n          \"description\": \"The minimum API level required for the application to run.\\n The Android system will prevent the user from installing the application if the system's API level is lower than the value specified.\",\n          \"default\": 24,\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"versionCode\": {\n          \"description\": \"The version code of the application.\\n It is limited to 2,100,000,000 as per Google Play Store requirements.\\n\\n By default we use your configured version and perform the following math:\\n versionCode = version.major * 1000000 + version.minor * 1000 + version.patch\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint32\",\n          \"maximum\": 2100000000.0,\n          \"minimum\": 1.0\n        },\n        \"autoIncrementVersionCode\": {\n          \"description\": \"Whether to automatically increment the `versionCode` on each build.\\n\\n - If `true`, the generator will try to read the last `versionCode` from\\n   `tauri.properties` and increment it by 1 for every build.\\n - If `false` or not set, it falls back to `version_code` or semver-derived logic.\\n\\n Note that to use this feature, you should remove `/tauri.properties` from `src-tauri/gen/android/app/.gitignore` so the current versionCode is committed to the repository.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"PluginConfig\": {\n      \"description\": \"The plugin configs holds a HashMap mapping a plugin name to its configuration object.\\n\\n See more: <https://v2.tauri.app/reference/config/#pluginconfig>\",\n      \"type\": \"object\",\n      \"additionalProperties\": true\n    }\n  }\n}"
  },
  {
    "path": "crates/tauri-schema-generator/schemas/permission.schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"title\": \"Permission\",\n  \"description\": \"Descriptions of explicit privileges of commands.\\n\\n It can enable commands to be accessible in the frontend of the application.\\n\\n If the scope is defined it can be used to fine grain control the access of individual or multiple commands.\",\n  \"type\": \"object\",\n  \"required\": [\n    \"identifier\"\n  ],\n  \"properties\": {\n    \"version\": {\n      \"description\": \"The version of the permission.\",\n      \"type\": [\n        \"integer\",\n        \"null\"\n      ],\n      \"format\": \"uint64\",\n      \"minimum\": 1.0\n    },\n    \"identifier\": {\n      \"description\": \"A unique identifier for the permission.\",\n      \"type\": \"string\"\n    },\n    \"description\": {\n      \"description\": \"Human-readable description of what the permission does.\\n Tauri internal convention is to use `<h4>` headings in markdown content\\n for Tauri documentation generation purposes.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"commands\": {\n      \"description\": \"Allowed or denied commands when using this permission.\",\n      \"default\": {\n        \"allow\": [],\n        \"deny\": []\n      },\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/Commands\"\n        }\n      ]\n    },\n    \"scope\": {\n      \"description\": \"Allowed or denied scoped when using this permission.\",\n      \"allOf\": [\n        {\n          \"$ref\": \"#/definitions/Scopes\"\n        }\n      ]\n    },\n    \"platforms\": {\n      \"description\": \"Target platforms this permission applies. By default all platforms are affected by this permission.\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/definitions/Target\"\n      }\n    }\n  },\n  \"definitions\": {\n    \"Commands\": {\n      \"description\": \"Allowed and denied commands inside a permission.\\n\\n If two commands clash inside of `allow` and `deny`, it should be denied by default.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"allow\": {\n          \"description\": \"Allowed command.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"deny\": {\n          \"description\": \"Denied command, which takes priority.\",\n          \"default\": [],\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"Scopes\": {\n      \"description\": \"An argument for fine grained behavior control of Tauri commands.\\n\\n It can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command.\\n The configured scope is passed to the command and will be enforced by the command implementation.\\n\\n ## Example\\n\\n ```json\\n {\\n   \\\"allow\\\": [{ \\\"path\\\": \\\"$HOME/**\\\" }],\\n   \\\"deny\\\": [{ \\\"path\\\": \\\"$HOME/secret.txt\\\" }]\\n }\\n ```\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"allow\": {\n          \"description\": \"Data that defines what is allowed by the scope.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        },\n        \"deny\": {\n          \"description\": \"Data that defines what is denied by the scope. This should be prioritized by validation logic.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        }\n      }\n    },\n    \"Value\": {\n      \"description\": \"All supported ACL values.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents a null JSON value.\",\n          \"type\": \"null\"\n        },\n        {\n          \"description\": \"Represents a [`bool`].\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Represents a valid ACL [`Number`].\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Number\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Represents a [`String`].\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Represents a list of other [`Value`]s.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        },\n        {\n          \"description\": \"Represents a map of [`String`] keys to [`Value`]s.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        }\n      ]\n    },\n    \"Number\": {\n      \"description\": \"A valid ACL number.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents an [`i64`].\",\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        {\n          \"description\": \"Represents a [`f64`].\",\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      ]\n    },\n    \"Target\": {\n      \"description\": \"Platform target.\",\n      \"oneOf\": [\n        {\n          \"description\": \"MacOS.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"macOS\"\n          ]\n        },\n        {\n          \"description\": \"Windows.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"windows\"\n          ]\n        },\n        {\n          \"description\": \"Linux.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"linux\"\n          ]\n        },\n        {\n          \"description\": \"Android.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"android\"\n          ]\n        },\n        {\n          \"description\": \"iOS.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"iOS\"\n          ]\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "crates/tauri-schema-generator/schemas/scope.schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"title\": \"Scopes\",\n  \"description\": \"An argument for fine grained behavior control of Tauri commands.\\n\\n It can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command.\\n The configured scope is passed to the command and will be enforced by the command implementation.\\n\\n ## Example\\n\\n ```json\\n {\\n   \\\"allow\\\": [{ \\\"path\\\": \\\"$HOME/**\\\" }],\\n   \\\"deny\\\": [{ \\\"path\\\": \\\"$HOME/secret.txt\\\" }]\\n }\\n ```\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"allow\": {\n      \"description\": \"Data that defines what is allowed by the scope.\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/definitions/Value\"\n      }\n    },\n    \"deny\": {\n      \"description\": \"Data that defines what is denied by the scope. This should be prioritized by validation logic.\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/definitions/Value\"\n      }\n    }\n  },\n  \"definitions\": {\n    \"Value\": {\n      \"description\": \"All supported ACL values.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents a null JSON value.\",\n          \"type\": \"null\"\n        },\n        {\n          \"description\": \"Represents a [`bool`].\",\n          \"type\": \"boolean\"\n        },\n        {\n          \"description\": \"Represents a valid ACL [`Number`].\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/Number\"\n            }\n          ]\n        },\n        {\n          \"description\": \"Represents a [`String`].\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Represents a list of other [`Value`]s.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        },\n        {\n          \"description\": \"Represents a map of [`String`] keys to [`Value`]s.\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/Value\"\n          }\n        }\n      ]\n    },\n    \"Number\": {\n      \"description\": \"A valid ACL number.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Represents an [`i64`].\",\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        {\n          \"description\": \"Represents a [`f64`].\",\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "crates/tauri-schema-generator/src/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Hosts the schema for the Tauri configuration file.\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n\nfn main() {}\n"
  },
  {
    "path": "crates/tauri-schema-worker/.gitignore",
    "content": "/target\n/node_modules\n/.wrangler\n/build\n"
  },
  {
    "path": "crates/tauri-schema-worker/Cargo.toml",
    "content": "[package]\nname = \"tauri-schema-worker\"\nversion = \"0.0.0\"\nedition = \"2021\"\npublish = false\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nworker = { version = \"0.7\", features = ['http', 'axum'] }\nworker-macros = { version = \"0.7\", features = ['http'] }\nconsole_error_panic_hook = { version = \"0.1\" }\naxum = { version = \"0.8\", default-features = false }\ntower-service = \"0.3\"\nsemver = { version = \"1\", features = [\"serde\"] }\nserde = { version = \"1\", features = [\"derive\"] }\nanyhow = \"1\"\n\n[lints.rust]\nunexpected_cfgs = { level = \"warn\", check-cfg = [\n  'cfg(wasm_bindgen_unstable_test_coverage)',\n] }\n\n# https://github.com/rustwasm/wasm-pack/issues/1501\n[package.metadata.wasm-pack.profile.release]\nwasm-opt = [\"--enable-bulk-memory\", \"--enable-nontrapping-float-to-int\"]\n"
  },
  {
    "path": "crates/tauri-schema-worker/README.md",
    "content": "# schema.tauri.app worker\n\nSource code for `https://schema.tauri.app` cloudflare worker.\n"
  },
  {
    "path": "crates/tauri-schema-worker/package.json",
    "content": "{\n  \"name\": \"tauri-schema-worker\",\n  \"version\": \"0.0.0\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"private\": \"true\",\n  \"scripts\": {\n    \"deploy\": \"wrangler deploy\",\n    \"dev\": \"wrangler dev\"\n  },\n  \"devDependencies\": {\n    \"wrangler\": \"^4.75.0\"\n  }\n}\n"
  },
  {
    "path": "crates/tauri-schema-worker/src/config.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse anyhow::Context;\nuse axum::{\n  extract::Path,\n  http::{header, StatusCode},\n  response::Result,\n  routing::get,\n  Router,\n};\nuse semver::{Version, VersionReq};\nuse serde::Deserialize;\nuse worker::*;\n\n#[derive(Deserialize)]\npub struct CrateReleases {\n  pub versions: Vec<CrateRelease>,\n}\n\n#[derive(Debug, Deserialize)]\npub struct CrateRelease {\n  #[serde(alias = \"num\")]\n  pub version: Version,\n  pub yanked: Option<bool>,\n}\n\n#[derive(Deserialize)]\npub struct CrateMetadataFull {\n  #[serde(rename = \"crate\")]\n  pub crate_: CrateMetadata,\n}\n\n#[derive(Deserialize)]\npub struct CrateMetadata {\n  pub max_stable_version: Version,\n}\n\nconst USERAGENT: &str = \"tauri-schema-worker (contact@tauri.app)\";\n\npub fn router() -> Router {\n  Router::new()\n    .route(\"/config\", get(stable_schema))\n    .route(\"/config/latest\", get(stable_schema))\n    .route(\"/config/stable\", get(stable_schema))\n    .route(\"/config/next\", get(next_schema)) // pre-releases versions, (rc, alpha and beta)\n    .route(\"/config/{version}\", get(schema_for_version))\n}\n\nasync fn schema_for_version(Path(version): Path<String>) -> Result<String> {\n  try_schema_for_version(version)\n    .await\n    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))\n    .map_err(Into::into)\n}\n\nasync fn stable_schema() -> Result<String> {\n  try_stable_schema()\n    .await\n    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))\n    .map_err(Into::into)\n}\n\nasync fn next_schema() -> Result<String> {\n  try_next_schema()\n    .await\n    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))\n    .map_err(Into::into)\n}\n\n#[worker::send]\nasync fn try_schema_for_version(version: String) -> anyhow::Result<String> {\n  let version = version.parse::<VersionReq>()?;\n\n  let releases = crate_releases(\"tauri\").await?;\n\n  if releases.is_empty() {\n    return try_stable_schema().await;\n  }\n\n  let Some(version) = releases.into_iter().find(|r| version.matches(&r.version)) else {\n    return try_stable_schema().await;\n  };\n\n  schema_file_for_version(version.version).await\n}\n\n#[worker::send]\nasync fn try_stable_schema() -> anyhow::Result<String> {\n  let max = stable_version(\"tauri\").await?;\n  schema_file_for_version(max).await\n}\n\n#[worker::send]\nasync fn try_next_schema() -> anyhow::Result<String> {\n  let releases = crate_releases(\"tauri\").await?;\n  let version = releases\n    .into_iter()\n    .filter(|r| !r.version.pre.is_empty())\n    .map(|r| r.version)\n    .max()\n    .context(\"Couldn't find latest pre-release\")?;\n  schema_file_for_version(version).await\n}\n\nasync fn schema_file_for_version(version: Version) -> anyhow::Result<String> {\n  let cache = Cache::open(\"schema\".to_string()).await;\n  let cache_key = format!(\"https://schema.tauri.app/config/{version}\");\n  if let Some(mut cached) = cache.get(cache_key.clone(), true).await? {\n    console_log!(\"Serving schema for {version} from cache\");\n    return cached.text().await.map_err(Into::into);\n  }\n\n  console_log!(\"Fetching schema for {version} from remote\");\n\n  let path = if version.major >= 2 {\n    \"crates/tauri-schema-generator/schemas/config.schema.json\"\n  } else {\n    \"core/tauri-config-schema/schema.json\"\n  };\n  let url = format!(\"https://raw.githubusercontent.com/tauri-apps/tauri/tauri-v{version}/{path}\");\n  let mut res = Fetch::Request(fetch_req(&url)?).send().await?;\n\n  cache.put(cache_key, res.cloned()?).await?;\n\n  res.text().await.map_err(Into::into)\n}\n\nasync fn crate_releases(crate_: &str) -> anyhow::Result<Vec<CrateRelease>> {\n  let url = format!(\"https://crates.io/api/v1/crates/{crate_}/versions\");\n  let mut res = Fetch::Request(fetch_req(&url)?).send().await?;\n\n  let versions: CrateReleases = res.json().await?;\n  let versions = versions.versions;\n\n  let flt = |r: &CrateRelease| r.yanked == Some(false);\n  Ok(versions.into_iter().filter(flt).collect())\n}\n\nasync fn stable_version(crate_: &str) -> anyhow::Result<Version> {\n  let url = format!(\"https://crates.io/api/v1/crates/{crate_}\");\n  let mut res = Fetch::Request(fetch_req(&url)?).send().await?;\n  let metadata: CrateMetadataFull = res.json().await?;\n  Ok(metadata.crate_.max_stable_version)\n}\n\nfn fetch_req(url: &str) -> anyhow::Result<worker::Request> {\n  let headers = Headers::new();\n  headers.append(header::USER_AGENT.as_str(), USERAGENT)?;\n\n  worker::Request::new_with_init(\n    url,\n    &RequestInit {\n      method: Method::Get,\n      headers,\n      cf: CfProperties {\n        cache_ttl: Some(86400),\n        cache_everything: Some(true),\n        cache_ttl_by_status: Some(\n          [\n            (\"200-299\".to_string(), 86400),\n            (\"404\".to_string(), 1),\n            (\"500-599\".to_string(), 0),\n          ]\n          .into(),\n        ),\n        ..Default::default()\n      },\n      ..Default::default()\n    },\n  )\n  .map_err(Into::into)\n}\n"
  },
  {
    "path": "crates/tauri-schema-worker/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse axum::{routing::get, Router};\nuse tower_service::Service;\nuse worker::*;\n\nmod config;\n\n#[worker::event(fetch)]\nasync fn main(\n  req: HttpRequest,\n  _env: Env,\n  _ctx: Context,\n) -> worker::Result<axum::http::Response<axum::body::Body>> {\n  console_error_panic_hook::set_once();\n  Ok(router().call(req).await?)\n}\n\nfn router() -> Router {\n  Router::new().route(\"/\", get(root)).merge(config::router())\n}\n\nasync fn root() -> &'static str {\n  \"tauri schema worker\"\n}\n"
  },
  {
    "path": "crates/tauri-schema-worker/wrangler.toml",
    "content": "# Copyright 2019-2022 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nname = \"tauri-schema\"\nmain = \"build/worker/shim.mjs\"\ncompatibility_date = \"2023-08-23\"\nsend_metrics = false\n\n# The minor version of worker-build must match worker/worker-macros in Cargo.toml!\n[build]\ncommand = \"cargo install -q worker-build@^0.7 && worker-build --release\"\n\n[observability]\nenabled = true\nhead_sampling_rate = 1\n"
  },
  {
    "path": "crates/tauri-utils/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.8.3]\n\n### Bug Fixes\n\n- [`7b16dafb1`](https://www.github.com/tauri-apps/tauri/commit/7b16dafb1dc417536ebb62df8eb66154dd97109d) ([#14986](https://www.github.com/tauri-apps/tauri/pull/14986) by [@montyc1999](https://www.github.com/tauri-apps/tauri/../../montyc1999)) Sort csp/plugin/header configs when generating HashMap constructors so that `generate_context!` is deterministic.\n\n  See: https://github.com/tauri-apps/tauri/issues/14978 for more information\n- [`c3cbff3f7`](https://www.github.com/tauri-apps/tauri/commit/c3cbff3f7430161715f80f82128b345a6f7140c9) ([#14662](https://www.github.com/tauri-apps/tauri/pull/14662) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix resource path handles `./` path differently (e.g. `./some-folder` should be the same as `some-folder`)\n\n## \\[2.8.2]\n\n### Enhancements\n\n- [`2d28e3143`](https://www.github.com/tauri-apps/tauri/commit/2d28e3143ee3d97d7570ea03877aa00a0d6e47d0) ([#14632](https://www.github.com/tauri-apps/tauri/pull/14632) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Small code refactors for improved code readability. No user facing changes.\n\n## \\[2.8.1]\n\n### Bug Fixes\n\n- [`1573c7240`](https://www.github.com/tauri-apps/tauri/commit/1573c72402352949d1fd3ca5c6fdbee46fe69fbb) ([#14561](https://www.github.com/tauri-apps/tauri/pull/14561) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused schema files to have `\\r` characters on Windows.\n\n## \\[2.8.0]\n\n### New Features\n\n- [`f5851ee00`](https://www.github.com/tauri-apps/tauri/commit/f5851ee00d6d1f4d560a220ca5a728fedd525092) ([#14089](https://www.github.com/tauri-apps/tauri/pull/14089)) Adds the `scrollBarStyle` option to the window configuration.\n- [`3b4fac201`](https://www.github.com/tauri-apps/tauri/commit/3b4fac2017832d426dd07c5e24e26684eda57f7b) ([#14194](https://www.github.com/tauri-apps/tauri/pull/14194)) Add `tauri.conf.json > bundle > android > autoIncrementVersionCode` config option to automatically increment the Android version code.\n- [`3d6868d09`](https://www.github.com/tauri-apps/tauri/commit/3d6868d09c323d68a152f3c3f8c7256311bd020a) ([#14128](https://www.github.com/tauri-apps/tauri/pull/14128)) Added `FileAssociation::exported_type` and `FileAssociation::content_types` for better support to defining custom types on macOS.\n- [`ed7c9a410`](https://www.github.com/tauri-apps/tauri/commit/ed7c9a4100e08c002212265549d12130d021ad1e) ([#14108](https://www.github.com/tauri-apps/tauri/pull/14108)) Added `bundle > macOS > infoPlist` and `bundle > iOS > infoPlist` configurations to allow defining custom Info.plist extensions.\n- [`7b0d4e732`](https://www.github.com/tauri-apps/tauri/commit/7b0d4e73227e42d88732b6d9fe643499dd78ec4e) ([#14265](https://www.github.com/tauri-apps/tauri/pull/14265)) Added `html::normalize_script_for_csp`.\n- [`cc8c0b531`](https://www.github.com/tauri-apps/tauri/commit/cc8c0b53171173dbd1d01781a50de1a3ea159031) ([#14031](https://www.github.com/tauri-apps/tauri/pull/14031)) Added support to universal app links on macOS with the `plugins > deep-link > desktop > domains` configuration.\n\n### Enhancements\n\n- [`59089723f`](https://www.github.com/tauri-apps/tauri/commit/59089723fc20d66f3f305f2008adeb279bf87462) ([#14091](https://www.github.com/tauri-apps/tauri/pull/14091)) Added a config to set a data_directory relative to the app-specific data dir in JavaScript and `tauri.conf.json`.\n\n## \\[2.7.0]\n\n### New Features\n\n- [`91508c0b8`](https://www.github.com/tauri-apps/tauri/commit/91508c0b8d16ec61c7706e93b711c5a85aaffb4a) ([#13881](https://www.github.com/tauri-apps/tauri/pull/13881) by [@pepperoni505](https://www.github.com/tauri-apps/tauri/../../pepperoni505)) Introduces a new configuration option that allows you to specify custom folders to watch for changes when running `tauri dev`.\n- [`0c402bfb6`](https://www.github.com/tauri-apps/tauri/commit/0c402bfb6bd0bec24d928fcabe2ffef1f5cff19a) ([#13997](https://www.github.com/tauri-apps/tauri/pull/13997) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Increase default iOS deployment target iOS to 14.0.\n\n### Enhancements\n\n- [`9300b59f6`](https://www.github.com/tauri-apps/tauri/commit/9300b59f65156a37f7bf9a629b69da6761ae734a) ([#13787](https://www.github.com/tauri-apps/tauri/pull/13787) by [@JakenHerman](https://www.github.com/tauri-apps/tauri/../../JakenHerman)) Added `fips_compliant` field to `WixConfig` so that it can be configured via `tauri.conf.json` as well.\n- [`72b4226ee`](https://www.github.com/tauri-apps/tauri/commit/72b4226ee9932b4dafa4837a49420b2c02d14bb7) ([#13809](https://www.github.com/tauri-apps/tauri/pull/13809) by [@Beanow](https://www.github.com/tauri-apps/tauri/../../Beanow)) Reduced `Debug` format size for binary buffers.\n\n### Bug Fixes\n\n- [`1a3d1a024`](https://www.github.com/tauri-apps/tauri/commit/1a3d1a024ebb2a3dd5d15849523a55246d78fda6) ([#13995](https://www.github.com/tauri-apps/tauri/pull/13995) by [@will3942](https://www.github.com/tauri-apps/tauri/../../will3942)) Fix Tauri iOS build with binary XCFramework dependencies, allows extracting binaryTargets that are zipped and also not including XCFrameworks when linking.\n\n## \\[2.6.0]\n\n### New Features\n\n- [`232265c70`](https://www.github.com/tauri-apps/tauri/commit/232265c70e1c213bbb3f84b5541ddc07d330fce1) ([#13209](https://www.github.com/tauri-apps/tauri/pull/13209) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Added `platform::bundle_type`.\n- [`33d079392`](https://www.github.com/tauri-apps/tauri/commit/33d079392ac4a5a153b7d8a6d82fefd6f54a2bdf) ([#13811](https://www.github.com/tauri-apps/tauri/pull/13811) by [@mhbagheri-99](https://www.github.com/tauri-apps/tauri/../../mhbagheri-99)) Allow runner configuration to be an object with cmd, cwd, and args properties. The runner can now be configured as `{ \"cmd\": \"my_runner\", \"cwd\": \"/path\", \"args\": [\"--quiet\"] }` while maintaining backwards compatibility with the existing string format.\n\n## \\[2.5.0]\n\n### New Features\n\n- [`414619c36`](https://www.github.com/tauri-apps/tauri/commit/414619c36e94e21939534dd72c0438b93da75546) ([#13536](https://www.github.com/tauri-apps/tauri/pull/13536) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) Added support for the `bundleName` property in the macOS bundler configuration. This allows specifying the `CFBundleName` value for generated macOS bundles.\n- [`09c19932d`](https://www.github.com/tauri-apps/tauri/commit/09c19932d2ddf05f28bcdc73796a966532e7ca1c) ([#13304](https://www.github.com/tauri-apps/tauri/pull/13304) by [@39zde](https://www.github.com/tauri-apps/tauri/../../39zde)) Adds the option to configure the HTTP `Service-Worker-Allowed` response header in `app > security > headers`\n\n### Bug Fixes\n\n- [`eb3f0248c`](https://www.github.com/tauri-apps/tauri/commit/eb3f0248c25f1d259cf0fa9448b3c7c2b75b24a7) ([#13646](https://www.github.com/tauri-apps/tauri/pull/13646) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Only write `This default permission set includes the following` to the reference if the default permission set is not empty\n- [`c8a30a61d`](https://www.github.com/tauri-apps/tauri/commit/c8a30a61d20552b43afd6f21cb66d18185314148) ([#13476](https://www.github.com/tauri-apps/tauri/pull/13476) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix capability filtering via `tauri.conf.json > app > security > capabilities` not working when generating allowed commands.\n- [`b52da29d5`](https://www.github.com/tauri-apps/tauri/commit/b52da29d5dbdb675ddba438a335e6a59f620e536) ([#13429](https://www.github.com/tauri-apps/tauri/pull/13429) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `mainBinaryName` doesn't work when there's `.` in it\n\n### What's Changed\n\n- [`168629646`](https://www.github.com/tauri-apps/tauri/commit/168629646335f24cc7f1c4a61df22688b2198f98) ([#13418](https://www.github.com/tauri-apps/tauri/pull/13418) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Put dynamic ACL into a feature `dynamic-acl`, this is currently enabled by default to align with the previous behaviors, you can disable it through `default-features = false` to reduce the final binary size by not including the ACL references\n\n### Dependencies\n\n- [`9c16eefa3`](https://www.github.com/tauri-apps/tauri/commit/9c16eefa319b4697bac1d1019bbb5f93eca63173) ([#13629](https://www.github.com/tauri-apps/tauri/pull/13629) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Update html5ever to 0.29 and kuchikiki to version 0.8.8-speedreader.\n\n### Breaking Changes\n\n- [`b7cdb3b39`](https://www.github.com/tauri-apps/tauri/commit/b7cdb3b39ef7e84773ce9312535825801350fa20) ([#13410](https://www.github.com/tauri-apps/tauri/pull/13410) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Feature gated the HTML manipulation code in `tauri-utils` behined a flag to reduce compile time\n\n## \\[2.4.0]\n\n### New Features\n\n- [`ea36294cb`](https://www.github.com/tauri-apps/tauri/commit/ea36294cbca98f7725c91d1464fd92e77c89698a) ([#13208](https://www.github.com/tauri-apps/tauri/pull/13208)) Added `disableInputAccessoryView: bool` config for iOS.\n- [`0aa48fb9e`](https://www.github.com/tauri-apps/tauri/commit/0aa48fb9e4b9d7b5bf3522000a76ebc1836394ed) ([#13030](https://www.github.com/tauri-apps/tauri/pull/13030)) Added `bundleVersion` to iOS and macOS configuration to support specifying a `CFBundleVersion`.\n- [`b072e2b29`](https://www.github.com/tauri-apps/tauri/commit/b072e2b2967640ae4fa1af466ae878c156551edd) ([#9687](https://www.github.com/tauri-apps/tauri/pull/9687)) Add `preventOverflow` config option to prevent the window from overflowing the monitor size on creation\n\n### Bug Fixes\n\n- [`2dccfab53`](https://www.github.com/tauri-apps/tauri/commit/2dccfab5321fef55d45f3a4c674b6151b1c4424a) ([#13236](https://www.github.com/tauri-apps/tauri/pull/13236)) Fix `fileAssociations` missing `LSHandlerRank` on macOS.\n\n## \\[2.3.1]\n\n### Enhancements\n\n- [`a851b6597`](https://www.github.com/tauri-apps/tauri/commit/a851b6597f7e37d12f9e4632945e8466800eb5ff) ([#13057](https://www.github.com/tauri-apps/tauri/pull/13057) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Enhanced the description of generated docs and schema for permission sets to include list of permissions within.\n\n### Bug Fixes\n\n- [`4ae14bf2f`](https://www.github.com/tauri-apps/tauri/commit/4ae14bf2f20546c0990c48dc465832ffc46a7247) ([#13093](https://www.github.com/tauri-apps/tauri/pull/13093) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Suppress deprecated warning in `TrayIconConfig`'s codegen\n\n## \\[2.3.0]\n\n### New Features\n\n- [`013f8f652`](https://www.github.com/tauri-apps/tauri/commit/013f8f652302f2d49c5ec0a075582033d8b074fb) ([#12890](https://www.github.com/tauri-apps/tauri/pull/12890) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Added `build > removeUnusedCommands` to trigger the build scripts and macros to remove unused commands based on the capabilities you defined. Note this won't be accounting for dynamically added ACLs so make sure to check it when using this.\n- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `trafficLightPosition` window configuration to set the traffic light buttons position on macOS.\n\n### Enhancements\n\n- [`f981a5ee8`](https://www.github.com/tauri-apps/tauri/commit/f981a5ee8b292b9ea09329f60cecc7f688dda734) ([#12602](https://www.github.com/tauri-apps/tauri/pull/12602) by [@kxxt](https://www.github.com/tauri-apps/tauri/../../kxxt)) Add basic support for linux riscv64 platform.\n\n### Bug Fixes\n\n- [`3cc4ad3c3`](https://www.github.com/tauri-apps/tauri/commit/3cc4ad3c381ee081cfcf0df28ea51507ad5c9b95) ([#12884](https://www.github.com/tauri-apps/tauri/pull/12884) by [@oscartbeaumont](https://www.github.com/tauri-apps/tauri/../../oscartbeaumont)) fix: allow double `--` in permissions as command can have double `__`\n\n### Performance Improvements\n\n- [`1cd8f55ee`](https://www.github.com/tauri-apps/tauri/commit/1cd8f55eed326d61860fee62ba2d2f4464bdcfcc) ([#13033](https://www.github.com/tauri-apps/tauri/pull/13033) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Don't ship global `bundle.global.js` if `app > withGlobalTauri` is set to false\n\n## \\[2.2.0]\n\n### Enhancements\n\n- [`a2d36b8c3`](https://www.github.com/tauri-apps/tauri/commit/a2d36b8c34a8dcfc6736797ca5cd4665faf75e7e) ([#12181](https://www.github.com/tauri-apps/tauri/pull/12181) by [@bastiankistner](https://www.github.com/tauri-apps/tauri/../../bastiankistner)) Add an option to change the default background throttling policy (currently for WebKit only).\n\n## \\[2.1.1]\n\n### Bug Fixes\n\n- [`46935212b`](https://www.github.com/tauri-apps/tauri/commit/46935212b61da44dc82dfeb803fceebf5659f7b7) ([#11658](https://www.github.com/tauri-apps/tauri/pull/11658) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `.json5` capability files not recognized even with `config-json5` feature enabled\n\n## \\[2.1.0]\n\n### New Features\n\n- [`fabc2f283`](https://www.github.com/tauri-apps/tauri/commit/fabc2f283e38b62c721326e44645d47138418cbc) ([#11485](https://www.github.com/tauri-apps/tauri/pull/11485) by [@39zde](https://www.github.com/tauri-apps/tauri/../../39zde)) Adds a new configuration option `app > security > headers` to define headers that will be added to every http response from tauri to the web view. This doesn't include IPC messages and error responses.\n- [`4d545ab3c`](https://www.github.com/tauri-apps/tauri/commit/4d545ab3ca228c8a21b966b709f84a0da2864479) ([#11486](https://www.github.com/tauri-apps/tauri/pull/11486) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Window::set_background_color` and `WindowBuilder::background_color`.\n- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > devtools` config option and when creating the webview from JS, to enable or disable devtools for a specific webview.\n- [`058c0db72`](https://www.github.com/tauri-apps/tauri/commit/058c0db72f43fbe1574d0db654560e693755cd7e) ([#11584](https://www.github.com/tauri-apps/tauri/pull/11584) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > rpm > compression` config option to control RPM bundle compression type and level.\n- [`f37e97d41`](https://www.github.com/tauri-apps/tauri/commit/f37e97d410c4a219e99f97692da05ca9d8e0ba3a) ([#11477](https://www.github.com/tauri-apps/tauri/pull/11477) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > useHttpsScheme` config option to choose whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android\n- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `app > windows > windowClassname` config option to specify the name of the window class on Windows.\n\n### Enhancements\n\n- [`c33bbf457`](https://www.github.com/tauri-apps/tauri/commit/c33bbf45740274b6918ea6c647f366fb6008e459) ([#11575](https://www.github.com/tauri-apps/tauri/pull/11575) by [@kornelski](https://www.github.com/tauri-apps/tauri/../../kornelski)) Include the path in ACL I/O errors.\n\n### Bug Fixes\n\n- [`378142914`](https://www.github.com/tauri-apps/tauri/commit/37814291475814b4a24cc77b6fa457ec9ba7a779) ([#11429](https://www.github.com/tauri-apps/tauri/pull/11429) by [@griffi-gh](https://www.github.com/tauri-apps/tauri/../../griffi-gh)) Enhance resource directory resolution to support running on distros like NixOS.\n\n## \\[2.0.2]\n\n### New Features\n\n- Add `bundler > windows > wix > version` to manually specify a wix-compatible version.\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`0ab2b3306`](https://www.github.com/tauri-apps/tauri/commit/0ab2b330644b6419f6cee1d5377bfb5cdda2ccf9) ([#11205](https://www.github.com/tauri-apps/tauri/pull/11205) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`382ed482b`](https://www.github.com/tauri-apps/tauri/commit/382ed482bd08157c39e62f9a0aaad8802f1092cb) Bump MSRV to 1.78.\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n## \\[2.0.0-rc.13]\n\n### New Features\n\n- [`a247170e1`](https://www.github.com/tauri-apps/tauri/commit/a247170e1f620a9b012274b11cfe51e90327d6e9) ([#11056](https://www.github.com/tauri-apps/tauri/pull/11056) by [@SpikeHD](https://www.github.com/tauri-apps/tauri/../../SpikeHD)) Expose the ability to enabled browser extensions in WebView2 on Windows.\n- [`f57a729cd`](https://www.github.com/tauri-apps/tauri/commit/f57a729cd8f7e10d8daf0b9d5b85f9c7ad530496) ([#11039](https://www.github.com/tauri-apps/tauri/pull/11039) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `upgradeCode` in `wix` configuration to set an upgrade code for your MSI installer. This is recommended to be set if you plan to change your `productName`.\n\n### Bug Fixes\n\n- [`1efa5e718`](https://www.github.com/tauri-apps/tauri/commit/1efa5e7184009537b688a395596c696173ae0231) ([#11099](https://www.github.com/tauri-apps/tauri/pull/11099) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Rerun build script if the platform-specific configuration file changes.\n\n## \\[2.0.0-rc.12]\n\n### New Features\n\n- [`ad294d274`](https://www.github.com/tauri-apps/tauri/commit/ad294d274dd81d2ef91ed73af9163b6e9b8eb964) ([#11032](https://www.github.com/tauri-apps/tauri/pull/11032) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > create` option to choose whether to create this window at app startup or not.\n\n## \\[2.0.0-rc.11]\n\n### New Features\n\n- [`35bd9dd3d`](https://www.github.com/tauri-apps/tauri/commit/35bd9dd3dc3d8972bbc4aa5f4a6c6fa14354e9bf) ([#10977](https://www.github.com/tauri-apps/tauri/pull/10977) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `mainBinaryName` config option to set the file name for the main binary.\n\n## \\[2.0.0-rc.10]\n\n### Bug Fixes\n\n- [`0a47bf043`](https://www.github.com/tauri-apps/tauri/commit/0a47bf04302ca8502d3da21b3bc27818720fe34a) ([#10946](https://www.github.com/tauri-apps/tauri/pull/10946) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that made the `identifier` in `tauri.conf.json` optional while it was actually required.\n\n## \\[2.0.0-rc.9]\n\n### Dependencies\n\n- [`d9c8d3cc8`](https://www.github.com/tauri-apps/tauri/commit/d9c8d3cc8d5ca67cd767ffc7a521f801b41ce201) ([#10902](https://www.github.com/tauri-apps/tauri/pull/10902) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Update infer to 0.16, tray icon to 0.17, urlpattern to 0.3, image to 0.25\n\n### Breaking Changes\n\n- [`faa259bac`](https://www.github.com/tauri-apps/tauri/commit/faa259bacf1ace670af763982c6903190faf163a) ([#10907](https://www.github.com/tauri-apps/tauri/pull/10907) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) The `Assets::iter` function now must return a iterator with `Item = (Cow<'_, str>, Cow<'_, [u8]>)` to be more flexible on contexts where the assets are not `'static`.\n\n## \\[2.0.0-rc.8]\n\n### Enhancements\n\n- [`f0acf504a`](https://www.github.com/tauri-apps/tauri/commit/f0acf504a2a972c063188a00143d8bf2b887691d) ([#10858](https://www.github.com/tauri-apps/tauri/pull/10858) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Validate duplicate capability identifier.\n\n## \\[2.0.0-rc.7]\n\n### New Features\n\n- [`58dda44a5`](https://www.github.com/tauri-apps/tauri/commit/58dda44a59b915f091602cdfc53385a148469793) ([#10339](https://www.github.com/tauri-apps/tauri/pull/10339) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Add a new option `minimumWebview2Version` for Windows NSIS installer to trigger a webview2 update if the user's webview2 is older than this version\n\n### Bug Fixes\n\n- [`03f2a5098`](https://www.github.com/tauri-apps/tauri/commit/03f2a50981b8c01b1c196811fce9d93f1bf0820d) ([#10718](https://www.github.com/tauri-apps/tauri/pull/10718) by [@rdlabo](https://www.github.com/tauri-apps/tauri/../../rdlabo)) Update swift-rs fixing a plugin build when native dependencies are used.\n\n### Breaking Changes\n\n- [`073bb4f45`](https://www.github.com/tauri-apps/tauri/commit/073bb4f459a923541b94970dfa7e087bccaa2cfd) ([#10772](https://www.github.com/tauri-apps/tauri/pull/10772) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Removed the deprecated `webview_fixed_runtime_path` config option, use the `webview_install_mode` instead.\n\n## \\[2.0.0-rc.6]\n\n### Bug Fixes\n\n- [`9bcff3cd7`](https://www.github.com/tauri-apps/tauri/commit/9bcff3cd7997fe13425a21b577f93317831f77fa) ([#10703](https://www.github.com/tauri-apps/tauri/pull/10703) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Properly remove isolation script on Android.\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n## \\[2.0.0-rc.5]\n\n### Bug Fixes\n\n- [`da381e07f`](https://www.github.com/tauri-apps/tauri/commit/da381e07f3770988fe6d0859a02331b87cc6723f) ([#10696](https://www.github.com/tauri-apps/tauri/pull/10696) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Implemented `resource_dir` on Android, which returns a URI that needs to be resolved using [AssetManager::open](https://developer.android.com/reference/android/content/res/AssetManager#open\\(java.lang.String,%20int\\)). This will be handled by the file system plugin.\n- [`da381e07f`](https://www.github.com/tauri-apps/tauri/commit/da381e07f3770988fe6d0859a02331b87cc6723f) ([#10696](https://www.github.com/tauri-apps/tauri/pull/10696) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `resource_dir` on iOS.\n\n## \\[2.0.0-rc.4]\n\n### New Features\n\n- [`8d148a9e2`](https://www.github.com/tauri-apps/tauri/commit/8d148a9e2566edebfea2d75f32df7c9396d765a4) ([#10634](https://www.github.com/tauri-apps/tauri/pull/10634) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) Custom sign command with object notation for whitespaces in the command path and arguments.\n\n### Bug Fixes\n\n- [`5c335ae9a`](https://www.github.com/tauri-apps/tauri/commit/5c335ae9ad88e46c2135a557390f6e808c9a6088) ([#10648](https://www.github.com/tauri-apps/tauri/pull/10648) by [@Flakebi](https://www.github.com/tauri-apps/tauri/../../Flakebi)) Prevent build script from rerunning unnecessarily by only writing files when the content changes.\n\n## \\[2.0.0-rc.3]\n\n### Enhancements\n\n- [`0bb7b0f35`](https://www.github.com/tauri-apps/tauri/commit/0bb7b0f352960fb5111a40157c0953d19e76f1fd) ([#10559](https://www.github.com/tauri-apps/tauri/pull/10559) by [@Norbiros](https://www.github.com/tauri-apps/tauri/../../Norbiros)) Return autogenerated permissions from `autogenerate_command_permissions`.\n\n### Bug Fixes\n\n- [`9e891933d`](https://www.github.com/tauri-apps/tauri/commit/9e891933d8ac7a67e37770a149d0a5dd385ee625) ([#10293](https://www.github.com/tauri-apps/tauri/pull/10293) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `ResourcePaths` iterator returning an unexpected result for mapped resources, for example `\"../resources/user.json\": \"resources/user.json\"` generates this resource `resources/user.json/user.json` where it should generate just `resources/user.json`.\n- [`9fe846615`](https://www.github.com/tauri-apps/tauri/commit/9fe846615b7c4f310f07897ded881c239e3df30a) ([#10547](https://www.github.com/tauri-apps/tauri/pull/10547) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix plugin permissions documentation heading for permissions table.\n\n### Dependencies\n\n- [`0afee5ed8`](https://www.github.com/tauri-apps/tauri/commit/0afee5ed80265c95c4581e173c4886677cfed593) ([#10436](https://www.github.com/tauri-apps/tauri/pull/10436) by [@nyurik](https://www.github.com/tauri-apps/tauri/../../nyurik)) Updated brotli to v6.\n\n## \\[2.0.0-rc.2]\n\n### Bug Fixes\n\n- [`f5dfc0280`](https://www.github.com/tauri-apps/tauri/commit/f5dfc02800dbd3bdee671b032454c49ac7102fb4) ([#10533](https://www.github.com/tauri-apps/tauri/pull/10533) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue causing `tauri ios init` to fail if `iOS.minimumSystemVersion` was not configured explicitly.\n\n## \\[2.0.0-rc.1]\n\n### New Features\n\n- [`8dc81b6cc`](https://www.github.com/tauri-apps/tauri/commit/8dc81b6cc2b8235b11f74a971d6aa3a5df5e9f68) ([#10496](https://www.github.com/tauri-apps/tauri/pull/10496) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `bundle > ios > template` configuration option for custom Xcode project YML Handlebars template using XcodeGen.\n- [`02c00abc6`](https://www.github.com/tauri-apps/tauri/commit/02c00abc63cf86e9bf9179cbb143d5145a9397b6) ([#10495](https://www.github.com/tauri-apps/tauri/pull/10495) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `bundle > ios > minimumSystemVersion` configuration option.\n\n### Bug Fixes\n\n- [`7e810cb2a`](https://www.github.com/tauri-apps/tauri/commit/7e810cb2a3fd934017ae973e737864dfa4bdf64e) ([#10485](https://www.github.com/tauri-apps/tauri/pull/10485) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) Fixed an issue where permission files will be generated with ':' in the file path.\n\n## \\[2.0.0-rc.0]\n\n### New Features\n\n- [`a5bfbaa62`](https://www.github.com/tauri-apps/tauri/commit/a5bfbaa62b8cd0aacbb33f730d4e30b43c461fe1)([#9962](https://www.github.com/tauri-apps/tauri/pull/9962)) Added `bundle > iOS > frameworks` configuration to define a list of frameworks that are linked to the Xcode project when it is generated.\n\n### Enhancements\n\n- [`7aeac39e7`](https://www.github.com/tauri-apps/tauri/commit/7aeac39e7fb97dc57ca278f1c097058275c20aa2) ([#10397](https://www.github.com/tauri-apps/tauri/pull/10397)) Make the set of gtk application id optional, to allow more then one instance of the app running at the same time.\n\n### Bug Fixes\n\n- [`498f405ca`](https://www.github.com/tauri-apps/tauri/commit/498f405ca80440447823dd3c9cd53c0f79d655b5) ([#10404](https://www.github.com/tauri-apps/tauri/pull/10404)) Fixed an issue where configuration parsing errors always displayed 'tauri.conf.json' as the file path, even when using 'Tauri.toml' or 'tauri.conf.json5'.\n\n  The error messages now correctly shows the actual config file being used.\n\n### Security fixes\n\n- [`426d14bb4`](https://www.github.com/tauri-apps/tauri/commit/426d14bb4164290d93b5a0f61e925cb2dfc4aafa) ([#10423](https://www.github.com/tauri-apps/tauri/pull/10423)) Explicitly check that the main frame's origin is the sender of Isolation Payloads\n\n## \\[2.0.0-beta.19]\n\n### New Features\n\n- [`4c239729c`](https://www.github.com/tauri-apps/tauri/commit/4c239729c3e1b899ecbc6793c3682848e8de1729) ([#10167](https://www.github.com/tauri-apps/tauri/pull/10167) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `RawIsolationPayload::content_type` method.\n\n## \\[2.0.0-beta.18]\n\n### New Features\n\n- [`fafc238f7`](https://www.github.com/tauri-apps/tauri/commit/fafc238f7288548975ca7d3e5207b925c0295c91) ([#9977](https://www.github.com/tauri-apps/tauri/pull/9977)) Add `bundle > homepage` option, if unset, it will fallback to `homepage` defined in `Cargo.toml`.\n- [`656a64974`](https://www.github.com/tauri-apps/tauri/commit/656a64974468bc207bf39537e02ae179bdee9b83) ([#9318](https://www.github.com/tauri-apps/tauri/pull/9318)) Added a configuration option to disable hardened runtime on macOS codesign.\n- [`5b769948a`](https://www.github.com/tauri-apps/tauri/commit/5b769948a81cac333f64c870a470ba6525bd5cd3) ([#9959](https://www.github.com/tauri-apps/tauri/pull/9959)) Add `include_image` macro to help embedding instances of `Image` struct at compile-time in rust to be used with window, menu or tray icons.\n- [`3ab170917`](https://www.github.com/tauri-apps/tauri/commit/3ab170917ed535fc9013f0a9255631fb34493e18) ([#9932](https://www.github.com/tauri-apps/tauri/pull/9932)) Add an option to disable NSIS compression `bundle > nsis > compression: \"none\"`\n- [`f21029b1b`](https://www.github.com/tauri-apps/tauri/commit/f21029b1bc25f5cb987e1a25de94c2d364e3e462) ([#9994](https://www.github.com/tauri-apps/tauri/pull/9994)) Add `bundle > nsis > startMenuFolder` option to customize start menu folder for NSIS installer\n\n### Enhancements\n\n- [`878198777`](https://www.github.com/tauri-apps/tauri/commit/878198777ef693efdbd394cb4be4b234e8a7ed3d) ([#9999](https://www.github.com/tauri-apps/tauri/pull/9999)) Mark ACL `permissions` array with unique items\n\n### Breaking Changes\n\n- [`3ab170917`](https://www.github.com/tauri-apps/tauri/commit/3ab170917ed535fc9013f0a9255631fb34493e18) ([#9932](https://www.github.com/tauri-apps/tauri/pull/9932)) Changed `NsisSettings::compression` field from `Option<NsisCompression>` to just `NsisCompression`\n- [`911242f09`](https://www.github.com/tauri-apps/tauri/commit/911242f0928e0a2add3595fa9de27850fb875fa6) ([#9883](https://www.github.com/tauri-apps/tauri/pull/9883)) Move updater target from `bundle > targets` to a separate field `bundle > createUpdaterArtifacts`\n- [`3ab170917`](https://www.github.com/tauri-apps/tauri/commit/3ab170917ed535fc9013f0a9255631fb34493e18) ([#9932](https://www.github.com/tauri-apps/tauri/pull/9932)) Changed `NsisConfig::compression` field from `Option<NsisCompression>` to just `NsisCompression`\n\n## \\[2.0.0-beta.17]\n\n### New Features\n\n- [`8a1ae2dea`](https://www.github.com/tauri-apps/tauri/commit/8a1ae2deaf3086e531ada25b1627f900e2e421fb)([#9843](https://www.github.com/tauri-apps/tauri/pull/9843)) Added an option to use a Xcode project for the iOS plugin instead of a plain SwiftPM project.\n- [`5462e5cad`](https://www.github.com/tauri-apps/tauri/commit/5462e5cadc73c1b9083d852061d7c7f982cfbe53)([#9731](https://www.github.com/tauri-apps/tauri/pull/9731)) Add `installer_hooks` NSIS configuration field\n- [`d6d3efbd1`](https://www.github.com/tauri-apps/tauri/commit/d6d3efbd125489cb46642b6d013cdc1eb7fc1a66)([#9865](https://www.github.com/tauri-apps/tauri/pull/9865)) Add `sign_command` in `WindowsConfig`\n\n### Breaking Changes\n\n- [`fc1543c65`](https://www.github.com/tauri-apps/tauri/commit/fc1543c65e736622bed93543dcc6504c43e200bb)([#9864](https://www.github.com/tauri-apps/tauri/pull/9864)) Removed `skip_webview_install` (`skipWebviewInstall`) option from config, which has been deprecated for a while now and planned to be removed in v2. Use `webview_install_mode` (`webviewInstallMode`) instead.\n- [`265c23886`](https://www.github.com/tauri-apps/tauri/commit/265c23886ee5efbcc6d7188ff5c84cb32fa82aea)([#9375](https://www.github.com/tauri-apps/tauri/pull/9375)) Removed `Config::binary_name` and `PackageInfo::package_name`\n\n## \\[2.0.0-beta.16]\n\n### Bug Fixes\n\n- [`be95d8d37`](https://www.github.com/tauri-apps/tauri/commit/be95d8d37c4b1a420c0d28a83b7efa40ab0b0ab5)([#9782](https://www.github.com/tauri-apps/tauri/pull/9782)) Fixes the `ToTokens` implementation for `Capability`.\n\n## \\[2.0.0-beta.15]\n\n### Bug Fixes\n\n- [`a5205f179`](https://www.github.com/tauri-apps/tauri/commit/a5205f179e577cce5c05b710b597da8f4e1d0780)([#9691](https://www.github.com/tauri-apps/tauri/pull/9691)) Fixes the ToTokens implementation of the window configuration `proxy_url` field.\n\n## \\[2.0.0-beta.14]\n\n### Bug Fixes\n\n- [`3fbc1703f`](https://www.github.com/tauri-apps/tauri/commit/3fbc1703f107dfb2c5a75e848805dcf60d449eb1)([#9676](https://www.github.com/tauri-apps/tauri/pull/9676)) Fixes `schemars` compilation issue.\n\n## \\[2.0.0-beta.13]\n\n### Bug Fixes\n\n- [`a1e0e268f`](https://www.github.com/tauri-apps/tauri/commit/a1e0e268f02f7e2934bec48de4cac0dc00529a2b)([#9477](https://www.github.com/tauri-apps/tauri/pull/9477)) Replace `tauri:` prefix with `tauri-` for temporary permission file names\n\n## \\[2.0.0-beta.12]\n\n### New Features\n\n- [`36b4c1249`](https://www.github.com/tauri-apps/tauri/commit/36b4c12497fbe636066f4848c6877b3ab6cc892e)([#9331](https://www.github.com/tauri-apps/tauri/pull/9331)) Added support for `provides`, `conflicts` and `replaces` (`obsoletes` for RPM) options for `bundler > deb` and `bundler > rpm` configs.\n\n## \\[2.0.0-beta.11]\n\n### New Features\n\n- [`259d84529`](https://www.github.com/tauri-apps/tauri/commit/259d845290dde40639537258b2810567910f47f3)([#9209](https://www.github.com/tauri-apps/tauri/pull/9209)) Added `preInstallScript`, `postInstallScript`, `preRemoveScript` and `postRemoveScript` options for `bundler > deb` and `bundler > rpm` configs.\n\n### Enhancements\n\n- [`7c334cb18`](https://www.github.com/tauri-apps/tauri/commit/7c334cb1851ab034a3cfb472dd99dfc61ad3ca7f)([#9327](https://www.github.com/tauri-apps/tauri/pull/9327)) Make the isolation pattern encrypt key unextractable.\n- [`a804a70a7`](https://www.github.com/tauri-apps/tauri/commit/a804a70a7aa1dc40fa9043206ad2265c6a5a437b)([#9328](https://www.github.com/tauri-apps/tauri/pull/9328)) The isolation iframe script now removes itself after execution.\n\n### Breaking Changes\n\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Rename `FileDrop` to `DragDrop` on structs, enums and enum variants. Also renamed `file_drop` to `drag_drop` on fields and function names.\n\n## \\[2.0.0-beta.10]\n\n### New Features\n\n- [`e227fe02f`](https://www.github.com/tauri-apps/tauri/commit/e227fe02f986e145c0731a64693e1c830a9eb5b0)([#9156](https://www.github.com/tauri-apps/tauri/pull/9156)) Added the `plugin` module.\n\n### Enhancements\n\n- [`7213b9e47`](https://www.github.com/tauri-apps/tauri/commit/7213b9e47242bef814aa7257e0bf84631bf5fe7e)([#9124](https://www.github.com/tauri-apps/tauri/pull/9124)) Fallback to an empty permission set if the plugin did not define its `default` permissions.\n\n## \\[2.0.0-beta.9]\n\n### Breaking Changes\n\n- [`490a6b424`](https://www.github.com/tauri-apps/tauri/commit/490a6b424e81714524150aef96fbf6cf7004b940)([#9147](https://www.github.com/tauri-apps/tauri/pull/9147)) Removed the `assets::Assets` trait which is now part of the `tauri` crate.\n\n## \\[2.0.0-beta.8]\n\n### Enhancements\n\n- [`3e472d0af`](https://www.github.com/tauri-apps/tauri/commit/3e472d0afcd67545dd6d9f18d304580a3b2759a8)([#9115](https://www.github.com/tauri-apps/tauri/pull/9115)) Changed the permission and capability platforms to be optional.\n\n### Breaking Changes\n\n- [`4ef17d083`](https://www.github.com/tauri-apps/tauri/commit/4ef17d08336a2e0df4a7ef9adea746d7419710b6)([#9116](https://www.github.com/tauri-apps/tauri/pull/9116)) The ACL configuration for remote URLs now uses the URLPattern standard instead of glob patterns.\n\n## \\[2.0.0-beta.7]\n\n### Bug Fixes\n\n- [`86fa339de`](https://www.github.com/tauri-apps/tauri/commit/86fa339de7b176efafa9b3e89f94dcad5fcd03da)([#9071](https://www.github.com/tauri-apps/tauri/pull/9071)) Fix compile time error in context generation when using `app.windows.windowEffects.color`\n- [`6c0683224`](https://www.github.com/tauri-apps/tauri/commit/6c068322460300e9d56a4fac5b018d4c437daa9e)([#9068](https://www.github.com/tauri-apps/tauri/pull/9068)) Fixes scope resolution grouping scopes for all windows.\n- [`c68218b36`](https://www.github.com/tauri-apps/tauri/commit/c68218b362c417b62e56c7a2b5b32c13fe035a83)([#8990](https://www.github.com/tauri-apps/tauri/pull/8990)) Fix `BundleTarget::to_vec` returning an empty vec for `BundleTarget::All` variant.\n- [`c68218b36`](https://www.github.com/tauri-apps/tauri/commit/c68218b362c417b62e56c7a2b5b32c13fe035a83)([#8990](https://www.github.com/tauri-apps/tauri/pull/8990)) Add `BundleType::all` method to return all possible `BundleType` variants.\n\n### Breaking Changes\n\n- [`9aa0d6e95`](https://www.github.com/tauri-apps/tauri/commit/9aa0d6e959269a9d99ff474e7f12bd397ea75fcd)([#9069](https://www.github.com/tauri-apps/tauri/pull/9069)) Removed `debug_eprintln!` and `consume_unused_variable` macros.\n- [`bb23511ea`](https://www.github.com/tauri-apps/tauri/commit/bb23511ea80bcaffbdebf057301e463fff268c90)([#9079](https://www.github.com/tauri-apps/tauri/pull/9079)) Changed `CapabiltyFile::List` enum variant to be a tuple-struct and added `CapabiltyFile::NamedList`. This allows more flexibility when parsing capabilties from JSON files.\n\n## \\[2.0.0-beta.6]\n\n### New Features\n\n- [`d7f56fef`](https://www.github.com/tauri-apps/tauri/commit/d7f56fef85cac3af4e2dbac1eac40e5567b1f160)([#9014](https://www.github.com/tauri-apps/tauri/pull/9014)) Allow defining a permission that only applies to a set of target platforms via the `platforms` configuration option.\n\n### Enhancements\n\n- [`04440edc`](https://www.github.com/tauri-apps/tauri/commit/04440edce870f9d06055616034941d79443d5a87)([#9019](https://www.github.com/tauri-apps/tauri/pull/9019)) Changed plugin markdown docs generation to table format.\n\n### Breaking Changes\n\n- [`3657ad82`](https://www.github.com/tauri-apps/tauri/commit/3657ad82f88ce528551d032d521c52eed3f396b4)([#9008](https://www.github.com/tauri-apps/tauri/pull/9008)) Allow defining permissions for the application commands via `tauri_build::Attributes::app_manifest`.\n\n## \\[2.0.0-beta.5]\n\n### Enhancements\n\n- [`bc5b5e67`](https://www.github.com/tauri-apps/tauri/commit/bc5b5e671a546512f823f1c157421b4c3311dfc0)([#8984](https://www.github.com/tauri-apps/tauri/pull/8984)) Do not include a CSP tag in the application HTML and rely on the custom protocol response header instead.\n\n## \\[2.0.0-beta.4]\n\n### Breaking Changes\n\n- [`a76fb118`](https://www.github.com/tauri-apps/tauri/commit/a76fb118ce2de22e1bdb4216bf0ac01dfc3e5799)([#8950](https://www.github.com/tauri-apps/tauri/pull/8950)) Changed the capability format to allow configuring both `remote: { urls: Vec<String> }` and `local: bool (default: true)` instead of choosing one on the `context` field.\n\n## \\[2.0.0-beta.3]\n\n### Breaking Changes\n\n- [`361ec37f`](https://www.github.com/tauri-apps/tauri/commit/361ec37fd4a5caa5b6630b9563ef079f53c6c336)([#8932](https://www.github.com/tauri-apps/tauri/pull/8932)) Moved `ProgressBarState` from `tauri-utils` to the `tauri::window` module and removed the `unity_uri` field.\n\n## \\[2.0.0-beta.2]\n\n### Enhancements\n\n- [`0cb0a15c`](https://www.github.com/tauri-apps/tauri/commit/0cb0a15ce22af3d649cf219ac04188c14c5f4905)([#8789](https://www.github.com/tauri-apps/tauri/pull/8789)) Add `webviews` array on the capability for usage on multiwebview contexts.\n- [`83a68deb`](https://www.github.com/tauri-apps/tauri/commit/83a68deb5676d39cd4728d2e140f6b46d5f787ed)([#8797](https://www.github.com/tauri-apps/tauri/pull/8797)) Added a new configuration option `tauri.conf.json > app > security > capabilities` to reference existing capabilities and inline new ones. If it is empty, all capabilities are still included preserving the current behavior.\n- [`8d16a80d`](https://www.github.com/tauri-apps/tauri/commit/8d16a80d2fb2468667e7987d0cc99dbc7e3b9d0a)([#8802](https://www.github.com/tauri-apps/tauri/pull/8802)) The `Context` struct now includes the runtime authority instead of the resolved ACL. This does not impact most applications.\n- [`28fb036c`](https://www.github.com/tauri-apps/tauri/commit/28fb036ce476c6f22815c35385f923135212c6f3)([#8852](https://www.github.com/tauri-apps/tauri/pull/8852)) Enhance resource directory resolution on development.\n- [`dd7571a7`](https://www.github.com/tauri-apps/tauri/commit/dd7571a7808676c8063a4983b9c6687dfaf03a09)([#8815](https://www.github.com/tauri-apps/tauri/pull/8815)) Do not generate JSON schema and markdown reference file if the plugin does not define any permissions and delete those files if they exist.\n- [`5618f6d2`](https://www.github.com/tauri-apps/tauri/commit/5618f6d2ffc9ebf40710145538b06bebfa55f878)([#8856](https://www.github.com/tauri-apps/tauri/pull/8856)) Relax requirements on plugin's identifiers to be alphanumeric and `-` instead of only lower alpha and `-`.\n- [`8d16a80d`](https://www.github.com/tauri-apps/tauri/commit/8d16a80d2fb2468667e7987d0cc99dbc7e3b9d0a)([#8802](https://www.github.com/tauri-apps/tauri/pull/8802)) Refactored the capability types and resolution algorithm.\n\n### Bug Fixes\n\n- [`ae0fe47c`](https://www.github.com/tauri-apps/tauri/commit/ae0fe47c4c35fa87c77acf42af32ef3f0615cb08)([#8774](https://www.github.com/tauri-apps/tauri/pull/8774)) Fix compile error when `tauri.conf.json` had `bundle > license` set.\n\n### Breaking Changes\n\n- [`f284f9c5`](https://www.github.com/tauri-apps/tauri/commit/f284f9c545deeb77d15b6e8b1d0d05f49c40634c)([#8898](https://www.github.com/tauri-apps/tauri/pull/8898)) Changed the capability `remote` configuration to take a list of `urls` instead of `domains` for more flexibility.\n\n## \\[2.0.0-beta.1]\n\n### Enhancements\n\n- [`4e101f80`](https://www.github.com/tauri-apps/tauri/commit/4e101f801657e7d01ce8c22f9c6468067d0caab2)([#8756](https://www.github.com/tauri-apps/tauri/pull/8756)) Moved the capability JSON schema to the `src-tauri/gen` folder so it's easier to track changes on the `capabilities` folder.\n\n### Bug Fixes\n\n- [`4e101f80`](https://www.github.com/tauri-apps/tauri/commit/4e101f801657e7d01ce8c22f9c6468067d0caab2)([#8756](https://www.github.com/tauri-apps/tauri/pull/8756)) Rerun build script when a new permission is added.\n\n## \\[2.0.0-beta.0]\n\n### New Features\n\n- [`74a2a603`](https://www.github.com/tauri-apps/tauri/commit/74a2a6036a5e57462f161d728cbd8a6f121028ca)([#8661](https://www.github.com/tauri-apps/tauri/pull/8661)) Implement access control list for IPC usage.\n- [`9eaeb5a8`](https://www.github.com/tauri-apps/tauri/commit/9eaeb5a8cd95ae24b5e66205bdc2763cb7f965ce)([#8622](https://www.github.com/tauri-apps/tauri/pull/8622)) Add `parent` option for window config.\n- [`58fe2e81`](https://www.github.com/tauri-apps/tauri/commit/58fe2e812a85b9f4eba105286a63f271ea637836)([#8670](https://www.github.com/tauri-apps/tauri/pull/8670)) Add `WebviewUrl::CustomProtocol` enum variant.\n\n### What's Changed\n\n- [`6639a579`](https://www.github.com/tauri-apps/tauri/commit/6639a579c76d45210f33a72d37e21d4c5a9d334b)([#8441](https://www.github.com/tauri-apps/tauri/pull/8441)) Added the `WindowConfig::proxy_url` `WebviewBuilder::proxy_url() / WebviewWindowBuilder::proxy_url()` options when creating a webview.\n\n### Breaking Changes\n\n- [`8de308d1`](https://www.github.com/tauri-apps/tauri/commit/8de308d1bf6a855d7a26af58bd0e744938ba47d8)([#8723](https://www.github.com/tauri-apps/tauri/pull/8723)) Restructured Tauri config per [RFC#5](https://github.com/tauri-apps/rfcs/blob/f3e82a6b0c5390401e855850d47dc7b7d9afd684/texts/0005-tauri-config-restructure.md):\n\n  - Moved `package.productName`, `package.version` and `tauri.bundle.identifier` fields to the top-level.\n  - Removed `package` object.\n  - Renamed `tauri` object to `app`.\n  - Moved `tauri.bundle` object to the top-level.\n  - Renamed `build.distDir` field to `frontendDist`.\n  - Renamed `build.devPath` field to `devUrl` and will no longer accepts paths, it will only accept URLs.\n  - Moved `tauri.pattern` to `app.security.pattern`.\n  - Removed `tauri.bundle.updater` object, and its fields have been moved to the updater plugin under `plugins.updater` object.\n  - Moved `build.withGlobalTauri` to `app.withGlobalTauri`.\n  - Moved `tauri.bundle.dmg` object to `bundle.macOS.dmg`.\n  - Moved `tauri.bundle.deb` object to `bundle.linux.deb`.\n  - Moved `tauri.bundle.appimage` object to `bundle.linux.appimage`.\n  - Removed all license fields from each bundle configuration object and instead added `bundle.license` and `bundle.licenseFile`.\n  - Renamed `AppUrl` to `FrontendDist` and refactored its variants to be more explicit.\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Renamed `config::WindowUrl` to `config::WebviewUrl`.\n- [`a093682d`](https://www.github.com/tauri-apps/tauri/commit/a093682d2df7169b024bb4f736c7f1fd2ea8b327)([#8621](https://www.github.com/tauri-apps/tauri/pull/8621)) Changed `error` field in `ConfigError::FormatToml` to be boxed `Box<toml::de::Error>` to reduce the enum `ConfigError` size in memory.\n- [`58fe2e81`](https://www.github.com/tauri-apps/tauri/commit/58fe2e812a85b9f4eba105286a63f271ea637836)([#8670](https://www.github.com/tauri-apps/tauri/pull/8670)) Changed `dist_dir` and `dev_path` config options to be optional.\n\n## \\[2.0.0-alpha.13]\n\n### Bug Fixes\n\n- [`9b230de7`](https://www.github.com/tauri-apps/tauri/commit/9b230de7bc6690c2733f5324d50b999af1f7a6ef)([#8407](https://www.github.com/tauri-apps/tauri/pull/8407)) Fix compile error when parsing config that includes float values.\n\n## \\[2.0.0-alpha.12]\n\n### New Features\n\n- [\\`\\`](https://www.github.com/tauri-apps/tauri/commit/undefined) Add bundle DMG configuration options.\n\n## \\[2.0.0-alpha.11]\n\n### Breaking Changes\n\n- [`5e84e92e`](https://www.github.com/tauri-apps/tauri/commit/5e84e92e99376f24b730f8eba002239379b593e1)([#8243](https://www.github.com/tauri-apps/tauri/pull/8243)) Changed `platform::windows_version` to return a `(u32, u32, u32)` instead of `Option<(u32, u32, u32)>`\n\n## \\[2.0.0-alpha.10]\n\n### Enhancements\n\n- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release.\n\n### Dependencies\n\n- [`c7c2507d`](https://www.github.com/tauri-apps/tauri/commit/c7c2507da16a9beb71bf06745fe7ac1325ab7c2a)([#8035](https://www.github.com/tauri-apps/tauri/pull/8035)) Update `windows` to version `0.51` and `webview2-com` to version `0.27`\n\n## \\[2.0.0-alpha.9]\n\n### New Features\n\n- [`c085adda`](https://www.github.com/tauri-apps/tauri/commit/c085addab58ba851398373c6fd13f9cb026d71e8)([#8009](https://www.github.com/tauri-apps/tauri/pull/8009)) Added `set_progress_bar` to `Window`.\n- [`c1ec0f15`](https://www.github.com/tauri-apps/tauri/commit/c1ec0f155118527361dd5645d920becbc8afd569)([#7933](https://www.github.com/tauri-apps/tauri/pull/7933)) Added the `always_on_bottom` option to the window configuration.\n- [`880266a7`](https://www.github.com/tauri-apps/tauri/commit/880266a7f697e1fe58d685de3bb6836ce5251e92)([#8031](https://www.github.com/tauri-apps/tauri/pull/8031)) Bump the MSRV to 1.70.\n- [`ed32257d`](https://www.github.com/tauri-apps/tauri/commit/ed32257d044f90b5eb15053efd1667125def2d2b)([#7794](https://www.github.com/tauri-apps/tauri/pull/7794)) On Windows, add `WindowEffect::Tabbed`,`WindowEffect::TabbedDark` and `WindowEffect::TabbedLight`\n\n### Breaking Changes\n\n- [`ebcc21e4`](https://www.github.com/tauri-apps/tauri/commit/ebcc21e4b95f4e8c27639fb1bca545b432f52d5e)([#8057](https://www.github.com/tauri-apps/tauri/pull/8057)) Renamed the beforeDevCommand, beforeBuildCommand and beforeBundleCommand hooks environment variables from `TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG` to `TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG` to differentiate the prefix with other CLI environment variables.\n\n## \\[2.0.0-alpha.8]\n\n### Enhancements\n\n- Add an option to specify `id` for the tray icon in the tauri configuration file.\n\n### Breaking Changes\n\n- [`100d9ede`](https://www.github.com/tauri-apps/tauri/commit/100d9ede35995d9db21d2087dd5606adfafb89a5)([#7802](https://www.github.com/tauri-apps/tauri/pull/7802)) Follow file name conventions set by desktop for mobile Tauri configuration files. Added `target` argument on most `config::parse` methods.\n\n## \\[2.0.0-alpha.7]\n\n### New Features\n\n- [`4db363a0`](https://www.github.com/tauri-apps/tauri/commit/4db363a03c182349f8491f46ced258d84723b11f)([#6589](https://www.github.com/tauri-apps/tauri/pull/6589)) Added `visible_on_all_workspaces` configuration option to `WindowBuilder`, `Window`, and `WindowConfig`.\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) Add option to specify a tooltip text for the tray icon in the config.\n- [`74b1f4fc`](https://www.github.com/tauri-apps/tauri/commit/74b1f4fc6625d5b4f9b86f70e4eebd6551c61809)([#7384](https://www.github.com/tauri-apps/tauri/pull/7384)) Add `WindowEffect::MicaDark` and `WindowEffect::MicaLight`\n- [`3b98141a`](https://www.github.com/tauri-apps/tauri/commit/3b98141aa26f74c641a4090874247b97079bd58a)([#3736](https://www.github.com/tauri-apps/tauri/pull/3736)) Add a configuration object for file associations under `BundleConfig`.\n\n### Enhancements\n\n- [`fbeb5b91`](https://www.github.com/tauri-apps/tauri/commit/fbeb5b9185baeda19e865228179e3e44c165f1d9)([#7170](https://www.github.com/tauri-apps/tauri/pull/7170)) Use custom protocols on the IPC implementation to enhance performance.\n\n### Security fixes\n\n- [`43c6285e`](https://www.github.com/tauri-apps/tauri/commit/43c6285e9006fb84066461d57fe09ea8db76d636)([#7359](https://www.github.com/tauri-apps/tauri/pull/7359)) Changed HTML implementation from unmaintained `kuchiki` to `kuchikiki`.\n\n### Breaking Changes\n\n- [`7fb419c3`](https://www.github.com/tauri-apps/tauri/commit/7fb419c326aaf72ecd556d8404377444ebb200e7)([#7535](https://www.github.com/tauri-apps/tauri/pull/7535)) `systemTray` config option has been renamed to `trayIcon`.\n\n## \\[2.0.0-alpha.6]\n\n### New Features\n\n- [`e0f0dce2`](https://www.github.com/tauri-apps/tauri/commit/e0f0dce220730e2822fc202463aedf0166145de7)([#6442](https://www.github.com/tauri-apps/tauri/pull/6442)) Added the `window_effects` option to the window configuration.\n\n## \\[2.0.0-alpha.5]\n\n- [`9a79dc08`](https://www.github.com/tauri-apps/tauri/commit/9a79dc085870e0c1a5df13481ff271b8c6cc3b78)([#6947](https://www.github.com/tauri-apps/tauri/pull/6947)) Remove `enable_tauri_api` from the IPC scope.\n- [`09376af5`](https://www.github.com/tauri-apps/tauri/commit/09376af59424cc27803fa2820d2ac0d4cdc90a6d)([#6704](https://www.github.com/tauri-apps/tauri/pull/6704)) Moved the `cli` feature to its own plugin in the plugins-workspace repository.\n- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Moved the `protocol` scope configuration to the `asset_protocol` field in `SecurityConfig`.\n- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Moved the updater configuration to the `BundleConfig`.\n- [`b072daa3`](https://www.github.com/tauri-apps/tauri/commit/b072daa3bd3e38b808466666619ddb885052c5b2)([#6919](https://www.github.com/tauri-apps/tauri/pull/6919)) Moved the `updater` feature to its own plugin in the plugins-workspace repository.\n- [`3188f376`](https://www.github.com/tauri-apps/tauri/commit/3188f3764978c6d1452ee31d5a91469691e95094)([#6883](https://www.github.com/tauri-apps/tauri/pull/6883)) Bump the MSRV to 1.65.\n- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Removed the allowlist configuration.\n- [`2d5378bf`](https://www.github.com/tauri-apps/tauri/commit/2d5378bfc1ba817ee2f331b41738a90e5997e5e8)([#6717](https://www.github.com/tauri-apps/tauri/pull/6717)) Remove the updater's dialog option.\n\n## \\[2.0.0-alpha.4]\n\n- Added `android` configuration object under `tauri > bundle`.\n  - [db4c9dc6](https://www.github.com/tauri-apps/tauri/commit/db4c9dc655e07ee2184fe04571f500f7910890cd) feat(core): add option to configure Android's minimum SDK version ([#6651](https://www.github.com/tauri-apps/tauri/pull/6651)) on 2023-04-07\n\n## \\[2.0.0-alpha.3]\n\n- Pull changes from Tauri 1.3 release.\n  - [](https://www.github.com/tauri-apps/tauri/commit/undefined)  on undefined\n\n## \\[2.0.0-alpha.2]\n\n- Added the `shadow` option to the window configuration and `set_shadow` option to the `window` allow list.\n  - [a81750d7](https://www.github.com/tauri-apps/tauri/commit/a81750d779bc72f0fdb7de90b7fbddfd8049b328) feat(core): add shadow APIs ([#6206](https://www.github.com/tauri-apps/tauri/pull/6206)) on 2023-02-08\n\n## \\[2.0.0-alpha.1]\n\n- Bump the MSRV to 1.64.\n  - [7eb9aa75](https://www.github.com/tauri-apps/tauri/commit/7eb9aa75cfd6a3176d3f566fdda02d88aa529b0f) Update gtk to 0.16 ([#6155](https://www.github.com/tauri-apps/tauri/pull/6155)) on 2023-01-30\n- Added `crate_name` field on `PackageInfo`.\n  - [630a7f4b](https://www.github.com/tauri-apps/tauri/commit/630a7f4b18cef169bfd48673609306fec434e397) refactor: remove mobile log initialization, ref [#6049](https://www.github.com/tauri-apps/tauri/pull/6049) ([#6081](https://www.github.com/tauri-apps/tauri/pull/6081)) on 2023-01-17\n\n## \\[2.0.0-alpha.0]\n\n- Parse `android` and `ios` Tauri configuration files.\n  - [b3a3afc7](https://www.github.com/tauri-apps/tauri/commit/b3a3afc7de8de4021d73559288f5192732a706cf) feat(core): detect android and ios platform configuration files ([#4997](https://www.github.com/tauri-apps/tauri/pull/4997)) on 2022-08-22\n- First mobile alpha release!\n  - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08\n\n## \\[1.5.3]\n\n### New features\n\n- [`7aa30dec`](https://www.github.com/tauri-apps/tauri/commit/7aa30dec85a17c3d3faaf3841b93e10991b991b0)([#8620](https://www.github.com/tauri-apps/tauri/pull/8620)) Add `priority`, `section` and `changelog` options in Debian config.\n\n## \\[1.5.2]\n\n### Bug Fixes\n\n- [`9b230de7`](https://www.github.com/tauri-apps/tauri/commit/9b230de7bc6690c2733f5324d50b999af1f7a6ef)([#8407](https://www.github.com/tauri-apps/tauri/pull/8407)) Fix compile error when parsing config that includes float values.\n\n## \\[1.5.3]\n\n### New Features\n\n- [`b3e53e72`](https://www.github.com/tauri-apps/tauri/commit/b3e53e7243311a2659b7569dddc20c56ac9f9d8e)([#8288](https://www.github.com/tauri-apps/tauri/pull/8288)) Added `Assets::iter` to iterate on all embedded assets.\n\n## \\[1.5.0]\n\n### New Features\n\n- [`4dd4893d`](https://www.github.com/tauri-apps/tauri/commit/4dd4893d7d166ac3a3b6dc2e3bd2540326352a78)([#5950](https://www.github.com/tauri-apps/tauri/pull/5950)) Allow specifying resources as a map specifying source and target paths.\n\n### Enhancements\n\n- [`9aa34ada`](https://www.github.com/tauri-apps/tauri/commit/9aa34ada5769dbefa7dfe5f7a6288b3d20b294e4)([#7645](https://www.github.com/tauri-apps/tauri/pull/7645)) Add setting to switch to `http://<scheme>.localhost/` for custom protocols on Windows.\n\n### Bug Fixes\n\n- [`a6b52e44`](https://www.github.com/tauri-apps/tauri/commit/a6b52e44f22844009e273fb0250368d7a463f095)([#6519](https://www.github.com/tauri-apps/tauri/pull/6519)) Fix `io::read_line` not including the new line character `\\n`.\n\n### Security fixes\n\n- [`eeff1784`](https://www.github.com/tauri-apps/tauri/commit/eeff1784e1ffa568e4ba024e17dd611f8e086784)([#7367](https://www.github.com/tauri-apps/tauri/pull/7367)) Changed HTML implementation from unmaintained `kuchiki` to `kuchikiki`.\n\n## \\[1.4.0]\n\n### New Features\n\n- [`acc36fe1`](https://www.github.com/tauri-apps/tauri/commit/acc36fe1176cc8aa9063bde932abeb94796c5c72)([#6158](https://www.github.com/tauri-apps/tauri/pull/6158)) Add option to configure `require_literal_leading_dot` on `fs` and `asset` protocol scopes.\n- [`35cd751a`](https://www.github.com/tauri-apps/tauri/commit/35cd751adc6fef1f792696fa0cfb471b0bf99374)([#5176](https://www.github.com/tauri-apps/tauri/pull/5176)) Added the `desktop_template` option on `tauri.conf.json > tauri > bundle > deb`.\n- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `maximizable`, `minimizable` and `closable` options to the window configuration.\n- [`3cb7a3e6`](https://www.github.com/tauri-apps/tauri/commit/3cb7a3e642bb10ee90dc1d24daa48b8c8c15c9ce)([#6997](https://www.github.com/tauri-apps/tauri/pull/6997)) Add `MimeType::parse_with_fallback` and `MimeType::parse_from_uri_with_fallback`\n- [`29488205`](https://www.github.com/tauri-apps/tauri/commit/2948820579d20dfaa0861c2f0a58bd7737a7ffd1)([#6867](https://www.github.com/tauri-apps/tauri/pull/6867)) Allow specifying custom language files of Tauri's custom messages for the NSIS installer\n- [`e092f799`](https://www.github.com/tauri-apps/tauri/commit/e092f799469ff32c7d1595d0f07d06fd2dab5c29)([#6887](https://www.github.com/tauri-apps/tauri/pull/6887)) Add `nsis > template` option to specify custom NSIS installer template.\n- [`cd3846c8`](https://www.github.com/tauri-apps/tauri/commit/cd3846c8ce61ab2879b3911e831525e6242aaab2)([#6955](https://www.github.com/tauri-apps/tauri/pull/6955)) Add `WindowsUpdateInstallMode::nsis_args`\n- [`85e77fb7`](https://www.github.com/tauri-apps/tauri/commit/85e77fb797ec17882f55d0944578d929fc6c9c1f)([#6762](https://www.github.com/tauri-apps/tauri/pull/6762)) Correctly determine MIME type of `.txt` files.\n\n## \\[1.3.0]\n\n- Added the `additional_browser_args` option to the window configuration.\n  - [3dc38b15](https://www.github.com/tauri-apps/tauri/commit/3dc38b150ea8c59c8ba67fd586f921016928f47c) feat(core): expose additional_browser_args to window config (fix: [#5757](https://www.github.com/tauri-apps/tauri/pull/5757)) ([#5799](https://www.github.com/tauri-apps/tauri/pull/5799)) on 2022-12-14\n- Added the `content_protected` option to the window configuration.\n  - [4ab5545b](https://www.github.com/tauri-apps/tauri/commit/4ab5545b7a831c549f3c65e74de487ede3ab7ce5) feat: add content protection api, closes [#5132](https://www.github.com/tauri-apps/tauri/pull/5132) ([#5513](https://www.github.com/tauri-apps/tauri/pull/5513)) on 2022-12-13\n- Correctly determine mime type of `.less`, `.sass` and `.styl` files.\n  - [5fdf8dcb](https://www.github.com/tauri-apps/tauri/commit/5fdf8dcb8ed171d06121dceb32078a7e4f86cc64) fix(core): mime type of .less, .sass and .styl files ([#6316](https://www.github.com/tauri-apps/tauri/pull/6316)) on 2023-02-19\n- Bump minimum supported Rust version to 1.60.\n  - [5fdc616d](https://www.github.com/tauri-apps/tauri/commit/5fdc616df9bea633810dcb814ac615911d77222c) feat: Use the zbus-backed of notify-rust ([#6332](https://www.github.com/tauri-apps/tauri/pull/6332)) on 2023-03-31\n- Add initial support for building `nsis` bundles on non-Windows platforms.\n  - [60e6f6c3](https://www.github.com/tauri-apps/tauri/commit/60e6f6c3f1605f3064b5bb177992530ff788ccf0) feat(bundler): Add support for creating NSIS bundles on unix hosts ([#5788](https://www.github.com/tauri-apps/tauri/pull/5788)) on 2023-01-19\n- Add `nsis` bundle target\n  - [c94e1326](https://www.github.com/tauri-apps/tauri/commit/c94e1326a7c0767a13128a8b1d327a00156ece12) feat(bundler): add `nsis`, closes [#4450](https://www.github.com/tauri-apps/tauri/pull/4450), closes [#2319](https://www.github.com/tauri-apps/tauri/pull/2319) ([#4674](https://www.github.com/tauri-apps/tauri/pull/4674)) on 2023-01-03\n- Added configuration to specify remote URLs allowed to access the IPC.\n  - [ee71c31f](https://www.github.com/tauri-apps/tauri/commit/ee71c31fd09cc5427da6d29d37c003a914547696) feat(core): allow configuring remote domains with IPC access, closes [#5088](https://www.github.com/tauri-apps/tauri/pull/5088) ([#5918](https://www.github.com/tauri-apps/tauri/pull/5918)) on 2023-04-11\n\n## \\[1.2.1]\n\n- Fix `allowlist > app > show/hide` always disabled when `allowlist > app > all: false`.\n  - [bb251087](https://www.github.com/tauri-apps/tauri/commit/bb2510876d0bdff736d36bf3a465cdbe4ad2b90c) fix(core): extend allowlist with `app`'s allowlist, closes [#5650](https://www.github.com/tauri-apps/tauri/pull/5650) ([#5652](https://www.github.com/tauri-apps/tauri/pull/5652)) on 2022-11-18\n\n## \\[1.2.0]\n\n- Validate `package > productName` in the tauri config and produce errors if it contains one of the following characters `/\\:*?\\\"<>|`\n  - [b9316a64](https://www.github.com/tauri-apps/tauri/commit/b9316a64eaa9348c79efafb8b94960d9b4d5b27a) fix(cli): validate `productName` in config, closes [#5233](https://www.github.com/tauri-apps/tauri/pull/5233) ([#5262](https://www.github.com/tauri-apps/tauri/pull/5262)) on 2022-09-28\n- Properly serialize HTML template tags.\n  - [aec5537d](https://www.github.com/tauri-apps/tauri/commit/aec5537de0205f62b2ae5c89da04d21930a6fc2e) fix(codegen): serialize template tags, closes [#4410](https://www.github.com/tauri-apps/tauri/pull/4410) ([#5247](https://www.github.com/tauri-apps/tauri/pull/5247)) on 2022-09-28\n- `PatternKind::Isolation` is now defined even without the `isolation` feature.\n  - [a178f95d](https://www.github.com/tauri-apps/tauri/commit/a178f95d68b773779b40235a3a22115a5e36aa6a) feat: config schema generator ([#5193](https://www.github.com/tauri-apps/tauri/pull/5193)) on 2022-10-28\n- Added the `app` allowlist module.\n  - [39bf895b](https://www.github.com/tauri-apps/tauri/commit/39bf895b73ec6b53f5758815396ba85dda6b9c67) feat(macOS): Add application `show` and `hide` methods ([#3689](https://www.github.com/tauri-apps/tauri/pull/3689)) on 2022-10-03\n- - [7d9aa398](https://www.github.com/tauri-apps/tauri/commit/7d9aa3987efce2d697179ffc33646d086c68030c) feat: bump MSRV to 1.59 ([#5296](https://www.github.com/tauri-apps/tauri/pull/5296)) on 2022-09-28\n- Add `tauri.conf.json > bundle > publisher` field to specify the app publisher.\n  - [628285c1](https://www.github.com/tauri-apps/tauri/commit/628285c1cf43f03ed62378f3b6cc0c991317526f) feat(bundler): add `publisher` field, closes [#5273](https://www.github.com/tauri-apps/tauri/pull/5273) ([#5283](https://www.github.com/tauri-apps/tauri/pull/5283)) on 2022-09-28\n- Canonicalize the return value of `platform::resource_dir`.\n  - [a06dc699](https://www.github.com/tauri-apps/tauri/commit/a06dc6993148f10ff7623c9dcc81f313dd960ad0) fix(core): canonicalize resource dir to fix scope check, closes [#5196](https://www.github.com/tauri-apps/tauri/pull/5196) ([#5218](https://www.github.com/tauri-apps/tauri/pull/5218)) on 2022-09-29\n- Added `title` option on the system tray configuration (macOS only).\n  - [8f1ace77](https://www.github.com/tauri-apps/tauri/commit/8f1ace77956ac3477826ceb059a191e55b3fff93) feat: expose `set_title` for MacOS tray ([#5182](https://www.github.com/tauri-apps/tauri/pull/5182)) on 2022-09-30\n- Added the `user_agent` option to the window configuration.\n  - [a6c94119](https://www.github.com/tauri-apps/tauri/commit/a6c94119d8545d509723b147c273ca5edfe3729f) feat(core): expose user_agent to window config ([#5317](https://www.github.com/tauri-apps/tauri/pull/5317)) on 2022-10-02\n- Add `mime_type` module.\n  - [54c337e0](https://www.github.com/tauri-apps/tauri/commit/54c337e06f3bc624c4780cf002bc54790f446c90) feat(cli): hotreload support for frontend static files, closes [#2173](https://www.github.com/tauri-apps/tauri/pull/2173) ([#5256](https://www.github.com/tauri-apps/tauri/pull/5256)) on 2022-09-28\n\n## \\[1.1.1]\n\n- Add missing allowlist config for `set_cursor_grab`, `set_cursor_visible`, `set_cursor_icon` and `set_cursor_position` APIs.\n  - [c764408d](https://www.github.com/tauri-apps/tauri/commit/c764408da7fae123edd41115bda42fa75a4731d2) fix: Add missing allowlist config for cursor apis, closes [#5207](https://www.github.com/tauri-apps/tauri/pull/5207) ([#5211](https://www.github.com/tauri-apps/tauri/pull/5211)) on 2022-09-16\n\n## \\[1.1.0]\n\n- Allow adding `build > beforeBundleCommand` in tauri.conf.json to run a shell command before the bundling phase.\n  - [57ab9847](https://www.github.com/tauri-apps/tauri/commit/57ab9847eb2d8c9a5da584b873b7c072e9ee26bf) feat(cli): add `beforeBundleCommand`, closes [#4879](https://www.github.com/tauri-apps/tauri/pull/4879) ([#4893](https://www.github.com/tauri-apps/tauri/pull/4893)) on 2022-08-09\n- Change `before_dev_command` and `before_build_command` config value to allow configuring the current working directory.\n  - [d6f7d3cf](https://www.github.com/tauri-apps/tauri/commit/d6f7d3cfe8a7ec8d68c8341016c4e0a3103da587) Add cwd option to `before` commands, add wait option to dev [#4740](https://www.github.com/tauri-apps/tauri/pull/4740) [#3551](https://www.github.com/tauri-apps/tauri/pull/3551) ([#4834](https://www.github.com/tauri-apps/tauri/pull/4834)) on 2022-08-02\n- Allow configuring the `before_dev_command` to force the CLI to wait for the command to finish before proceeding.\n  - [d6f7d3cf](https://www.github.com/tauri-apps/tauri/commit/d6f7d3cfe8a7ec8d68c8341016c4e0a3103da587) Add cwd option to `before` commands, add wait option to dev [#4740](https://www.github.com/tauri-apps/tauri/pull/4740) [#3551](https://www.github.com/tauri-apps/tauri/pull/3551) ([#4834](https://www.github.com/tauri-apps/tauri/pull/4834)) on 2022-08-02\n- Added support to configuration files in TOML format (Tauri.toml file).\n  - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02\n- Refactored the `config::parse` module.\n  - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02\n- Update windows to 0.39.0 and webview2-com to 0.19.1.\n  - [e6d9b670](https://www.github.com/tauri-apps/tauri/commit/e6d9b670b0b314ed667b0e164f2c8d27048e678f) refactor: remove unneeded focus code ([#5065](https://www.github.com/tauri-apps/tauri/pull/5065)) on 2022-09-03\n\n## \\[1.0.3]\n\n- Added `menu_on_left_click: bool` to the `SystemTrayConfig`.\n  - [f8a3becb](https://www.github.com/tauri-apps/tauri/commit/f8a3becb287942db7f7b551b5db6aeb5a2e939ee) feat(core): add option to disable tray menu on left click, closes [#4584](https://www.github.com/tauri-apps/tauri/pull/4584) ([#4587](https://www.github.com/tauri-apps/tauri/pull/4587)) on 2022-07-05\n- Added `config::parse::read_platform` and `config::parse::get_platform_config_filename`.\n  - [8e3e7fc6](https://www.github.com/tauri-apps/tauri/commit/8e3e7fc64641afc7a6833bc93205e6f525562545) feat(cli): improve bundle identifier validation, closes [#4589](https://www.github.com/tauri-apps/tauri/pull/4589) ([#4596](https://www.github.com/tauri-apps/tauri/pull/4596)) on 2022-07-05\n\n## \\[1.0.2]\n\n- Expose `platform::windows_version` function.\n  - [bf764e83](https://www.github.com/tauri-apps/tauri/commit/bf764e83e01e7443e6cc54572001e1c98c357465) feat(utils): expose `windows_version` function ([#4534](https://www.github.com/tauri-apps/tauri/pull/4534)) on 2022-06-30\n\n## \\[1.0.1]\n\n- Changed the `BundleConfig::targets` to a `BundleTarget` enum to enhance generated documentation.\n  - [31c15cd2](https://www.github.com/tauri-apps/tauri/commit/31c15cd2bd94dbe39fb94982a15cbe02ac5d8925) docs(config): enhance documentation for bundle targets, closes [#3251](https://www.github.com/tauri-apps/tauri/pull/3251) ([#4418](https://www.github.com/tauri-apps/tauri/pull/4418)) on 2022-06-21\n- Added `platform::is_windows_7`.\n  - [57039fb2](https://www.github.com/tauri-apps/tauri/commit/57039fb2166571de85271b014a8711a29f06be1a) fix(core): add windows 7 notification support ([#4491](https://www.github.com/tauri-apps/tauri/pull/4491)) on 2022-06-28\n- Suppress unused variable warning in release builds.\n  - [45981851](https://www.github.com/tauri-apps/tauri/commit/45981851e35119266c1a079e1ff27a39f1fdfaed) chore(lint): unused variable warnings for release builds ([#4411](https://www.github.com/tauri-apps/tauri/pull/4411)) on 2022-06-22\n- Added webview install mode options.\n  - [2ca762d2](https://www.github.com/tauri-apps/tauri/commit/2ca762d207030a892a6d128b519e150e2d733468) feat(bundler): extend webview2 installation options, closes [#2882](https://www.github.com/tauri-apps/tauri/pull/2882) [#2452](https://www.github.com/tauri-apps/tauri/pull/2452) ([#4466](https://www.github.com/tauri-apps/tauri/pull/4466)) on 2022-06-26\n\n## \\[1.0.0]\n\n- Upgrade to `stable`!\n  - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15\n\n## \\[1.0.0-rc.9]\n\n- Added a config flag to bundle the media framework used by webkit2gtk `tauri.conf.json > tauri > bundle > appimage > bundleMediaFramework`.\n  - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10\n\n## \\[1.0.0-rc.8]\n\n- **Breaking change:** `PackageInfo::version` is now a `semver::Version` instead of a `String`.\n  - [2badbd2d](https://www.github.com/tauri-apps/tauri/commit/2badbd2d7ed51bf33c1b547b4c837b600574bd4a) refactor: force semver versions, change updater `should_install` sig ([#4215](https://www.github.com/tauri-apps/tauri/pull/4215)) on 2022-05-25\n  - [a7388e23](https://www.github.com/tauri-apps/tauri/commit/a7388e23c3b9019d48b078cae00a75c74d74d11b) fix(ci): adjust change file to include tauri-utils and tauri-codegen on 2022-05-27\n\n## \\[1.0.0-rc.7]\n\n- Allow configuring the display options for the MSI execution allowing quieter updates.\n  - [9f2c3413](https://www.github.com/tauri-apps/tauri/commit/9f2c34131952ea83c3f8e383bc3cec7e1450429f) feat(core): configure msiexec display options, closes [#3951](https://www.github.com/tauri-apps/tauri/pull/3951) ([#4061](https://www.github.com/tauri-apps/tauri/pull/4061)) on 2022-05-15\n\n## \\[1.0.0-rc.6]\n\n- Added `$schema` support to `tauri.conf.json`.\n  - [715cbde3](https://www.github.com/tauri-apps/tauri/commit/715cbde3842a916c4ebeab2cab348e1774b5c192) feat(config): add `$schema` to `tauri.conf.json`, closes [#3464](https://www.github.com/tauri-apps/tauri/pull/3464) ([#4031](https://www.github.com/tauri-apps/tauri/pull/4031)) on 2022-05-03\n- The `dangerous_allow_asset_csp_modification` configuration value has been changed to allow a list of CSP directives to disable.\n  - [164078c0](https://www.github.com/tauri-apps/tauri/commit/164078c0b719ccbc12e956fecf8a7d4a3c5044e1) feat: allow limiting dangerousDisableAssetCspModification, closes [#3831](https://www.github.com/tauri-apps/tauri/pull/3831) ([#4021](https://www.github.com/tauri-apps/tauri/pull/4021)) on 2022-05-02\n\n## \\[1.0.0-rc.5]\n\n- Added the `io` module with the `read_line` method.\n  - [b5622882](https://www.github.com/tauri-apps/tauri/commit/b5622882cf3748e1e5a90915f415c0cd922aaaf8) fix(cli): exit on non-compilation Cargo errors, closes [#3930](https://www.github.com/tauri-apps/tauri/pull/3930) ([#3942](https://www.github.com/tauri-apps/tauri/pull/3942)) on 2022-04-22\n- **Breaking change:** Removed the `useBootstrapper` option. Use https://github.com/tauri-apps/fix-path-env-rs instead.\n  - [6a5ff08c](https://www.github.com/tauri-apps/tauri/commit/6a5ff08ce9052b656aa40accedfd4315825164a3) refactor: remove bootstrapper, closes [#3786](https://www.github.com/tauri-apps/tauri/pull/3786) ([#3832](https://www.github.com/tauri-apps/tauri/pull/3832)) on 2022-03-31\n\n## \\[1.0.0-rc.4]\n\n- Added an option to disable the CSP injection of distributable assets nonces and hashes.\n  - [f6e32ee1](https://www.github.com/tauri-apps/tauri/commit/f6e32ee1880eb364ed76beb937c9d12e14d54910) feat(core): add dangerous option to disable compile time CSP injection ([#3775](https://www.github.com/tauri-apps/tauri/pull/3775)) on 2022-03-28\n\n- Use the default value for `MacConfig.minimumSystemVersion` if the value is set to an empty string.\n  - [c81534eb](https://www.github.com/tauri-apps/tauri/commit/c81534ebd873c358e0346c7949aeb171803149a5) feat(cli): use default macOS minimum system version when it is empty ([#3658](https://www.github.com/tauri-apps/tauri/pull/3658)) on 2022-03-13\n\n- Replace multiple dependencies who's C code compiled concurrently and caused\n  the other ones to bloat compile time significantly.\n\n- `zstd` -> `brotli`\n\n- `blake3` -> a vendored version of the blake3 reference\n\n- `ring` -> `getrandom`\n\nSee https://github.com/tauri-apps/tauri/pull/3773 for more information about\nthese specific choices.\n\n- [8661e3e2](https://www.github.com/tauri-apps/tauri/commit/8661e3e24d96c399bfbcdee5d8e9d6beba2265a7) replace dependencies with long build times when used together (closes [#3571](https://www.github.com/tauri-apps/tauri/pull/3571)) ([#3773](https://www.github.com/tauri-apps/tauri/pull/3773)) on 2022-03-27\n\n## \\[1.0.0-rc.3]\n\n- Use `is_symlink` API compatible with Rust v1.57 instead of std/fs/struct.Metadata.html#method.is_symlink.\n  - [73388119](https://www.github.com/tauri-apps/tauri/commit/73388119e653e7902b19beef2ab6d7c5f8a7b83a) use older symlink check function ([#3579](https://www.github.com/tauri-apps/tauri/pull/3579)) on 2022-03-01\n\n## \\[1.0.0-rc.2]\n\n- Changed the default value for `tauri > bundle > macOS > minimumSystemVersion` to `10.13`.\n  - [fce344b9](https://www.github.com/tauri-apps/tauri/commit/fce344b90b7227f8f5514853c2f885fb24d3648e) feat(core): set default value for `minimum_system_version` to 10.13 ([#3497](https://www.github.com/tauri-apps/tauri/pull/3497)) on 2022-02-17\n\n## \\[1.0.0-rc.1]\n\n- Change default value for the `freezePrototype` configuration to `false`.\n  - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12\n\n## \\[1.0.0-rc.0]\n\n- The `allowlist` configuration now includes a `clipboard` object, controlling the exposure of the `writeText` and `readText` APIs.\n  - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \\[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09\n- The dialog allowlist now includes flags for the `message`, `ask` and `confirm` APIs.\n  - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \\[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09\n- The `allowlist` configuration now includes a `process` object, controlling the exposure of the `relaunch` and `exit` APIs.\n  - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \\[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09\n- The `window` allowlist now includes options to enable all window modification APIs: `center`, `close`, `create`, `hide`, `maximize`, `minimize`, `print`, `requestUserAttention`, `setAlwaysOnTop`, `setDecorations`, `setFocus`, `setFullscreen`, `setIcon`, `setMaxSize`, `setMinSize`, `setPosition`, `setResizable`, `setSize`, `setSkipTaskbar`, `setTitle`, `show`, `startDragging`, `unmaximize` and `unminimize`.\n  - [d660cab3](https://www.github.com/tauri-apps/tauri/commit/d660cab38d7d703e8b2bb85a3e9462d9e28b086b) feat: enhance allowlist configuration \\[TRI-027] ([#11](https://www.github.com/tauri-apps/tauri/pull/11)) on 2022-01-09\n- Added `asset` allowlist configuration, which enables the `asset` protocol and defines it access scope.\n  - [7920ff14](https://www.github.com/tauri-apps/tauri/commit/7920ff14e6424079c48ea5645d9aa13e7a272b87) feat: scope the `fs` API and the `asset` protocol \\[TRI-026] \\[TRI-010] \\[TRI-011] ([#10](https://www.github.com/tauri-apps/tauri/pull/10)) on 2022-01-09\n- Change `CliArg` numeric types from `u64` to `usize`.\n  - [1f988535](https://www.github.com/tauri-apps/tauri/commit/1f98853573a837dd0cfc2161b206a5033ec2da5e) chore(deps) Update Tauri Core ([#2480](https://www.github.com/tauri-apps/tauri/pull/2480)) on 2021-08-24\n- Apply `nonce` to `script` and `style` tags and set them on the `CSP` (`script-src` and `style-src` fetch directives).\n  - [cf54dcf9](https://www.github.com/tauri-apps/tauri/commit/cf54dcf9c81730e42c9171daa9c8aa474c95b522) feat: improve `CSP` security with nonces and hashes, add `devCsp` \\[TRI-004] ([#8](https://www.github.com/tauri-apps/tauri/pull/8)) on 2022-01-09\n- The path returned from `tauri::api::process::current_binary` is now cached when loading the binary.\n  - [7c3db7a3](https://www.github.com/tauri-apps/tauri/commit/7c3db7a3811fd4de3e71c78cfd00894fa51ab786) cache current binary path much sooner ([#45](https://www.github.com/tauri-apps/tauri/pull/45)) on 2022-02-01\n- Added `dev_csp` to the `security` configuration object.\n  - [cf54dcf9](https://www.github.com/tauri-apps/tauri/commit/cf54dcf9c81730e42c9171daa9c8aa474c95b522) feat: improve `CSP` security with nonces and hashes, add `devCsp` \\[TRI-004] ([#8](https://www.github.com/tauri-apps/tauri/pull/8)) on 2022-01-09\n- Fixes resource directory resolution on Linux.\n  - [1a28904b](https://www.github.com/tauri-apps/tauri/commit/1a28904b8ebea92e143d5dc21ebd209e9edec531) fix(core): resource path resolution on Linux, closes [#2493](https://www.github.com/tauri-apps/tauri/pull/2493) on 2021-08-22\n- Allow using a fixed version for the Webview2 runtime via the `tauri > bundle > windows > webviewFixedRuntimePath` config option.\n  - [85df94f2](https://www.github.com/tauri-apps/tauri/commit/85df94f2b0d40255812b42c5e32a70c4b45392df) feat(core): config for fixed webview2 runtime version path ([#27](https://www.github.com/tauri-apps/tauri/pull/27)) on 2021-11-02\n- The updater `pubkey` is now a required field for security reasons. Sign your updates with the `tauri signer` command.\n  - [d95cc831](https://www.github.com/tauri-apps/tauri/commit/d95cc83105dda52df7514e30e54f3676cdb374ee) feat: enforce updater public key \\[TRI-015] ([#42](https://www.github.com/tauri-apps/tauri/pull/42)) on 2022-01-09\n- Added the `isolation` pattern.\n  - [d5d6d2ab](https://www.github.com/tauri-apps/tauri/commit/d5d6d2abc17cd89c3a079d2ce01581193469dbc0) Isolation Pattern ([#43](https://www.github.com/tauri-apps/tauri/pull/43)) Co-authored-by: Ngo Iok Ui (Wu Yu Wei) <wusyong9104@gmail.com> Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app> on 2022-01-17\n- Adds support for using JSON5 format for the `tauri.conf.json` file, along with also supporting the `.json5` extension.\n\nHere is the logic flow that determines if JSON or JSON5 will be used to parse the config:\n\n1. Check if `tauri.conf.json` exists\n   a. Parse it with `serde_json`\n   b. Parse it with `json5` if `serde_json` fails\n   c. Return original `serde_json` error if all above steps failed\n2. Check if `tauri.conf.json5` exists\n   a. Parse it with `json5`\n   b. Return error if all above steps failed\n3. Return error if all above steps failed\n\n- [995de57a](https://www.github.com/tauri-apps/tauri/commit/995de57a76cf51215277673e526d7ec32b86b564) Add seamless support for using JSON5 in the config file ([#47](https://www.github.com/tauri-apps/tauri/pull/47)) on 2022-02-03\n- Move the copying of resources and sidecars from `cli.rs` to `tauri-build` so using the Cargo CLI directly processes the files for the application execution in development.\n  - [5eb72c24](https://www.github.com/tauri-apps/tauri/commit/5eb72c24deddf5a01093bea96b90c0d8806afc3f) refactor: copy resources and sidecars on the Cargo build script ([#3357](https://www.github.com/tauri-apps/tauri/pull/3357)) on 2022-02-08\n- **Breaking change**\\* Remove default webview window when `tauri.conf.json > tauri > windows` is not set.\n  - [c119060e](https://www.github.com/tauri-apps/tauri/commit/c119060e3d9a5a824639fb6b3c45a87e7a62e4e2) refactor(core): empty default value for config > tauri > windows ([#3380](https://www.github.com/tauri-apps/tauri/pull/3380)) on 2022-02-10\n- The minimum Rust version is now `1.56`.\n  - [a9dfc015](https://www.github.com/tauri-apps/tauri/commit/a9dfc015505afe91281c2027954ffcc588b1a59c) feat: update to edition 2021 and set minimum rust to 1.56 ([#2789](https://www.github.com/tauri-apps/tauri/pull/2789)) on 2021-10-22\n- Adds `scope` glob array config under `tauri > allowlist > fs`.\n  Adds `assetScope` glob array config under `tauri > allowlist > protocol`.\n  Adds `scope` URL array config under `tauri > allowlist > http`.\n  - [7920ff14](https://www.github.com/tauri-apps/tauri/commit/7920ff14e6424079c48ea5645d9aa13e7a272b87) feat: scope the `fs` API and the `asset` protocol \\[TRI-026] \\[TRI-010] \\[TRI-011] ([#10](https://www.github.com/tauri-apps/tauri/pull/10)) on 2022-01-09\n  - [0ad1c651](https://www.github.com/tauri-apps/tauri/commit/0ad1c6515f696fadefddbf133a9561836b3d5934) feat(core): add `http` allowlist scope \\[TRI-008] ([#24](https://www.github.com/tauri-apps/tauri/pull/24)) on 2021-10-29\n- The `shell` allowlist now includes a `sidecar` flag, which enables the use of the `shell` API to execute sidecars.\n  - [eed01728](https://www.github.com/tauri-apps/tauri/commit/eed017287fed2ade689af4268e8b63b9c9f2e585) feat(core): add `shell > sidecar` allowlist and `process` feature flag \\[TRI-037] ([#18](https://www.github.com/tauri-apps/tauri/pull/18)) on 2021-10-24\n- Force updater endpoint URL to use `https` on release builds.\n  - [c077f449](https://www.github.com/tauri-apps/tauri/commit/c077f449270cffbf7956b1af81e1fb237ebf564a) feat: force endpoint URL to use https on release \\[TRI-015] ([#41](https://www.github.com/tauri-apps/tauri/pull/41)) on 2022-01-09\n\n## \\[1.0.0-beta.3]\n\n- Fixes minimum window height being used as maximum height.\n  - [e3f99165](https://www.github.com/tauri-apps/tauri/commit/e3f9916526b226866137cb663e5cafab2b6a0e01) fix(core) minHeight being used as maxHeight ([#2247](https://www.github.com/tauri-apps/tauri/pull/2247)) on 2021-07-19\n- Implement `Debug` on public API structs and enums.\n  - [fa9341ba](https://www.github.com/tauri-apps/tauri/commit/fa9341ba18ba227735341530900714dba0f27291) feat(core): implement `Debug` on public API structs/enums, closes [#2292](https://www.github.com/tauri-apps/tauri/pull/2292) ([#2387](https://www.github.com/tauri-apps/tauri/pull/2387)) on 2021-08-11\n- Keep original value on `config > package > productName` on Linux (previously converted to kebab-case).\n  - [3f039cb8](https://www.github.com/tauri-apps/tauri/commit/3f039cb8a308b0f18deaa37d7cfb1cc50d308d0e) fix: keep original `productName` for .desktop `Name` field, closes [#2295](https://www.github.com/tauri-apps/tauri/pull/2295) ([#2384](https://www.github.com/tauri-apps/tauri/pull/2384)) on 2021-08-10\n- Inject the invoke key on regular `<script></script>` tags.\n  - [d0142e87](https://www.github.com/tauri-apps/tauri/commit/d0142e87ddf5231fd46e2cbe4769bb16f3fe01e9) fix(core): invoke key injection on regular JS scripts, closes [#2342](https://www.github.com/tauri-apps/tauri/pull/2342) ([#2344](https://www.github.com/tauri-apps/tauri/pull/2344)) on 2021-08-03\n\n## \\[1.0.0-beta.2]\n\n- Inject invoke key on `script` tags with `type=\"module\"`.\n  - [f03eea9c](https://www.github.com/tauri-apps/tauri/commit/f03eea9c9b964709532afbc4d1dd343b3fd96081) feat(core): inject invoke key on `<script type=\"module\">` ([#2120](https://www.github.com/tauri-apps/tauri/pull/2120)) on 2021-06-29\n- `Params` has been removed, along with all the associated types on it. Functions that previously accepted those\n  associated types now accept strings instead. Type that used a generic parameter `Params` now use `Runtime` instead. If\n  you use the `wry` feature, then types with a `Runtime` generic parameter should default to `Wry`, letting you omit the\n  explicit type and let the compiler infer it instead.\n\n`tauri`:\n\n- See `Params` note\n- If you were using `Params` inside a function parameter or definition, all references to it have been replaced with a\n  simple runtime that defaults to `Wry`. If you are not using a custom runtime, just remove `Params` from the definition\n  of functions/items that previously took it. If you are using a custom runtime, you *may* need to pass the runtime type\n  to these functions.\n- If you were using custom types for `Params` (uncommon and if you don't understand you probably were not using it), all\n  methods that were previously taking the custom type now takes an `Into<String>` or a `&str`. The types were already\n  required to be string-able, so just make sure to convert it into a string before passing it in if this breaking change\n  affects you.\n\n`tauri-macros`:\n\n- (internal) Added private `default_runtime` proc macro to allow us to give item definitions a custom runtime only when\n  the specified feature is enabled.\n\n`tauri-runtime`:\n\n- See `Params` note\n- Removed `Params`, `MenuId`, `Tag`, `TagRef`.\n- Added `menu::{MenuHash, MenuId, MenuIdRef}` as type aliases for the internal type that menu types now use.\n  - All previous menu items that had a `MenuId` generic now use the underlying `MenuId` type without a generic.\n- `Runtime`, `RuntimeHandle`, and `Dispatch` have no more generic parameter on `create_window(...)` and instead use the\n  `Runtime` type directly\n- `Runtime::system_tray` has no more `MenuId` generic and uses the string based `SystemTray` type directly.\n- (internal) `CustomMenuItem::id_value()` is now hashed on creation and exposed as the `id` field with type `MenuHash`.\n\n`tauri-runtime-wry`:\n\n- See `Params` note\n- update menu and runtime related types to the ones changed in `tauri-runtime`.\n\n`tauri-utils`:\n\n- `Assets::get` signature has changed to take a `&AssetKey` instead of `impl Into<AssetKey>` to become trait object\n  safe.\n- [fd8fab50](https://www.github.com/tauri-apps/tauri/commit/fd8fab507c8fa1b113b841af14c6693eb3955f6b) refactor(core): remove `Params` and replace with strings ([#2191](https://www.github.com/tauri-apps/tauri/pull/2191)) on 2021-07-15\n\n## \\[1.0.0-beta.1]\n\n- Allow `dev_path` and `dist_dir` to be an array of root files and directories to embed.\n  - [6ec54c53](https://www.github.com/tauri-apps/tauri/commit/6ec54c53b504eec3873d326b1a45e450227d46ed) feat(core): allow `dev_path`, `dist_dir` as array of paths, fixes [#1897](https://www.github.com/tauri-apps/tauri/pull/1897) ([#1926](https://www.github.com/tauri-apps/tauri/pull/1926)) on 2021-05-31\n- Validate `tauri.conf.json > build > devPath` and `tauri.conf.json > build > distDir` values.\n  - [e97846aa](https://www.github.com/tauri-apps/tauri/commit/e97846aae933cad5cba284a2a133ae7aaee1107c) feat(core): validate `devPath` and `distDir` values ([#1848](https://www.github.com/tauri-apps/tauri/pull/1848)) on 2021-05-17\n- Adds `file_drop_enabled` flag on `WindowConfig`.\n  - [9cd10df4](https://www.github.com/tauri-apps/tauri/commit/9cd10df4d520de12f3b13fe88cc1c1a1b4bd48bf) feat(core): allow disabling file drop handler, closes [#2014](https://www.github.com/tauri-apps/tauri/pull/2014) ([#2030](https://www.github.com/tauri-apps/tauri/pull/2030)) on 2021-06-21\n- Hide `phf` crate export (not public API).\n  - [cd1a299a](https://www.github.com/tauri-apps/tauri/commit/cd1a299a7d5a9bd164063a32c87a27762b71e9a8) chore(core): hide phf, closes [#1961](https://www.github.com/tauri-apps/tauri/pull/1961) ([#1964](https://www.github.com/tauri-apps/tauri/pull/1964)) on 2021-06-09\n\n## \\[1.0.0-beta.0]\n\n- **Breaking:** The `assets` field on the `tauri::Context` struct is now a `Arc<impl Assets>`.\n  - [5110c70](https://www.github.com/tauri-apps/tauri/commit/5110c704be67e51d49fb83f3710afb593973dcf9) feat(core): allow users to access the Assets instance ([#1691](https://www.github.com/tauri-apps/tauri/pull/1691)) on 2021-05-03\n- Reintroduce `csp` injection, configured on `tauri.conf.json > tauri > security > csp`.\n  - [6132f3f](https://www.github.com/tauri-apps/tauri/commit/6132f3f4feb64488ef618f690a4f06adce864d91) feat(core): reintroduce CSP injection ([#1704](https://www.github.com/tauri-apps/tauri/pull/1704)) on 2021-05-04\n- Added the \\`#\\[non_exhaustive] attribute where appropriate.\n  - [e087f0f](https://www.github.com/tauri-apps/tauri/commit/e087f0f9374355ac4b4a48f94727ef8b26b1c4cf) feat: add `#[non_exhaustive]` attribute ([#1725](https://www.github.com/tauri-apps/tauri/pull/1725)) on 2021-05-05\n- The `platform::resource_dir` API now takes the `PackageInfo`.\n  - [7bb7dda](https://www.github.com/tauri-apps/tauri/commit/7bb7dda7523bc1a81e890e0aeafffd35e3ed767f) refactor(core): resolve resource_dir using the package info ([#1762](https://www.github.com/tauri-apps/tauri/pull/1762)) on 2021-05-10\n\n## \\[1.0.0-beta-rc.1]\n\n- The package info APIs now checks the `package` object on `tauri.conf.json`.\n  - [8fd1baf](https://www.github.com/tauri-apps/tauri/commit/8fd1baf69b14bb81d7be9d31605ed7f02058b392) fix(core): pull package info from tauri.conf.json if set ([#1581](https://www.github.com/tauri-apps/tauri/pull/1581)) on 2021-04-22\n  - [f575aaa](https://www.github.com/tauri-apps/tauri/commit/f575aaad71f23d44b2f89cf9ee5d84817dc3bb7a) fix: change files not referencing core packages ([#1619](https://www.github.com/tauri-apps/tauri/pull/1619)) on 2021-04-25\n\n## \\[1.0.0-beta-rc.0]\n\n- The Tauri files are now read on the app space instead of the `tauri` create.\n  Also, the `AppBuilder` `build` function now returns a Result.\n  - [e02c941](https://www.github.com/tauri-apps/tauri/commit/e02c9419cb8c66f4e43ed598d2fc74d4b19384ec) refactor(tauri): support for building without environmental variables ([#850](https://www.github.com/tauri-apps/tauri/pull/850)) on 2021-02-09\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- Update all code files to have our license header.\n  - [bf82136](https://www.github.com/tauri-apps/tauri/commit/bf8213646689175f8a158b956911f3a43e360690) feat(license): SPDX Headers ([#1449](https://www.github.com/tauri-apps/tauri/pull/1449)) on 2021-04-11\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n"
  },
  {
    "path": "crates/tauri-utils/Cargo.toml",
    "content": "[package]\nname = \"tauri-utils\"\nversion = \"2.8.3\"\ndescription = \"Utilities for Tauri\"\nexclude = [\"CHANGELOG.md\", \"/target\"]\nreadme = \"README.md\"\nauthors.workspace = true\nhomepage.workspace = true\nrepository.workspace = true\ncategories.workspace = true\nlicense.workspace = true\nedition.workspace = true\nrust-version.workspace = true\n\n[dependencies]\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\nanyhow = \"1\"\nthiserror = \"2\"\nphf = { version = \"0.11\", features = [\"macros\"] }\nbrotli = { version = \"8\", optional = true, default-features = false, features = [\n  \"std\",\n] }\nurl = { version = \"2\", features = [\"serde\"] }\nhtml5ever = { version = \"0.29\", optional = true }\nkuchiki = { package = \"kuchikiki\", version = \"0.8.8-speedreader\", optional = true }\nproc-macro2 = { version = \"1\", optional = true }\nquote = { version = \"1\", optional = true }\n# Our code requires at least 0.8.21 so don't change this to 0.8\nschemars = { version = \"0.8.21\", features = [\"url\", \"uuid1\"], optional = true }\nserde_with = \"3\"\naes-gcm = { version = \"0.10\", optional = true }\ngetrandom = { version = \"0.3\", optional = true, features = [\"std\"] }\nserialize-to-javascript = { version = \"0.1.2\", optional = true }\nctor = \"0.2\"\njson5 = { version = \"0.4\", optional = true }\n# Part of public api in error type\ntoml = { version = \">=0.9, <=1\", features = [\"parse\"] }\njson-patch = \"3.0\"\n# Our code requires at least 0.3.1\nglob = \"0.3.1\"\nurlpattern = \"0.3\"\nregex = \"1\"\nwalkdir = { version = \"2\", optional = true }\nmemchr = \"2\"\nsemver = \"1\"\ninfer = \"0.19\"\ndunce = \"1\"\nlog = \"0.4.21\"\ncargo_metadata = { version = \"0.19\", optional = true }\nserde-untagged = \"0.1\"\nuuid = { version = \"1\", features = [\"serde\"] }\nhttp = \"1\"\n\n[target.\"cfg(target_os = \\\"macos\\\")\".dependencies]\nswift-rs = { version = \"1\", optional = true, features = [\"build\"] }\n\n[dev-dependencies]\ngetrandom = { version = \"0.3\", features = [\"std\"] }\nserial_test = \"3\"\ntauri = { path = \"../tauri\" }\n\n[features]\nbuild = [\n  \"proc-macro2\",\n  \"quote\",\n  \"cargo_metadata\",\n  \"schema\",\n  \"swift-rs\",\n  \"html-manipulation\",\n]\ncompression = [\"brotli\"]\nschema = [\"schemars\"]\nisolation = [\"aes-gcm\", \"getrandom\", \"serialize-to-javascript\"]\nprocess-relaunch-dangerous-allow-symlink-macos = []\nconfig-json5 = [\"json5\"]\nconfig-toml = []\nresources = [\"walkdir\"]\nhtml-manipulation = [\"dep:html5ever\", \"dep:kuchiki\"]\n"
  },
  {
    "path": "crates/tauri-utils/LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/tauri-utils/LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2017 - Present Tauri Apps Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "crates/tauri-utils/README.md",
    "content": "# tauri-utils\n\n <img align=\"right\" src=\"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\" height=\"128\" width=\"128\">\n\n[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev)\n[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri)\n[![test core](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-core.yml?label=test%20core&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield)\n[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S)\n[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app)\n[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)\n[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri)\n\n| Component   | Version                                                                                                    |\n| ----------- | ---------------------------------------------------------------------------------------------------------- |\n| tauri-utils | [![](https://img.shields.io/crates/v/tauri-utils?style=flat-square)](https://crates.io/crates/tauri-utils) |\n\n## About Tauri\n\nTauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.\n\nTauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.\n\n## This module\n\nThis is common code that is reused in many places and offers useful utilities like parsing configuration files, detecting platform triples, injecting the CSP, and managing assets.\n\nTo learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.\n\n## Semver\n\n**tauri** is following [Semantic Versioning 2.0](https://semver.org/).\n\n## Licenses\n\nCode: (c) 2021 - The Tauri Programme within The Commons Conservancy.\n\nMIT or MIT/Apache 2.0 where applicable.\n\nLogo: CC-BY-NC-ND\n\n- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)\n"
  },
  {
    "path": "crates/tauri-utils/src/acl/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! ACL items that are only useful inside of build script/codegen context.\n\nuse std::{\n  collections::{BTreeMap, HashMap},\n  env, fs,\n  path::{Path, PathBuf},\n};\n\nuse crate::{\n  acl::{has_app_manifest, AllowedCommands, Error},\n  config::Config,\n  write_if_changed,\n};\n\nuse super::{\n  capability::{Capability, CapabilityFile},\n  manifest::PermissionFile,\n  ALLOWED_COMMANDS_FILE_NAME, PERMISSION_SCHEMAS_FOLDER_NAME, PERMISSION_SCHEMA_FILE_NAME,\n  REMOVE_UNUSED_COMMANDS_ENV_VAR,\n};\n\n/// Known name of the folder containing autogenerated permissions.\npub const AUTOGENERATED_FOLDER_NAME: &str = \"autogenerated\";\n\n/// Cargo cfg key for permissions file paths\npub const PERMISSION_FILES_PATH_KEY: &str = \"PERMISSION_FILES_PATH\";\n\n/// Cargo cfg key for global scope schemas\npub const GLOBAL_SCOPE_SCHEMA_PATH_KEY: &str = \"GLOBAL_SCOPE_SCHEMA_PATH\";\n\n/// Allowed permission file extensions\npub const PERMISSION_FILE_EXTENSIONS: &[&str] = &[\"json\", \"toml\"];\n\n/// Known filename of the permission documentation file\npub const PERMISSION_DOCS_FILE_NAME: &str = \"reference.md\";\n\n/// Allowed capability file extensions\nconst CAPABILITY_FILE_EXTENSIONS: &[&str] = &[\n  \"json\",\n  #[cfg(feature = \"config-json5\")]\n  \"json5\",\n  \"toml\",\n];\n\n/// Known folder name of the capability schemas\nconst CAPABILITIES_SCHEMA_FOLDER_NAME: &str = \"schemas\";\n\nconst CORE_PLUGIN_PERMISSIONS_TOKEN: &str = \"__CORE_PLUGIN__\";\n\nfn parse_permissions(paths: Vec<PathBuf>) -> Result<Vec<PermissionFile>, Error> {\n  let mut permissions = Vec::new();\n  for path in paths {\n    let ext = path.extension().unwrap().to_string_lossy().to_string();\n    let permission_file = fs::read_to_string(&path).map_err(|e| Error::ReadFile(e, path))?;\n    let permission: PermissionFile = match ext.as_str() {\n      \"toml\" => toml::from_str(&permission_file)?,\n      \"json\" => serde_json::from_str(&permission_file)?,\n      _ => return Err(Error::UnknownPermissionFormat(ext)),\n    };\n    permissions.push(permission);\n  }\n  Ok(permissions)\n}\n\n/// Write the permissions to a temporary directory and pass it to the immediate consuming crate.\npub fn define_permissions<F: Fn(&Path) -> bool>(\n  pattern: &str,\n  pkg_name: &str,\n  out_dir: &Path,\n  filter_fn: F,\n) -> Result<Vec<PermissionFile>, Error> {\n  let permission_files = glob::glob(pattern)?\n    .flatten()\n    .flat_map(|p| p.canonicalize())\n    // filter extension\n    .filter(|p| {\n      p.extension()\n        .and_then(|e| e.to_str())\n        .map(|e| PERMISSION_FILE_EXTENSIONS.contains(&e))\n        .unwrap_or_default()\n    })\n    .filter(|p| filter_fn(p))\n    // filter schemas\n    .filter(|p| p.parent().unwrap().file_name().unwrap() != PERMISSION_SCHEMAS_FOLDER_NAME)\n    .collect::<Vec<PathBuf>>();\n\n  let pkg_name_valid_path = pkg_name.replace(':', \"-\");\n  let permission_files_path = out_dir.join(format!(\"{pkg_name_valid_path}-permission-files\"));\n  let permission_files_json = serde_json::to_string(&permission_files)?;\n\n  write_if_changed(&permission_files_path, permission_files_json)\n    .map_err(|e| Error::WriteFile(e, permission_files_path.clone()))?;\n\n  if let Some(plugin_name) = pkg_name.strip_prefix(\"tauri:\") {\n    println!(\n      \"cargo:{plugin_name}{CORE_PLUGIN_PERMISSIONS_TOKEN}_{PERMISSION_FILES_PATH_KEY}={}\",\n      permission_files_path.display()\n    );\n  } else {\n    println!(\n      \"cargo:{PERMISSION_FILES_PATH_KEY}={}\",\n      permission_files_path.display()\n    );\n  }\n\n  parse_permissions(permission_files)\n}\n\n/// Read all permissions listed from the defined cargo cfg key value.\npub fn read_permissions() -> Result<HashMap<String, Vec<PermissionFile>>, Error> {\n  let mut permissions_map = HashMap::new();\n\n  for (key, value) in env::vars_os() {\n    let key = key.to_string_lossy();\n\n    if let Some(plugin_crate_name_var) = key\n      .strip_prefix(\"DEP_\")\n      .and_then(|v| v.strip_suffix(&format!(\"_{PERMISSION_FILES_PATH_KEY}\")))\n      .map(|v| {\n        v.strip_suffix(CORE_PLUGIN_PERMISSIONS_TOKEN)\n          .and_then(|v| v.strip_prefix(\"TAURI_\"))\n          .unwrap_or(v)\n      })\n    {\n      let permissions_path = PathBuf::from(value);\n      let permissions_str =\n        fs::read_to_string(&permissions_path).map_err(|e| Error::ReadFile(e, permissions_path))?;\n      let permissions: Vec<PathBuf> = serde_json::from_str(&permissions_str)?;\n      let permissions = parse_permissions(permissions)?;\n\n      let plugin_crate_name = plugin_crate_name_var.to_lowercase().replace('_', \"-\");\n      let plugin_crate_name = plugin_crate_name\n        .strip_prefix(\"tauri-plugin-\")\n        .map(ToString::to_string)\n        .unwrap_or(plugin_crate_name);\n\n      permissions_map.insert(plugin_crate_name, permissions);\n    }\n  }\n\n  Ok(permissions_map)\n}\n\n/// Define the global scope schema JSON file path if it exists and pass it to the immediate consuming crate.\npub fn define_global_scope_schema(\n  schema: schemars::schema::RootSchema,\n  pkg_name: &str,\n  out_dir: &Path,\n) -> Result<(), Error> {\n  let path = out_dir.join(\"global-scope.json\");\n  write_if_changed(&path, serde_json::to_vec(&schema)?)\n    .map_err(|e| Error::WriteFile(e, path.clone()))?;\n\n  if let Some(plugin_name) = pkg_name.strip_prefix(\"tauri:\") {\n    println!(\n      \"cargo:{plugin_name}{CORE_PLUGIN_PERMISSIONS_TOKEN}_{GLOBAL_SCOPE_SCHEMA_PATH_KEY}={}\",\n      path.display()\n    );\n  } else {\n    println!(\"cargo:{GLOBAL_SCOPE_SCHEMA_PATH_KEY}={}\", path.display());\n  }\n\n  Ok(())\n}\n\n/// Read all global scope schemas listed from the defined cargo cfg key value.\npub fn read_global_scope_schemas() -> Result<HashMap<String, serde_json::Value>, Error> {\n  let mut schemas_map = HashMap::new();\n\n  for (key, value) in env::vars_os() {\n    let key = key.to_string_lossy();\n\n    if let Some(plugin_crate_name_var) = key\n      .strip_prefix(\"DEP_\")\n      .and_then(|v| v.strip_suffix(&format!(\"_{GLOBAL_SCOPE_SCHEMA_PATH_KEY}\")))\n      .map(|v| {\n        v.strip_suffix(CORE_PLUGIN_PERMISSIONS_TOKEN)\n          .and_then(|v| v.strip_prefix(\"TAURI_\"))\n          .unwrap_or(v)\n      })\n    {\n      let path = PathBuf::from(value);\n      let json = fs::read_to_string(&path).map_err(|e| Error::ReadFile(e, path))?;\n      let schema: serde_json::Value = serde_json::from_str(&json)?;\n\n      let plugin_crate_name = plugin_crate_name_var.to_lowercase().replace('_', \"-\");\n      let plugin_crate_name = plugin_crate_name\n        .strip_prefix(\"tauri-plugin-\")\n        .map(ToString::to_string)\n        .unwrap_or(plugin_crate_name);\n\n      schemas_map.insert(plugin_crate_name, schema);\n    }\n  }\n\n  Ok(schemas_map)\n}\n\n/// Parses all capability files with the given glob pattern.\npub fn parse_capabilities(pattern: &str) -> Result<BTreeMap<String, Capability>, Error> {\n  let mut capabilities_map = BTreeMap::new();\n\n  for path in glob::glob(pattern)?\n    .flatten() // filter extension\n    .filter(|p| {\n      p.extension()\n        .and_then(|e| e.to_str())\n        .map(|e| CAPABILITY_FILE_EXTENSIONS.contains(&e))\n        .unwrap_or_default()\n    })\n    // filter schema files\n    // TODO: remove this before stable\n    .filter(|p| p.parent().unwrap().file_name().unwrap() != CAPABILITIES_SCHEMA_FOLDER_NAME)\n  {\n    match CapabilityFile::load(&path)? {\n      CapabilityFile::Capability(capability) => {\n        if capabilities_map.contains_key(&capability.identifier) {\n          return Err(Error::CapabilityAlreadyExists {\n            identifier: capability.identifier,\n          });\n        }\n\n        capabilities_map.insert(capability.identifier.clone(), capability);\n      }\n      CapabilityFile::List(capabilities) | CapabilityFile::NamedList { capabilities } => {\n        for capability in capabilities {\n          if capabilities_map.contains_key(&capability.identifier) {\n            return Err(Error::CapabilityAlreadyExists {\n              identifier: capability.identifier,\n            });\n          }\n\n          capabilities_map.insert(capability.identifier.clone(), capability);\n        }\n      }\n    }\n  }\n\n  Ok(capabilities_map)\n}\n\n/// Permissions that are generated from commands using [`autogenerate_command_permissions`].\npub struct AutogeneratedPermissions {\n  /// The allow permissions generated from commands.\n  pub allowed: Vec<String>,\n  /// The deny permissions generated from commands.\n  pub denied: Vec<String>,\n}\n\n/// Autogenerate permission files for a list of commands.\npub fn autogenerate_command_permissions(\n  path: &Path,\n  commands: &[&str],\n  license_header: &str,\n  schema_ref: bool,\n) -> AutogeneratedPermissions {\n  if !path.exists() {\n    fs::create_dir_all(path).expect(\"unable to create autogenerated commands dir\");\n  }\n\n  let schema_entry = if schema_ref {\n    let cwd = env::current_dir().unwrap();\n    let components_len = path.strip_prefix(&cwd).unwrap_or(path).components().count();\n    let schema_path = (1..components_len)\n      .map(|_| \"..\")\n      .collect::<PathBuf>()\n      .join(PERMISSION_SCHEMAS_FOLDER_NAME)\n      .join(PERMISSION_SCHEMA_FILE_NAME);\n    format!(\n      \"\\n\\\"$schema\\\" = \\\"{}\\\"\\n\",\n      dunce::simplified(&schema_path)\n        .display()\n        .to_string()\n        .replace('\\\\', \"/\")\n    )\n  } else {\n    \"\".to_string()\n  };\n\n  let mut autogenerated = AutogeneratedPermissions {\n    allowed: Vec::new(),\n    denied: Vec::new(),\n  };\n\n  for command in commands {\n    let slugified_command = command.replace('_', \"-\");\n\n    let toml = format!(\n      r###\"{license_header}# Automatically generated - DO NOT EDIT!\n{schema_entry}\n[[permission]]\nidentifier = \"allow-{slugified_command}\"\ndescription = \"Enables the {command} command without any pre-configured scope.\"\ncommands.allow = [\"{command}\"]\n\n[[permission]]\nidentifier = \"deny-{slugified_command}\"\ndescription = \"Denies the {command} command without any pre-configured scope.\"\ncommands.deny = [\"{command}\"]\n\"###,\n    );\n\n    let out_path = path.join(format!(\"{command}.toml\"));\n    write_if_changed(&out_path, toml)\n      .unwrap_or_else(|_| panic!(\"unable to autogenerate {out_path:?}\"));\n\n    autogenerated\n      .allowed\n      .push(format!(\"allow-{slugified_command}\"));\n    autogenerated\n      .denied\n      .push(format!(\"deny-{slugified_command}\"));\n  }\n\n  autogenerated\n}\n\nconst PERMISSION_TABLE_HEADER: &str =\n  \"## Permission Table\\n\\n<table>\\n<tr>\\n<th>Identifier</th>\\n<th>Description</th>\\n</tr>\\n\";\n\n/// Generate a markdown documentation page containing the list of permissions of the plugin.\npub fn generate_docs(\n  permissions: &[PermissionFile],\n  out_dir: &Path,\n  plugin_identifier: &str,\n) -> Result<(), Error> {\n  let mut default_permission = \"\".to_owned();\n  let mut permission_table = \"\".to_string();\n\n  fn docs_from(id: &str, description: Option<&str>, plugin_identifier: &str) -> String {\n    let mut docs = format!(\"\\n<tr>\\n<td>\\n\\n`{plugin_identifier}:{id}`\\n\\n</td>\\n\");\n    if let Some(d) = description {\n      docs.push_str(&format!(\"<td>\\n\\n{d}\\n\\n</td>\"));\n    }\n    docs.push_str(\"\\n</tr>\");\n    docs\n  }\n\n  for permission in permissions {\n    for set in &permission.set {\n      permission_table.push_str(&docs_from(\n        &set.identifier,\n        Some(&set.description),\n        plugin_identifier,\n      ));\n      permission_table.push('\\n');\n    }\n\n    if let Some(default) = &permission.default {\n      default_permission.push_str(\"## Default Permission\\n\\n\");\n      default_permission.push_str(default.description.as_deref().unwrap_or_default().trim());\n      default_permission.push('\\n');\n      default_permission.push('\\n');\n      if !default.permissions.is_empty() {\n        default_permission.push_str(\"#### This default permission set includes the following:\\n\\n\");\n        for permission in &default.permissions {\n          default_permission.push_str(&format!(\"- `{permission}`\\n\"));\n        }\n        default_permission.push('\\n');\n      }\n    }\n\n    for permission in &permission.permission {\n      permission_table.push_str(&docs_from(\n        &permission.identifier,\n        permission.description.as_deref(),\n        plugin_identifier,\n      ));\n      permission_table.push('\\n');\n    }\n  }\n\n  let docs = format!(\"{default_permission}{PERMISSION_TABLE_HEADER}\\n{permission_table}</table>\\n\");\n\n  let reference_path = out_dir.join(PERMISSION_DOCS_FILE_NAME);\n  write_if_changed(&reference_path, docs).map_err(|e| Error::WriteFile(e, reference_path))?;\n\n  Ok(())\n}\n\n// TODO: We have way too many duplicated code around getting the config files, e.g.\n//  - crates/tauri-codegen/src/lib.rs          (`get_config`)\n//  - crates/tauri-build/src/lib.rs            (`try_build`)\n//  - crates/tauri-cli/src/helpers/config.rs   (`get_internal`)\n/// Generate allowed commands file for the `generate_handler` macro to remove never allowed commands\npub fn generate_allowed_commands(\n  out_dir: &Path,\n  capabilities_from_files: Option<BTreeMap<String, Capability>>,\n  permissions_map: BTreeMap<String, Vec<PermissionFile>>,\n) -> Result<(), anyhow::Error> {\n  println!(\"cargo:rerun-if-env-changed={REMOVE_UNUSED_COMMANDS_ENV_VAR}\");\n\n  let allowed_commands_file_path = out_dir.join(ALLOWED_COMMANDS_FILE_NAME);\n\n  let remove_unused_commands_env_var = std::env::var(REMOVE_UNUSED_COMMANDS_ENV_VAR);\n\n  let should_generate_allowed_commands =\n    remove_unused_commands_env_var.is_ok() && !permissions_map.is_empty();\n\n  if !should_generate_allowed_commands {\n    let _ = std::fs::remove_file(allowed_commands_file_path);\n    return Ok(());\n  }\n\n  // It's safe to `unwrap` here since we have checked if the result is ok above\n  let config_directory = PathBuf::from(remove_unused_commands_env_var.unwrap());\n  let capabilities_path = config_directory.join(\"capabilities\");\n  // Cargo re-builds if the variable points to an empty path,\n  // so we check for exists here\n  // see https://github.com/rust-lang/cargo/issues/4213\n  if capabilities_path.exists() {\n    println!(\"cargo:rerun-if-changed={}\", capabilities_path.display());\n  }\n\n  let target_triple = env::var(\"TARGET\")?;\n  let target = crate::platform::Target::from_triple(&target_triple);\n  let (mut config, config_paths) = crate::config::parse::read_from(target, &config_directory)?;\n\n  for config_file_path in config_paths {\n    println!(\"cargo:rerun-if-changed={}\", config_file_path.display());\n  }\n\n  if let Ok(env) = std::env::var(\"TAURI_CONFIG\") {\n    let merge_config: serde_json::Value = serde_json::from_str(&env)?;\n    json_patch::merge(&mut config, &merge_config);\n  }\n\n  println!(\"cargo:rerun-if-env-changed=TAURI_CONFIG\");\n\n  // Set working directory to where `tauri.config.json` is, so that relative paths in it are parsed correctly.\n  let old_cwd = std::env::current_dir()?;\n  std::env::set_current_dir(config_directory)?;\n\n  let config: Config = serde_json::from_value(config)?;\n\n  // Reset working directory.\n  std::env::set_current_dir(old_cwd)?;\n\n  let acl: BTreeMap<String, crate::acl::manifest::Manifest> = permissions_map\n    .into_iter()\n    .map(|(key, permissions)| {\n      let key = key\n        .strip_prefix(\"tauri-plugin-\")\n        .unwrap_or(&key)\n        .to_string();\n      let manifest = crate::acl::manifest::Manifest::new(permissions, None);\n      (key, manifest)\n    })\n    .collect();\n\n  let capabilities_from_files = if let Some(capabilities) = capabilities_from_files {\n    capabilities\n  } else {\n    crate::acl::build::parse_capabilities(&format!(\n      \"{}/**/*\",\n      glob::Pattern::escape(&capabilities_path.to_string_lossy())\n    ))?\n  };\n  let capabilities = crate::acl::get_capabilities(&config, capabilities_from_files, None)?;\n\n  let permission_entries = capabilities\n    .into_iter()\n    .flat_map(|(_, capabilities)| capabilities.permissions);\n  let mut allowed_commands = AllowedCommands {\n    has_app_acl: has_app_manifest(&acl),\n    ..Default::default()\n  };\n  for permission_entry in permission_entries {\n    let Ok(permissions) =\n      crate::acl::resolved::get_permissions(permission_entry.identifier(), &acl)\n    else {\n      continue;\n    };\n    for permission in permissions {\n      let plugin_name = permission.key;\n      let allowed_command_names = &permission.permission.commands.allow;\n      for allowed_command in allowed_command_names {\n        let command_name = if plugin_name == crate::acl::APP_ACL_KEY {\n          allowed_command.to_string()\n        } else if let Some(core_plugin_name) = plugin_name.strip_prefix(\"core:\") {\n          format!(\"plugin:{core_plugin_name}|{allowed_command}\")\n        } else {\n          format!(\"plugin:{plugin_name}|{allowed_command}\")\n        };\n        allowed_commands.commands.insert(command_name);\n      }\n    }\n  }\n\n  write_if_changed(\n    allowed_commands_file_path,\n    serde_json::to_string(&allowed_commands)?,\n  )?;\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/acl/capability.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! End-user abstraction for selecting permissions a window has access to.\n\nuse std::{path::Path, str::FromStr};\n\nuse crate::{acl::Identifier, platform::Target};\nuse serde::{\n  de::{Error, IntoDeserializer},\n  Deserialize, Deserializer, Serialize,\n};\nuse serde_untagged::UntaggedEnumVisitor;\n\nuse super::Scopes;\n\n/// An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`]\n/// or an object that references a permission and extends its scope.\n#[derive(Debug, Clone, PartialEq, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\npub enum PermissionEntry {\n  /// Reference a permission or permission set by identifier.\n  PermissionRef(Identifier),\n  /// Reference a permission or permission set by identifier and extends its scope.\n  ExtendedPermission {\n    /// Identifier of the permission or permission set.\n    identifier: Identifier,\n    /// Scope to append to the existing permission scope.\n    #[serde(default, flatten)]\n    scope: Scopes,\n  },\n}\n\nimpl PermissionEntry {\n  /// The identifier of the permission referenced in this entry.\n  pub fn identifier(&self) -> &Identifier {\n    match self {\n      Self::PermissionRef(identifier) => identifier,\n      Self::ExtendedPermission {\n        identifier,\n        scope: _,\n      } => identifier,\n    }\n  }\n}\n\nimpl<'de> Deserialize<'de> for PermissionEntry {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    #[derive(Deserialize)]\n    struct ExtendedPermissionStruct {\n      identifier: Identifier,\n      #[serde(default, flatten)]\n      scope: Scopes,\n    }\n\n    UntaggedEnumVisitor::new()\n      .string(|string| {\n        let de = string.into_deserializer();\n        Identifier::deserialize(de).map(Self::PermissionRef)\n      })\n      .map(|map| {\n        let ext_perm = map.deserialize::<ExtendedPermissionStruct>()?;\n        Ok(Self::ExtendedPermission {\n          identifier: ext_perm.identifier,\n          scope: ext_perm.scope,\n        })\n      })\n      .deserialize(deserializer)\n  }\n}\n\n/// A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n///\n/// It controls application windows' and webviews' fine grained access\n/// to the Tauri core, application, or plugin commands.\n/// If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n///\n/// This can be done to create groups of windows, based on their required system access, which can reduce\n/// impact of frontend vulnerabilities in less privileged windows.\n/// Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\n/// A Window can have none, one, or multiple associated capabilities.\n///\n/// ## Example\n///\n/// ```json\n/// {\n///   \"identifier\": \"main-user-files-write\",\n///   \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\",\n///   \"windows\": [\n///     \"main\"\n///   ],\n///   \"permissions\": [\n///     \"core:default\",\n///     \"dialog:open\",\n///     {\n///       \"identifier\": \"fs:allow-write-text-file\",\n///       \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n///     },\n///   ],\n///   \"platforms\": [\"macOS\",\"windows\"]\n/// }\n/// ```\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub struct Capability {\n  /// Identifier of the capability.\n  ///\n  /// ## Example\n  ///\n  /// `main-user-files-write`\n  ///\n  pub identifier: String,\n  /// Description of what the capability is intended to allow on associated windows.\n  ///\n  /// It should contain a description of what the grouped permissions should allow.\n  ///\n  /// ## Example\n  ///\n  /// This capability allows the `main` window access to `filesystem` write related\n  /// commands and `dialog` commands to enable programmatic access to files selected by the user.\n  #[serde(default)]\n  pub description: String,\n  /// Configure remote URLs that can use the capability permissions.\n  ///\n  /// This setting is optional and defaults to not being set, as our\n  /// default use case is that the content is served from our local application.\n  ///\n  /// :::caution\n  /// Make sure you understand the security implications of providing remote\n  /// sources with local system access.\n  /// :::\n  ///\n  /// ## Example\n  ///\n  /// ```json\n  /// {\n  ///   \"urls\": [\"https://*.mydomain.dev\"]\n  /// }\n  /// ```\n  #[serde(default, skip_serializing_if = \"Option::is_none\")]\n  pub remote: Option<CapabilityRemote>,\n  /// Whether this capability is enabled for local app URLs or not. Defaults to `true`.\n  #[serde(default = \"default_capability_local\")]\n  pub local: bool,\n  /// List of windows that are affected by this capability. Can be a glob pattern.\n  ///\n  /// If a window label matches any of the patterns in this list,\n  /// the capability will be enabled on all the webviews of that window,\n  /// regardless of the value of [`Self::webviews`].\n  ///\n  /// On multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`]\n  /// for a fine grained access control.\n  ///\n  /// ## Example\n  ///\n  /// `[\"main\"]`\n  #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n  pub windows: Vec<String>,\n  /// List of webviews that are affected by this capability. Can be a glob pattern.\n  ///\n  /// The capability will be enabled on all the webviews\n  /// whose label matches any of the patterns in this list,\n  /// regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n  ///\n  /// ## Example\n  ///\n  /// `[\"sub-webview-one\", \"sub-webview-two\"]`\n  #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n  pub webviews: Vec<String>,\n  /// List of permissions attached to this capability.\n  ///\n  /// Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\n  /// For commands directly implemented in the application itself only `${permission-name}`\n  /// is required.\n  ///\n  /// ## Example\n  ///\n  /// ```json\n  /// [\n  ///   \"core:default\",\n  ///   \"shell:allow-open\",\n  ///   \"dialog:open\",\n  ///   {\n  ///     \"identifier\": \"fs:allow-write-text-file\",\n  ///     \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n  ///   }\n  /// ]\n  /// ```\n  #[cfg_attr(feature = \"schema\", schemars(schema_with = \"unique_permission\"))]\n  pub permissions: Vec<PermissionEntry>,\n  /// Limit which target platforms this capability applies to.\n  ///\n  /// By default all platforms are targeted.\n  ///\n  /// ## Example\n  ///\n  /// `[\"macOS\",\"windows\"]`\n  #[serde(skip_serializing_if = \"Option::is_none\")]\n  pub platforms: Option<Vec<Target>>,\n}\n\nimpl Capability {\n  /// Whether this capability should be active based on the platform target or not.\n  pub fn is_active(&self, target: &Target) -> bool {\n    self\n      .platforms\n      .as_ref()\n      .map(|platforms| platforms.contains(target))\n      .unwrap_or(true)\n  }\n}\n\n#[cfg(feature = \"schema\")]\nfn unique_permission(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n  use schemars::schema;\n  schema::SchemaObject {\n    instance_type: Some(schema::InstanceType::Array.into()),\n    array: Some(Box::new(schema::ArrayValidation {\n      unique_items: Some(true),\n      items: Some(gen.subschema_for::<PermissionEntry>().into()),\n      ..Default::default()\n    })),\n    ..Default::default()\n  }\n  .into()\n}\n\nfn default_capability_local() -> bool {\n  true\n}\n\n/// Configuration for remote URLs that are associated with the capability.\n#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord, Hash)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\")]\npub struct CapabilityRemote {\n  /// Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n  ///\n  /// ## Examples\n  ///\n  /// - \"https://*.mydomain.dev\": allows subdomains of mydomain.dev\n  /// - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api\n  pub urls: Vec<String>,\n}\n\n/// Capability formats accepted in a capability file.\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"schema\", schemars(untagged))]\n#[cfg_attr(test, derive(Debug, PartialEq))]\npub enum CapabilityFile {\n  /// A single capability.\n  Capability(Capability),\n  /// A list of capabilities.\n  List(Vec<Capability>),\n  /// A list of capabilities.\n  NamedList {\n    /// The list of capabilities.\n    capabilities: Vec<Capability>,\n  },\n}\n\nimpl CapabilityFile {\n  /// Load the given capability file.\n  pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, super::Error> {\n    let path = path.as_ref();\n    let capability_file =\n      std::fs::read_to_string(path).map_err(|e| super::Error::ReadFile(e, path.into()))?;\n    let ext = path.extension().unwrap().to_string_lossy().to_string();\n    let file: Self = match ext.as_str() {\n      \"toml\" => toml::from_str(&capability_file)?,\n      \"json\" => serde_json::from_str(&capability_file)?,\n      #[cfg(feature = \"config-json5\")]\n      \"json5\" => json5::from_str(&capability_file)?,\n      _ => return Err(super::Error::UnknownCapabilityFormat(ext)),\n    };\n    Ok(file)\n  }\n}\n\nimpl<'de> Deserialize<'de> for CapabilityFile {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    UntaggedEnumVisitor::new()\n      .seq(|seq| seq.deserialize::<Vec<Capability>>().map(Self::List))\n      .map(|map| {\n        #[derive(Deserialize)]\n        struct CapabilityNamedList {\n          capabilities: Vec<Capability>,\n        }\n\n        let value: serde_json::Map<String, serde_json::Value> = map.deserialize()?;\n        if value.contains_key(\"capabilities\") {\n          serde_json::from_value::<CapabilityNamedList>(value.into())\n            .map(|named| Self::NamedList {\n              capabilities: named.capabilities,\n            })\n            .map_err(|e| serde_untagged::de::Error::custom(e.to_string()))\n        } else {\n          serde_json::from_value::<Capability>(value.into())\n            .map(Self::Capability)\n            .map_err(|e| serde_untagged::de::Error::custom(e.to_string()))\n        }\n      })\n      .deserialize(deserializer)\n  }\n}\n\nimpl FromStr for CapabilityFile {\n  type Err = super::Error;\n\n  fn from_str(s: &str) -> Result<Self, Self::Err> {\n    serde_json::from_str(s)\n      .or_else(|_| toml::from_str(s))\n      .map_err(Into::into)\n  }\n}\n\n#[cfg(feature = \"build\")]\nmod build {\n  use std::convert::identity;\n\n  use proc_macro2::TokenStream;\n  use quote::{quote, ToTokens, TokenStreamExt};\n\n  use super::*;\n  use crate::{literal_struct, tokens::*};\n\n  impl ToTokens for CapabilityRemote {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let urls = vec_lit(&self.urls, str_lit);\n      literal_struct!(\n        tokens,\n        ::tauri::utils::acl::capability::CapabilityRemote,\n        urls\n      );\n    }\n  }\n\n  impl ToTokens for PermissionEntry {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::acl::capability::PermissionEntry };\n\n      tokens.append_all(match self {\n        Self::PermissionRef(id) => {\n          quote! { #prefix::PermissionRef(#id) }\n        }\n        Self::ExtendedPermission { identifier, scope } => {\n          quote! { #prefix::ExtendedPermission {\n            identifier: #identifier,\n            scope: #scope\n          } }\n        }\n      });\n    }\n  }\n\n  impl ToTokens for Capability {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let identifier = str_lit(&self.identifier);\n      let description = str_lit(&self.description);\n      let remote = opt_lit(self.remote.as_ref());\n      let local = self.local;\n      let windows = vec_lit(&self.windows, str_lit);\n      let webviews = vec_lit(&self.webviews, str_lit);\n      let permissions = vec_lit(&self.permissions, identity);\n      let platforms = opt_vec_lit(self.platforms.as_ref(), identity);\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::acl::capability::Capability,\n        identifier,\n        description,\n        remote,\n        local,\n        windows,\n        webviews,\n        permissions,\n        platforms\n      );\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use crate::acl::{Identifier, Scopes};\n\n  use super::{Capability, CapabilityFile, PermissionEntry};\n\n  #[test]\n  fn permission_entry_de() {\n    let identifier = Identifier::try_from(\"plugin:perm\".to_string()).unwrap();\n    let identifier_json = serde_json::to_string(&identifier).unwrap();\n    assert_eq!(\n      serde_json::from_str::<PermissionEntry>(&identifier_json).unwrap(),\n      PermissionEntry::PermissionRef(identifier.clone())\n    );\n\n    assert_eq!(\n      serde_json::from_value::<PermissionEntry>(serde_json::json!({\n        \"identifier\": identifier,\n        \"allow\": [],\n        \"deny\": null\n      }))\n      .unwrap(),\n      PermissionEntry::ExtendedPermission {\n        identifier,\n        scope: Scopes {\n          allow: Some(vec![]),\n          deny: None\n        }\n      }\n    );\n  }\n\n  #[test]\n  fn capability_file_de() {\n    let capability = Capability {\n      identifier: \"test\".into(),\n      description: \"\".into(),\n      remote: None,\n      local: true,\n      windows: vec![],\n      webviews: vec![],\n      permissions: vec![],\n      platforms: None,\n    };\n    let capability_json = serde_json::to_string(&capability).unwrap();\n\n    assert_eq!(\n      serde_json::from_str::<CapabilityFile>(&capability_json).unwrap(),\n      CapabilityFile::Capability(capability.clone())\n    );\n\n    assert_eq!(\n      serde_json::from_str::<CapabilityFile>(&format!(\"[{capability_json}]\")).unwrap(),\n      CapabilityFile::List(vec![capability.clone()])\n    );\n\n    assert_eq!(\n      serde_json::from_str::<CapabilityFile>(&format!(\n        \"{{ \\\"capabilities\\\": [{capability_json}] }}\"\n      ))\n      .unwrap(),\n      CapabilityFile::NamedList {\n        capabilities: vec![capability]\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/acl/identifier.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Identifier for plugins.\n\nuse serde::{Deserialize, Deserializer, Serialize, Serializer};\nuse std::num::NonZeroU8;\nuse thiserror::Error;\n\nconst IDENTIFIER_SEPARATOR: u8 = b':';\nconst PLUGIN_PREFIX: &str = \"tauri-plugin-\";\nconst CORE_PLUGIN_IDENTIFIER_PREFIX: &str = \"core:\";\n\n// <https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field>\nconst MAX_LEN_PREFIX: usize = 64 - PLUGIN_PREFIX.len();\nconst MAX_LEN_BASE: usize = 64;\nconst MAX_LEN_IDENTIFIER: usize = MAX_LEN_PREFIX + 1 + MAX_LEN_BASE;\n\n/// Plugin identifier.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct Identifier {\n  inner: String,\n  separator: Option<NonZeroU8>,\n}\n\n#[cfg(feature = \"schema\")]\nimpl schemars::JsonSchema for Identifier {\n  fn schema_name() -> String {\n    \"Identifier\".to_string()\n  }\n\n  fn schema_id() -> std::borrow::Cow<'static, str> {\n    // Include the module, in case a type with the same name is in another module/crate\n    std::borrow::Cow::Borrowed(concat!(module_path!(), \"::Identifier\"))\n  }\n\n  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    String::json_schema(gen)\n  }\n}\n\nimpl AsRef<str> for Identifier {\n  #[inline(always)]\n  fn as_ref(&self) -> &str {\n    &self.inner\n  }\n}\n\nimpl Identifier {\n  /// Get the identifier str.\n  #[inline(always)]\n  pub fn get(&self) -> &str {\n    self.as_ref()\n  }\n\n  /// Get the identifier without prefix.\n  pub fn get_base(&self) -> &str {\n    match self.separator_index() {\n      None => self.get(),\n      Some(i) => &self.inner[i + 1..],\n    }\n  }\n\n  /// Get the prefix of the identifier.\n  pub fn get_prefix(&self) -> Option<&str> {\n    self.separator_index().map(|i| &self.inner[0..i])\n  }\n\n  /// Set the identifier prefix.\n  pub fn set_prefix(&mut self) -> Result<(), ParseIdentifierError> {\n    todo!()\n  }\n\n  /// Get the identifier string and its separator.\n  pub fn into_inner(self) -> (String, Option<NonZeroU8>) {\n    (self.inner, self.separator)\n  }\n\n  fn separator_index(&self) -> Option<usize> {\n    self.separator.map(|i| i.get() as usize)\n  }\n}\n\n#[derive(Debug)]\nenum ValidByte {\n  Separator,\n  Byte(u8),\n}\n\nimpl ValidByte {\n  fn alpha_numeric(byte: u8) -> Option<Self> {\n    byte.is_ascii_alphanumeric().then_some(Self::Byte(byte))\n  }\n\n  fn alpha_numeric_hyphen(byte: u8) -> Option<Self> {\n    (byte.is_ascii_alphanumeric() || byte == b'-').then_some(Self::Byte(byte))\n  }\n\n  fn next(&self, next: u8) -> Option<ValidByte> {\n    match (self, next) {\n      (ValidByte::Byte(b'-'), IDENTIFIER_SEPARATOR) => None,\n      (ValidByte::Separator, b'-') => None,\n\n      (_, IDENTIFIER_SEPARATOR) => Some(ValidByte::Separator),\n      (ValidByte::Separator, next) => ValidByte::alpha_numeric(next),\n      (ValidByte::Byte(b'-'), next) => ValidByte::alpha_numeric_hyphen(next),\n      (ValidByte::Byte(b'_'), next) => ValidByte::alpha_numeric_hyphen(next),\n      (ValidByte::Byte(_), next) => ValidByte::alpha_numeric_hyphen(next),\n    }\n  }\n}\n\n/// Errors that can happen when parsing an identifier.\n#[derive(Debug, Error)]\npub enum ParseIdentifierError {\n  /// Identifier start with the plugin prefix.\n  #[error(\"identifiers cannot start with {}\", PLUGIN_PREFIX)]\n  StartsWithTauriPlugin,\n\n  /// Identifier empty.\n  #[error(\"identifiers cannot be empty\")]\n  Empty,\n\n  /// Identifier is too long.\n  #[error(\"identifiers cannot be longer than {len}, found {0}\", len = MAX_LEN_IDENTIFIER)]\n  Humongous(usize),\n\n  /// Identifier is not in a valid format.\n  #[error(\"identifiers can only include lowercase ASCII, hyphens which are not leading or trailing, and a single colon if using a prefix\")]\n  InvalidFormat,\n\n  /// Identifier has multiple separators.\n  #[error(\n    \"identifiers can only include a single separator '{}'\",\n    IDENTIFIER_SEPARATOR\n  )]\n  MultipleSeparators,\n\n  /// Identifier has a trailing hyphen.\n  #[error(\"identifiers cannot have a trailing hyphen\")]\n  TrailingHyphen,\n\n  /// Identifier has a prefix without a base.\n  #[error(\"identifiers cannot have a prefix without a base\")]\n  PrefixWithoutBase,\n}\n\nimpl TryFrom<String> for Identifier {\n  type Error = ParseIdentifierError;\n\n  fn try_from(value: String) -> Result<Self, Self::Error> {\n    if value.starts_with(PLUGIN_PREFIX) {\n      return Err(Self::Error::StartsWithTauriPlugin);\n    }\n\n    if value.is_empty() {\n      return Err(Self::Error::Empty);\n    }\n\n    if value.len() > MAX_LEN_IDENTIFIER {\n      return Err(Self::Error::Humongous(value.len()));\n    }\n\n    let is_core_identifier = value.starts_with(CORE_PLUGIN_IDENTIFIER_PREFIX);\n\n    let mut bytes = value.bytes();\n\n    // grab the first byte only before parsing the rest\n    let mut prev = bytes\n      .next()\n      .and_then(ValidByte::alpha_numeric)\n      .ok_or(Self::Error::InvalidFormat)?;\n\n    let mut idx = 0;\n    let mut separator = None;\n    for byte in bytes {\n      idx += 1; // we already consumed first item\n      match prev.next(byte) {\n        None => return Err(Self::Error::InvalidFormat),\n        Some(next @ ValidByte::Byte(_)) => prev = next,\n        Some(ValidByte::Separator) => {\n          if separator.is_none() || is_core_identifier {\n            // safe to unwrap because idx starts at 1 and cannot go over MAX_IDENTIFIER_LEN\n            separator = Some(idx.try_into().unwrap());\n            prev = ValidByte::Separator\n          } else {\n            return Err(Self::Error::MultipleSeparators);\n          }\n        }\n      }\n    }\n\n    match prev {\n      // empty base\n      ValidByte::Separator => return Err(Self::Error::PrefixWithoutBase),\n\n      // trailing hyphen\n      ValidByte::Byte(b'-') => return Err(Self::Error::TrailingHyphen),\n\n      _ => (),\n    }\n\n    Ok(Self {\n      inner: value,\n      separator,\n    })\n  }\n}\n\nimpl<'de> Deserialize<'de> for Identifier {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    Self::try_from(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)\n  }\n}\n\nimpl Serialize for Identifier {\n  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    serializer.serialize_str(self.get())\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use super::*;\n\n  fn ident(s: impl Into<String>) -> Result<Identifier, ParseIdentifierError> {\n    Identifier::try_from(s.into())\n  }\n\n  #[test]\n  fn max_len_fits_in_u8() {\n    assert!(MAX_LEN_IDENTIFIER < u8::MAX as usize)\n  }\n\n  #[test]\n  fn format() {\n    assert!(ident(\"prefix:base\").is_ok());\n    assert!(ident(\"prefix3:base\").is_ok());\n    assert!(ident(\"preFix:base\").is_ok());\n\n    // bad\n    assert!(ident(\"tauri-plugin-prefix:base\").is_err());\n\n    assert!(ident(\"-prefix-:-base-\").is_err());\n    assert!(ident(\"-prefix:base\").is_err());\n    assert!(ident(\"prefix-:base\").is_err());\n    assert!(ident(\"prefix:-base\").is_err());\n    assert!(ident(\"prefix:base-\").is_err());\n\n    assert!(ident(\"pre--fix:base--sep\").is_ok());\n    assert!(ident(\"prefix:base--sep\").is_ok());\n    assert!(ident(\"pre--fix:base\").is_ok());\n\n    assert!(ident(\"prefix::base\").is_err());\n    assert!(ident(\":base\").is_err());\n    assert!(ident(\"prefix:\").is_err());\n    assert!(ident(\":prefix:base:\").is_err());\n    assert!(ident(\"base:\").is_err());\n\n    assert!(ident(\"\").is_err());\n    assert!(ident(\"💩\").is_err());\n\n    assert!(ident(\"a\".repeat(MAX_LEN_IDENTIFIER + 1)).is_err());\n  }\n\n  #[test]\n  fn base() {\n    assert_eq!(ident(\"prefix:base\").unwrap().get_base(), \"base\");\n    assert_eq!(ident(\"base\").unwrap().get_base(), \"base\");\n  }\n\n  #[test]\n  fn prefix() {\n    assert_eq!(ident(\"prefix:base\").unwrap().get_prefix(), Some(\"prefix\"));\n    assert_eq!(ident(\"base\").unwrap().get_prefix(), None);\n  }\n}\n\n#[cfg(feature = \"build\")]\nmod build {\n  use proc_macro2::TokenStream;\n  use quote::{quote, ToTokens, TokenStreamExt};\n\n  use super::*;\n\n  impl ToTokens for Identifier {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let s = self.get();\n      tokens\n        .append_all(quote! { ::tauri::utils::acl::Identifier::try_from(#s.to_string()).unwrap() })\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/acl/manifest.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Plugin ACL types.\n\nuse std::{collections::BTreeMap, num::NonZeroU64};\n\nuse super::{Permission, PermissionSet};\n#[cfg(feature = \"schema\")]\nuse schemars::schema::*;\nuse serde::{Deserialize, Serialize};\n\n/// The default permission set of the plugin.\n///\n/// Works similarly to a permission with the \"default\" identifier.\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub struct DefaultPermission {\n  /// The version of the permission.\n  pub version: Option<NonZeroU64>,\n\n  /// Human-readable description of what the permission does.\n  /// Tauri convention is to use `<h4>` headings in markdown content\n  /// for Tauri documentation generation purposes.\n  pub description: Option<String>,\n\n  /// All permissions this set contains.\n  pub permissions: Vec<String>,\n}\n\n/// Permission file that can define a default permission, a set of permissions or a list of inlined permissions.\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub struct PermissionFile {\n  /// The default permission set for the plugin\n  pub default: Option<DefaultPermission>,\n\n  /// A list of permissions sets defined\n  #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n  pub set: Vec<PermissionSet>,\n\n  /// A list of inlined permissions\n  #[serde(default)]\n  pub permission: Vec<Permission>,\n}\n\n/// Plugin manifest.\n#[derive(Debug, Serialize, Deserialize, Default)]\npub struct Manifest {\n  /// Default permission.\n  pub default_permission: Option<PermissionSet>,\n  /// Plugin permissions.\n  pub permissions: BTreeMap<String, Permission>,\n  /// Plugin permission sets.\n  pub permission_sets: BTreeMap<String, PermissionSet>,\n  /// The global scope schema.\n  pub global_scope_schema: Option<serde_json::Value>,\n}\n\nimpl Manifest {\n  /// Creates a new manifest from the given plugin permission files and global scope schema.\n  pub fn new(\n    permission_files: Vec<PermissionFile>,\n    global_scope_schema: Option<serde_json::Value>,\n  ) -> Self {\n    let mut manifest = Self {\n      default_permission: None,\n      permissions: BTreeMap::new(),\n      permission_sets: BTreeMap::new(),\n      global_scope_schema,\n    };\n\n    for permission_file in permission_files {\n      if let Some(default) = permission_file.default {\n        manifest.default_permission.replace(PermissionSet {\n          identifier: \"default\".into(),\n          description: default\n            .description\n            .unwrap_or_else(|| \"Default plugin permissions.\".to_string()),\n          permissions: default.permissions,\n        });\n      }\n\n      for permission in permission_file.permission {\n        let key = permission.identifier.clone();\n        manifest.permissions.insert(key, permission);\n      }\n\n      for set in permission_file.set {\n        let key = set.identifier.clone();\n        manifest.permission_sets.insert(key, set);\n      }\n    }\n\n    manifest\n  }\n}\n\n#[cfg(feature = \"schema\")]\ntype ScopeSchema = (Schema, schemars::Map<String, Schema>);\n\n#[cfg(feature = \"schema\")]\nimpl Manifest {\n  /// Return scope schema and extra schema definitions for this plugin manifest.\n  pub fn global_scope_schema(&self) -> Result<Option<ScopeSchema>, super::Error> {\n    self\n      .global_scope_schema\n      .as_ref()\n      .map(|s| {\n        serde_json::from_value::<RootSchema>(s.clone()).map(|s| {\n          // convert RootSchema to Schema\n          let scope_schema = Schema::Object(SchemaObject {\n            array: Some(Box::new(ArrayValidation {\n              items: Some(Schema::Object(s.schema).into()),\n              ..Default::default()\n            })),\n            ..Default::default()\n          });\n\n          (scope_schema, s.definitions)\n        })\n      })\n      .transpose()\n      .map_err(Into::into)\n  }\n}\n\n#[cfg(feature = \"build\")]\nmod build {\n  use proc_macro2::TokenStream;\n  use quote::{quote, ToTokens, TokenStreamExt};\n  use std::convert::identity;\n\n  use super::*;\n  use crate::{literal_struct, tokens::*};\n\n  impl ToTokens for DefaultPermission {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let version = opt_lit_owned(self.version.as_ref().map(|v| {\n        let v = v.get();\n        quote!(::core::num::NonZeroU64::new(#v).unwrap())\n      }));\n      // Only used in build script and macros, so don't include them in runtime\n      let description = quote! { ::core::option::Option::None };\n      let permissions = vec_lit(&self.permissions, str_lit);\n      literal_struct!(\n        tokens,\n        ::tauri::utils::acl::plugin::DefaultPermission,\n        version,\n        description,\n        permissions\n      )\n    }\n  }\n\n  impl ToTokens for Manifest {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let default_permission = opt_lit(self.default_permission.as_ref());\n\n      let permissions = map_lit(\n        quote! { ::std::collections::BTreeMap },\n        &self.permissions,\n        str_lit,\n        identity,\n      );\n\n      let permission_sets = map_lit(\n        quote! { ::std::collections::BTreeMap },\n        &self.permission_sets,\n        str_lit,\n        identity,\n      );\n\n      // Only used in build script and macros, so don't include them in runtime\n      // let global_scope_schema =\n      //   opt_lit_owned(self.global_scope_schema.as_ref().map(json_value_lit));\n      let global_scope_schema = quote! { ::core::option::Option::None };\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::acl::manifest::Manifest,\n        default_permission,\n        permissions,\n        permission_sets,\n        global_scope_schema\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/acl/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Access Control List types.\n//!\n//! # Stability\n//!\n//! This is a core functionality that is not considered part of the stable API.\n//! If you use it, note that it may include breaking changes in the future.\n//!\n//! These items are intended to be non-breaking from a de/serialization standpoint only.\n//! Using and modifying existing config values will try to avoid breaking changes, but they are\n//! free to add fields in the future - causing breaking changes for creating and full destructuring.\n//!\n//! To avoid this, [ignore unknown fields when destructuring] with the `{my, config, ..}` pattern.\n//! If you need to create the Rust config directly without deserializing, then create the struct\n//! the [Struct Update Syntax] with `..Default::default()`, which may need a\n//! `#[allow(clippy::needless_update)]` attribute if you are declaring all fields.\n//!\n//! [ignore unknown fields when destructuring]: https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#ignoring-remaining-parts-of-a-value-with-\n//! [Struct Update Syntax]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax\n\nuse anyhow::Context;\nuse capability::{Capability, CapabilityFile};\nuse serde::{Deserialize, Serialize};\nuse std::{\n  collections::{BTreeMap, HashSet},\n  fs,\n  num::NonZeroU64,\n  path::PathBuf,\n  str::FromStr,\n  sync::Arc,\n};\nuse thiserror::Error;\nuse url::Url;\n\nuse crate::{\n  config::{CapabilityEntry, Config},\n  platform::Target,\n};\n\npub use self::{identifier::*, value::*};\n\n/// Known foldername of the permission schema files\npub const PERMISSION_SCHEMAS_FOLDER_NAME: &str = \"schemas\";\n/// Known filename of the permission schema JSON file\npub const PERMISSION_SCHEMA_FILE_NAME: &str = \"schema.json\";\n/// Known ACL key for the app permissions.\npub const APP_ACL_KEY: &str = \"__app-acl__\";\n/// Known acl manifests file\npub const ACL_MANIFESTS_FILE_NAME: &str = \"acl-manifests.json\";\n/// Known capabilities file\npub const CAPABILITIES_FILE_NAME: &str = \"capabilities.json\";\n/// Allowed commands file name\npub const ALLOWED_COMMANDS_FILE_NAME: &str = \"allowed-commands.json\";\n/// Set by the CLI with when `build > removeUnusedCommands` is set for dead code elimination,\n/// the value is set to the config's directory\npub const REMOVE_UNUSED_COMMANDS_ENV_VAR: &str = \"REMOVE_UNUSED_COMMANDS\";\n\n#[cfg(feature = \"build\")]\npub mod build;\npub mod capability;\npub mod identifier;\npub mod manifest;\npub mod resolved;\n#[cfg(feature = \"schema\")]\npub mod schema;\npub mod value;\n\n/// Possible errors while processing ACL files.\n#[derive(Debug, Error)]\npub enum Error {\n  /// Could not find an environmental variable that is set inside of build scripts.\n  ///\n  /// Whatever generated this should be called inside of a build script.\n  #[error(\"expected build script env var {0}, but it was not found - ensure this is called in a build script\")]\n  BuildVar(&'static str),\n\n  /// The links field in the manifest **MUST** be set and match the name of the crate.\n  #[error(\"package.links field in the Cargo manifest is not set, it should be set to the same as package.name\")]\n  LinksMissing,\n\n  /// The links field in the manifest **MUST** match the name of the crate.\n  #[error(\n    \"package.links field in the Cargo manifest MUST be set to the same value as package.name\"\n  )]\n  LinksName,\n\n  /// IO error while reading a file\n  #[error(\"failed to read file '{}': {}\", _1.display(), _0)]\n  ReadFile(std::io::Error, PathBuf),\n\n  /// IO error while writing a file\n  #[error(\"failed to write file '{}': {}\", _1.display(), _0)]\n  WriteFile(std::io::Error, PathBuf),\n\n  /// IO error while creating a file\n  #[error(\"failed to create file '{}': {}\", _1.display(), _0)]\n  CreateFile(std::io::Error, PathBuf),\n\n  /// IO error while creating a dir\n  #[error(\"failed to create dir '{}': {}\", _1.display(), _0)]\n  CreateDir(std::io::Error, PathBuf),\n\n  /// [`cargo_metadata`] was not able to complete successfully\n  #[cfg(feature = \"build\")]\n  #[error(\"failed to execute: {0}\")]\n  Metadata(#[from] ::cargo_metadata::Error),\n\n  /// Invalid glob\n  #[error(\"failed to run glob: {0}\")]\n  Glob(#[from] glob::PatternError),\n\n  /// Invalid TOML encountered\n  #[error(\"failed to parse TOML: {0}\")]\n  Toml(#[from] toml::de::Error),\n\n  /// Invalid JSON encountered\n  #[error(\"failed to parse JSON: {0}\")]\n  Json(#[from] serde_json::Error),\n\n  /// Invalid JSON5 encountered\n  #[cfg(feature = \"config-json5\")]\n  #[error(\"failed to parse JSON5: {0}\")]\n  Json5(#[from] json5::Error),\n\n  /// Invalid permissions file format\n  #[error(\"unknown permission format {0}\")]\n  UnknownPermissionFormat(String),\n\n  /// Invalid capabilities file format\n  #[error(\"unknown capability format {0}\")]\n  UnknownCapabilityFormat(String),\n\n  /// Permission referenced in set not found.\n  #[error(\"permission {permission} not found from set {set}\")]\n  SetPermissionNotFound {\n    /// Permission identifier.\n    permission: String,\n    /// Set identifier.\n    set: String,\n  },\n\n  /// Unknown ACL manifest.\n  #[error(\"unknown ACL for {key}, expected one of {available}\")]\n  UnknownManifest {\n    /// Manifest key.\n    key: String,\n    /// Available manifest keys.\n    available: String,\n  },\n\n  /// Unknown permission.\n  #[error(\"unknown permission {permission} for {key}\")]\n  UnknownPermission {\n    /// Manifest key.\n    key: String,\n\n    /// Permission identifier.\n    permission: String,\n  },\n\n  /// Capability with the given identifier already exists.\n  #[error(\"capability with identifier `{identifier}` already exists\")]\n  CapabilityAlreadyExists {\n    /// Capability identifier.\n    identifier: String,\n  },\n}\n\n/// Allowed and denied commands inside a permission.\n///\n/// If two commands clash inside of `allow` and `deny`, it should be denied by default.\n#[derive(Debug, Clone, Default, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub struct Commands {\n  /// Allowed command.\n  #[serde(default)]\n  pub allow: Vec<String>,\n\n  /// Denied command, which takes priority.\n  #[serde(default)]\n  pub deny: Vec<String>,\n}\n\n/// An argument for fine grained behavior control of Tauri commands.\n///\n/// It can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command.\n/// The configured scope is passed to the command and will be enforced by the command implementation.\n///\n/// ## Example\n///\n/// ```json\n/// {\n///   \"allow\": [{ \"path\": \"$HOME/**\" }],\n///   \"deny\": [{ \"path\": \"$HOME/secret.txt\" }]\n/// }\n/// ```\n#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub struct Scopes {\n  /// Data that defines what is allowed by the scope.\n  #[serde(skip_serializing_if = \"Option::is_none\")]\n  pub allow: Option<Vec<Value>>,\n  /// Data that defines what is denied by the scope. This should be prioritized by validation logic.\n  #[serde(skip_serializing_if = \"Option::is_none\")]\n  pub deny: Option<Vec<Value>>,\n}\n\nimpl Scopes {\n  fn is_empty(&self) -> bool {\n    self.allow.is_none() && self.deny.is_none()\n  }\n}\n\n/// Descriptions of explicit privileges of commands.\n///\n/// It can enable commands to be accessible in the frontend of the application.\n///\n/// If the scope is defined it can be used to fine grain control the access of individual or multiple commands.\n#[derive(Debug, Clone, Serialize, Deserialize, Default)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub struct Permission {\n  /// The version of the permission.\n  #[serde(skip_serializing_if = \"Option::is_none\")]\n  pub version: Option<NonZeroU64>,\n\n  /// A unique identifier for the permission.\n  pub identifier: String,\n\n  /// Human-readable description of what the permission does.\n  /// Tauri internal convention is to use `<h4>` headings in markdown content\n  /// for Tauri documentation generation purposes.\n  #[serde(skip_serializing_if = \"Option::is_none\")]\n  pub description: Option<String>,\n\n  /// Allowed or denied commands when using this permission.\n  #[serde(default)]\n  pub commands: Commands,\n\n  /// Allowed or denied scoped when using this permission.\n  #[serde(default, skip_serializing_if = \"Scopes::is_empty\")]\n  pub scope: Scopes,\n\n  /// Target platforms this permission applies. By default all platforms are affected by this permission.\n  #[serde(skip_serializing_if = \"Option::is_none\")]\n  pub platforms: Option<Vec<Target>>,\n}\n\nimpl Permission {\n  /// Whether this permission should be active based on the platform target or not.\n  pub fn is_active(&self, target: &Target) -> bool {\n    self\n      .platforms\n      .as_ref()\n      .map(|platforms| platforms.contains(target))\n      .unwrap_or(true)\n  }\n}\n\n/// A set of direct permissions grouped together under a new name.\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub struct PermissionSet {\n  /// A unique identifier for the permission.\n  pub identifier: String,\n\n  /// Human-readable description of what the permission does.\n  pub description: String,\n\n  /// All permissions this set contains.\n  pub permissions: Vec<String>,\n}\n\n/// UrlPattern for [`ExecutionContext::Remote`].\n#[derive(Debug, Clone)]\npub struct RemoteUrlPattern(Arc<urlpattern::UrlPattern>, String);\n\nimpl FromStr for RemoteUrlPattern {\n  type Err = urlpattern::quirks::Error;\n\n  fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n    let mut init = urlpattern::UrlPatternInit::parse_constructor_string::<regex::Regex>(s, None)?;\n    if init.search.as_ref().map(|p| p.is_empty()).unwrap_or(true) {\n      init.search.replace(\"*\".to_string());\n    }\n    if init.hash.as_ref().map(|p| p.is_empty()).unwrap_or(true) {\n      init.hash.replace(\"*\".to_string());\n    }\n    if init\n      .pathname\n      .as_ref()\n      .map(|p| p.is_empty() || p == \"/\")\n      .unwrap_or(true)\n    {\n      init.pathname.replace(\"*\".to_string());\n    }\n    let pattern = urlpattern::UrlPattern::parse(init, Default::default())?;\n    Ok(Self(Arc::new(pattern), s.to_string()))\n  }\n}\n\nimpl RemoteUrlPattern {\n  #[doc(hidden)]\n  pub fn as_str(&self) -> &str {\n    &self.1\n  }\n\n  /// Test if a given URL matches the pattern.\n  pub fn test(&self, url: &Url) -> bool {\n    self\n      .0\n      .test(urlpattern::UrlPatternMatchInput::Url(url.clone()))\n      .unwrap_or_default()\n  }\n}\n\nimpl PartialEq for RemoteUrlPattern {\n  fn eq(&self, other: &Self) -> bool {\n    self.0.protocol() == other.0.protocol()\n      && self.0.username() == other.0.username()\n      && self.0.password() == other.0.password()\n      && self.0.hostname() == other.0.hostname()\n      && self.0.port() == other.0.port()\n      && self.0.pathname() == other.0.pathname()\n      && self.0.search() == other.0.search()\n      && self.0.hash() == other.0.hash()\n  }\n}\n\nimpl Eq for RemoteUrlPattern {}\n\n/// Execution context of an IPC call.\n#[derive(Debug, Default, Clone, Eq, PartialEq)]\npub enum ExecutionContext {\n  /// A local URL is used (the Tauri app URL).\n  #[default]\n  Local,\n  /// Remote URL is trying to use the IPC.\n  Remote {\n    /// The URL trying to access the IPC (URL pattern).\n    url: RemoteUrlPattern,\n  },\n}\n\n/// Test if the app has an application manifest from the ACL\npub fn has_app_manifest(acl: &BTreeMap<String, crate::acl::manifest::Manifest>) -> bool {\n  acl.contains_key(APP_ACL_KEY)\n}\n\n/// Get the capabilities from the config file\npub fn get_capabilities(\n  config: &Config,\n  mut capabilities_from_files: BTreeMap<String, Capability>,\n  additional_capability_files: Option<&[PathBuf]>,\n) -> anyhow::Result<BTreeMap<String, Capability>> {\n  let mut capabilities = if config.app.security.capabilities.is_empty() {\n    capabilities_from_files\n  } else {\n    let mut capabilities = BTreeMap::new();\n    for capability_entry in &config.app.security.capabilities {\n      match capability_entry {\n        CapabilityEntry::Inlined(capability) => {\n          capabilities.insert(capability.identifier.clone(), capability.clone());\n        }\n        CapabilityEntry::Reference(id) => {\n          let capability = capabilities_from_files\n            .remove(id)\n            .with_context(|| format!(\"capability with identifier {id} not found\"))?;\n          capabilities.insert(id.clone(), capability);\n        }\n      }\n    }\n    capabilities\n  };\n\n  if let Some(paths) = additional_capability_files {\n    for path in paths {\n      let capability = CapabilityFile::load(path)\n        .with_context(|| format!(\"failed to read capability {}\", path.display()))?;\n      match capability {\n        CapabilityFile::Capability(c) => {\n          capabilities.insert(c.identifier.clone(), c);\n        }\n        CapabilityFile::List(capabilities_list)\n        | CapabilityFile::NamedList {\n          capabilities: capabilities_list,\n        } => {\n          capabilities.extend(\n            capabilities_list\n              .into_iter()\n              .map(|c| (c.identifier.clone(), c)),\n          );\n        }\n      }\n    }\n  }\n\n  Ok(capabilities)\n}\n\n/// Allowed commands used to communicate between `generate_handle` and `generate_allowed_commands` through json files\n#[derive(Debug, Default, Serialize, Deserialize)]\npub struct AllowedCommands {\n  /// The commands allowed\n  pub commands: HashSet<String>,\n  /// Has application ACL or not\n  pub has_app_acl: bool,\n}\n\n/// Try to reads allowed commands from the out dir made by our build script\npub fn read_allowed_commands() -> Option<AllowedCommands> {\n  let out_file = std::env::var(\"OUT_DIR\")\n    .map(PathBuf::from)\n    .ok()?\n    .join(ALLOWED_COMMANDS_FILE_NAME);\n  let file = fs::read_to_string(&out_file).ok()?;\n  let json = serde_json::from_str(&file).ok()?;\n  Some(json)\n}\n\n#[cfg(test)]\nmod tests {\n  use crate::acl::RemoteUrlPattern;\n\n  #[test]\n  fn url_pattern_domain_wildcard() {\n    let pattern: RemoteUrlPattern = \"http://*\".parse().unwrap();\n\n    assert!(pattern.test(&\"http://tauri.app/path\".parse().unwrap()));\n    assert!(pattern.test(&\"http://tauri.app/path?q=1\".parse().unwrap()));\n\n    assert!(pattern.test(&\"http://localhost/path\".parse().unwrap()));\n    assert!(pattern.test(&\"http://localhost/path?q=1\".parse().unwrap()));\n\n    let pattern: RemoteUrlPattern = \"http://*.tauri.app\".parse().unwrap();\n\n    assert!(!pattern.test(&\"http://tauri.app/path\".parse().unwrap()));\n    assert!(!pattern.test(&\"http://tauri.app/path?q=1\".parse().unwrap()));\n    assert!(pattern.test(&\"http://api.tauri.app/path\".parse().unwrap()));\n    assert!(pattern.test(&\"http://api.tauri.app/path?q=1\".parse().unwrap()));\n    assert!(!pattern.test(&\"http://localhost/path\".parse().unwrap()));\n    assert!(!pattern.test(&\"http://localhost/path?q=1\".parse().unwrap()));\n  }\n\n  #[test]\n  fn url_pattern_path_wildcard() {\n    let pattern: RemoteUrlPattern = \"http://localhost/*\".parse().unwrap();\n    assert!(pattern.test(&\"http://localhost/path\".parse().unwrap()));\n    assert!(pattern.test(&\"http://localhost/path?q=1\".parse().unwrap()));\n  }\n\n  #[test]\n  fn url_pattern_scheme_wildcard() {\n    let pattern: RemoteUrlPattern = \"*://localhost\".parse().unwrap();\n    assert!(pattern.test(&\"http://localhost/path\".parse().unwrap()));\n    assert!(pattern.test(&\"https://localhost/path?q=1\".parse().unwrap()));\n    assert!(pattern.test(&\"custom://localhost/path\".parse().unwrap()));\n  }\n}\n\n#[cfg(feature = \"build\")]\nmod build_ {\n  use std::convert::identity;\n\n  use crate::{literal_struct, tokens::*};\n\n  use super::*;\n  use proc_macro2::TokenStream;\n  use quote::{quote, ToTokens, TokenStreamExt};\n\n  impl ToTokens for ExecutionContext {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::acl::ExecutionContext };\n\n      tokens.append_all(match self {\n        Self::Local => {\n          quote! { #prefix::Local }\n        }\n        Self::Remote { url } => {\n          let url = url.as_str();\n          quote! { #prefix::Remote { url: #url.parse().unwrap() } }\n        }\n      });\n    }\n  }\n\n  impl ToTokens for Commands {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let allow = vec_lit(&self.allow, str_lit);\n      let deny = vec_lit(&self.deny, str_lit);\n      literal_struct!(tokens, ::tauri::utils::acl::Commands, allow, deny)\n    }\n  }\n\n  impl ToTokens for Scopes {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let allow = opt_vec_lit(self.allow.as_ref(), identity);\n      let deny = opt_vec_lit(self.deny.as_ref(), identity);\n      literal_struct!(tokens, ::tauri::utils::acl::Scopes, allow, deny)\n    }\n  }\n\n  impl ToTokens for Permission {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let version = opt_lit_owned(self.version.as_ref().map(|v| {\n        let v = v.get();\n        quote!(::core::num::NonZeroU64::new(#v).unwrap())\n      }));\n      let identifier = str_lit(&self.identifier);\n      // Only used in build script and macros, so don't include them in runtime\n      let description = quote! { ::core::option::Option::None };\n      let commands = &self.commands;\n      let scope = &self.scope;\n      let platforms = opt_vec_lit(self.platforms.as_ref(), identity);\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::acl::Permission,\n        version,\n        identifier,\n        description,\n        commands,\n        scope,\n        platforms\n      )\n    }\n  }\n\n  impl ToTokens for PermissionSet {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let identifier = str_lit(&self.identifier);\n      // Only used in build script and macros, so don't include them in runtime\n      let description = quote! { \"\".to_string() };\n      let permissions = vec_lit(&self.permissions, str_lit);\n      literal_struct!(\n        tokens,\n        ::tauri::utils::acl::PermissionSet,\n        identifier,\n        description,\n        permissions\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/acl/resolved.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Resolved ACL for runtime usage.\n\nuse std::{collections::BTreeMap, fmt};\n\nuse crate::platform::Target;\n\nuse super::{\n  capability::{Capability, PermissionEntry},\n  has_app_manifest,\n  manifest::Manifest,\n  Commands, Error, ExecutionContext, Identifier, Permission, PermissionSet, Scopes, Value,\n  APP_ACL_KEY,\n};\n\n/// A key for a scope, used to link a [`ResolvedCommand#structfield.scope`] to the store [`Resolved#structfield.scopes`].\npub type ScopeKey = u64;\n\n/// Metadata for what referenced a [`ResolvedCommand`].\n#[cfg(debug_assertions)]\n#[derive(Default, Clone, PartialEq, Eq)]\npub struct ResolvedCommandReference {\n  /// Identifier of the capability.\n  pub capability: String,\n  /// Identifier of the permission.\n  pub permission: String,\n}\n\n/// A resolved command permission.\n#[derive(Default, Clone, PartialEq, Eq)]\npub struct ResolvedCommand {\n  /// The execution context of this command.\n  pub context: ExecutionContext,\n  /// The capability/permission that referenced this command.\n  #[cfg(debug_assertions)]\n  pub referenced_by: ResolvedCommandReference,\n  /// The list of window label patterns that was resolved for this command.\n  pub windows: Vec<glob::Pattern>,\n  /// The list of webview label patterns that was resolved for this command.\n  pub webviews: Vec<glob::Pattern>,\n  /// The reference of the scope that is associated with this command. See [`Resolved#structfield.command_scopes`].\n  pub scope_id: Option<ScopeKey>,\n}\n\nimpl fmt::Debug for ResolvedCommand {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    f.debug_struct(\"ResolvedCommand\")\n      .field(\"context\", &self.context)\n      .field(\"windows\", &self.windows)\n      .field(\"webviews\", &self.webviews)\n      .field(\"scope_id\", &self.scope_id)\n      .finish()\n  }\n}\n\n/// A resolved scope. Merges all scopes defined for a single command.\n#[derive(Debug, Default, Clone)]\npub struct ResolvedScope {\n  /// Allows something on the command.\n  pub allow: Vec<Value>,\n  /// Denies something on the command.\n  pub deny: Vec<Value>,\n}\n\n/// Resolved access control list.\n#[derive(Debug, Default)]\npub struct Resolved {\n  /// If we should check the ACL for the app commands\n  pub has_app_acl: bool,\n  /// The commands that are allowed. Map each command with its context to a [`ResolvedCommand`].\n  pub allowed_commands: BTreeMap<String, Vec<ResolvedCommand>>,\n  /// The commands that are denied. Map each command with its context to a [`ResolvedCommand`].\n  pub denied_commands: BTreeMap<String, Vec<ResolvedCommand>>,\n  /// The store of scopes referenced by a [`ResolvedCommand`].\n  pub command_scope: BTreeMap<ScopeKey, ResolvedScope>,\n  /// The global scope.\n  pub global_scope: BTreeMap<String, ResolvedScope>,\n}\n\nimpl Resolved {\n  /// Resolves the ACL for the given plugin permissions and app capabilities.\n  pub fn resolve(\n    acl: &BTreeMap<String, Manifest>,\n    mut capabilities: BTreeMap<String, Capability>,\n    target: Target,\n  ) -> Result<Self, Error> {\n    let mut allowed_commands = BTreeMap::new();\n    let mut denied_commands = BTreeMap::new();\n\n    let mut current_scope_id = 0;\n    let mut command_scope = BTreeMap::new();\n    let mut global_scope: BTreeMap<String, Vec<Scopes>> = BTreeMap::new();\n\n    // resolve commands\n    for capability in capabilities.values_mut().filter(|c| c.is_active(&target)) {\n      with_resolved_permissions(\n        capability,\n        acl,\n        target,\n        |ResolvedPermission {\n           key,\n           commands,\n           scope,\n           #[cfg_attr(not(debug_assertions), allow(unused))]\n           permission_name,\n         }| {\n          if commands.allow.is_empty() && commands.deny.is_empty() {\n            // global scope\n            global_scope.entry(key.to_string()).or_default().push(scope);\n          } else {\n            let scope_id = if scope.allow.is_some() || scope.deny.is_some() {\n              current_scope_id += 1;\n              command_scope.insert(\n                current_scope_id,\n                ResolvedScope {\n                  allow: scope.allow.unwrap_or_default(),\n                  deny: scope.deny.unwrap_or_default(),\n                },\n              );\n              Some(current_scope_id)\n            } else {\n              None\n            };\n\n            for allowed_command in &commands.allow {\n              resolve_command(\n                &mut allowed_commands,\n                if key == APP_ACL_KEY {\n                  allowed_command.to_string()\n                } else if let Some(core_plugin_name) = key.strip_prefix(\"core:\") {\n                  format!(\"plugin:{core_plugin_name}|{allowed_command}\")\n                } else {\n                  format!(\"plugin:{key}|{allowed_command}\")\n                },\n                capability,\n                scope_id,\n                #[cfg(debug_assertions)]\n                permission_name.to_string(),\n              )?;\n            }\n\n            for denied_command in &commands.deny {\n              resolve_command(\n                &mut denied_commands,\n                if key == APP_ACL_KEY {\n                  denied_command.to_string()\n                } else if let Some(core_plugin_name) = key.strip_prefix(\"core:\") {\n                  format!(\"plugin:{core_plugin_name}|{denied_command}\")\n                } else {\n                  format!(\"plugin:{key}|{denied_command}\")\n                },\n                capability,\n                scope_id,\n                #[cfg(debug_assertions)]\n                permission_name.to_string(),\n              )?;\n            }\n          }\n\n          Ok(())\n        },\n      )?;\n    }\n\n    let global_scope = global_scope\n      .into_iter()\n      .map(|(key, scopes)| {\n        let mut resolved_scope = ResolvedScope {\n          allow: Vec::new(),\n          deny: Vec::new(),\n        };\n        for scope in scopes {\n          if let Some(allow) = scope.allow {\n            resolved_scope.allow.extend(allow);\n          }\n          if let Some(deny) = scope.deny {\n            resolved_scope.deny.extend(deny);\n          }\n        }\n        (key, resolved_scope)\n      })\n      .collect();\n\n    let resolved = Self {\n      has_app_acl: has_app_manifest(acl),\n      allowed_commands,\n      denied_commands,\n      command_scope,\n      global_scope,\n    };\n\n    Ok(resolved)\n  }\n}\n\nfn parse_glob_patterns(mut raw: Vec<String>) -> Result<Vec<glob::Pattern>, Error> {\n  raw.sort();\n\n  let mut patterns = Vec::new();\n  for pattern in raw {\n    patterns.push(glob::Pattern::new(&pattern)?);\n  }\n\n  Ok(patterns)\n}\n\nfn resolve_command(\n  commands: &mut BTreeMap<String, Vec<ResolvedCommand>>,\n  command: String,\n  capability: &Capability,\n  scope_id: Option<ScopeKey>,\n  #[cfg(debug_assertions)] referenced_by_permission_identifier: String,\n) -> Result<(), Error> {\n  let mut contexts = Vec::new();\n  if capability.local {\n    contexts.push(ExecutionContext::Local);\n  }\n  if let Some(remote) = &capability.remote {\n    contexts.extend(remote.urls.iter().map(|url| {\n      ExecutionContext::Remote {\n        url: url\n          .parse()\n          .unwrap_or_else(|e| panic!(\"invalid URL pattern for remote URL {url}: {e}\")),\n      }\n    }));\n  }\n\n  for context in contexts {\n    let resolved_list = commands.entry(command.clone()).or_default();\n\n    resolved_list.push(ResolvedCommand {\n      context,\n      #[cfg(debug_assertions)]\n      referenced_by: ResolvedCommandReference {\n        capability: capability.identifier.clone(),\n        permission: referenced_by_permission_identifier.clone(),\n      },\n      windows: parse_glob_patterns(capability.windows.clone())?,\n      webviews: parse_glob_patterns(capability.webviews.clone())?,\n      scope_id,\n    });\n  }\n\n  Ok(())\n}\n\nstruct ResolvedPermission<'a> {\n  key: &'a str,\n  permission_name: &'a str,\n  commands: Commands,\n  scope: Scopes,\n}\n\n/// Iterate over permissions in a capability, resolving permission sets if necessary\n/// to produce a [`ResolvedPermission`] and calling the provided callback with it.\nfn with_resolved_permissions<F: FnMut(ResolvedPermission<'_>) -> Result<(), Error>>(\n  capability: &Capability,\n  acl: &BTreeMap<String, Manifest>,\n  target: Target,\n  mut f: F,\n) -> Result<(), Error> {\n  for permission_entry in &capability.permissions {\n    let permission_id = permission_entry.identifier();\n\n    let permissions = get_permissions(permission_id, acl)?\n      .into_iter()\n      .filter(|p| p.permission.is_active(&target));\n\n    for TraversedPermission {\n      key,\n      permission_name,\n      permission,\n    } in permissions\n    {\n      let mut resolved_scope = Scopes::default();\n      let mut commands = Commands::default();\n\n      if let PermissionEntry::ExtendedPermission {\n        identifier: _,\n        scope,\n      } = permission_entry\n      {\n        if let Some(allow) = scope.allow.clone() {\n          resolved_scope\n            .allow\n            .get_or_insert_with(Default::default)\n            .extend(allow);\n        }\n        if let Some(deny) = scope.deny.clone() {\n          resolved_scope\n            .deny\n            .get_or_insert_with(Default::default)\n            .extend(deny);\n        }\n      }\n\n      if let Some(allow) = permission.scope.allow.clone() {\n        resolved_scope\n          .allow\n          .get_or_insert_with(Default::default)\n          .extend(allow);\n      }\n      if let Some(deny) = permission.scope.deny.clone() {\n        resolved_scope\n          .deny\n          .get_or_insert_with(Default::default)\n          .extend(deny);\n      }\n\n      commands.allow.extend(permission.commands.allow.clone());\n      commands.deny.extend(permission.commands.deny.clone());\n\n      f(ResolvedPermission {\n        key: &key,\n        permission_name: &permission_name,\n        commands,\n        scope: resolved_scope,\n      })?;\n    }\n  }\n\n  Ok(())\n}\n\n/// Traversed permission\n#[derive(Debug)]\npub struct TraversedPermission<'a> {\n  /// Plugin name without the tauri-plugin- prefix\n  pub key: String,\n  /// Permission's name\n  pub permission_name: String,\n  /// Permission details\n  pub permission: &'a Permission,\n}\n\n/// Expand a permissions id based on the ACL to get the associated permissions (e.g. expand some-plugin:default)\npub fn get_permissions<'a>(\n  permission_id: &Identifier,\n  acl: &'a BTreeMap<String, Manifest>,\n) -> Result<Vec<TraversedPermission<'a>>, Error> {\n  let key = permission_id.get_prefix().unwrap_or(APP_ACL_KEY);\n  let permission_name = permission_id.get_base();\n\n  let manifest = acl.get(key).ok_or_else(|| Error::UnknownManifest {\n    key: display_perm_key(key).to_string(),\n    available: acl.keys().cloned().collect::<Vec<_>>().join(\", \"),\n  })?;\n\n  if permission_name == \"default\" {\n    manifest\n      .default_permission\n      .as_ref()\n      .map(|default| get_permission_set_permissions(permission_id, acl, manifest, default))\n      .unwrap_or_else(|| Ok(Default::default()))\n  } else if let Some(set) = manifest.permission_sets.get(permission_name) {\n    get_permission_set_permissions(permission_id, acl, manifest, set)\n  } else if let Some(permission) = manifest.permissions.get(permission_name) {\n    Ok(vec![TraversedPermission {\n      key: key.to_string(),\n      permission_name: permission_name.to_string(),\n      permission,\n    }])\n  } else {\n    Err(Error::UnknownPermission {\n      key: display_perm_key(key).to_string(),\n      permission: permission_name.to_string(),\n    })\n  }\n}\n\n// get the permissions from a permission set\nfn get_permission_set_permissions<'a>(\n  permission_id: &Identifier,\n  acl: &'a BTreeMap<String, Manifest>,\n  manifest: &'a Manifest,\n  set: &'a PermissionSet,\n) -> Result<Vec<TraversedPermission<'a>>, Error> {\n  let key = permission_id.get_prefix().unwrap_or(APP_ACL_KEY);\n\n  let mut permissions = Vec::new();\n\n  for perm in &set.permissions {\n    // a set could include permissions from other plugins\n    // for example `dialog:default`, could include `fs:default`\n    // in this case `perm = \"fs:default\"` which is not a permission\n    // in the dialog manifest so we check if `perm` still have a prefix (i.e `fs:`)\n    // and if so, we resolve this prefix from `acl` first before proceeding\n    let id = Identifier::try_from(perm.clone()).expect(\"invalid identifier in permission set?\");\n    let (manifest, permission_id, key, permission_name) =\n      if let Some((new_key, manifest)) = id.get_prefix().and_then(|k| acl.get(k).map(|m| (k, m))) {\n        (manifest, &id, new_key, id.get_base())\n      } else {\n        (manifest, permission_id, key, perm.as_str())\n      };\n\n    if permission_name == \"default\" {\n      permissions.extend(\n        manifest\n          .default_permission\n          .as_ref()\n          .map(|default| get_permission_set_permissions(permission_id, acl, manifest, default))\n          .transpose()?\n          .unwrap_or_default(),\n      );\n    } else if let Some(permission) = manifest.permissions.get(permission_name) {\n      permissions.push(TraversedPermission {\n        key: key.to_string(),\n        permission_name: permission_name.to_string(),\n        permission,\n      });\n    } else if let Some(permission_set) = manifest.permission_sets.get(permission_name) {\n      permissions.extend(get_permission_set_permissions(\n        permission_id,\n        acl,\n        manifest,\n        permission_set,\n      )?);\n    } else {\n      return Err(Error::SetPermissionNotFound {\n        permission: permission_name.to_string(),\n        set: set.identifier.clone(),\n      });\n    }\n  }\n\n  Ok(permissions)\n}\n\n#[inline]\nfn display_perm_key(prefix: &str) -> &str {\n  if prefix == APP_ACL_KEY {\n    \"app manifest\"\n  } else {\n    prefix\n  }\n}\n\n#[cfg(feature = \"build\")]\nmod build {\n  use proc_macro2::TokenStream;\n  use quote::{quote, ToTokens, TokenStreamExt};\n  use std::convert::identity;\n\n  use super::*;\n  use crate::{literal_struct, tokens::*};\n\n  #[cfg(debug_assertions)]\n  impl ToTokens for ResolvedCommandReference {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let capability = str_lit(&self.capability);\n      let permission = str_lit(&self.permission);\n      literal_struct!(\n        tokens,\n        ::tauri::utils::acl::resolved::ResolvedCommandReference,\n        capability,\n        permission\n      )\n    }\n  }\n\n  impl ToTokens for ResolvedCommand {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      #[cfg(debug_assertions)]\n      let referenced_by = &self.referenced_by;\n\n      let context = &self.context;\n\n      let windows = vec_lit(&self.windows, |window| {\n        let w = window.as_str();\n        quote!(#w.parse().unwrap())\n      });\n      let webviews = vec_lit(&self.webviews, |window| {\n        let w = window.as_str();\n        quote!(#w.parse().unwrap())\n      });\n      let scope_id = opt_lit(self.scope_id.as_ref());\n\n      #[cfg(debug_assertions)]\n      {\n        literal_struct!(\n          tokens,\n          ::tauri::utils::acl::resolved::ResolvedCommand,\n          context,\n          referenced_by,\n          windows,\n          webviews,\n          scope_id\n        )\n      }\n      #[cfg(not(debug_assertions))]\n      literal_struct!(\n        tokens,\n        ::tauri::utils::acl::resolved::ResolvedCommand,\n        context,\n        windows,\n        webviews,\n        scope_id\n      )\n    }\n  }\n\n  impl ToTokens for ResolvedScope {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let allow = vec_lit(&self.allow, identity);\n      let deny = vec_lit(&self.deny, identity);\n      literal_struct!(\n        tokens,\n        ::tauri::utils::acl::resolved::ResolvedScope,\n        allow,\n        deny\n      )\n    }\n  }\n\n  impl ToTokens for Resolved {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let has_app_acl = self.has_app_acl;\n\n      let allowed_commands = map_lit(\n        quote! { ::std::collections::BTreeMap },\n        &self.allowed_commands,\n        str_lit,\n        |v| vec_lit(v, identity),\n      );\n\n      let denied_commands = map_lit(\n        quote! { ::std::collections::BTreeMap },\n        &self.denied_commands,\n        str_lit,\n        |v| vec_lit(v, identity),\n      );\n\n      let command_scope = map_lit(\n        quote! { ::std::collections::BTreeMap },\n        &self.command_scope,\n        identity,\n        identity,\n      );\n\n      let global_scope = map_lit(\n        quote! { ::std::collections::BTreeMap },\n        &self.global_scope,\n        str_lit,\n        identity,\n      );\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::acl::resolved::Resolved,\n        has_app_acl,\n        allowed_commands,\n        denied_commands,\n        command_scope,\n        global_scope\n      )\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n\n  use super::{get_permissions, Identifier, Manifest, Permission, PermissionSet};\n\n  fn manifest<const P: usize, const S: usize>(\n    name: &str,\n    permissions: [&str; P],\n    default_set: Option<&[&str]>,\n    sets: [(&str, &[&str]); S],\n  ) -> (String, Manifest) {\n    (\n      name.to_string(),\n      Manifest {\n        default_permission: default_set.map(|perms| PermissionSet {\n          identifier: \"default\".to_string(),\n          description: \"default set\".to_string(),\n          permissions: perms.iter().map(|s| s.to_string()).collect(),\n        }),\n        permissions: permissions\n          .iter()\n          .map(|p| {\n            (\n              p.to_string(),\n              Permission {\n                identifier: p.to_string(),\n                ..Default::default()\n              },\n            )\n          })\n          .collect(),\n        permission_sets: sets\n          .iter()\n          .map(|(s, perms)| {\n            (\n              s.to_string(),\n              PermissionSet {\n                identifier: s.to_string(),\n                description: format!(\"{s} set\"),\n                permissions: perms.iter().map(|s| s.to_string()).collect(),\n              },\n            )\n          })\n          .collect(),\n        ..Default::default()\n      },\n    )\n  }\n\n  fn id(id: &str) -> Identifier {\n    Identifier::try_from(id.to_string()).unwrap()\n  }\n\n  #[test]\n  fn resolves_permissions_from_other_plugins() {\n    let acl = [\n      manifest(\n        \"fs\",\n        [\"read\", \"write\", \"rm\", \"exist\"],\n        Some(&[\"read\", \"exist\"]),\n        [],\n      ),\n      manifest(\n        \"http\",\n        [\"fetch\", \"fetch-cancel\"],\n        None,\n        [(\"fetch-with-cancel\", &[\"fetch\", \"fetch-cancel\"])],\n      ),\n      manifest(\n        \"dialog\",\n        [\"open\", \"save\"],\n        None,\n        [(\n          \"extra\",\n          &[\n            \"save\",\n            \"fs:default\",\n            \"fs:write\",\n            \"http:default\",\n            \"http:fetch-with-cancel\",\n          ],\n        )],\n      ),\n    ]\n    .into();\n\n    let permissions = get_permissions(&id(\"fs:default\"), &acl).unwrap();\n    assert_eq!(permissions.len(), 2);\n    assert_eq!(permissions[0].key, \"fs\");\n    assert_eq!(permissions[0].permission_name, \"read\");\n    assert_eq!(permissions[1].key, \"fs\");\n    assert_eq!(permissions[1].permission_name, \"exist\");\n\n    let permissions = get_permissions(&id(\"fs:rm\"), &acl).unwrap();\n    assert_eq!(permissions.len(), 1);\n    assert_eq!(permissions[0].key, \"fs\");\n    assert_eq!(permissions[0].permission_name, \"rm\");\n\n    let permissions = get_permissions(&id(\"http:fetch-with-cancel\"), &acl).unwrap();\n    assert_eq!(permissions.len(), 2);\n    assert_eq!(permissions[0].key, \"http\");\n    assert_eq!(permissions[0].permission_name, \"fetch\");\n    assert_eq!(permissions[1].key, \"http\");\n    assert_eq!(permissions[1].permission_name, \"fetch-cancel\");\n\n    let permissions = get_permissions(&id(\"dialog:extra\"), &acl).unwrap();\n    assert_eq!(permissions.len(), 6);\n    assert_eq!(permissions[0].key, \"dialog\");\n    assert_eq!(permissions[0].permission_name, \"save\");\n    assert_eq!(permissions[1].key, \"fs\");\n    assert_eq!(permissions[1].permission_name, \"read\");\n    assert_eq!(permissions[2].key, \"fs\");\n    assert_eq!(permissions[2].permission_name, \"exist\");\n    assert_eq!(permissions[3].key, \"fs\");\n    assert_eq!(permissions[3].permission_name, \"write\");\n    assert_eq!(permissions[4].key, \"http\");\n    assert_eq!(permissions[4].permission_name, \"fetch\");\n    assert_eq!(permissions[5].key, \"http\");\n    assert_eq!(permissions[5].permission_name, \"fetch-cancel\");\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/acl/schema.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Schema generation for ACL items.\n\nuse std::{\n  collections::{btree_map::Values, BTreeMap},\n  fs,\n  path::{Path, PathBuf},\n  slice::Iter,\n};\n\nuse schemars::schema::*;\n\nuse super::{Error, PERMISSION_SCHEMAS_FOLDER_NAME};\nuse crate::{platform::Target, write_if_changed};\n\nuse super::{\n  capability::CapabilityFile,\n  manifest::{Manifest, PermissionFile},\n  Permission, PermissionSet, PERMISSION_SCHEMA_FILE_NAME,\n};\n\n/// Capability schema file name.\npub const CAPABILITIES_SCHEMA_FILE_NAME: &str = \"schema.json\";\n/// Path of the folder where schemas are saved.\npub const CAPABILITIES_SCHEMA_FOLDER_PATH: &str = \"gen/schemas\";\n\n// TODO: once MSRV is high enough, remove generic and use impl <trait>\n// see https://github.com/tauri-apps/tauri/commit/b5561d74aee431f93c0c5b0fa6784fc0a956effe#diff-7c31d393f83cae149122e74ad44ac98e7d70ffb45c9e5b0a94ec52881b6f1cebR30-R42\n/// Permission schema generator trait\npub trait PermissionSchemaGenerator<\n  'a,\n  Ps: Iterator<Item = &'a PermissionSet>,\n  P: Iterator<Item = &'a Permission>,\n>\n{\n  /// Whether has a default permission set or not.\n  fn has_default_permission_set(&self) -> bool;\n\n  /// Default permission set description if any.\n  fn default_set_description(&self) -> Option<&str>;\n\n  /// Default permission set's permissions if any.\n  fn default_set_permissions(&self) -> Option<&Vec<String>>;\n\n  /// Permissions sets to generate schema for.\n  fn permission_sets(&'a self) -> Ps;\n\n  /// Permissions to generate schema for.\n  fn permissions(&'a self) -> P;\n\n  /// A utility function to generate a schema for a permission identifier\n  fn perm_id_schema(name: Option<&str>, id: &str, description: Option<&str>) -> Schema {\n    let command_name = match name {\n      Some(name) if name == super::APP_ACL_KEY => id.to_string(),\n      Some(name) => format!(\"{name}:{id}\"),\n      _ => id.to_string(),\n    };\n\n    let extensions = if let Some(description) = description {\n      [(\n        // This is non-standard, and only used by vscode right now,\n        // but it does work really well\n        \"markdownDescription\".to_string(),\n        serde_json::Value::String(description.to_string()),\n      )]\n      .into()\n    } else {\n      Default::default()\n    };\n\n    Schema::Object(SchemaObject {\n      metadata: Some(Box::new(Metadata {\n        description: description.map(ToString::to_string),\n        ..Default::default()\n      })),\n      instance_type: Some(InstanceType::String.into()),\n      const_value: Some(serde_json::Value::String(command_name)),\n      extensions,\n      ..Default::default()\n    })\n  }\n\n  /// Generate schemas for all possible permissions.\n  fn gen_possible_permission_schemas(&'a self, name: Option<&str>) -> Vec<Schema> {\n    let mut permission_schemas = Vec::new();\n\n    // schema for default set\n    if self.has_default_permission_set() {\n      let description = self.default_set_description().unwrap_or_default();\n      let description = if let Some(permissions) = self.default_set_permissions() {\n        add_permissions_to_description(description, permissions, true)\n      } else {\n        description.to_string()\n      };\n      if !description.is_empty() {\n        let default = Self::perm_id_schema(name, \"default\", Some(&description));\n        permission_schemas.push(default);\n      }\n    }\n\n    // schema for each permission set\n    for set in self.permission_sets() {\n      let description = add_permissions_to_description(&set.description, &set.permissions, false);\n      let schema = Self::perm_id_schema(name, &set.identifier, Some(&description));\n      permission_schemas.push(schema);\n    }\n\n    // schema for each permission\n    for perm in self.permissions() {\n      let schema = Self::perm_id_schema(name, &perm.identifier, perm.description.as_deref());\n      permission_schemas.push(schema);\n    }\n\n    permission_schemas\n  }\n}\n\nfn add_permissions_to_description(\n  description: &str,\n  permissions: &[String],\n  is_default: bool,\n) -> String {\n  if permissions.is_empty() {\n    return description.to_string();\n  }\n  let permissions_list = permissions\n    .iter()\n    .map(|permission| format!(\"- `{permission}`\"))\n    .collect::<Vec<_>>()\n    .join(\"\\n\");\n  let default_permission_set = if is_default {\n    \"default permission set\"\n  } else {\n    \"permission set\"\n  };\n  format!(\"{description}\\n#### This {default_permission_set} includes:\\n\\n{permissions_list}\")\n}\n\nimpl<'a>\n  PermissionSchemaGenerator<\n    'a,\n    Values<'a, std::string::String, PermissionSet>,\n    Values<'a, std::string::String, Permission>,\n  > for Manifest\n{\n  fn has_default_permission_set(&self) -> bool {\n    self.default_permission.is_some()\n  }\n\n  fn default_set_description(&self) -> Option<&str> {\n    self\n      .default_permission\n      .as_ref()\n      .map(|d| d.description.as_str())\n  }\n\n  fn default_set_permissions(&self) -> Option<&Vec<String>> {\n    self.default_permission.as_ref().map(|d| &d.permissions)\n  }\n\n  fn permission_sets(&'a self) -> Values<'a, std::string::String, PermissionSet> {\n    self.permission_sets.values()\n  }\n\n  fn permissions(&'a self) -> Values<'a, std::string::String, Permission> {\n    self.permissions.values()\n  }\n}\n\nimpl<'a> PermissionSchemaGenerator<'a, Iter<'a, PermissionSet>, Iter<'a, Permission>>\n  for PermissionFile\n{\n  fn has_default_permission_set(&self) -> bool {\n    self.default.is_some()\n  }\n\n  fn default_set_description(&self) -> Option<&str> {\n    self.default.as_ref().and_then(|d| d.description.as_deref())\n  }\n\n  fn default_set_permissions(&self) -> Option<&Vec<String>> {\n    self.default.as_ref().map(|d| &d.permissions)\n  }\n\n  fn permission_sets(&'a self) -> Iter<'a, PermissionSet> {\n    self.set.iter()\n  }\n\n  fn permissions(&'a self) -> Iter<'a, Permission> {\n    self.permission.iter()\n  }\n}\n\n/// Collect and include all possible identifiers in `Identifier` definition in the schema\nfn extend_identifier_schema(schema: &mut RootSchema, acl: &BTreeMap<String, Manifest>) {\n  if let Some(Schema::Object(identifier_schema)) = schema.definitions.get_mut(\"Identifier\") {\n    let permission_schemas = acl\n      .iter()\n      .flat_map(|(name, manifest)| manifest.gen_possible_permission_schemas(Some(name)))\n      .collect::<Vec<_>>();\n\n    let new_subschemas = Box::new(SubschemaValidation {\n      one_of: Some(permission_schemas),\n      ..Default::default()\n    });\n\n    identifier_schema.subschemas = Some(new_subschemas);\n    identifier_schema.object = None;\n    identifier_schema.instance_type = None;\n    identifier_schema.metadata().description = Some(\"Permission identifier\".to_string());\n  }\n}\n\n/// Collect permission schemas and its associated scope schema and schema definitions from plugins\n/// and replace `PermissionEntry` extend object syntax with a new schema that does conditional\n/// checks to serve the relevant scope schema for the right permissions schema, in a nutshell, it\n/// will look something like this:\n/// ```text\n/// PermissionEntry {\n///   anyOf {\n///     String,  // default string syntax\n///     Object { // extended object syntax\n///       allOf { // JSON allOf is used but actually means anyOf\n///         {\n///           \"if\": \"identifier\" property anyOf \"fs\" plugin permission,\n///           \"then\": add \"allow\" and \"deny\" properties that match \"fs\" plugin scope schema\n///         },\n///         {\n///           \"if\": \"identifier\" property anyOf \"http\" plugin permission,\n///           \"then\": add \"allow\" and \"deny\" properties that match \"http\" plugin scope schema\n///         },\n///         ...etc,\n///         {\n///           No \"if\" or \"then\", just \"allow\" and \"deny\" properties with default \"#/definitions/Value\"\n///         },\n///       }\n///     }\n///   }\n/// }\n/// ```\nfn extend_permission_entry_schema(root_schema: &mut RootSchema, acl: &BTreeMap<String, Manifest>) {\n  const IDENTIFIER: &str = \"identifier\";\n  const ALLOW: &str = \"allow\";\n  const DENY: &str = \"deny\";\n\n  let mut collected_defs = vec![];\n\n  if let Some(Schema::Object(obj)) = root_schema.definitions.get_mut(\"PermissionEntry\") {\n    let any_of = obj.subschemas().any_of.as_mut().unwrap();\n    let Schema::Object(extend_permission_entry) = any_of.last_mut().unwrap() else {\n      unreachable!(\"PermissionsEntry should be an object not a boolean\");\n    };\n\n    // remove default properties and save it to be added later as a fallback\n    let obj = extend_permission_entry.object.as_mut().unwrap();\n    let default_properties = std::mem::take(&mut obj.properties);\n\n    let default_identifier = default_properties.get(IDENTIFIER).cloned().unwrap();\n    let default_identifier = (IDENTIFIER.to_string(), default_identifier);\n\n    let mut all_of = vec![];\n\n    let schemas = acl.iter().filter_map(|(name, manifest)| {\n      manifest\n        .global_scope_schema()\n        .unwrap_or_else(|e| panic!(\"invalid JSON schema for plugin {name}: {e}\"))\n        .map(|s| (s, manifest.gen_possible_permission_schemas(Some(name))))\n    });\n\n    for ((scope_schema, defs), acl_perm_schema) in schemas {\n      let mut perm_schema = SchemaObject::default();\n      perm_schema.subschemas().any_of = Some(acl_perm_schema);\n\n      let mut if_schema = SchemaObject::default();\n      if_schema.object().properties = [(IDENTIFIER.to_string(), perm_schema.into())].into();\n\n      let mut then_schema = SchemaObject::default();\n      then_schema.object().properties = [\n        (ALLOW.to_string(), scope_schema.clone()),\n        (DENY.to_string(), scope_schema.clone()),\n      ]\n      .into();\n\n      let mut obj = SchemaObject::default();\n      obj.object().properties = [default_identifier.clone()].into();\n      obj.subschemas().if_schema = Some(Box::new(if_schema.into()));\n      obj.subschemas().then_schema = Some(Box::new(then_schema.into()));\n\n      all_of.push(Schema::Object(obj));\n      collected_defs.extend(defs);\n    }\n\n    // add back default properties as a fallback\n    let mut default_obj = SchemaObject::default();\n    default_obj.object().properties = default_properties;\n    all_of.push(Schema::Object(default_obj));\n\n    // replace extended PermissionEntry with the new schema\n    extend_permission_entry.subschemas().all_of = Some(all_of);\n  }\n\n  // extend root schema with definitions collected from plugins\n  root_schema.definitions.extend(collected_defs);\n}\n\n/// Generate schema for CapabilityFile with all possible plugins permissions\npub fn generate_capability_schema(\n  acl: &BTreeMap<String, Manifest>,\n  target: Target,\n) -> crate::Result<()> {\n  let mut schema = schemars::schema_for!(CapabilityFile);\n\n  extend_identifier_schema(&mut schema, acl);\n  extend_permission_entry_schema(&mut schema, acl);\n\n  let schema_str = serde_json::to_string_pretty(&schema).unwrap();\n  // FIXME: in schemars@v1 this doesn't seem to be necessary anymore. If it is, find a better solution.\n  let schema_str = schema_str.replace(\"\\\\r\\\\n\", \"\\\\n\");\n\n  let out_dir = PathBuf::from(CAPABILITIES_SCHEMA_FOLDER_PATH);\n  fs::create_dir_all(&out_dir)?;\n\n  let schema_path = out_dir.join(format!(\"{target}-{CAPABILITIES_SCHEMA_FILE_NAME}\"));\n  if schema_str != fs::read_to_string(&schema_path).unwrap_or_default() {\n    fs::write(&schema_path, schema_str)?;\n\n    fs::copy(\n      schema_path,\n      out_dir.join(format!(\n        \"{}-{CAPABILITIES_SCHEMA_FILE_NAME}\",\n        if target.is_desktop() {\n          \"desktop\"\n        } else {\n          \"mobile\"\n        }\n      )),\n    )?;\n  }\n\n  Ok(())\n}\n\n/// Extend schema with collected permissions from the passed [`PermissionFile`]s.\nfn extend_permission_file_schema(schema: &mut RootSchema, permissions: &[PermissionFile]) {\n  // collect possible permissions\n  let permission_schemas = permissions\n    .iter()\n    .flat_map(|p| p.gen_possible_permission_schemas(None))\n    .collect();\n\n  if let Some(Schema::Object(obj)) = schema.definitions.get_mut(\"PermissionSet\") {\n    let permissions_obj = obj.object().properties.get_mut(\"permissions\");\n    if let Some(Schema::Object(permissions_obj)) = permissions_obj {\n      // replace the permissions property schema object\n      // from a mere string to a reference to `PermissionKind`\n      permissions_obj.array().items.replace(\n        Schema::Object(SchemaObject {\n          reference: Some(\"#/definitions/PermissionKind\".into()),\n          ..Default::default()\n        })\n        .into(),\n      );\n\n      // add the new `PermissionKind` definition in the schema that\n      // is a list of all possible permissions collected\n      schema.definitions.insert(\n        \"PermissionKind\".into(),\n        Schema::Object(SchemaObject {\n          instance_type: Some(InstanceType::String.into()),\n          subschemas: Some(Box::new(SubschemaValidation {\n            one_of: Some(permission_schemas),\n            ..Default::default()\n          })),\n          ..Default::default()\n        }),\n      );\n    }\n  }\n}\n\n/// Generate and write a schema based on the format of a [`PermissionFile`].\npub fn generate_permissions_schema<P: AsRef<Path>>(\n  permissions: &[PermissionFile],\n  out_dir: P,\n) -> Result<(), Error> {\n  let mut schema = schemars::schema_for!(PermissionFile);\n\n  extend_permission_file_schema(&mut schema, permissions);\n\n  let schema_str = serde_json::to_string_pretty(&schema)?;\n\n  // FIXME: in schemars@v1 this doesn't seem to be necessary anymore. If it is, find a better solution.\n  let schema_str = schema_str.replace(\"\\\\r\\\\n\", \"\\\\n\");\n\n  let out_dir = out_dir.as_ref().join(PERMISSION_SCHEMAS_FOLDER_NAME);\n  fs::create_dir_all(&out_dir).map_err(|e| Error::CreateDir(e, out_dir.clone()))?;\n\n  let schema_path = out_dir.join(PERMISSION_SCHEMA_FILE_NAME);\n  write_if_changed(&schema_path, schema_str).map_err(|e| Error::WriteFile(e, schema_path))?;\n\n  Ok(())\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/acl/value.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! A [`Value`] that is used instead of [`toml::Value`] or [`serde_json::Value`]\n//! to support both formats.\n\nuse std::collections::BTreeMap;\nuse std::fmt::Debug;\n\nuse serde::{Deserialize, Serialize};\n\n/// A valid ACL number.\n#[derive(Debug, PartialEq, Serialize, Deserialize, Copy, Clone, PartialOrd)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\npub enum Number {\n  /// Represents an [`i64`].\n  Int(i64),\n\n  /// Represents a [`f64`].\n  Float(f64),\n}\n\nimpl From<i64> for Number {\n  #[inline(always)]\n  fn from(value: i64) -> Self {\n    Self::Int(value)\n  }\n}\n\nimpl From<f64> for Number {\n  #[inline(always)]\n  fn from(value: f64) -> Self {\n    Self::Float(value)\n  }\n}\n\n/// All supported ACL values.\n#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, PartialOrd)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\npub enum Value {\n  /// Represents a null JSON value.\n  Null,\n\n  /// Represents a [`bool`].\n  Bool(bool),\n\n  /// Represents a valid ACL [`Number`].\n  Number(Number),\n\n  /// Represents a [`String`].\n  String(String),\n\n  /// Represents a list of other [`Value`]s.\n  List(Vec<Value>),\n\n  /// Represents a map of [`String`] keys to [`Value`]s.\n  Map(BTreeMap<String, Value>),\n}\n\nimpl From<Value> for serde_json::Value {\n  fn from(value: Value) -> Self {\n    match value {\n      Value::Null => serde_json::Value::Null,\n      Value::Bool(b) => serde_json::Value::Bool(b),\n      Value::Number(Number::Float(f)) => {\n        serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())\n      }\n      Value::Number(Number::Int(i)) => serde_json::Value::Number(i.into()),\n      Value::String(s) => serde_json::Value::String(s),\n      Value::List(list) => serde_json::Value::Array(list.into_iter().map(Into::into).collect()),\n      Value::Map(map) => serde_json::Value::Object(\n        map\n          .into_iter()\n          .map(|(key, value)| (key, value.into()))\n          .collect(),\n      ),\n    }\n  }\n}\n\nimpl From<serde_json::Value> for Value {\n  fn from(value: serde_json::Value) -> Self {\n    match value {\n      serde_json::Value::Null => Value::Null,\n      serde_json::Value::Bool(b) => Value::Bool(b),\n      serde_json::Value::Number(n) => Value::Number(if let Some(f) = n.as_f64() {\n        Number::Float(f)\n      } else if let Some(n) = n.as_u64() {\n        Number::Int(n as i64)\n      } else if let Some(n) = n.as_i64() {\n        Number::Int(n)\n      } else {\n        Number::Int(0)\n      }),\n      serde_json::Value::String(s) => Value::String(s),\n      serde_json::Value::Array(list) => Value::List(list.into_iter().map(Into::into).collect()),\n      serde_json::Value::Object(map) => Value::Map(\n        map\n          .into_iter()\n          .map(|(key, value)| (key, value.into()))\n          .collect(),\n      ),\n    }\n  }\n}\n\nimpl From<bool> for Value {\n  #[inline(always)]\n  fn from(value: bool) -> Self {\n    Self::Bool(value)\n  }\n}\n\nimpl<T: Into<Number>> From<T> for Value {\n  #[inline(always)]\n  fn from(value: T) -> Self {\n    Self::Number(value.into())\n  }\n}\n\nimpl From<String> for Value {\n  #[inline(always)]\n  fn from(value: String) -> Self {\n    Value::String(value)\n  }\n}\n\nimpl From<toml::Value> for Value {\n  #[inline(always)]\n  fn from(value: toml::Value) -> Self {\n    use toml::Value as Toml;\n\n    match value {\n      Toml::String(s) => s.into(),\n      Toml::Integer(i) => i.into(),\n      Toml::Float(f) => f.into(),\n      Toml::Boolean(b) => b.into(),\n      Toml::Datetime(d) => d.to_string().into(),\n      Toml::Array(a) => Value::List(a.into_iter().map(Value::from).collect()),\n      Toml::Table(t) => Value::Map(t.into_iter().map(|(k, v)| (k, v.into())).collect()),\n    }\n  }\n}\n\n#[cfg(feature = \"build\")]\nmod build {\n  use std::convert::identity;\n\n  use crate::tokens::*;\n\n  use super::*;\n  use proc_macro2::TokenStream;\n  use quote::{quote, ToTokens, TokenStreamExt};\n\n  impl ToTokens for Number {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::acl::Number };\n\n      tokens.append_all(match self {\n        Self::Int(i) => {\n          quote! { #prefix::Int(#i) }\n        }\n        Self::Float(f) => {\n          quote! { #prefix::Float (#f) }\n        }\n      });\n    }\n  }\n\n  impl ToTokens for Value {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::acl::Value };\n\n      tokens.append_all(match self {\n        Value::Null => quote! { #prefix::Null },\n        Value::Bool(bool) => quote! { #prefix::Bool(#bool) },\n        Value::Number(number) => quote! { #prefix::Number(#number) },\n        Value::String(str) => {\n          let s = str_lit(str);\n          quote! { #prefix::String(#s) }\n        }\n        Value::List(vec) => {\n          let items = vec_lit(vec, identity);\n          quote! { #prefix::List(#items) }\n        }\n        Value::Map(map) => {\n          let map = map_lit(\n            quote! { ::std::collections::BTreeMap },\n            map,\n            str_lit,\n            identity,\n          );\n          quote! { #prefix::Map(#map) }\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/assets.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The Assets module allows you to read files that have been bundled by tauri\n//! during both compile time and runtime.\n\n#[doc(hidden)]\npub use phf;\nuse std::{\n  borrow::Cow,\n  path::{Component, Path},\n};\n\n/// The token used for script nonces.\npub const SCRIPT_NONCE_TOKEN: &str = \"__TAURI_SCRIPT_NONCE__\";\n/// The token used for style nonces.\npub const STYLE_NONCE_TOKEN: &str = \"__TAURI_STYLE_NONCE__\";\n\n/// Assets iterator.\npub type AssetsIter<'a> = dyn Iterator<Item = (Cow<'a, str>, Cow<'a, [u8]>)> + 'a;\n\n/// Represent an asset file path in a normalized way.\n///\n/// The following rules are enforced and added if needed:\n/// * Unix path component separators\n/// * Has a root directory\n/// * No trailing slash - directories are not included in assets\n#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]\npub struct AssetKey(String);\n\nimpl From<AssetKey> for String {\n  fn from(key: AssetKey) -> Self {\n    key.0\n  }\n}\n\nimpl AsRef<str> for AssetKey {\n  fn as_ref(&self) -> &str {\n    &self.0\n  }\n}\n\nimpl<P: AsRef<Path>> From<P> for AssetKey {\n  fn from(path: P) -> Self {\n    // TODO: change this to utilize `Cow` to prevent allocating an intermediate `PathBuf` when not necessary\n    let path = path.as_ref().to_owned();\n\n    // add in root to mimic how it is used from a server url\n    let path = if path.has_root() {\n      path\n    } else {\n      Path::new(&Component::RootDir).join(path)\n    };\n\n    let buf = if cfg!(windows) {\n      let mut buf = String::new();\n      for component in path.components() {\n        match component {\n          Component::RootDir => buf.push('/'),\n          Component::CurDir => buf.push_str(\"./\"),\n          Component::ParentDir => buf.push_str(\"../\"),\n          Component::Prefix(prefix) => buf.push_str(&prefix.as_os_str().to_string_lossy()),\n          Component::Normal(s) => {\n            buf.push_str(&s.to_string_lossy());\n            buf.push('/')\n          }\n        }\n      }\n\n      // remove the last slash\n      if buf != \"/\" {\n        buf.pop();\n      }\n\n      buf\n    } else {\n      path.to_string_lossy().to_string()\n    };\n\n    AssetKey(buf)\n  }\n}\n\n/// A Content-Security-Policy hash value for a specific directive.\n/// For more information see [the MDN page](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives).\n#[non_exhaustive]\n#[derive(Debug, Clone, Copy)]\npub enum CspHash<'a> {\n  /// The `script-src` directive.\n  Script(&'a str),\n\n  /// The `style-src` directive.\n  Style(&'a str),\n}\n\nimpl CspHash<'_> {\n  /// The Content-Security-Policy directive this hash applies to.\n  pub fn directive(&self) -> &'static str {\n    match self {\n      Self::Script(_) => \"script-src\",\n      Self::Style(_) => \"style-src\",\n    }\n  }\n\n  /// The value of the Content-Security-Policy hash.\n  pub fn hash(&self) -> &str {\n    match self {\n      Self::Script(hash) => hash,\n      Self::Style(hash) => hash,\n    }\n  }\n}\n\n/// [`Assets`] implementation that only contains compile-time compressed and embedded assets.\npub struct EmbeddedAssets {\n  assets: phf::Map<&'static str, &'static [u8]>,\n  // Hashes that must be injected to the CSP of every HTML file.\n  global_hashes: &'static [CspHash<'static>],\n  // Hashes that are associated to the CSP of the HTML file identified by the map key (the HTML asset key).\n  html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,\n}\n\n/// Temporary struct that overrides the Debug formatting for the `assets` field.\n///\n/// It reduces the output size compared to the default, as that would format the binary\n/// data as a slice of numbers like `[65, 66, 67]` for \"ABC\". This instead shows the length\n/// of the slice.\n///\n/// For example: `{\"/index.html\": [u8; 1835], \"/index.js\": [u8; 212]}`\nstruct DebugAssetMap<'a>(&'a phf::Map<&'static str, &'static [u8]>);\n\nimpl std::fmt::Debug for DebugAssetMap<'_> {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    let mut map = f.debug_map();\n    for (k, v) in self.0.entries() {\n      map.key(k);\n      map.value(&format_args!(\"[u8; {}]\", v.len()));\n    }\n    map.finish()\n  }\n}\n\nimpl std::fmt::Debug for EmbeddedAssets {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    f.debug_struct(\"EmbeddedAssets\")\n      .field(\"assets\", &DebugAssetMap(&self.assets))\n      .field(\"global_hashes\", &self.global_hashes)\n      .field(\"html_hashes\", &self.html_hashes)\n      .finish()\n  }\n}\n\nimpl EmbeddedAssets {\n  /// Creates a new instance from the given asset map and script hash list.\n  pub const fn new(\n    map: phf::Map<&'static str, &'static [u8]>,\n    global_hashes: &'static [CspHash<'static>],\n    html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,\n  ) -> Self {\n    Self {\n      assets: map,\n      global_hashes,\n      html_hashes,\n    }\n  }\n\n  /// Get an asset by key.\n  #[cfg(feature = \"compression\")]\n  pub fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {\n    let &(mut asdf) = self.assets.get(key.as_ref())?;\n    // with the exception of extremely small files, output should usually be\n    // at least as large as the compressed version.\n    let mut buf = Vec::with_capacity(asdf.len());\n    brotli::BrotliDecompress(&mut asdf, &mut buf).ok()?;\n    Some(Cow::Owned(buf))\n  }\n\n  /// Get an asset by key.\n  #[cfg(not(feature = \"compression\"))]\n  pub fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {\n    Some(Cow::Borrowed(self.assets.get(key.as_ref())?))\n  }\n\n  /// Iterate on the assets.\n  pub fn iter(&self) -> Box<AssetsIter<'_>> {\n    Box::new(\n      self\n        .assets\n        .into_iter()\n        .map(|(k, b)| (Cow::Borrowed(*k), Cow::Borrowed(*b))),\n    )\n  }\n\n  /// CSP hashes for the given asset.\n  pub fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {\n    Box::new(\n      self\n        .global_hashes\n        .iter()\n        .chain(\n          self\n            .html_hashes\n            .get(html_path.as_ref())\n            .copied()\n            .into_iter()\n            .flatten(),\n        )\n        .copied(),\n    )\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Build script utilities.\n\n/// Link a Swift library.\n#[cfg(target_os = \"macos\")]\npub fn link_apple_library(name: &str, source: impl AsRef<std::path::Path>) {\n  if source.as_ref().join(\"Package.swift\").exists() {\n    link_swift_library(name, source);\n  } else {\n    link_xcode_library(name, source);\n  }\n}\n\n/// Link a Swift library.\n#[cfg(target_os = \"macos\")]\nfn link_swift_library(name: &str, source: impl AsRef<std::path::Path>) {\n  let source = source.as_ref();\n\n  let sdk_root = std::env::var_os(\"SDKROOT\");\n  std::env::remove_var(\"SDKROOT\");\n\n  swift_rs::SwiftLinker::new(\n    &std::env::var(\"MACOSX_DEPLOYMENT_TARGET\").unwrap_or_else(|_| \"10.13\".into()),\n  )\n  .with_ios(&std::env::var(\"IPHONEOS_DEPLOYMENT_TARGET\").unwrap_or_else(|_| \"13.0\".into()))\n  .with_package(name, source)\n  .link();\n\n  if let Some(root) = sdk_root {\n    std::env::set_var(\"SDKROOT\", root);\n  }\n}\n\n/// Link a Xcode library.\n#[cfg(target_os = \"macos\")]\nfn link_xcode_library(name: &str, source: impl AsRef<std::path::Path>) {\n  use std::{path::PathBuf, process::Command};\n\n  let source = source.as_ref();\n  let configuration = if std::env::var(\"DEBUG\")\n    .map(|v| v == \"true\")\n    .unwrap_or_default()\n  {\n    \"Debug\"\n  } else {\n    \"Release\"\n  };\n\n  let (sdk, arch) = match std::env::var(\"TARGET\").unwrap().as_str() {\n    \"aarch64-apple-ios\" => (\"iphoneos\", \"arm64\"),\n    \"aarch64-apple-ios-sim\" => (\"iphonesimulator\", \"arm64\"),\n    \"x86_64-apple-ios\" => (\"iphonesimulator\", \"x86_64\"),\n    _ => return,\n  };\n\n  let out_dir = std::env::var_os(\"OUT_DIR\").map(PathBuf::from).unwrap();\n  let derived_data_path = out_dir.join(format!(\"derivedData-{name}\"));\n\n  let status = Command::new(\"xcodebuild\")\n    .arg(\"build\")\n    .arg(\"-scheme\")\n    .arg(name)\n    .arg(\"-configuration\")\n    .arg(configuration)\n    .arg(\"-sdk\")\n    .arg(sdk)\n    .arg(\"-arch\")\n    .arg(arch)\n    .arg(\"-derivedDataPath\")\n    .arg(&derived_data_path)\n    .arg(\"BUILD_LIBRARY_FOR_DISTRIBUTION=YES\")\n    .arg(\"OTHER_SWIFT_FLAGS=-no-verify-emitted-module-interface\")\n    .current_dir(source)\n    .env_clear()\n    .env(\"PATH\", std::env::var_os(\"PATH\").unwrap_or_default())\n    .status()\n    .unwrap();\n\n  assert!(status.success());\n\n  let lib_out_dir = derived_data_path\n    .join(\"Build\")\n    .join(\"Products\")\n    .join(format!(\"{configuration}-{sdk}\"));\n\n  println!(\n    \"cargo::rustc-link-search=framework={}\",\n    lib_out_dir.display()\n  );\n  println!(\"cargo:rerun-if-changed={}\", source.display());\n  println!(\"cargo:rustc-link-search=native={}\", lib_out_dir.display());\n  println!(\"cargo:rustc-link-lib=static={name}\");\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/config/parse.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse crate::config::Config;\nuse crate::platform::Target;\nuse json_patch::merge;\nuse serde::de::DeserializeOwned;\nuse serde_json::Value;\nuse std::ffi::OsStr;\nuse std::path::{Path, PathBuf};\nuse thiserror::Error;\n\n/// All extensions that are possibly supported, but perhaps not enabled.\npub const EXTENSIONS_SUPPORTED: &[&str] = &[\"json\", \"json5\", \"toml\"];\n\n/// All configuration formats that are possibly supported, but perhaps not enabled.\npub const SUPPORTED_FORMATS: &[ConfigFormat] =\n  &[ConfigFormat::Json, ConfigFormat::Json5, ConfigFormat::Toml];\n\n/// All configuration formats that are currently enabled.\npub const ENABLED_FORMATS: &[ConfigFormat] = &[\n  ConfigFormat::Json,\n  #[cfg(feature = \"config-json5\")]\n  ConfigFormat::Json5,\n  #[cfg(feature = \"config-toml\")]\n  ConfigFormat::Toml,\n];\n\n/// The available configuration formats.\n#[derive(Debug, Copy, Clone)]\npub enum ConfigFormat {\n  /// The default JSON (tauri.conf.json) format.\n  Json,\n  /// The JSON5 (tauri.conf.json5) format.\n  Json5,\n  /// The TOML (Tauri.toml file) format.\n  Toml,\n}\n\nimpl ConfigFormat {\n  /// Maps the config format to its file name.\n  pub fn into_file_name(self) -> &'static str {\n    match self {\n      Self::Json => \"tauri.conf.json\",\n      Self::Json5 => \"tauri.conf.json5\",\n      Self::Toml => \"Tauri.toml\",\n    }\n  }\n\n  fn into_platform_file_name(self, target: Target) -> &'static str {\n    match self {\n      Self::Json => match target {\n        Target::MacOS => \"tauri.macos.conf.json\",\n        Target::Windows => \"tauri.windows.conf.json\",\n        Target::Linux => \"tauri.linux.conf.json\",\n        Target::Android => \"tauri.android.conf.json\",\n        Target::Ios => \"tauri.ios.conf.json\",\n      },\n      Self::Json5 => match target {\n        Target::MacOS => \"tauri.macos.conf.json5\",\n        Target::Windows => \"tauri.windows.conf.json5\",\n        Target::Linux => \"tauri.linux.conf.json5\",\n        Target::Android => \"tauri.android.conf.json5\",\n        Target::Ios => \"tauri.ios.conf.json5\",\n      },\n      Self::Toml => match target {\n        Target::MacOS => \"Tauri.macos.toml\",\n        Target::Windows => \"Tauri.windows.toml\",\n        Target::Linux => \"Tauri.linux.toml\",\n        Target::Android => \"Tauri.android.toml\",\n        Target::Ios => \"Tauri.ios.toml\",\n      },\n    }\n  }\n}\n\n/// Represents all the errors that can happen while reading the config.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum ConfigError {\n  /// Failed to parse from JSON.\n  #[error(\"unable to parse JSON Tauri config file at {path} because {error}\")]\n  FormatJson {\n    /// The path that failed to parse into JSON.\n    path: PathBuf,\n\n    /// The parsing [`serde_json::Error`].\n    error: serde_json::Error,\n  },\n\n  /// Failed to parse from JSON5.\n  #[cfg(feature = \"config-json5\")]\n  #[error(\"unable to parse JSON5 Tauri config file at {path} because {error}\")]\n  FormatJson5 {\n    /// The path that failed to parse into JSON5.\n    path: PathBuf,\n\n    /// The parsing [`json5::Error`].\n    error: ::json5::Error,\n  },\n\n  /// Failed to parse from TOML.\n  #[cfg(feature = \"config-toml\")]\n  #[error(\"unable to parse toml Tauri config file at {path} because {error}\")]\n  FormatToml {\n    /// The path that failed to parse into TOML.\n    path: PathBuf,\n\n    /// The parsing [`toml::Error`].\n    error: Box<::toml::de::Error>,\n  },\n\n  /// Unknown config file name encountered.\n  #[error(\"unsupported format encountered {0}\")]\n  UnsupportedFormat(String),\n\n  /// Known file extension encountered, but corresponding parser is not enabled (cargo features).\n  #[error(\"supported (but disabled) format encountered {extension} - try enabling `{feature}` \")]\n  DisabledFormat {\n    /// The extension encountered.\n    extension: String,\n\n    /// The cargo feature to enable it.\n    feature: String,\n  },\n\n  /// A generic IO error with context of what caused it.\n  #[error(\"unable to read Tauri config file at {path} because {error}\")]\n  Io {\n    /// The path the IO error occurred on.\n    path: PathBuf,\n\n    /// The [`std::io::Error`].\n    error: std::io::Error,\n  },\n}\n\n/// Determines if the given folder has a configuration file.\npub fn folder_has_configuration_file(target: Target, folder: &Path) -> bool {\n  folder.join(ConfigFormat::Json.into_file_name()).exists()\n      || folder.join(ConfigFormat::Json5.into_file_name()).exists()\n      || folder.join(ConfigFormat::Toml.into_file_name()).exists()\n       // platform file names\n       || folder.join(ConfigFormat::Json.into_platform_file_name(target)).exists()\n      || folder.join(ConfigFormat::Json5.into_platform_file_name(target)).exists()\n      || folder.join(ConfigFormat::Toml.into_platform_file_name(target)).exists()\n}\n\n/// Determines if the given file path represents a Tauri configuration file.\npub fn is_configuration_file(target: Target, path: &Path) -> bool {\n  path\n    .file_name()\n    .map(|file_name| {\n      file_name == OsStr::new(ConfigFormat::Json.into_file_name())\n        || file_name == OsStr::new(ConfigFormat::Json5.into_file_name())\n        || file_name == OsStr::new(ConfigFormat::Toml.into_file_name())\n      // platform file names\n      || file_name == OsStr::new(ConfigFormat::Json.into_platform_file_name(target))\n        || file_name == OsStr::new(ConfigFormat::Json5.into_platform_file_name(target))\n        || file_name == OsStr::new(ConfigFormat::Toml.into_platform_file_name(target))\n    })\n    .unwrap_or_default()\n}\n\n/// Reads the configuration from the given root directory.\n///\n/// It first looks for a `tauri.conf.json[5]` or `Tauri.toml` file on the given directory. The file must exist.\n/// Then it looks for a platform-specific configuration file:\n/// - `tauri.macos.conf.json[5]` or `Tauri.macos.toml` on macOS\n/// - `tauri.linux.conf.json[5]` or `Tauri.linux.toml` on Linux\n/// - `tauri.windows.conf.json[5]` or `Tauri.windows.toml` on Windows\n/// - `tauri.android.conf.json[5]` or `Tauri.android.toml` on Android\n/// - `tauri.ios.conf.json[5]` or `Tauri.ios.toml` on iOS\n///   Merging the configurations using [JSON Merge Patch (RFC 7396)].\n///\n/// Returns the raw configuration and used config paths.\n///\n/// [JSON Merge Patch (RFC 7396)]: https://datatracker.ietf.org/doc/html/rfc7396.\npub fn read_from(target: Target, root_dir: &Path) -> Result<(Value, Vec<PathBuf>), ConfigError> {\n  let (mut config, config_file_path) = parse_value(target, root_dir.join(\"tauri.conf.json\"))?;\n  let mut config_paths = vec![config_file_path];\n  if let Some((platform_config, path)) = read_platform(target, root_dir)? {\n    config_paths.push(path);\n    merge(&mut config, &platform_config);\n  }\n  Ok((config, config_paths))\n}\n\n/// Reads the platform-specific configuration file from the given root directory if it exists.\n///\n/// Check [`read_from`] for more information.\npub fn read_platform(\n  target: Target,\n  root_dir: &Path,\n) -> Result<Option<(Value, PathBuf)>, ConfigError> {\n  let platform_config_path = root_dir.join(ConfigFormat::Json.into_platform_file_name(target));\n  if does_supported_file_name_exist(target, &platform_config_path) {\n    let (platform_config, path): (Value, PathBuf) = parse_value(target, platform_config_path)?;\n    Ok(Some((platform_config, path)))\n  } else {\n    Ok(None)\n  }\n}\n\n/// Check if a supported config file exists at path.\n///\n/// The passed path is expected to be the path to the \"default\" configuration format, in this case\n/// JSON with `.json`.\npub fn does_supported_file_name_exist(target: Target, path: impl Into<PathBuf>) -> bool {\n  let path = path.into();\n  let source_file_name = path.file_name().unwrap();\n  let lookup_platform_config = ENABLED_FORMATS\n    .iter()\n    .any(|format| source_file_name == format.into_platform_file_name(target));\n  ENABLED_FORMATS.iter().any(|format| {\n    path\n      .with_file_name(if lookup_platform_config {\n        format.into_platform_file_name(target)\n      } else {\n        format.into_file_name()\n      })\n      .exists()\n  })\n}\n\n/// Parse the config from path, including alternative formats.\n///\n/// Hierarchy:\n/// 1. Check if `tauri.conf.json` exists\n///    a. Parse it with `serde_json`\n///    b. Parse it with `json5` if `serde_json` fails\n///    c. Return original `serde_json` error if all above steps failed\n/// 2. Check if `tauri.conf.json5` exists\n///    a. Parse it with `json5`\n///    b. Return error if all above steps failed\n/// 3. Check if `Tauri.json` exists\n///    a. Parse it with `toml`\n///    b. Return error if all above steps failed\n/// 4. Return error if all above steps failed\npub fn parse(target: Target, path: impl Into<PathBuf>) -> Result<(Config, PathBuf), ConfigError> {\n  do_parse(target, path.into())\n}\n\n/// See [`parse`] for specifics, returns a JSON [`Value`] instead of [`Config`].\npub fn parse_value(\n  target: Target,\n  path: impl Into<PathBuf>,\n) -> Result<(Value, PathBuf), ConfigError> {\n  do_parse(target, path.into())\n}\n\nfn do_parse<D: DeserializeOwned>(\n  target: Target,\n  path: PathBuf,\n) -> Result<(D, PathBuf), ConfigError> {\n  let file_name = path\n    .file_name()\n    .map(OsStr::to_string_lossy)\n    .unwrap_or_default();\n  let lookup_platform_config = ENABLED_FORMATS\n    .iter()\n    .any(|format| file_name == format.into_platform_file_name(target));\n\n  let json5 = path.with_file_name(if lookup_platform_config {\n    ConfigFormat::Json5.into_platform_file_name(target)\n  } else {\n    ConfigFormat::Json5.into_file_name()\n  });\n  let toml = path.with_file_name(if lookup_platform_config {\n    ConfigFormat::Toml.into_platform_file_name(target)\n  } else {\n    ConfigFormat::Toml.into_file_name()\n  });\n\n  let path_ext = path\n    .extension()\n    .map(OsStr::to_string_lossy)\n    .unwrap_or_default();\n\n  if path.exists() {\n    let raw = read_to_string(&path)?;\n\n    // to allow us to easily use the compile-time #[cfg], we always bind\n    #[allow(clippy::let_and_return)]\n    let json = do_parse_json(&raw, &path);\n\n    // we also want to support **valid** json5 in the .json extension if the feature is enabled.\n    // if the json5 is not valid the serde_json error for regular json will be returned.\n    // this could be a bit confusing, so we may want to encourage users using json5 to use the\n    // .json5 extension instead of .json\n    #[cfg(feature = \"config-json5\")]\n    let json = {\n      match do_parse_json5(&raw, &path) {\n        json5 @ Ok(_) => json5,\n\n        // assume any errors from json5 in a .json file is because it's not json5\n        Err(_) => json,\n      }\n    };\n\n    json.map(|j| (j, path))\n  } else if json5.exists() {\n    #[cfg(feature = \"config-json5\")]\n    {\n      let raw = read_to_string(&json5)?;\n      do_parse_json5(&raw, &json5).map(|config| (config, json5))\n    }\n\n    #[cfg(not(feature = \"config-json5\"))]\n    Err(ConfigError::DisabledFormat {\n      extension: \".json5\".into(),\n      feature: \"config-json5\".into(),\n    })\n  } else if toml.exists() {\n    #[cfg(feature = \"config-toml\")]\n    {\n      let raw = read_to_string(&toml)?;\n      do_parse_toml(&raw, &toml).map(|config| (config, toml))\n    }\n\n    #[cfg(not(feature = \"config-toml\"))]\n    Err(ConfigError::DisabledFormat {\n      extension: \".toml\".into(),\n      feature: \"config-toml\".into(),\n    })\n  } else if !EXTENSIONS_SUPPORTED.contains(&path_ext.as_ref()) {\n    Err(ConfigError::UnsupportedFormat(path_ext.to_string()))\n  } else {\n    Err(ConfigError::Io {\n      path,\n      error: std::io::ErrorKind::NotFound.into(),\n    })\n  }\n}\n\n/// \"Low-level\" helper to parse JSON into a [`Config`].\n///\n/// `raw` should be the contents of the file that is represented by `path`.\npub fn parse_json(raw: &str, path: &Path) -> Result<Config, ConfigError> {\n  do_parse_json(raw, path)\n}\n\n/// \"Low-level\" helper to parse JSON into a JSON [`Value`].\n///\n/// `raw` should be the contents of the file that is represented by `path`.\npub fn parse_json_value(raw: &str, path: &Path) -> Result<Value, ConfigError> {\n  do_parse_json(raw, path)\n}\n\nfn do_parse_json<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {\n  serde_json::from_str(raw).map_err(|error| ConfigError::FormatJson {\n    path: path.into(),\n    error,\n  })\n}\n\n/// \"Low-level\" helper to parse JSON5 into a [`Config`].\n///\n/// `raw` should be the contents of the file that is represented by `path`. This function requires\n/// the `config-json5` feature to be enabled.\n#[cfg(feature = \"config-json5\")]\npub fn parse_json5(raw: &str, path: &Path) -> Result<Config, ConfigError> {\n  do_parse_json5(raw, path)\n}\n\n/// \"Low-level\" helper to parse JSON5 into a JSON [`Value`].\n///\n/// `raw` should be the contents of the file that is represented by `path`. This function requires\n/// the `config-json5` feature to be enabled.\n#[cfg(feature = \"config-json5\")]\npub fn parse_json5_value(raw: &str, path: &Path) -> Result<Value, ConfigError> {\n  do_parse_json5(raw, path)\n}\n\n#[cfg(feature = \"config-json5\")]\nfn do_parse_json5<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {\n  ::json5::from_str(raw).map_err(|error| ConfigError::FormatJson5 {\n    path: path.into(),\n    error,\n  })\n}\n\n#[cfg(feature = \"config-toml\")]\nfn do_parse_toml<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {\n  ::toml::from_str(raw).map_err(|error| ConfigError::FormatToml {\n    path: path.into(),\n    error: Box::new(error),\n  })\n}\n\n/// Helper function to wrap IO errors from [`std::fs::read_to_string`] into a [`ConfigError`].\nfn read_to_string(path: &Path) -> Result<String, ConfigError> {\n  std::fs::read_to_string(path).map_err(|error| ConfigError::Io {\n    path: path.into(),\n    error,\n  })\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/config.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The Tauri configuration used at runtime.\n//!\n//! It is pulled from a `tauri.conf.json` file and the [`Config`] struct is generated at compile time.\n//!\n//! # Stability\n//!\n//! This is a core functionality that is not considered part of the stable API.\n//! If you use it, note that it may include breaking changes in the future.\n//!\n//! These items are intended to be non-breaking from a de/serialization standpoint only.\n//! Using and modifying existing config values will try to avoid breaking changes, but they are\n//! free to add fields in the future - causing breaking changes for creating and full destructuring.\n//!\n//! To avoid this, [ignore unknown fields when destructuring] with the `{my, config, ..}` pattern.\n//! If you need to create the Rust config directly without deserializing, then create the struct\n//! the [Struct Update Syntax] with `..Default::default()`, which may need a\n//! `#[allow(clippy::needless_update)]` attribute if you are declaring all fields.\n//!\n//! [ignore unknown fields when destructuring]: https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#ignoring-remaining-parts-of-a-value-with-\n//! [Struct Update Syntax]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax\n\nuse http::response::Builder;\n#[cfg(feature = \"schema\")]\nuse schemars::schema::Schema;\n#[cfg(feature = \"schema\")]\nuse schemars::JsonSchema;\nuse semver::Version;\nuse serde::{\n  de::{Deserializer, Error as DeError, Visitor},\n  Deserialize, Serialize, Serializer,\n};\nuse serde_json::Value as JsonValue;\nuse serde_untagged::UntaggedEnumVisitor;\nuse serde_with::skip_serializing_none;\nuse url::Url;\n\nuse std::{\n  collections::HashMap,\n  fmt::{self, Display},\n  fs::read_to_string,\n  path::PathBuf,\n  str::FromStr,\n};\n\n#[cfg(feature = \"schema\")]\nfn add_description(schema: Schema, description: impl Into<String>) -> Schema {\n  let value = description.into();\n  if value.is_empty() {\n    schema\n  } else {\n    let mut schema_obj = schema.into_object();\n    schema_obj.metadata().description = value.into();\n    Schema::Object(schema_obj)\n  }\n}\n\n/// Items to help with parsing content into a [`Config`].\npub mod parse;\n\nuse crate::{acl::capability::Capability, TitleBarStyle, WindowEffect, WindowEffectState};\n\npub use self::parse::parse;\n\nfn default_true() -> bool {\n  true\n}\n\n/// An URL to open on a Tauri webview window.\n#[derive(PartialEq, Eq, Debug, Clone, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(untagged)]\n#[non_exhaustive]\npub enum WebviewUrl {\n  /// An external URL. Must use either the `http` or `https` schemes.\n  External(Url),\n  /// The path portion of an app URL.\n  /// For instance, to load `tauri://localhost/users/john`,\n  /// you can simply provide `users/john` in this configuration.\n  App(PathBuf),\n  /// A custom protocol url, for example, `doom://index.html`\n  CustomProtocol(Url),\n}\n\nimpl<'de> Deserialize<'de> for WebviewUrl {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    #[derive(Deserialize)]\n    #[serde(untagged)]\n    enum WebviewUrlDeserializer {\n      Url(Url),\n      Path(PathBuf),\n    }\n\n    match WebviewUrlDeserializer::deserialize(deserializer)? {\n      WebviewUrlDeserializer::Url(u) => {\n        if u.scheme() == \"https\" || u.scheme() == \"http\" {\n          Ok(Self::External(u))\n        } else {\n          Ok(Self::CustomProtocol(u))\n        }\n      }\n      WebviewUrlDeserializer::Path(p) => Ok(Self::App(p)),\n    }\n  }\n}\n\nimpl fmt::Display for WebviewUrl {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    match self {\n      Self::External(url) | Self::CustomProtocol(url) => write!(f, \"{url}\"),\n      Self::App(path) => write!(f, \"{}\", path.display()),\n    }\n  }\n}\n\nimpl Default for WebviewUrl {\n  fn default() -> Self {\n    Self::App(\"index.html\".into())\n  }\n}\n\n/// A bundle referenced by tauri-bundler.\n#[derive(Debug, PartialEq, Eq, Clone)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[cfg_attr(feature = \"schema\", schemars(rename_all = \"lowercase\"))]\npub enum BundleType {\n  /// The debian bundle (.deb).\n  Deb,\n  /// The RPM bundle (.rpm).\n  Rpm,\n  /// The AppImage bundle (.appimage).\n  AppImage,\n  /// The Microsoft Installer bundle (.msi).\n  Msi,\n  /// The NSIS bundle (.exe).\n  Nsis,\n  /// The macOS application bundle (.app).\n  App,\n  /// The Apple Disk Image bundle (.dmg).\n  Dmg,\n}\n\nimpl BundleType {\n  /// All bundle types.\n  fn all() -> &'static [Self] {\n    &[\n      BundleType::Deb,\n      BundleType::Rpm,\n      BundleType::AppImage,\n      BundleType::Msi,\n      BundleType::Nsis,\n      BundleType::App,\n      BundleType::Dmg,\n    ]\n  }\n}\n\nimpl Display for BundleType {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    write!(\n      f,\n      \"{}\",\n      match self {\n        Self::Deb => \"deb\",\n        Self::Rpm => \"rpm\",\n        Self::AppImage => \"appimage\",\n        Self::Msi => \"msi\",\n        Self::Nsis => \"nsis\",\n        Self::App => \"app\",\n        Self::Dmg => \"dmg\",\n      }\n    )\n  }\n}\n\nimpl Serialize for BundleType {\n  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    serializer.serialize_str(self.to_string().as_ref())\n  }\n}\n\nimpl<'de> Deserialize<'de> for BundleType {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let s = String::deserialize(deserializer)?;\n    match s.to_lowercase().as_str() {\n      \"deb\" => Ok(Self::Deb),\n      \"rpm\" => Ok(Self::Rpm),\n      \"appimage\" => Ok(Self::AppImage),\n      \"msi\" => Ok(Self::Msi),\n      \"nsis\" => Ok(Self::Nsis),\n      \"app\" => Ok(Self::App),\n      \"dmg\" => Ok(Self::Dmg),\n      _ => Err(DeError::custom(format!(\"unknown bundle target '{s}'\"))),\n    }\n  }\n}\n\n/// Targets to bundle. Each value is case insensitive.\n#[derive(Debug, PartialEq, Eq, Clone, Default)]\npub enum BundleTarget {\n  /// Bundle all targets.\n  #[default]\n  All,\n  /// A list of bundle targets.\n  List(Vec<BundleType>),\n  /// A single bundle target.\n  One(BundleType),\n}\n\n#[cfg(feature = \"schema\")]\nimpl schemars::JsonSchema for BundleTarget {\n  fn schema_name() -> std::string::String {\n    \"BundleTarget\".to_owned()\n  }\n\n  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    let any_of = vec![\n      schemars::schema::SchemaObject {\n        const_value: Some(\"all\".into()),\n        metadata: Some(Box::new(schemars::schema::Metadata {\n          description: Some(\"Bundle all targets.\".to_owned()),\n          ..Default::default()\n        })),\n        ..Default::default()\n      }\n      .into(),\n      add_description(\n        gen.subschema_for::<Vec<BundleType>>(),\n        \"A list of bundle targets.\",\n      ),\n      add_description(gen.subschema_for::<BundleType>(), \"A single bundle target.\"),\n    ];\n\n    schemars::schema::SchemaObject {\n      subschemas: Some(Box::new(schemars::schema::SubschemaValidation {\n        any_of: Some(any_of),\n        ..Default::default()\n      })),\n      metadata: Some(Box::new(schemars::schema::Metadata {\n        description: Some(\"Targets to bundle. Each value is case insensitive.\".to_owned()),\n        ..Default::default()\n      })),\n      ..Default::default()\n    }\n    .into()\n  }\n}\n\nimpl Serialize for BundleTarget {\n  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    match self {\n      Self::All => serializer.serialize_str(\"all\"),\n      Self::List(l) => l.serialize(serializer),\n      Self::One(t) => serializer.serialize_str(t.to_string().as_ref()),\n    }\n  }\n}\n\nimpl<'de> Deserialize<'de> for BundleTarget {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    #[derive(Deserialize, Serialize)]\n    #[serde(untagged)]\n    pub enum BundleTargetInner {\n      List(Vec<BundleType>),\n      One(BundleType),\n      All(String),\n    }\n\n    match BundleTargetInner::deserialize(deserializer)? {\n      BundleTargetInner::All(s) if s.to_lowercase() == \"all\" => Ok(Self::All),\n      BundleTargetInner::All(t) => Err(DeError::custom(format!(\n        \"invalid bundle type {t}, expected one of `all`, {}\",\n        BundleType::all()\n          .iter()\n          .map(|b| format!(\"`{b}`\"))\n          .collect::<Vec<_>>()\n          .join(\", \")\n      ))),\n      BundleTargetInner::List(l) => Ok(Self::List(l)),\n      BundleTargetInner::One(t) => Ok(Self::One(t)),\n    }\n  }\n}\n\nimpl BundleTarget {\n  /// Gets the bundle targets as a [`Vec`]. The vector is empty when set to [`BundleTarget::All`].\n  #[allow(dead_code)]\n  pub fn to_vec(&self) -> Vec<BundleType> {\n    match self {\n      Self::All => BundleType::all().to_vec(),\n      Self::List(list) => list.clone(),\n      Self::One(i) => vec![i.clone()],\n    }\n  }\n}\n\n/// Configuration for AppImage bundles.\n///\n/// See more: <https://v2.tauri.app/reference/config/#appimageconfig>\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct AppImageConfig {\n  /// Include additional gstreamer dependencies needed for audio and video playback.\n  /// This increases the bundle size by ~15-35MB depending on your build system.\n  #[serde(default, alias = \"bundle-media-framework\")]\n  pub bundle_media_framework: bool,\n  /// The files to include in the Appimage Binary.\n  #[serde(default)]\n  pub files: HashMap<PathBuf, PathBuf>,\n}\n\n/// Configuration for Debian (.deb) bundles.\n///\n/// See more: <https://v2.tauri.app/reference/config/#debconfig>\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct DebConfig {\n  /// The list of deb dependencies your application relies on.\n  pub depends: Option<Vec<String>>,\n  /// The list of deb dependencies your application recommends.\n  pub recommends: Option<Vec<String>>,\n  /// The list of dependencies the package provides.\n  pub provides: Option<Vec<String>>,\n  /// The list of package conflicts.\n  pub conflicts: Option<Vec<String>>,\n  /// The list of package replaces.\n  pub replaces: Option<Vec<String>>,\n  /// The files to include on the package.\n  #[serde(default)]\n  pub files: HashMap<PathBuf, PathBuf>,\n  /// Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections\n  pub section: Option<String>,\n  /// Change the priority of the Debian Package. By default, it is set to `optional`.\n  /// Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\n  pub priority: Option<String>,\n  /// Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\n  /// <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>\n  pub changelog: Option<PathBuf>,\n  /// Path to a custom desktop file Handlebars template.\n  ///\n  /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n  #[serde(alias = \"desktop-template\")]\n  pub desktop_template: Option<PathBuf>,\n  /// Path to script that will be executed before the package is unpacked. See\n  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\n  #[serde(alias = \"pre-install-script\")]\n  pub pre_install_script: Option<PathBuf>,\n  /// Path to script that will be executed after the package is unpacked. See\n  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\n  #[serde(alias = \"post-install-script\")]\n  pub post_install_script: Option<PathBuf>,\n  /// Path to script that will be executed before the package is removed. See\n  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\n  #[serde(alias = \"pre-remove-script\")]\n  pub pre_remove_script: Option<PathBuf>,\n  /// Path to script that will be executed after the package is removed. See\n  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>\n  #[serde(alias = \"post-remove-script\")]\n  pub post_remove_script: Option<PathBuf>,\n}\n\n/// Configuration for Linux bundles.\n///\n/// See more: <https://v2.tauri.app/reference/config/#linuxconfig>\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct LinuxConfig {\n  /// Configuration for the AppImage bundle.\n  #[serde(default)]\n  pub appimage: AppImageConfig,\n  /// Configuration for the Debian bundle.\n  #[serde(default)]\n  pub deb: DebConfig,\n  /// Configuration for the RPM bundle.\n  #[serde(default)]\n  pub rpm: RpmConfig,\n}\n\n/// Compression algorithms used when bundling RPM packages.\n#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields, tag = \"type\")]\n#[non_exhaustive]\npub enum RpmCompression {\n  /// Gzip compression\n  Gzip {\n    /// Gzip compression level\n    level: u32,\n  },\n  /// Zstd compression\n  Zstd {\n    /// Zstd compression level\n    level: i32,\n  },\n  /// Xz compression\n  Xz {\n    /// Xz compression level\n    level: u32,\n  },\n  /// Bzip2 compression\n  Bzip2 {\n    /// Bzip2 compression level\n    level: u32,\n  },\n  /// Disable compression\n  None,\n}\n\n/// Configuration for RPM bundles.\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct RpmConfig {\n  /// The list of RPM dependencies your application relies on.\n  pub depends: Option<Vec<String>>,\n  /// The list of RPM dependencies your application recommends.\n  pub recommends: Option<Vec<String>>,\n  /// The list of RPM dependencies your application provides.\n  pub provides: Option<Vec<String>>,\n  /// The list of RPM dependencies your application conflicts with. They must not be present\n  /// in order for the package to be installed.\n  pub conflicts: Option<Vec<String>>,\n  /// The list of RPM dependencies your application supersedes - if this package is installed,\n  /// packages listed as \"obsoletes\" will be automatically removed (if they are present).\n  pub obsoletes: Option<Vec<String>>,\n  /// The RPM release tag.\n  #[serde(default = \"default_release\")]\n  pub release: String,\n  /// The RPM epoch.\n  #[serde(default)]\n  pub epoch: u32,\n  /// The files to include on the package.\n  #[serde(default)]\n  pub files: HashMap<PathBuf, PathBuf>,\n  /// Path to a custom desktop file Handlebars template.\n  ///\n  /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n  #[serde(alias = \"desktop-template\")]\n  pub desktop_template: Option<PathBuf>,\n  /// Path to script that will be executed before the package is unpacked. See\n  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\n  #[serde(alias = \"pre-install-script\")]\n  pub pre_install_script: Option<PathBuf>,\n  /// Path to script that will be executed after the package is unpacked. See\n  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\n  #[serde(alias = \"post-install-script\")]\n  pub post_install_script: Option<PathBuf>,\n  /// Path to script that will be executed before the package is removed. See\n  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\n  #[serde(alias = \"pre-remove-script\")]\n  pub pre_remove_script: Option<PathBuf>,\n  /// Path to script that will be executed after the package is removed. See\n  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>\n  #[serde(alias = \"post-remove-script\")]\n  pub post_remove_script: Option<PathBuf>,\n  /// Compression algorithm and level. Defaults to `Gzip` with level 6.\n  pub compression: Option<RpmCompression>,\n}\n\nimpl Default for RpmConfig {\n  fn default() -> Self {\n    Self {\n      depends: None,\n      recommends: None,\n      provides: None,\n      conflicts: None,\n      obsoletes: None,\n      release: default_release(),\n      epoch: 0,\n      files: Default::default(),\n      desktop_template: None,\n      pre_install_script: None,\n      post_install_script: None,\n      pre_remove_script: None,\n      post_remove_script: None,\n      compression: None,\n    }\n  }\n}\n\nfn default_release() -> String {\n  \"1\".into()\n}\n\n/// Position coordinates struct.\n#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct Position {\n  /// X coordinate.\n  pub x: u32,\n  /// Y coordinate.\n  pub y: u32,\n}\n\n/// Position coordinates struct.\n#[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct LogicalPosition {\n  /// X coordinate.\n  pub x: f64,\n  /// Y coordinate.\n  pub y: f64,\n}\n\n/// Size of the window.\n#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct Size {\n  /// Width of the window.\n  pub width: u32,\n  /// Height of the window.\n  pub height: u32,\n}\n\n/// Configuration for Apple Disk Image (.dmg) bundles.\n///\n/// See more: <https://v2.tauri.app/reference/config/#dmgconfig>\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct DmgConfig {\n  /// Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.\n  pub background: Option<PathBuf>,\n  /// Position of volume window on screen.\n  pub window_position: Option<Position>,\n  /// Size of volume window.\n  #[serde(default = \"dmg_window_size\", alias = \"window-size\")]\n  pub window_size: Size,\n  /// Position of app file on window.\n  #[serde(default = \"dmg_app_position\", alias = \"app-position\")]\n  pub app_position: Position,\n  /// Position of application folder on window.\n  #[serde(\n    default = \"dmg_application_folder_position\",\n    alias = \"application-folder-position\"\n  )]\n  pub application_folder_position: Position,\n}\n\nimpl Default for DmgConfig {\n  fn default() -> Self {\n    Self {\n      background: None,\n      window_position: None,\n      window_size: dmg_window_size(),\n      app_position: dmg_app_position(),\n      application_folder_position: dmg_application_folder_position(),\n    }\n  }\n}\n\nfn dmg_window_size() -> Size {\n  Size {\n    width: 660,\n    height: 400,\n  }\n}\n\nfn dmg_app_position() -> Position {\n  Position { x: 180, y: 170 }\n}\n\nfn dmg_application_folder_position() -> Position {\n  Position { x: 480, y: 170 }\n}\n\nfn de_macos_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>\nwhere\n  D: Deserializer<'de>,\n{\n  let version = Option::<String>::deserialize(deserializer)?;\n  match version {\n    Some(v) if v.is_empty() => Ok(macos_minimum_system_version()),\n    e => Ok(e),\n  }\n}\n\n/// Configuration for the macOS bundles.\n///\n/// See more: <https://v2.tauri.app/reference/config/#macconfig>\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct MacConfig {\n  /// A list of strings indicating any macOS X frameworks that need to be bundled with the application.\n  ///\n  /// If a name is used, \".framework\" must be omitted and it will look for standard install locations. You may also use a path to a specific framework.\n  pub frameworks: Option<Vec<String>>,\n  /// The files to include in the application relative to the Contents directory.\n  #[serde(default)]\n  pub files: HashMap<PathBuf, PathBuf>,\n  /// The version of the build that identifies an iteration of the bundle.\n  ///\n  /// Translates to the bundle's CFBundleVersion property.\n  #[serde(alias = \"bundle-version\")]\n  pub bundle_version: Option<String>,\n  /// The name of the builder that built the bundle.\n  ///\n  /// Translates to the bundle's CFBundleName property.\n  ///\n  /// If not set, defaults to the package's product name.\n  #[serde(alias = \"bundle-name\")]\n  pub bundle_name: Option<String>,\n  /// A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\n  ///\n  /// Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`\n  /// and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\n  ///\n  /// Ignored in `tauri dev`.\n  ///\n  /// An empty string is considered an invalid value so the default value is used.\n  #[serde(\n    deserialize_with = \"de_macos_minimum_system_version\",\n    default = \"macos_minimum_system_version\",\n    alias = \"minimum-system-version\"\n  )]\n  pub minimum_system_version: Option<String>,\n  /// Allows your application to communicate with the outside world.\n  /// It should be a lowercase, without port and protocol domain name.\n  #[serde(alias = \"exception-domain\")]\n  pub exception_domain: Option<String>,\n  /// Identity to use for code signing.\n  #[serde(alias = \"signing-identity\")]\n  pub signing_identity: Option<String>,\n  /// Whether the codesign should enable [hardened runtime](https://developer.apple.com/documentation/security/hardened_runtime) (for executables) or not.\n  #[serde(alias = \"hardened-runtime\", default = \"default_true\")]\n  pub hardened_runtime: bool,\n  /// Provider short name for notarization.\n  #[serde(alias = \"provider-short-name\")]\n  pub provider_short_name: Option<String>,\n  /// Path to the entitlements file.\n  pub entitlements: Option<String>,\n  /// Path to a Info.plist file to merge with the default Info.plist.\n  ///\n  /// Note that Tauri also looks for a `Info.plist` file in the same directory as the Tauri configuration file.\n  #[serde(alias = \"info-plist\")]\n  pub info_plist: Option<PathBuf>,\n  /// DMG-specific settings.\n  #[serde(default)]\n  pub dmg: DmgConfig,\n}\n\nimpl Default for MacConfig {\n  fn default() -> Self {\n    Self {\n      frameworks: None,\n      files: HashMap::new(),\n      bundle_version: None,\n      bundle_name: None,\n      minimum_system_version: macos_minimum_system_version(),\n      exception_domain: None,\n      signing_identity: None,\n      hardened_runtime: true,\n      provider_short_name: None,\n      entitlements: None,\n      info_plist: None,\n      dmg: Default::default(),\n    }\n  }\n}\n\nfn macos_minimum_system_version() -> Option<String> {\n  Some(\"10.13\".into())\n}\n\nfn ios_minimum_system_version() -> String {\n  \"14.0\".into()\n}\n\n/// Configuration for a target language for the WiX build.\n///\n/// See more: <https://v2.tauri.app/reference/config/#wixlanguageconfig>\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct WixLanguageConfig {\n  /// The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.\n  #[serde(alias = \"locale-path\")]\n  pub locale_path: Option<String>,\n}\n\n/// The languages to build using WiX.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(untagged)]\npub enum WixLanguage {\n  /// A single language to build, without configuration.\n  One(String),\n  /// A list of languages to build, without configuration.\n  List(Vec<String>),\n  /// A map of languages and its configuration.\n  Localized(HashMap<String, WixLanguageConfig>),\n}\n\nimpl Default for WixLanguage {\n  fn default() -> Self {\n    Self::One(\"en-US\".into())\n  }\n}\n\n/// Configuration for the MSI bundle using WiX.\n///\n/// See more: <https://v2.tauri.app/reference/config/#wixconfig>\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct WixConfig {\n  /// MSI installer version in the format `major.minor.patch.build` (build is optional).\n  ///\n  /// Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.\n  ///\n  /// The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\n  /// The third and fourth fields have a maximum value of 65,535.\n  ///\n  /// See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.\n  pub version: Option<String>,\n  /// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,\n  /// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\n  ///\n  /// By default, tauri generates this code by generating a Uuid v5 using the string `<productName>.exe.app.x64` in the DNS namespace.\n  /// You can use Tauri's CLI to generate and print this code for you, run `tauri inspect wix-upgrade-code`.\n  ///\n  /// It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code\n  /// whenever you want to change your product name.\n  #[serde(alias = \"upgrade-code\")]\n  pub upgrade_code: Option<uuid::Uuid>,\n  /// The installer languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.\n  #[serde(default)]\n  pub language: WixLanguage,\n  /// A custom .wxs template to use.\n  pub template: Option<PathBuf>,\n  /// A list of paths to .wxs files with WiX fragments to use.\n  #[serde(default, alias = \"fragment-paths\")]\n  pub fragment_paths: Vec<PathBuf>,\n  /// The ComponentGroup element ids you want to reference from the fragments.\n  #[serde(default, alias = \"component-group-refs\")]\n  pub component_group_refs: Vec<String>,\n  /// The Component element ids you want to reference from the fragments.\n  #[serde(default, alias = \"component-refs\")]\n  pub component_refs: Vec<String>,\n  /// The FeatureGroup element ids you want to reference from the fragments.\n  #[serde(default, alias = \"feature-group-refs\")]\n  pub feature_group_refs: Vec<String>,\n  /// The Feature element ids you want to reference from the fragments.\n  #[serde(default, alias = \"feature-refs\")]\n  pub feature_refs: Vec<String>,\n  /// The Merge element ids you want to reference from the fragments.\n  #[serde(default, alias = \"merge-refs\")]\n  pub merge_refs: Vec<String>,\n  /// Create an elevated update task within Windows Task Scheduler.\n  #[serde(default, alias = \"enable-elevated-update-task\")]\n  pub enable_elevated_update_task: bool,\n  /// Path to a bitmap file to use as the installation user interface banner.\n  /// This bitmap will appear at the top of all but the first page of the installer.\n  ///\n  /// The required dimensions are 493px × 58px.\n  #[serde(alias = \"banner-path\")]\n  pub banner_path: Option<PathBuf>,\n  /// Path to a bitmap file to use on the installation user interface dialogs.\n  /// It is used on the welcome and completion dialogs.\n  ///\n  /// The required dimensions are 493px × 312px.\n  #[serde(alias = \"dialog-image-path\")]\n  pub dialog_image_path: Option<PathBuf>,\n  /// Enables FIPS compliant algorithms.\n  /// Can also be enabled via the `TAURI_BUNDLER_WIX_FIPS_COMPLIANT` env var.\n  #[serde(default, alias = \"fips-compliant\")]\n  pub fips_compliant: bool,\n}\n\n/// Compression algorithms used in the NSIS installer.\n///\n/// See <https://nsis.sourceforge.io/Reference/SetCompressor>\n#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub enum NsisCompression {\n  /// ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.\n  Zlib,\n  /// BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.\n  Bzip2,\n  /// LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.\n  #[default]\n  Lzma,\n  /// Disable compression\n  None,\n}\n\n/// Install Modes for the NSIS installer.\n#[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\npub enum NSISInstallerMode {\n  /// Default mode for the installer.\n  ///\n  /// Install the app by default in a directory that doesn't require Administrator access.\n  ///\n  /// Installer metadata will be saved under the `HKCU` registry path.\n  #[default]\n  CurrentUser,\n  /// Install the app by default in the `Program Files` folder directory requires Administrator\n  /// access for the installation.\n  ///\n  /// Installer metadata will be saved under the `HKLM` registry path.\n  PerMachine,\n  /// Combines both modes and allows the user to choose at install time\n  /// whether to install for the current user or per machine. Note that this mode\n  /// will require Administrator access even if the user wants to install it for the current user only.\n  ///\n  /// Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.\n  Both,\n}\n\n/// Configuration for the Installer bundle using NSIS.\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct NsisConfig {\n  /// A custom .nsi template to use.\n  pub template: Option<PathBuf>,\n  /// The path to a bitmap file to display on the header of installers pages.\n  ///\n  /// The recommended dimensions are 150px x 57px.\n  #[serde(alias = \"header-image\")]\n  pub header_image: Option<PathBuf>,\n  /// The path to a bitmap file for the Welcome page and the Finish page.\n  ///\n  /// The recommended dimensions are 164px x 314px.\n  #[serde(alias = \"sidebar-image\")]\n  pub sidebar_image: Option<PathBuf>,\n  /// The path to an icon file used as the installer icon.\n  #[serde(alias = \"install-icon\")]\n  pub installer_icon: Option<PathBuf>,\n  /// Whether the installation will be for all users or just the current user.\n  #[serde(default, alias = \"install-mode\")]\n  pub install_mode: NSISInstallerMode,\n  /// A list of installer languages.\n  /// By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.\n  /// To allow the user to select the language, set `display_language_selector` to `true`.\n  ///\n  /// See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.\n  pub languages: Option<Vec<String>>,\n  /// A key-value pair where the key is the language and the\n  /// value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.\n  ///\n  /// See <https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-bundler/src/bundle/windows/nsis/languages/English.nsh> for an example `.nsh` file.\n  ///\n  /// **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array,\n  pub custom_language_files: Option<HashMap<String, PathBuf>>,\n  /// Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.\n  /// By default the OS language is selected, with a fallback to the first language in the `languages` array.\n  #[serde(default, alias = \"display-language-selector\")]\n  pub display_language_selector: bool,\n  /// Set the compression algorithm used to compress files in the installer.\n  ///\n  /// See <https://nsis.sourceforge.io/Reference/SetCompressor>\n  #[serde(default)]\n  pub compression: NsisCompression,\n  /// Set the folder name for the start menu shortcut.\n  ///\n  /// Use this option if you have multiple apps and wish to group their shortcuts under one folder\n  /// or if you generally prefer to set your shortcut inside a folder.\n  ///\n  /// Examples:\n  /// - `AwesomePublisher`, shortcut will be placed in `%AppData%\\Microsoft\\Windows\\Start Menu\\Programs\\AwesomePublisher\\<your-app>.lnk`\n  /// - If unset, shortcut will be placed in `%AppData%\\Microsoft\\Windows\\Start Menu\\Programs\\<your-app>.lnk`\n  #[serde(alias = \"start-menu-folder\")]\n  pub start_menu_folder: Option<String>,\n  /// A path to a `.nsh` file that contains special NSIS macros to be hooked into the\n  /// main installer.nsi script.\n  ///\n  /// Supported hooks are:\n  ///\n  /// - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.\n  /// - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.\n  /// - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.\n  /// - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.\n  ///\n  /// ### Example\n  ///\n  /// ```nsh\n  /// !macro NSIS_HOOK_PREINSTALL\n  ///   MessageBox MB_OK \"PreInstall\"\n  /// !macroend\n  ///\n  /// !macro NSIS_HOOK_POSTINSTALL\n  ///   MessageBox MB_OK \"PostInstall\"\n  /// !macroend\n  ///\n  /// !macro NSIS_HOOK_PREUNINSTALL\n  ///   MessageBox MB_OK \"PreUnInstall\"\n  /// !macroend\n  ///\n  /// !macro NSIS_HOOK_POSTUNINSTALL\n  ///   MessageBox MB_OK \"PostUninstall\"\n  /// !macroend\n  /// ```\n  #[serde(alias = \"installer-hooks\")]\n  pub installer_hooks: Option<PathBuf>,\n  /// Try to ensure that the WebView2 version is equal to or newer than this version,\n  /// if the user's WebView2 is older than this version,\n  /// the installer will try to trigger a WebView2 update.\n  #[serde(alias = \"minimum-webview2-version\")]\n  pub minimum_webview2_version: Option<String>,\n}\n\n/// Install modes for the Webview2 runtime.\n/// Note that for the updater bundle [`Self::DownloadBootstrapper`] is used.\n///\n/// For more information see <https://v2.tauri.app/distribute/windows-installer/#webview2-installation-options>.\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]\n#[serde(tag = \"type\", rename_all = \"camelCase\", deny_unknown_fields)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\npub enum WebviewInstallMode {\n  /// Do not install the Webview2 as part of the Windows Installer.\n  Skip,\n  /// Download the bootstrapper and run it.\n  /// Requires an internet connection.\n  /// Results in a smaller installer size, but is not recommended on Windows 7.\n  DownloadBootstrapper {\n    /// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.\n    #[serde(default = \"default_true\")]\n    silent: bool,\n  },\n  /// Embed the bootstrapper and run it.\n  /// Requires an internet connection.\n  /// Increases the installer size by around 1.8MB, but offers better support on Windows 7.\n  EmbedBootstrapper {\n    /// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.\n    #[serde(default = \"default_true\")]\n    silent: bool,\n  },\n  /// Embed the offline installer and run it.\n  /// Does not require an internet connection.\n  /// Increases the installer size by around 127MB.\n  OfflineInstaller {\n    /// Instructs the installer to run the installer in silent mode. Defaults to `true`.\n    #[serde(default = \"default_true\")]\n    silent: bool,\n  },\n  /// Embed a fixed webview2 version and use it at runtime.\n  /// Increases the installer size by around 180MB.\n  FixedRuntime {\n    /// The path to the fixed runtime to use.\n    ///\n    /// The fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section).\n    /// The `.cab` file must be extracted to a folder and this folder path must be defined on this field.\n    path: PathBuf,\n  },\n}\n\nimpl Default for WebviewInstallMode {\n  fn default() -> Self {\n    Self::DownloadBootstrapper { silent: true }\n  }\n}\n\n/// Custom Signing Command configuration.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields, untagged)]\npub enum CustomSignCommandConfig {\n  /// A string notation of the script to execute.\n  ///\n  /// \"%1\" will be replaced with the path to the binary to be signed.\n  ///\n  /// This is a simpler notation for the command.\n  /// Tauri will split the string with `' '` and use the first element as the command name and the rest as arguments.\n  ///\n  /// If you need to use whitespace in the command or arguments, use the object notation [`Self::CommandWithOptions`].\n  Command(String),\n  /// An object notation of the command.\n  ///\n  /// This is more complex notation for the command but\n  /// this allows you to use whitespace in the command and arguments.\n  CommandWithOptions {\n    /// The command to run to sign the binary.\n    cmd: String,\n    /// The arguments to pass to the command.\n    ///\n    /// \"%1\" will be replaced with the path to the binary to be signed.\n    args: Vec<String>,\n  },\n}\n\n/// Windows bundler configuration.\n///\n/// See more: <https://v2.tauri.app/reference/config/#windowsconfig>\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct WindowsConfig {\n  /// Specifies the file digest algorithm to use for creating file signatures.\n  /// Required for code signing. SHA-256 is recommended.\n  #[serde(alias = \"digest-algorithm\")]\n  pub digest_algorithm: Option<String>,\n  /// Specifies the SHA1 hash of the signing certificate.\n  #[serde(alias = \"certificate-thumbprint\")]\n  pub certificate_thumbprint: Option<String>,\n  /// Server to use during timestamping.\n  #[serde(alias = \"timestamp-url\")]\n  pub timestamp_url: Option<String>,\n  /// Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may\n  /// use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.\n  #[serde(default)]\n  pub tsp: bool,\n  /// The installation mode for the Webview2 runtime.\n  #[serde(default, alias = \"webview-install-mode\")]\n  pub webview_install_mode: WebviewInstallMode,\n  /// Validates a second app installation, blocking the user from installing an older version if set to `false`.\n  ///\n  /// For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\n  ///\n  /// The default value of this flag is `true`.\n  #[serde(default = \"default_true\", alias = \"allow-downgrades\")]\n  pub allow_downgrades: bool,\n  /// Configuration for the MSI generated with WiX.\n  pub wix: Option<WixConfig>,\n  /// Configuration for the installer generated with NSIS.\n  pub nsis: Option<NsisConfig>,\n  /// Specify a custom command to sign the binaries.\n  /// This command needs to have a `%1` in args which is just a placeholder for the binary path,\n  /// which we will detect and replace before calling the command.\n  ///\n  /// By Default we use `signtool.exe` which can be found only on Windows so\n  /// if you are on another platform and want to cross-compile and sign you will\n  /// need to use another tool like `osslsigncode`.\n  #[serde(alias = \"sign-command\")]\n  pub sign_command: Option<CustomSignCommandConfig>,\n}\n\nimpl Default for WindowsConfig {\n  fn default() -> Self {\n    Self {\n      digest_algorithm: None,\n      certificate_thumbprint: None,\n      timestamp_url: None,\n      tsp: false,\n      webview_install_mode: Default::default(),\n      allow_downgrades: true,\n      wix: None,\n      nsis: None,\n      sign_command: None,\n    }\n  }\n}\n\n/// macOS-only. Corresponds to CFBundleTypeRole\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\npub enum BundleTypeRole {\n  /// CFBundleTypeRole.Editor. Files can be read and edited.\n  #[default]\n  Editor,\n  /// CFBundleTypeRole.Viewer. Files can be read.\n  Viewer,\n  /// CFBundleTypeRole.Shell\n  Shell,\n  /// CFBundleTypeRole.QLGenerator\n  QLGenerator,\n  /// CFBundleTypeRole.None\n  None,\n}\n\nimpl Display for BundleTypeRole {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    match self {\n      Self::Editor => write!(f, \"Editor\"),\n      Self::Viewer => write!(f, \"Viewer\"),\n      Self::Shell => write!(f, \"Shell\"),\n      Self::QLGenerator => write!(f, \"QLGenerator\"),\n      Self::None => write!(f, \"None\"),\n    }\n  }\n}\n\n// Issue #13159 - Missing the LSHandlerRank and Apple warns after uploading to App Store Connect.\n// https://github.com/tauri-apps/tauri/issues/13159\n/// Corresponds to LSHandlerRank\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\npub enum HandlerRank {\n  /// LSHandlerRank.Default. This app is an opener of files of this type; this value is also used if no rank is specified.\n  #[default]\n  Default,\n  /// LSHandlerRank.Owner. This app is the primary creator of files of this type.\n  Owner,\n  /// LSHandlerRank.Alternate. This app is a secondary viewer of files of this type.\n  Alternate,\n  /// LSHandlerRank.None. This app is never selected to open files of this type, but it accepts drops of files of this type.\n  None,\n}\n\nimpl Display for HandlerRank {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    match self {\n      Self::Default => write!(f, \"Default\"),\n      Self::Owner => write!(f, \"Owner\"),\n      Self::Alternate => write!(f, \"Alternate\"),\n      Self::None => write!(f, \"None\"),\n    }\n  }\n}\n\n/// An extension for a [`FileAssociation`].\n///\n/// A leading `.` is automatically stripped.\n#[derive(Debug, PartialEq, Eq, Clone, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\npub struct AssociationExt(pub String);\n\nimpl fmt::Display for AssociationExt {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    write!(f, \"{}\", self.0)\n  }\n}\n\nimpl<'d> serde::Deserialize<'d> for AssociationExt {\n  fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {\n    let ext = String::deserialize(deserializer)?;\n    if let Some(ext) = ext.strip_prefix('.') {\n      Ok(AssociationExt(ext.into()))\n    } else {\n      Ok(AssociationExt(ext))\n    }\n  }\n}\n\n/// File association\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct FileAssociation {\n  /// File extensions to associate with this app. e.g. 'png'\n  pub ext: Vec<AssociationExt>,\n  /// Declare support to a file with the given content type. Maps to `LSItemContentTypes` on macOS.\n  ///\n  /// This allows supporting any file format declared by another application that conforms to this type.\n  /// Declaration of new types can be done with [`Self::exported_type`] and linking to certain content types are done via [`ExportedFileAssociation::conforms_to`].\n  #[serde(alias = \"content-types\")]\n  pub content_types: Option<Vec<String>>,\n  /// The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`\n  pub name: Option<String>,\n  /// The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer.\n  pub description: Option<String>,\n  /// The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.\n  #[serde(default)]\n  pub role: BundleTypeRole,\n  /// The mime-type e.g. 'image/png' or 'text/plain'. Linux-only.\n  #[serde(alias = \"mime-type\")]\n  pub mime_type: Option<String>,\n  /// The ranking of this app among apps that declare themselves as editors or viewers of the given file type.  Maps to `LSHandlerRank` on macOS.\n  #[serde(default)]\n  pub rank: HandlerRank,\n  /// The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.\n  ///\n  /// You should define this if the associated file is a custom file type defined by your application.\n  pub exported_type: Option<ExportedFileAssociation>,\n}\n\n/// The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct ExportedFileAssociation {\n  /// The unique identifier for the exported type. Maps to `UTTypeIdentifier`.\n  pub identifier: String,\n  /// The types that this type conforms to. Maps to `UTTypeConformsTo`.\n  ///\n  /// Examples are `public.data`, `public.image`, `public.json` and `public.database`.\n  #[serde(alias = \"conforms-to\")]\n  pub conforms_to: Option<Vec<String>>,\n}\n\n/// Deep link protocol configuration.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct DeepLinkProtocol {\n  /// URL schemes to associate with this app without `://`. For example `my-app`\n  #[serde(default)]\n  pub schemes: Vec<String>,\n  /// Domains to associate with this app. For example `example.com`.\n  /// Currently only supported on macOS, translating to an [universal app link].\n  ///\n  /// Note that universal app links require signed apps with a provisioning profile to work.\n  /// You can accomplish that by including the `embedded.provisionprofile` file in the `macOS > files` option.\n  ///\n  /// [universal app link]: https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app\n  #[serde(default)]\n  pub domains: Vec<String>,\n  /// The protocol name. **macOS-only** and maps to `CFBundleTypeName`. Defaults to `<bundle-id>.<schemes[0]>`\n  pub name: Option<String>,\n  /// The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.\n  #[serde(default)]\n  pub role: BundleTypeRole,\n}\n\n/// Definition for bundle resources.\n/// Can be either a list of paths to include or a map of source to target paths.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields, untagged)]\npub enum BundleResources {\n  /// A list of paths to include.\n  List(Vec<String>),\n  /// A map of source to target paths.\n  Map(HashMap<String, String>),\n}\n\nimpl BundleResources {\n  /// Adds a path to the resource collection.\n  pub fn push(&mut self, path: impl Into<String>) {\n    match self {\n      Self::List(l) => l.push(path.into()),\n      Self::Map(l) => {\n        let path = path.into();\n        l.insert(path.clone(), path);\n      }\n    }\n  }\n}\n\n/// Updater type\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields, untagged)]\npub enum Updater {\n  /// Generates legacy zipped v1 compatible updaters\n  String(V1Compatible),\n  /// Produce updaters and their signatures or not\n  // Can't use untagged on enum field here: https://github.com/GREsau/schemars/issues/222\n  Bool(bool),\n}\n\nimpl Default for Updater {\n  fn default() -> Self {\n    Self::Bool(false)\n  }\n}\n\n/// Generates legacy zipped v1 compatible updaters\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub enum V1Compatible {\n  /// Generates legacy zipped v1 compatible updaters\n  V1Compatible,\n}\n\n/// Configuration for tauri-bundler.\n///\n/// See more: <https://v2.tauri.app/reference/config/#bundleconfig>\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct BundleConfig {\n  /// Whether Tauri should bundle your application or just output the executable.\n  #[serde(default)]\n  pub active: bool,\n  /// The bundle targets, currently supports [\"deb\", \"rpm\", \"appimage\", \"nsis\", \"msi\", \"app\", \"dmg\"] or \"all\".\n  #[serde(default)]\n  pub targets: BundleTarget,\n  #[serde(default)]\n  /// Produce updaters and their signatures or not\n  pub create_updater_artifacts: Updater,\n  /// The application's publisher. Defaults to the second element in the identifier string.\n  ///\n  /// Currently maps to the Manufacturer property of the Windows Installer\n  /// and the Maintainer field of debian packages if the Cargo.toml does not have the authors field.\n  pub publisher: Option<String>,\n  /// A url to the home page of your application. If unset, will\n  /// fallback to `homepage` defined in `Cargo.toml`.\n  ///\n  /// Supported bundle targets: `deb`, `rpm`, `nsis` and `msi`.\n  pub homepage: Option<String>,\n  /// The app's icons\n  #[serde(default)]\n  pub icon: Vec<String>,\n  /// App resources to bundle.\n  /// Each resource is a path to a file or directory.\n  /// Glob patterns are supported.\n  ///\n  /// ## Examples\n  ///\n  /// To include a list of files:\n  ///\n  /// ```json\n  /// {\n  ///   \"bundle\": {\n  ///     \"resources\": [\n  ///       \"./path/to/some-file.txt\",\n  ///       \"/absolute/path/to/textfile.txt\",\n  ///       \"../relative/path/to/jsonfile.json\",\n  ///       \"some-folder/\",\n  ///       \"resources/**/*.md\"\n  ///     ]\n  ///   }\n  /// }\n  /// ```\n  ///\n  /// The bundled files will be in `$RESOURCES/` with the original directory structure preserved,\n  /// for example: `./path/to/some-file.txt` -> `$RESOURCE/path/to/some-file.txt`\n  ///\n  /// To fine control where the files will get copied to, use a map instead\n  ///\n  /// ```json\n  /// {\n  ///   \"bundle\": {\n  ///     \"resources\": {\n  ///       \"/absolute/path/to/textfile.txt\": \"resources/textfile.txt\",\n  ///       \"relative/path/to/jsonfile.json\": \"resources/jsonfile.json\",\n  ///       \"resources/\": \"\",\n  ///       \"docs/**/*md\": \"website-docs/\"\n  ///     }\n  ///   }\n  /// }\n  /// ```\n  ///\n  /// Note that when using glob pattern in this case, the original directory structure is not preserved,\n  /// everything gets copied to the target directory directly\n  ///\n  /// See more: <https://v2.tauri.app/develop/resources/>\n  pub resources: Option<BundleResources>,\n  /// A copyright string associated with your application.\n  pub copyright: Option<String>,\n  /// The package's license identifier to be included in the appropriate bundles.\n  /// If not set, defaults to the license from the Cargo.toml file.\n  pub license: Option<String>,\n  /// The path to the license file to be included in the appropriate bundles.\n  #[serde(alias = \"license-file\")]\n  pub license_file: Option<PathBuf>,\n  /// The application kind.\n  ///\n  /// Should be one of the following:\n  /// Business, DeveloperTool, Education, Entertainment, Finance, Game, ActionGame, AdventureGame, ArcadeGame, BoardGame, CardGame, CasinoGame, DiceGame, EducationalGame, FamilyGame, KidsGame, MusicGame, PuzzleGame, RacingGame, RolePlayingGame, SimulationGame, SportsGame, StrategyGame, TriviaGame, WordGame, GraphicsAndDesign, HealthcareAndFitness, Lifestyle, Medical, Music, News, Photography, Productivity, Reference, SocialNetworking, Sports, Travel, Utility, Video, Weather.\n  pub category: Option<String>,\n  /// File types to associate with the application.\n  pub file_associations: Option<Vec<FileAssociation>>,\n  /// A short description of your application.\n  #[serde(alias = \"short-description\")]\n  pub short_description: Option<String>,\n  /// A longer, multi-line description of the application.\n  #[serde(alias = \"long-description\")]\n  pub long_description: Option<String>,\n  /// Whether to use the project's `target` directory, for caching build tools (e.g., Wix and NSIS) when building this application. Defaults to `false`.\n  ///\n  /// If true, tools will be cached in `target/.tauri/`.\n  /// If false, tools will be cached in the current user's platform-specific cache directory.\n  ///\n  /// An example where it can be appropriate to set this to `true` is when building this application as a Windows System user (e.g., AWS EC2 workloads),\n  /// because the Window system's app data directory is restricted.\n  #[serde(default, alias = \"use-local-tools-dir\")]\n  pub use_local_tools_dir: bool,\n  /// A list of—either absolute or relative—paths to binaries to embed with your application.\n  ///\n  /// Note that Tauri will look for system-specific binaries following the pattern \"binary-name{-target-triple}{.system-extension}\".\n  ///\n  /// E.g. for the external binary \"my-binary\", Tauri looks for:\n  ///\n  /// - \"my-binary-x86_64-pc-windows-msvc.exe\" for Windows\n  /// - \"my-binary-x86_64-apple-darwin\" for macOS\n  /// - \"my-binary-x86_64-unknown-linux-gnu\" for Linux\n  ///\n  /// so don't forget to provide binaries for all targeted platforms.\n  #[serde(alias = \"external-bin\")]\n  pub external_bin: Option<Vec<String>>,\n  /// Configuration for the Windows bundles.\n  #[serde(default)]\n  pub windows: WindowsConfig,\n  /// Configuration for the Linux bundles.\n  #[serde(default)]\n  pub linux: LinuxConfig,\n  /// Configuration for the macOS bundles.\n  #[serde(rename = \"macOS\", alias = \"macos\", default)]\n  pub macos: MacConfig,\n  /// iOS configuration.\n  #[serde(rename = \"iOS\", alias = \"ios\", default)]\n  pub ios: IosConfig,\n  /// Android configuration.\n  #[serde(default)]\n  pub android: AndroidConfig,\n}\n\n/// A tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255.\n#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct Color(pub u8, pub u8, pub u8, pub u8);\n\nimpl From<Color> for (u8, u8, u8, u8) {\n  fn from(value: Color) -> Self {\n    (value.0, value.1, value.2, value.3)\n  }\n}\n\nimpl From<Color> for (u8, u8, u8) {\n  fn from(value: Color) -> Self {\n    (value.0, value.1, value.2)\n  }\n}\n\nimpl From<(u8, u8, u8, u8)> for Color {\n  fn from(value: (u8, u8, u8, u8)) -> Self {\n    Color(value.0, value.1, value.2, value.3)\n  }\n}\n\nimpl From<(u8, u8, u8)> for Color {\n  fn from(value: (u8, u8, u8)) -> Self {\n    Color(value.0, value.1, value.2, 255)\n  }\n}\n\nimpl From<Color> for [u8; 4] {\n  fn from(value: Color) -> Self {\n    [value.0, value.1, value.2, value.3]\n  }\n}\n\nimpl From<Color> for [u8; 3] {\n  fn from(value: Color) -> Self {\n    [value.0, value.1, value.2]\n  }\n}\n\nimpl From<[u8; 4]> for Color {\n  fn from(value: [u8; 4]) -> Self {\n    Color(value[0], value[1], value[2], value[3])\n  }\n}\n\nimpl From<[u8; 3]> for Color {\n  fn from(value: [u8; 3]) -> Self {\n    Color(value[0], value[1], value[2], 255)\n  }\n}\n\nimpl FromStr for Color {\n  type Err = String;\n  fn from_str(mut color: &str) -> Result<Self, Self::Err> {\n    color = color.trim().strip_prefix('#').unwrap_or(color);\n    let color = match color.len() {\n      // TODO: use repeat_n once our MSRV is bumped to 1.82\n      3 => color.chars()\n            .flat_map(|c| std::iter::repeat(c).take(2))\n            .chain(std::iter::repeat('f').take(2))\n            .collect(),\n      6 => format!(\"{color}FF\"),\n      8 => color.to_string(),\n      _ => return Err(\"Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff\".into()),\n    };\n\n    let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?;\n    let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?;\n    let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?;\n    let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?;\n\n    Ok(Color(r, g, b, a))\n  }\n}\n\nfn default_alpha() -> u8 {\n  255\n}\n\n#[derive(Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(untagged)]\nenum InnerColor {\n  /// Color hex string, for example: #fff, #ffffff, or #ffffffff.\n  String(String),\n  /// Array of RGB colors. Each value has minimum of 0 and maximum of 255.\n  Rgb((u8, u8, u8)),\n  /// Array of RGBA colors. Each value has minimum of 0 and maximum of 255.\n  Rgba((u8, u8, u8, u8)),\n  /// Object of red, green, blue, alpha color values. Each value has minimum of 0 and maximum of 255.\n  RgbaObject {\n    red: u8,\n    green: u8,\n    blue: u8,\n    #[serde(default = \"default_alpha\")]\n    alpha: u8,\n  },\n}\n\nimpl<'de> Deserialize<'de> for Color {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let color = InnerColor::deserialize(deserializer)?;\n    let color = match color {\n      InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?,\n      InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255),\n      InnerColor::Rgba(rgb) => rgb.into(),\n      InnerColor::RgbaObject {\n        red,\n        green,\n        blue,\n        alpha,\n      } => Color(red, green, blue, alpha),\n    };\n\n    Ok(color)\n  }\n}\n\n#[cfg(feature = \"schema\")]\nimpl schemars::JsonSchema for Color {\n  fn schema_name() -> String {\n    \"Color\".to_string()\n  }\n\n  fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    let mut schema = schemars::schema_for!(InnerColor).schema;\n    schema.metadata = None; // Remove `title: InnerColor` from schema\n\n    // add hex color pattern validation\n    let any_of = schema.subschemas().any_of.as_mut().unwrap();\n    let schemars::schema::Schema::Object(str_schema) = any_of.first_mut().unwrap() else {\n      unreachable!()\n    };\n    str_schema.string().pattern = Some(\"^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$\".into());\n\n    schema.into()\n  }\n}\n\n/// Background throttling policy.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub enum BackgroundThrottlingPolicy {\n  /// A policy where background throttling is disabled\n  Disabled,\n  /// A policy where a web view that's not in a window fully suspends tasks. This is usually the default behavior in case no policy is set.\n  Suspend,\n  /// A policy where a web view that's not in a window limits processing, but does not fully suspend tasks.\n  Throttle,\n}\n\n/// The window effects configuration object\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct WindowEffectsConfig {\n  /// List of Window effects to apply to the Window.\n  /// Conflicting effects will apply the first one and ignore the rest.\n  pub effects: Vec<WindowEffect>,\n  /// Window effect state **macOS Only**\n  pub state: Option<WindowEffectState>,\n  /// Window effect corner radius **macOS Only**\n  pub radius: Option<f64>,\n  /// Window effect color. Affects [`WindowEffect::Blur`] and [`WindowEffect::Acrylic`] only\n  /// on Windows 10 v1903+. Doesn't have any effect on Windows 7 or Windows 11.\n  pub color: Option<Color>,\n}\n\n/// Enable prevent overflow with a margin\n/// so that the window's size + this margin won't overflow the workarea\n#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct PreventOverflowMargin {\n  /// Horizontal margin in physical pixels\n  pub width: u32,\n  /// Vertical margin in physical pixels\n  pub height: u32,\n}\n\n/// Prevent overflow with a margin\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(untagged)]\npub enum PreventOverflowConfig {\n  /// Enable prevent overflow or not\n  Enable(bool),\n  /// Enable prevent overflow with a margin\n  /// so that the window's size + this margin won't overflow the workarea\n  Margin(PreventOverflowMargin),\n}\n\n/// The scrollbar style to use in the webview.\n///\n/// ## Platform-specific\n///\n/// - **Windows**: This option must be given the same value for all webviews that target the same data directory.\n#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub enum ScrollBarStyle {\n  #[default]\n  /// The scrollbar style to use in the webview.\n  Default,\n\n  /// Fluent UI style overlay scrollbars. **Windows Only**\n  ///\n  /// Requires WebView2 Runtime version 125.0.2535.41 or higher, does nothing on older versions,\n  /// see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/?tabs=dotnetcsharp#10253541\n  FluentOverlay,\n}\n\n/// The window configuration object.\n///\n/// See more: <https://v2.tauri.app/reference/config/#windowconfig>\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct WindowConfig {\n  /// The window identifier. It must be alphanumeric.\n  #[serde(default = \"default_window_label\")]\n  pub label: String,\n  /// Whether Tauri should create this window at app startup or not.\n  ///\n  /// When this is set to `false` you must manually grab the config object via `app.config().app.windows`\n  /// and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).\n  ///\n  /// ## Example:\n  ///\n  /// ```rust\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     tauri::WebviewWindowBuilder::from_config(app.handle(), &app.config().app.windows[0])?.build()?;\n  ///     Ok(())\n  ///   });\n  /// ```\n  #[serde(default = \"default_true\")]\n  pub create: bool,\n  /// The window webview URL.\n  #[serde(default)]\n  pub url: WebviewUrl,\n  /// The user agent for the webview\n  #[serde(alias = \"user-agent\")]\n  pub user_agent: Option<String>,\n  /// Whether the drag and drop is enabled or not on the webview. By default it is enabled.\n  ///\n  /// Disabling it is required to use HTML5 drag and drop on the frontend on Windows.\n  #[serde(default = \"default_true\", alias = \"drag-drop-enabled\")]\n  pub drag_drop_enabled: bool,\n  /// Whether or not the window starts centered or not.\n  #[serde(default)]\n  pub center: bool,\n  /// The horizontal position of the window's top left corner in logical pixels\n  pub x: Option<f64>,\n  /// The vertical position of the window's top left corner in logical pixels\n  pub y: Option<f64>,\n  /// The window width in logical pixels.\n  #[serde(default = \"default_width\")]\n  pub width: f64,\n  /// The window height in logical pixels.\n  #[serde(default = \"default_height\")]\n  pub height: f64,\n  /// The min window width in logical pixels.\n  #[serde(alias = \"min-width\")]\n  pub min_width: Option<f64>,\n  /// The min window height in logical pixels.\n  #[serde(alias = \"min-height\")]\n  pub min_height: Option<f64>,\n  /// The max window width in logical pixels.\n  #[serde(alias = \"max-width\")]\n  pub max_width: Option<f64>,\n  /// The max window height in logical pixels.\n  #[serde(alias = \"max-height\")]\n  pub max_height: Option<f64>,\n  /// Whether or not to prevent the window from overflowing the workarea\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **iOS / Android:** Unsupported.\n  #[serde(alias = \"prevent-overflow\")]\n  pub prevent_overflow: Option<PreventOverflowConfig>,\n  /// Whether the window is resizable or not. When resizable is set to false, native window's maximize button is automatically disabled.\n  #[serde(default = \"default_true\")]\n  pub resizable: bool,\n  /// Whether the window's native maximize button is enabled or not.\n  /// If resizable is set to false, this setting is ignored.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Disables the \"zoom\" button in the window titlebar, which is also used to enter fullscreen mode.\n  /// - **Linux / iOS / Android:** Unsupported.\n  #[serde(default = \"default_true\")]\n  pub maximizable: bool,\n  /// Whether the window's native minimize button is enabled or not.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  #[serde(default = \"default_true\")]\n  pub minimizable: bool,\n  /// Whether the window's native close button is enabled or not.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** \"GTK+ will do its best to convince the window manager not to show a close button.\n  ///   Depending on the system, this function may not have any effect when called on a window that is already visible\"\n  /// - **iOS / Android:** Unsupported.\n  #[serde(default = \"default_true\")]\n  pub closable: bool,\n  /// The window title.\n  #[serde(default = \"default_title\")]\n  pub title: String,\n  /// Whether the window starts as fullscreen or not.\n  #[serde(default)]\n  pub fullscreen: bool,\n  /// Whether the window will be initially focused or not.\n  #[serde(default = \"default_true\")]\n  pub focus: bool,\n  /// Whether the window will be focusable or not.\n  #[serde(default = \"default_true\")]\n  pub focusable: bool,\n  /// Whether the window is transparent or not.\n  ///\n  /// Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri > macOSPrivateApi`.\n  /// WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.\n  #[serde(default)]\n  pub transparent: bool,\n  /// Whether the window is maximized or not.\n  #[serde(default)]\n  pub maximized: bool,\n  /// Whether the window is visible or not.\n  #[serde(default = \"default_true\")]\n  pub visible: bool,\n  /// Whether the window should have borders and bars.\n  #[serde(default = \"default_true\")]\n  pub decorations: bool,\n  /// Whether the window should always be below other windows.\n  #[serde(default, alias = \"always-on-bottom\")]\n  pub always_on_bottom: bool,\n  /// Whether the window should always be on top of other windows.\n  #[serde(default, alias = \"always-on-top\")]\n  pub always_on_top: bool,\n  /// Whether the window should be visible on all workspaces or virtual desktops.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows / iOS / Android:** Unsupported.\n  #[serde(default, alias = \"visible-on-all-workspaces\")]\n  pub visible_on_all_workspaces: bool,\n  /// Prevents the window contents from being captured by other apps.\n  #[serde(default, alias = \"content-protected\")]\n  pub content_protected: bool,\n  /// If `true`, hides the window icon from the taskbar on Windows and Linux.\n  #[serde(default, alias = \"skip-taskbar\")]\n  pub skip_taskbar: bool,\n  /// The name of the window class created on Windows to create the window. **Windows only**.\n  pub window_classname: Option<String>,\n  /// The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.\n  pub theme: Option<crate::Theme>,\n  /// The style of the macOS title bar.\n  #[serde(default, alias = \"title-bar-style\")]\n  pub title_bar_style: TitleBarStyle,\n  /// The position of the window controls on macOS.\n  ///\n  /// Requires titleBarStyle: Overlay and decorations: true.\n  #[serde(default, alias = \"traffic-light-position\")]\n  pub traffic_light_position: Option<LogicalPosition>,\n  /// If `true`, sets the window title to be hidden on macOS.\n  #[serde(default, alias = \"hidden-title\")]\n  pub hidden_title: bool,\n  /// Whether clicking an inactive window also clicks through to the webview on macOS.\n  #[serde(default, alias = \"accept-first-mouse\")]\n  pub accept_first_mouse: bool,\n  /// Defines the window [tabbing identifier] for macOS.\n  ///\n  /// Windows with matching tabbing identifiers will be grouped together.\n  /// If the tabbing identifier is not set, automatic tabbing will be disabled.\n  ///\n  /// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>\n  #[serde(default, alias = \"tabbing-identifier\")]\n  pub tabbing_identifier: Option<String>,\n  /// Defines additional browser arguments on Windows. By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`\n  /// so if you use this method, you also need to disable these components by yourself if you want.\n  #[serde(default, alias = \"additional-browser-args\")]\n  pub additional_browser_args: Option<String>,\n  /// Whether or not the window has shadow.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows:**\n  ///   - `false` has no effect on decorated window, shadow are always ON.\n  ///   - `true` will make undecorated window have a 1px white border,\n  /// and on Windows 11, it will have a rounded corners.\n  /// - **Linux:** Unsupported.\n  #[serde(default = \"default_true\")]\n  pub shadow: bool,\n  /// Window effects.\n  ///\n  /// Requires the window to be transparent.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>\n  /// - **Linux**: Unsupported\n  #[serde(default, alias = \"window-effects\")]\n  pub window_effects: Option<WindowEffectsConfig>,\n  /// Whether or not the webview should be launched in incognito  mode.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Android**: Unsupported.\n  #[serde(default)]\n  pub incognito: bool,\n  /// Sets the window associated with this label to be the parent of the window to be created.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows**: This sets the passed parent as an owner window to the window to be created.\n  ///   From [MSDN owned windows docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows):\n  ///     - An owned window is always above its owner in the z-order.\n  ///     - The system automatically destroys an owned window when its owner is destroyed.\n  ///     - An owned window is hidden when its owner is minimized.\n  /// - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\n  /// - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>\n  pub parent: Option<String>,\n  /// The proxy URL for the WebView for all network requests.\n  ///\n  /// Must be either a `http://` or a `socks5://` URL.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.\n  #[serde(alias = \"proxy-url\")]\n  pub proxy_url: Option<Url>,\n  /// Whether page zooming by hotkeys is enabled\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.\n  /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,\n  /// 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission\n  ///\n  /// - **Android / iOS**: Unsupported.\n  #[serde(default, alias = \"zoom-hotkeys-enabled\")]\n  pub zoom_hotkeys_enabled: bool,\n  /// Whether browser extensions can be installed for the webview process\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)\n  /// - **MacOS / Linux / iOS / Android** - Unsupported.\n  #[serde(default, alias = \"browser-extensions-enabled\")]\n  pub browser_extensions_enabled: bool,\n\n  /// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.\n  ///\n  /// ## Note\n  ///\n  /// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.\n  ///\n  /// ## Warning\n  ///\n  /// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.\n  #[serde(default, alias = \"use-https-scheme\")]\n  pub use_https_scheme: bool,\n  /// Enable web inspector which is usually called browser devtools. Enabled by default.\n  ///\n  /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - macOS: This will call private functions on **macOS**.\n  /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.\n  /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.\n  pub devtools: Option<bool>,\n\n  /// Set the window and webview background color.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Windows**: alpha channel is ignored for the window layer.\n  /// - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.\n  /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored for the webview layer.\n  #[serde(alias = \"background-color\")]\n  pub background_color: Option<Color>,\n\n  /// Change the default background throttling behaviour.\n  ///\n  /// By default, browsers use a suspend policy that will throttle timers and even unload\n  /// the whole tab (view) to free resources after roughly 5 minutes when a view became\n  /// minimized or hidden. This will pause all tasks until the documents visibility state\n  /// changes back from hidden to visible by bringing the view back to the foreground.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.\n  /// - **iOS**: Supported since version 17.0+.\n  /// - **macOS**: Supported since version 14.0+.\n  ///\n  /// see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578\n  #[serde(default, alias = \"background-throttling\")]\n  pub background_throttling: Option<BackgroundThrottlingPolicy>,\n  /// Whether we should disable JavaScript code execution on the webview or not.\n  #[serde(default, alias = \"javascript-disabled\")]\n  pub javascript_disabled: bool,\n  /// on macOS and iOS there is a link preview on long pressing links, this is enabled by default.\n  /// see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview\n  #[serde(default = \"default_true\", alias = \"allow-link-preview\")]\n  pub allow_link_preview: bool,\n  /// Allows disabling the input accessory view on iOS.\n  ///\n  /// The accessory view is the view that appears above the keyboard when a text input element is focused.\n  /// It usually displays a view with \"Done\", \"Next\" buttons.\n  #[serde(\n    default,\n    alias = \"disable-input-accessory-view\",\n    alias = \"disable_input_accessory_view\"\n  )]\n  pub disable_input_accessory_view: bool,\n  ///\n  /// Set a custom path for the webview's data directory (localStorage, cache, etc.) **relative to [`appDataDir()`]/${label}**.\n  ///\n  /// To set absolute paths, use [`WebviewWindowBuilder::data_directory`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.data_directory)\n  ///\n  /// #### Platform-specific:\n  ///\n  /// - **Windows**: WebViews with different values for settings like `additionalBrowserArgs`, `browserExtensionsEnabled` or `scrollBarStyle` must have different data directories.\n  /// - **macOS / iOS**: Unsupported, use `dataStoreIdentifier` instead.\n  /// - **Android**: Unsupported.\n  #[serde(default, alias = \"data-directory\")]\n  pub data_directory: Option<PathBuf>,\n  ///\n  /// Initialize the WebView with a custom data store identifier. This can be seen as a replacement for `dataDirectory` which is unavailable in WKWebView.\n  /// See https://developer.apple.com/documentation/webkit/wkwebsitedatastore/init(foridentifier:)?language=objc\n  ///\n  /// The array must contain 16 u8 numbers.\n  ///\n  /// #### Platform-specific:\n  ///\n  /// - **iOS**: Supported since version 17.0+.\n  /// - **macOS**: Supported since version 14.0+.\n  /// - **Windows / Linux / Android**: Unsupported.\n  #[serde(default, alias = \"data-store-identifier\")]\n  pub data_store_identifier: Option<[u8; 16]>,\n\n  /// Specifies the native scrollbar style to use with the webview.\n  /// CSS styles that modify the scrollbar are applied on top of the native appearance configured here.\n  ///\n  /// Defaults to `default`, which is the browser default.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Windows**:\n  ///   - `fluentOverlay` requires WebView2 Runtime version 125.0.2535.41 or higher,\n  ///     and does nothing on older versions.\n  ///   - This option must be given the same value for all webviews that target the same data directory.\n  /// - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.\n  #[serde(default, alias = \"scroll-bar-style\")]\n  pub scroll_bar_style: ScrollBarStyle,\n}\n\nimpl Default for WindowConfig {\n  fn default() -> Self {\n    Self {\n      label: default_window_label(),\n      url: WebviewUrl::default(),\n      create: true,\n      user_agent: None,\n      drag_drop_enabled: true,\n      center: false,\n      x: None,\n      y: None,\n      width: default_width(),\n      height: default_height(),\n      min_width: None,\n      min_height: None,\n      max_width: None,\n      max_height: None,\n      prevent_overflow: None,\n      resizable: true,\n      maximizable: true,\n      minimizable: true,\n      closable: true,\n      title: default_title(),\n      fullscreen: false,\n      focus: true,\n      focusable: true,\n      transparent: false,\n      maximized: false,\n      visible: true,\n      decorations: true,\n      always_on_bottom: false,\n      always_on_top: false,\n      visible_on_all_workspaces: false,\n      content_protected: false,\n      skip_taskbar: false,\n      window_classname: None,\n      theme: None,\n      title_bar_style: Default::default(),\n      traffic_light_position: None,\n      hidden_title: false,\n      accept_first_mouse: false,\n      tabbing_identifier: None,\n      additional_browser_args: None,\n      shadow: true,\n      window_effects: None,\n      incognito: false,\n      parent: None,\n      proxy_url: None,\n      zoom_hotkeys_enabled: false,\n      browser_extensions_enabled: false,\n      use_https_scheme: false,\n      devtools: None,\n      background_color: None,\n      background_throttling: None,\n      javascript_disabled: false,\n      allow_link_preview: true,\n      disable_input_accessory_view: false,\n      data_directory: None,\n      data_store_identifier: None,\n      scroll_bar_style: ScrollBarStyle::Default,\n    }\n  }\n}\n\nfn default_window_label() -> String {\n  \"main\".to_string()\n}\n\nfn default_width() -> f64 {\n  800f64\n}\n\nfn default_height() -> f64 {\n  600f64\n}\n\nfn default_title() -> String {\n  \"Tauri App\".to_string()\n}\n\n/// A Content-Security-Policy directive source list.\n/// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources>.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", untagged)]\npub enum CspDirectiveSources {\n  /// An inline list of CSP sources. Same as [`Self::List`], but concatenated with a space separator.\n  Inline(String),\n  /// A list of CSP sources. The collection will be concatenated with a space separator for the CSP string.\n  List(Vec<String>),\n}\n\nimpl Default for CspDirectiveSources {\n  fn default() -> Self {\n    Self::List(Vec::new())\n  }\n}\n\nimpl From<CspDirectiveSources> for Vec<String> {\n  fn from(sources: CspDirectiveSources) -> Self {\n    match sources {\n      CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),\n      CspDirectiveSources::List(l) => l,\n    }\n  }\n}\n\nimpl CspDirectiveSources {\n  /// Whether the given source is configured on this directive or not.\n  pub fn contains(&self, source: &str) -> bool {\n    match self {\n      Self::Inline(s) => s.contains(&format!(\"{source} \")) || s.contains(&format!(\" {source}\")),\n      Self::List(l) => l.contains(&source.into()),\n    }\n  }\n\n  /// Appends the given source to this directive.\n  pub fn push<S: AsRef<str>>(&mut self, source: S) {\n    match self {\n      Self::Inline(s) => {\n        s.push(' ');\n        s.push_str(source.as_ref());\n      }\n      Self::List(l) => {\n        l.push(source.as_ref().to_string());\n      }\n    }\n  }\n\n  /// Extends this CSP directive source list with the given array of sources.\n  pub fn extend(&mut self, sources: Vec<String>) {\n    for s in sources {\n      self.push(s);\n    }\n  }\n}\n\n/// A Content-Security-Policy definition.\n/// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", untagged)]\npub enum Csp {\n  /// The entire CSP policy in a single text string.\n  Policy(String),\n  /// An object mapping a directive with its sources values as a list of strings.\n  DirectiveMap(HashMap<String, CspDirectiveSources>),\n}\n\nimpl From<HashMap<String, CspDirectiveSources>> for Csp {\n  fn from(map: HashMap<String, CspDirectiveSources>) -> Self {\n    Self::DirectiveMap(map)\n  }\n}\n\nimpl From<Csp> for HashMap<String, CspDirectiveSources> {\n  fn from(csp: Csp) -> Self {\n    match csp {\n      Csp::Policy(policy) => {\n        let mut map = HashMap::new();\n        for directive in policy.split(';') {\n          let mut tokens = directive.trim().split(' ');\n          if let Some(directive) = tokens.next() {\n            let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();\n            map.insert(directive.to_string(), CspDirectiveSources::List(sources));\n          }\n        }\n        map\n      }\n      Csp::DirectiveMap(m) => m,\n    }\n  }\n}\n\nimpl Display for Csp {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    match self {\n      Self::Policy(s) => write!(f, \"{s}\"),\n      Self::DirectiveMap(m) => {\n        let len = m.len();\n        let mut i = 0;\n        for (directive, sources) in m {\n          let sources: Vec<String> = sources.clone().into();\n          write!(f, \"{} {}\", directive, sources.join(\" \"))?;\n          i += 1;\n          if i != len {\n            write!(f, \"; \")?;\n          }\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\n/// The possible values for the `dangerous_disable_asset_csp_modification` config option.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[serde(untagged)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\npub enum DisabledCspModificationKind {\n  /// If `true`, disables all CSP modification.\n  /// `false` is the default value and it configures Tauri to control the CSP.\n  Flag(bool),\n  /// Disables the given list of CSP directives modifications.\n  List(Vec<String>),\n}\n\nimpl DisabledCspModificationKind {\n  /// Determines whether the given CSP directive can be modified or not.\n  pub fn can_modify(&self, directive: &str) -> bool {\n    match self {\n      Self::Flag(f) => !f,\n      Self::List(l) => !l.contains(&directive.into()),\n    }\n  }\n}\n\nimpl Default for DisabledCspModificationKind {\n  fn default() -> Self {\n    Self::Flag(false)\n  }\n}\n\n/// Protocol scope definition.\n/// It is a list of glob patterns that restrict the API access from the webview.\n///\n/// Each pattern can start with a variable that resolves to a system base directory.\n/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,\n/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,\n/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$TEMP`,\n/// `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[serde(untagged)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\npub enum FsScope {\n  /// A list of paths that are allowed by this scope.\n  AllowedPaths(Vec<PathBuf>),\n  /// A complete scope configuration.\n  #[serde(rename_all = \"camelCase\")]\n  Scope {\n    /// A list of paths that are allowed by this scope.\n    #[serde(default)]\n    allow: Vec<PathBuf>,\n    /// A list of paths that are not allowed by this scope.\n    /// This gets precedence over the [`Self::Scope::allow`] list.\n    #[serde(default)]\n    deny: Vec<PathBuf>,\n    /// Whether or not paths that contain components that start with a `.`\n    /// will require that `.` appears literally in the pattern; `*`, `?`, `**`,\n    /// or `[...]` will not match. This is useful because such files are\n    /// conventionally considered hidden on Unix systems and it might be\n    /// desirable to skip them when listing files.\n    ///\n    /// Defaults to `true` on Unix systems and `false` on Windows\n    // dotfiles are not supposed to be exposed by default on unix\n    #[serde(alias = \"require-literal-leading-dot\")]\n    require_literal_leading_dot: Option<bool>,\n  },\n}\n\nimpl Default for FsScope {\n  fn default() -> Self {\n    Self::AllowedPaths(Vec::new())\n  }\n}\n\nimpl FsScope {\n  /// The list of allowed paths.\n  pub fn allowed_paths(&self) -> &Vec<PathBuf> {\n    match self {\n      Self::AllowedPaths(p) => p,\n      Self::Scope { allow, .. } => allow,\n    }\n  }\n\n  /// The list of forbidden paths.\n  pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {\n    match self {\n      Self::AllowedPaths(_) => None,\n      Self::Scope { deny, .. } => Some(deny),\n    }\n  }\n}\n\n/// Config for the asset custom protocol.\n///\n/// See more: <https://v2.tauri.app/reference/config/#assetprotocolconfig>\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct AssetProtocolConfig {\n  /// The access scope for the asset protocol.\n  #[serde(default)]\n  pub scope: FsScope,\n  /// Enables the asset protocol.\n  #[serde(default)]\n  pub enable: bool,\n}\n\n/// definition of a header source\n///\n/// The header value to a header name\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", untagged)]\npub enum HeaderSource {\n  /// string version of the header Value\n  Inline(String),\n  /// list version of the header value. Item are joined by \",\" for the real header value\n  List(Vec<String>),\n  /// (Rust struct | Json | JavaScript Object) equivalent of the header value. Items are composed from: key + space + value. Item are then joined by \";\" for the real header value\n  Map(HashMap<String, String>),\n}\n\nimpl Display for HeaderSource {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    match self {\n      Self::Inline(s) => write!(f, \"{s}\"),\n      Self::List(l) => write!(f, \"{}\", l.join(\", \")),\n      Self::Map(m) => {\n        let len = m.len();\n        let mut i = 0;\n        for (key, value) in m {\n          write!(f, \"{key} {value}\")?;\n          i += 1;\n          if i != len {\n            write!(f, \"; \")?;\n          }\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\n/// A trait which implements on the [`Builder`] of the http create\n///\n/// Must add headers defined in the tauri configuration file to http responses\npub trait HeaderAddition {\n  /// adds all headers defined on the config file, given the current HeaderConfig\n  fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder;\n}\n\nimpl HeaderAddition for Builder {\n  /// Add the headers defined in the tauri configuration file to http responses\n  ///\n  /// this is a utility function, which is used in the same way as the `.header(..)` of the rust http library\n  fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder {\n    if let Some(headers) = headers {\n      // Add the header Access-Control-Allow-Credentials, if we find a value for it\n      if let Some(value) = &headers.access_control_allow_credentials {\n        self = self.header(\"Access-Control-Allow-Credentials\", value.to_string());\n      };\n\n      // Add the header Access-Control-Allow-Headers, if we find a value for it\n      if let Some(value) = &headers.access_control_allow_headers {\n        self = self.header(\"Access-Control-Allow-Headers\", value.to_string());\n      };\n\n      // Add the header Access-Control-Allow-Methods, if we find a value for it\n      if let Some(value) = &headers.access_control_allow_methods {\n        self = self.header(\"Access-Control-Allow-Methods\", value.to_string());\n      };\n\n      // Add the header Access-Control-Expose-Headers, if we find a value for it\n      if let Some(value) = &headers.access_control_expose_headers {\n        self = self.header(\"Access-Control-Expose-Headers\", value.to_string());\n      };\n\n      // Add the header Access-Control-Max-Age, if we find a value for it\n      if let Some(value) = &headers.access_control_max_age {\n        self = self.header(\"Access-Control-Max-Age\", value.to_string());\n      };\n\n      // Add the header Cross-Origin-Embedder-Policy, if we find a value for it\n      if let Some(value) = &headers.cross_origin_embedder_policy {\n        self = self.header(\"Cross-Origin-Embedder-Policy\", value.to_string());\n      };\n\n      // Add the header Cross-Origin-Opener-Policy, if we find a value for it\n      if let Some(value) = &headers.cross_origin_opener_policy {\n        self = self.header(\"Cross-Origin-Opener-Policy\", value.to_string());\n      };\n\n      // Add the header Cross-Origin-Resource-Policy, if we find a value for it\n      if let Some(value) = &headers.cross_origin_resource_policy {\n        self = self.header(\"Cross-Origin-Resource-Policy\", value.to_string());\n      };\n\n      // Add the header Permission-Policy, if we find a value for it\n      if let Some(value) = &headers.permissions_policy {\n        self = self.header(\"Permission-Policy\", value.to_string());\n      };\n\n      if let Some(value) = &headers.service_worker_allowed {\n        self = self.header(\"Service-Worker-Allowed\", value.to_string());\n      }\n\n      // Add the header Timing-Allow-Origin, if we find a value for it\n      if let Some(value) = &headers.timing_allow_origin {\n        self = self.header(\"Timing-Allow-Origin\", value.to_string());\n      };\n\n      // Add the header X-Content-Type-Options, if we find a value for it\n      if let Some(value) = &headers.x_content_type_options {\n        self = self.header(\"X-Content-Type-Options\", value.to_string());\n      };\n\n      // Add the header Tauri-Custom-Header, if we find a value for it\n      if let Some(value) = &headers.tauri_custom_header {\n        // Keep in mind to correctly set the Access-Control-Expose-Headers\n        self = self.header(\"Tauri-Custom-Header\", value.to_string());\n      };\n    }\n    self\n  }\n}\n\n/// A struct, where the keys are some specific http header names.\n///\n/// If the values to those keys are defined, then they will be send as part of a response message.\n/// This does not include error messages and ipc messages\n///\n/// ## Example configuration\n/// ```javascript\n/// {\n///  //..\n///   app:{\n///     //..\n///     security: {\n///       headers: {\n///         \"Cross-Origin-Opener-Policy\": \"same-origin\",\n///         \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n///         \"Timing-Allow-Origin\": [\n///           \"https://developer.mozilla.org\",\n///           \"https://example.com\",\n///         ],\n///         \"Access-Control-Expose-Headers\": \"Tauri-Custom-Header\",\n///         \"Tauri-Custom-Header\": {\n///           \"key1\": \"'value1' 'value2'\",\n///           \"key2\": \"'value3'\"\n///         }\n///       },\n///       csp: \"default-src 'self'; connect-src ipc: http://ipc.localhost\",\n///     }\n///     //..\n///   }\n///  //..\n/// }\n/// ```\n/// In this example `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` are set to allow for the use of [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).\n/// The result is, that those headers are then set on every response sent via the `get_response` function in crates/tauri/src/protocol/tauri.rs.\n/// The Content-Security-Policy header is defined separately, because it is also handled separately.\n///\n/// For the helloworld example, this config translates into those response headers:\n/// ```http\n/// access-control-allow-origin:  http://tauri.localhost\n/// access-control-expose-headers: Tauri-Custom-Header\n/// content-security-policy: default-src 'self'; connect-src ipc: http://ipc.localhost; script-src 'self' 'sha256-Wjjrs6qinmnr+tOry8x8PPwI77eGpUFR3EEGZktjJNs='\n/// content-type: text/html\n/// cross-origin-embedder-policy: require-corp\n/// cross-origin-opener-policy: same-origin\n/// tauri-custom-header: key1 'value1' 'value2'; key2 'value3'\n/// timing-allow-origin: https://developer.mozilla.org, https://example.com\n/// ```\n/// Since the resulting header values are always 'string-like'. So depending on the what data type the HeaderSource is, they need to be converted.\n///  - `String`(JS/Rust): stay the same for the resulting header value\n///  - `Array`(JS)/`Vec\\<String\\>`(Rust): Item are joined by \", \" for the resulting header value\n///  - `Object`(JS)/ `Hashmap\\<String,String\\>`(Rust): Items are composed from: key + space + value. Item are then joined by \"; \" for the resulting header value\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(deny_unknown_fields)]\npub struct HeaderConfig {\n  /// The Access-Control-Allow-Credentials response header tells browsers whether the\n  /// server allows cross-origin HTTP requests to include credentials.\n  ///\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials>\n  #[serde(rename = \"Access-Control-Allow-Credentials\")]\n  pub access_control_allow_credentials: Option<HeaderSource>,\n  /// The Access-Control-Allow-Headers response header is used in response\n  /// to a preflight request which includes the Access-Control-Request-Headers\n  /// to indicate which HTTP headers can be used during the actual request.\n  ///\n  /// This header is required if the request has an Access-Control-Request-Headers header.\n  ///\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers>\n  #[serde(rename = \"Access-Control-Allow-Headers\")]\n  pub access_control_allow_headers: Option<HeaderSource>,\n  /// The Access-Control-Allow-Methods response header specifies one or more methods\n  /// allowed when accessing a resource in response to a preflight request.\n  ///\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods>\n  #[serde(rename = \"Access-Control-Allow-Methods\")]\n  pub access_control_allow_methods: Option<HeaderSource>,\n  /// The Access-Control-Expose-Headers response header allows a server to indicate\n  /// which response headers should be made available to scripts running in the browser,\n  /// in response to a cross-origin request.\n  ///\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers>\n  #[serde(rename = \"Access-Control-Expose-Headers\")]\n  pub access_control_expose_headers: Option<HeaderSource>,\n  /// The Access-Control-Max-Age response header indicates how long the results of a\n  /// preflight request (that is the information contained in the\n  /// Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can\n  /// be cached.\n  ///\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age>\n  #[serde(rename = \"Access-Control-Max-Age\")]\n  pub access_control_max_age: Option<HeaderSource>,\n  /// The HTTP Cross-Origin-Embedder-Policy (COEP) response header configures embedding\n  /// cross-origin resources into the document.\n  ///\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy>\n  #[serde(rename = \"Cross-Origin-Embedder-Policy\")]\n  pub cross_origin_embedder_policy: Option<HeaderSource>,\n  /// The HTTP Cross-Origin-Opener-Policy (COOP) response header allows you to ensure a\n  /// top-level document does not share a browsing context group with cross-origin documents.\n  /// COOP will process-isolate your document and potential attackers can't access your global\n  /// object if they were to open it in a popup, preventing a set of cross-origin attacks dubbed XS-Leaks.\n  ///\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy>\n  #[serde(rename = \"Cross-Origin-Opener-Policy\")]\n  pub cross_origin_opener_policy: Option<HeaderSource>,\n  /// The HTTP Cross-Origin-Resource-Policy response header conveys a desire that the\n  /// browser blocks no-cors cross-origin/cross-site requests to the given resource.\n  ///\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy>\n  #[serde(rename = \"Cross-Origin-Resource-Policy\")]\n  pub cross_origin_resource_policy: Option<HeaderSource>,\n  /// The HTTP Permissions-Policy header provides a mechanism to allow and deny the\n  /// use of browser features in a document or within any \\<iframe\\> elements in the document.\n  ///\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy>\n  #[serde(rename = \"Permissions-Policy\")]\n  pub permissions_policy: Option<HeaderSource>,\n  /// The HTTP Service-Worker-Allowed response header is used to broaden the path restriction for a\n  /// service worker's default scope.\n  ///\n  /// By default, the scope for a service worker registration is the directory where the service\n  /// worker script is located. For example, if the script `sw.js` is located in `/js/sw.js`,\n  /// it can only control URLs under `/js/` by default. Servers can use the `Service-Worker-Allowed`\n  /// header to allow a service worker to control URLs outside of its own directory.\n  ///\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Service-Worker-Allowed>\n  #[serde(rename = \"Service-Worker-Allowed\")]\n  pub service_worker_allowed: Option<HeaderSource>,\n  /// The Timing-Allow-Origin response header specifies origins that are allowed to see values\n  /// of attributes retrieved via features of the Resource Timing API, which would otherwise be\n  /// reported as zero due to cross-origin restrictions.\n  ///\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin>\n  #[serde(rename = \"Timing-Allow-Origin\")]\n  pub timing_allow_origin: Option<HeaderSource>,\n  /// The X-Content-Type-Options response HTTP header is a marker used by the server to indicate\n  /// that the MIME types advertised in the Content-Type headers should be followed and not be\n  /// changed. The header allows you to avoid MIME type sniffing by saying that the MIME types\n  /// are deliberately configured.\n  ///\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options>\n  #[serde(rename = \"X-Content-Type-Options\")]\n  pub x_content_type_options: Option<HeaderSource>,\n  /// A custom header field Tauri-Custom-Header, don't use it.\n  /// Remember to set Access-Control-Expose-Headers accordingly\n  ///\n  /// **NOT INTENDED FOR PRODUCTION USE**\n  #[serde(rename = \"Tauri-Custom-Header\")]\n  pub tauri_custom_header: Option<HeaderSource>,\n}\n\nimpl HeaderConfig {\n  /// creates a new header config\n  pub fn new() -> Self {\n    HeaderConfig {\n      access_control_allow_credentials: None,\n      access_control_allow_methods: None,\n      access_control_allow_headers: None,\n      access_control_expose_headers: None,\n      access_control_max_age: None,\n      cross_origin_embedder_policy: None,\n      cross_origin_opener_policy: None,\n      cross_origin_resource_policy: None,\n      permissions_policy: None,\n      service_worker_allowed: None,\n      timing_allow_origin: None,\n      x_content_type_options: None,\n      tauri_custom_header: None,\n    }\n  }\n}\n\n/// Security configuration.\n///\n/// See more: <https://v2.tauri.app/reference/config/#securityconfig>\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct SecurityConfig {\n  /// The Content Security Policy that will be injected on all HTML files on the built application.\n  /// If [`dev_csp`](#SecurityConfig.devCsp) is not specified, this value is also injected on dev.\n  ///\n  /// This is a really important part of the configuration since it helps you ensure your WebView is secured.\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\n  pub csp: Option<Csp>,\n  /// The Content Security Policy that will be injected on all HTML files on development.\n  ///\n  /// This is a really important part of the configuration since it helps you ensure your WebView is secured.\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\n  #[serde(alias = \"dev-csp\")]\n  pub dev_csp: Option<Csp>,\n  /// Freeze the `Object.prototype` when using the custom protocol.\n  #[serde(default, alias = \"freeze-prototype\")]\n  pub freeze_prototype: bool,\n  /// Disables the Tauri-injected CSP sources.\n  ///\n  /// At compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy\n  /// to only allow loading of your own scripts and styles by injecting nonce and hash sources.\n  /// This stricts your CSP, which may introduce issues when using along with other flexing sources.\n  ///\n  /// This configuration option allows both a boolean and a list of strings as value.\n  /// A boolean instructs Tauri to disable the injection for all CSP injections,\n  /// and a list of strings indicates the CSP directives that Tauri cannot inject.\n  ///\n  /// **WARNING:** Only disable this if you know what you are doing and have properly configured the CSP.\n  /// Your application might be vulnerable to XSS attacks without this Tauri protection.\n  #[serde(default, alias = \"dangerous-disable-asset-csp-modification\")]\n  pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,\n  /// Custom protocol config.\n  #[serde(default, alias = \"asset-protocol\")]\n  pub asset_protocol: AssetProtocolConfig,\n  /// The pattern to use.\n  #[serde(default)]\n  pub pattern: PatternKind,\n  /// List of capabilities that are enabled on the application.\n  ///\n  /// By default (not set or empty list), all capability files from `./capabilities/` are included,\n  /// by setting values in this entry, you have fine grained control over which capabilities are included\n  ///\n  /// You can either reference a capability file defined in `./capabilities/` with its identifier or inline a [`Capability`]\n  ///\n  /// ### Example\n  ///\n  /// ```json\n  /// {\n  ///   \"app\": {\n  ///     \"capabilities\": [\n  ///       \"main-window\",\n  ///       {\n  ///         \"identifier\": \"drag-window\",\n  ///         \"permissions\": [\"core:window:allow-start-dragging\"]\n  ///       }\n  ///     ]\n  ///   }\n  /// }\n  /// ```\n  #[serde(default)]\n  pub capabilities: Vec<CapabilityEntry>,\n  /// The headers, which are added to every http response from tauri to the web view\n  /// This doesn't include IPC Messages and error responses\n  #[serde(default)]\n  pub headers: Option<HeaderConfig>,\n}\n\n/// A capability entry which can be either an inlined capability or a reference to a capability defined on its own file.\n#[derive(Debug, Clone, PartialEq, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(untagged)]\npub enum CapabilityEntry {\n  /// An inlined capability.\n  Inlined(Capability),\n  /// Reference to a capability identifier.\n  Reference(String),\n}\n\nimpl<'de> Deserialize<'de> for CapabilityEntry {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    UntaggedEnumVisitor::new()\n      .string(|string| Ok(Self::Reference(string.to_owned())))\n      .map(|map| map.deserialize::<Capability>().map(Self::Inlined))\n      .deserialize(deserializer)\n  }\n}\n\n/// The application pattern.\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]\n#[serde(rename_all = \"lowercase\", tag = \"use\", content = \"options\")]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\npub enum PatternKind {\n  /// Brownfield pattern.\n  #[default]\n  Brownfield,\n  /// Isolation pattern. Recommended for security purposes.\n  Isolation {\n    /// The dir containing the index.html file that contains the secure isolation application.\n    dir: PathBuf,\n  },\n}\n\n/// The App configuration object.\n///\n/// See more: <https://v2.tauri.app/reference/config/#appconfig>\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct AppConfig {\n  /// The app windows configuration.\n  ///\n  /// ## Example:\n  ///\n  /// To create a window at app startup\n  ///\n  /// ```json\n  /// {\n  ///   \"app\": {\n  ///     \"windows\": [\n  ///       { \"width\": 800, \"height\": 600 }\n  ///     ]\n  ///   }\n  /// }\n  /// ```\n  ///\n  /// If not specified, the window's label (its identifier) defaults to \"main\",\n  /// you can use this label to get the window through\n  /// `app.get_webview_window` in Rust or `WebviewWindow.getByLabel` in JavaScript\n  ///\n  /// When working with multiple windows, each window will need an unique label\n  ///\n  /// ```json\n  /// {\n  ///   \"app\": {\n  ///     \"windows\": [\n  ///       { \"label\": \"main\", \"width\": 800, \"height\": 600 },\n  ///       { \"label\": \"secondary\", \"width\": 800, \"height\": 600 }\n  ///     ]\n  ///   }\n  /// }\n  /// ```\n  ///\n  /// You can also set `create` to false and use this config through the Rust APIs\n  ///\n  /// ```json\n  /// {\n  ///   \"app\": {\n  ///     \"windows\": [\n  ///       { \"create\": false, \"width\": 800, \"height\": 600 }\n  ///     ]\n  ///   }\n  /// }\n  /// ```\n  ///\n  /// and use it like this\n  ///\n  /// ```rust\n  /// tauri::Builder::default()\n  ///   .setup(|app| {\n  ///     tauri::WebviewWindowBuilder::from_config(app.handle(), &app.config().app.windows[0])?.build()?;\n  ///     Ok(())\n  ///   });\n  /// ```\n  #[serde(default)]\n  pub windows: Vec<WindowConfig>,\n  /// Security configuration.\n  #[serde(default)]\n  pub security: SecurityConfig,\n  /// Configuration for app tray icon.\n  #[serde(alias = \"tray-icon\")]\n  pub tray_icon: Option<TrayIconConfig>,\n  /// MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`.\n  #[serde(rename = \"macOSPrivateApi\", alias = \"macos-private-api\", default)]\n  pub macos_private_api: bool,\n  /// Whether we should inject the Tauri API on `window.__TAURI__` or not.\n  #[serde(default, alias = \"with-global-tauri\")]\n  pub with_global_tauri: bool,\n  /// If set to true \"identifier\" will be set as GTK app ID (on systems that use GTK).\n  #[serde(rename = \"enableGTKAppId\", alias = \"enable-gtk-app-id\", default)]\n  pub enable_gtk_app_id: bool,\n}\n\nimpl AppConfig {\n  /// Returns all Cargo features.\n  pub fn all_features() -> Vec<&'static str> {\n    vec![\n      \"tray-icon\",\n      \"macos-private-api\",\n      \"protocol-asset\",\n      \"isolation\",\n    ]\n  }\n\n  /// Returns the enabled Cargo features.\n  pub fn features(&self) -> Vec<&str> {\n    let mut features = Vec::new();\n    if self.tray_icon.is_some() {\n      features.push(\"tray-icon\");\n    }\n    if self.macos_private_api {\n      features.push(\"macos-private-api\");\n    }\n    if self.security.asset_protocol.enable {\n      features.push(\"protocol-asset\");\n    }\n\n    if let PatternKind::Isolation { .. } = self.security.pattern {\n      features.push(\"isolation\");\n    }\n\n    features.sort_unstable();\n    features\n  }\n}\n\n/// Configuration for application tray icon.\n///\n/// See more: <https://v2.tauri.app/reference/config/#trayiconconfig>\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct TrayIconConfig {\n  /// Set an id for this tray icon so you can reference it later, defaults to `main`.\n  pub id: Option<String>,\n  /// Path to the default icon to use for the tray icon.\n  ///\n  /// Note: this stores the image in raw pixels to the final binary,\n  /// so keep the icon size (width and height) small\n  /// or else it's going to bloat your final executable\n  #[serde(alias = \"icon-path\")]\n  pub icon_path: PathBuf,\n  /// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.\n  #[serde(default, alias = \"icon-as-template\")]\n  pub icon_as_template: bool,\n  /// A Boolean value that determines whether the menu should appear when the tray icon receives a left click.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux**: Unsupported.\n  #[serde(default = \"default_true\", alias = \"menu-on-left-click\")]\n  #[deprecated(since = \"2.2.0\", note = \"Use `show_menu_on_left_click` instead.\")]\n  pub menu_on_left_click: bool,\n  /// A Boolean value that determines whether the menu should appear when the tray icon receives a left click.\n  ///\n  /// ## Platform-specific:\n  ///\n  /// - **Linux**: Unsupported.\n  #[serde(default = \"default_true\", alias = \"show-menu-on-left-click\")]\n  pub show_menu_on_left_click: bool,\n  /// Title for MacOS tray\n  pub title: Option<String>,\n  /// Tray icon tooltip on Windows and macOS\n  pub tooltip: Option<String>,\n}\n\n/// General configuration for the iOS target.\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct IosConfig {\n  /// A custom [XcodeGen] project.yml template to use.\n  ///\n  /// [XcodeGen]: <https://github.com/yonaskolb/XcodeGen>\n  pub template: Option<PathBuf>,\n  /// A list of strings indicating any iOS frameworks that need to be bundled with the application.\n  ///\n  /// Note that you need to recreate the iOS project for the changes to be applied.\n  pub frameworks: Option<Vec<String>>,\n  /// The development team. This value is required for iOS development because code signing is enforced.\n  /// The `APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.\n  #[serde(alias = \"development-team\")]\n  pub development_team: Option<String>,\n  /// The version of the build that identifies an iteration of the bundle.\n  ///\n  /// Translates to the bundle's CFBundleVersion property.\n  #[serde(alias = \"bundle-version\")]\n  pub bundle_version: Option<String>,\n  /// A version string indicating the minimum iOS version that the bundled application supports. Defaults to `13.0`.\n  ///\n  /// Maps to the IPHONEOS_DEPLOYMENT_TARGET value.\n  #[serde(\n    alias = \"minimum-system-version\",\n    default = \"ios_minimum_system_version\"\n  )]\n  pub minimum_system_version: String,\n  /// Path to a Info.plist file to merge with the default Info.plist.\n  ///\n  /// Note that Tauri also looks for a `Info.plist` and `Info.ios.plist` file in the same directory as the Tauri configuration file.\n  #[serde(alias = \"info-plist\")]\n  pub info_plist: Option<PathBuf>,\n}\n\nimpl Default for IosConfig {\n  fn default() -> Self {\n    Self {\n      template: None,\n      frameworks: None,\n      development_team: None,\n      bundle_version: None,\n      minimum_system_version: ios_minimum_system_version(),\n      info_plist: None,\n    }\n  }\n}\n\n/// General configuration for the Android target.\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct AndroidConfig {\n  /// The minimum API level required for the application to run.\n  /// The Android system will prevent the user from installing the application if the system's API level is lower than the value specified.\n  #[serde(alias = \"min-sdk-version\", default = \"default_min_sdk_version\")]\n  pub min_sdk_version: u32,\n\n  /// The version code of the application.\n  /// It is limited to 2,100,000,000 as per Google Play Store requirements.\n  ///\n  /// By default we use your configured version and perform the following math:\n  /// versionCode = version.major * 1000000 + version.minor * 1000 + version.patch\n  #[serde(alias = \"version-code\")]\n  #[cfg_attr(feature = \"schema\", validate(range(min = 1, max = 2_100_000_000)))]\n  pub version_code: Option<u32>,\n\n  /// Whether to automatically increment the `versionCode` on each build.\n  ///\n  /// - If `true`, the generator will try to read the last `versionCode` from\n  ///   `tauri.properties` and increment it by 1 for every build.\n  /// - If `false` or not set, it falls back to `version_code` or semver-derived logic.\n  ///\n  /// Note that to use this feature, you should remove `/tauri.properties` from `src-tauri/gen/android/app/.gitignore` so the current versionCode is committed to the repository.\n  #[serde(alias = \"auto-increment-version-code\", default)]\n  pub auto_increment_version_code: bool,\n}\n\nimpl Default for AndroidConfig {\n  fn default() -> Self {\n    Self {\n      min_sdk_version: default_min_sdk_version(),\n      version_code: None,\n      auto_increment_version_code: false,\n    }\n  }\n}\n\nfn default_min_sdk_version() -> u32 {\n  24\n}\n\n/// Defines the URL or assets to embed in the application.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(untagged, deny_unknown_fields)]\n#[non_exhaustive]\npub enum FrontendDist {\n  /// An external URL that should be used as the default application URL. No assets are embedded in the app in this case.\n  Url(Url),\n  /// Path to a directory containing the frontend dist assets.\n  Directory(PathBuf),\n  /// An array of files to embed in the app.\n  Files(Vec<PathBuf>),\n}\n\nimpl std::fmt::Display for FrontendDist {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    match self {\n      Self::Url(url) => write!(f, \"{url}\"),\n      Self::Directory(p) => write!(f, \"{}\", p.display()),\n      Self::Files(files) => write!(f, \"{}\", serde_json::to_string(files).unwrap()),\n    }\n  }\n}\n\n/// Describes the shell command to run before `tauri dev`.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", untagged)]\npub enum BeforeDevCommand {\n  /// Run the given script with the default options.\n  Script(String),\n  /// Run the given script with custom options.\n  ScriptWithOptions {\n    /// The script to execute.\n    script: String,\n    /// The current working directory.\n    cwd: Option<String>,\n    /// Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`.\n    #[serde(default)]\n    wait: bool,\n  },\n}\n\n/// Describes a shell command to be executed when a CLI hook is triggered.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", untagged)]\npub enum HookCommand {\n  /// Run the given script with the default options.\n  Script(String),\n  /// Run the given script with custom options.\n  ScriptWithOptions {\n    /// The script to execute.\n    script: String,\n    /// The current working directory.\n    cwd: Option<String>,\n  },\n}\n\n/// The runner configuration.\n#[skip_serializing_none]\n#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(untagged)]\npub enum RunnerConfig {\n  /// A string specifying the binary to run.\n  String(String),\n  /// An object with advanced configuration options.\n  Object {\n    /// The binary to run.\n    cmd: String,\n    /// The current working directory to run the command from.\n    cwd: Option<String>,\n    /// Arguments to pass to the command.\n    args: Option<Vec<String>>,\n  },\n}\n\nimpl Default for RunnerConfig {\n  fn default() -> Self {\n    RunnerConfig::String(\"cargo\".to_string())\n  }\n}\n\nimpl RunnerConfig {\n  /// Returns the command to run.\n  pub fn cmd(&self) -> &str {\n    match self {\n      RunnerConfig::String(cmd) => cmd,\n      RunnerConfig::Object { cmd, .. } => cmd,\n    }\n  }\n\n  /// Returns the working directory.\n  pub fn cwd(&self) -> Option<&str> {\n    match self {\n      RunnerConfig::String(_) => None,\n      RunnerConfig::Object { cwd, .. } => cwd.as_deref(),\n    }\n  }\n\n  /// Returns the arguments.\n  pub fn args(&self) -> Option<&[String]> {\n    match self {\n      RunnerConfig::String(_) => None,\n      RunnerConfig::Object { args, .. } => args.as_deref(),\n    }\n  }\n}\n\nimpl std::str::FromStr for RunnerConfig {\n  type Err = std::convert::Infallible;\n\n  fn from_str(s: &str) -> Result<Self, Self::Err> {\n    Ok(RunnerConfig::String(s.to_string()))\n  }\n}\n\nimpl From<&str> for RunnerConfig {\n  fn from(s: &str) -> Self {\n    RunnerConfig::String(s.to_string())\n  }\n}\n\nimpl From<String> for RunnerConfig {\n  fn from(s: String) -> Self {\n    RunnerConfig::String(s)\n  }\n}\n\n/// The Build configuration object.\n///\n/// See more: <https://v2.tauri.app/reference/config/#buildconfig>\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct BuildConfig {\n  /// The binary used to build and run the application.\n  pub runner: Option<RunnerConfig>,\n  /// The URL to load in development.\n  ///\n  /// This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.\n  /// Most modern JavaScript bundlers like [Vite](https://vite.dev/guide/) provides a way to start a dev server by default.\n  ///\n  /// If you don't have a dev server or don't want to use one, ignore this option and use [`frontendDist`](BuildConfig::frontend_dist)\n  /// and point to a web assets directory, and Tauri CLI will run its built-in dev server and provide a simple hot-reload experience.\n  #[serde(alias = \"dev-url\")]\n  pub dev_url: Option<Url>,\n  /// The path to the application assets (usually the `dist` folder of your javascript bundler)\n  /// or a URL that could be either a custom protocol registered in the tauri app (for example: `myprotocol://`)\n  /// or a remote URL (for example: `https://site.com/app`).\n  ///\n  /// When a path relative to the configuration file is provided,\n  /// it is read recursively and all files are embedded in the application binary.\n  /// Tauri then looks for an `index.html` and serves it as the default entry point for your application.\n  ///\n  /// You can also provide a list of paths to be embedded, which allows granular control over what files are added to the binary.\n  /// In this case, all files are added to the root and you must reference it that way in your HTML files.\n  ///\n  /// When a URL is provided, the application won't have bundled assets\n  /// and the application will load that URL by default.\n  #[serde(alias = \"frontend-dist\")]\n  pub frontend_dist: Option<FrontendDist>,\n  /// A shell command to run before `tauri dev` kicks in.\n  ///\n  /// The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\n  #[serde(alias = \"before-dev-command\")]\n  pub before_dev_command: Option<BeforeDevCommand>,\n  /// A shell command to run before `tauri build` kicks in.\n  ///\n  /// The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\n  #[serde(alias = \"before-build-command\")]\n  pub before_build_command: Option<HookCommand>,\n  /// A shell command to run before the bundling phase in `tauri build` kicks in.\n  ///\n  /// The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.\n  #[serde(alias = \"before-bundle-command\")]\n  pub before_bundle_command: Option<HookCommand>,\n  /// Features passed to `cargo` commands.\n  pub features: Option<Vec<String>>,\n  /// Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\n  /// the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\n  /// and they'll try to get all the allowed commands and remove the rest\n  ///\n  /// Note:\n  ///   - This won't be accounting for dynamically added ACLs when you use features from the `dynamic-acl` (currently enabled by default) feature flag, so make sure to check it when using this\n  ///   - This feature requires tauri-plugin 2.1 and tauri 2.4\n  #[serde(alias = \"remove-unused-commands\", default)]\n  pub remove_unused_commands: bool,\n  /// Additional paths to watch for changes when running `tauri dev`.\n  #[serde(alias = \"additional-watch-directories\", default)]\n  pub additional_watch_folders: Vec<PathBuf>,\n}\n\n#[derive(Debug, PartialEq, Eq)]\nstruct PackageVersion(String);\n\nimpl<'d> serde::Deserialize<'d> for PackageVersion {\n  fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {\n    struct PackageVersionVisitor;\n\n    impl Visitor<'_> for PackageVersionVisitor {\n      type Value = PackageVersion;\n\n      fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n          formatter,\n          \"a semver string or a path to a package.json file\"\n        )\n      }\n\n      fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {\n        let path = PathBuf::from(value);\n        if path.exists() {\n          let json_str = read_to_string(&path)\n            .map_err(|e| DeError::custom(format!(\"failed to read version JSON file: {e}\")))?;\n          let package_json: serde_json::Value = serde_json::from_str(&json_str)\n            .map_err(|e| DeError::custom(format!(\"failed to read version JSON file: {e}\")))?;\n          if let Some(obj) = package_json.as_object() {\n            let version = obj\n              .get(\"version\")\n              .ok_or_else(|| DeError::custom(\"JSON must contain a `version` field\"))?\n              .as_str()\n              .ok_or_else(|| {\n                DeError::custom(format!(\"`{} > version` must be a string\", path.display()))\n              })?;\n            Ok(PackageVersion(\n              Version::from_str(version)\n                .map_err(|_| {\n                  DeError::custom(\"`tauri.conf.json > version` must be a semver string\")\n                })?\n                .to_string(),\n            ))\n          } else {\n            Err(DeError::custom(\n              \"`tauri.conf.json > version` value is not a path to a JSON object\",\n            ))\n          }\n        } else {\n          Ok(PackageVersion(\n            Version::from_str(value)\n              .map_err(|_| DeError::custom(\"`tauri.conf.json > version` must be a semver string\"))?\n              .to_string(),\n          ))\n        }\n      }\n    }\n\n    deserializer.deserialize_string(PackageVersionVisitor {})\n  }\n}\n\nfn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>\nwhere\n  D: Deserializer<'de>,\n{\n  Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))\n}\n\n/// The Tauri configuration object.\n/// It is read from a file where you can define your frontend assets,\n/// configure the bundler and define a tray icon.\n///\n/// The configuration file is generated by the\n/// [`tauri init`](https://v2.tauri.app/reference/cli/#init) command that lives in\n/// your Tauri application source directory (src-tauri).\n///\n/// Once generated, you may modify it at will to customize your Tauri application.\n///\n/// ## File Formats\n///\n/// By default, the configuration is defined as a JSON file named `tauri.conf.json`.\n///\n/// Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.\n/// The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.\n/// The TOML file name is `Tauri.toml`.\n///\n/// ## Platform-Specific Configuration\n///\n/// In addition to the default configuration file, Tauri can\n/// read a platform-specific configuration from `tauri.linux.conf.json`,\n/// `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`\n/// (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),\n/// which gets merged with the main configuration object.\n///\n/// ## Configuration Structure\n///\n/// The configuration is composed of the following objects:\n///\n/// - [`app`](#appconfig): The Tauri configuration\n/// - [`build`](#buildconfig): The build configuration\n/// - [`bundle`](#bundleconfig): The bundle configurations\n/// - [`plugins`](#pluginconfig): The plugins configuration\n///\n/// Example tauri.config.json file:\n///\n/// ```json\n/// {\n///   \"productName\": \"tauri-app\",\n///   \"version\": \"0.1.0\",\n///   \"build\": {\n///     \"beforeBuildCommand\": \"\",\n///     \"beforeDevCommand\": \"\",\n///     \"devUrl\": \"http://localhost:3000\",\n///     \"frontendDist\": \"../dist\"\n///   },\n///   \"app\": {\n///     \"security\": {\n///       \"csp\": null\n///     },\n///     \"windows\": [\n///       {\n///         \"fullscreen\": false,\n///         \"height\": 600,\n///         \"resizable\": true,\n///         \"title\": \"Tauri App\",\n///         \"width\": 800\n///       }\n///     ]\n///   },\n///   \"bundle\": {},\n///   \"plugins\": {}\n/// }\n/// ```\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct Config {\n  /// The JSON schema for the Tauri config.\n  #[serde(rename = \"$schema\")]\n  pub schema: Option<String>,\n  /// App name.\n  #[serde(alias = \"product-name\")]\n  #[cfg_attr(feature = \"schema\", validate(regex(pattern = \"^[^/\\\\:*?\\\"<>|]+$\")))]\n  pub product_name: Option<String>,\n  /// Overrides app's main binary filename.\n  ///\n  /// By default, Tauri uses the output binary from `cargo`, by setting this, we will rename that binary in `tauri-cli`'s\n  /// `tauri build` command, and target `tauri bundle` to it\n  ///\n  /// If possible, change the [`package name`] or set the [`name field`] instead,\n  /// and if that's not enough and you're using nightly, consider using the [`different-binary-name`] feature instead\n  ///\n  /// Note: this config should not include the binary extension (e.g. `.exe`), we'll add that for you\n  ///\n  /// [`package name`]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field\n  /// [`name field`]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-name-field\n  /// [`different-binary-name`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#different-binary-name\n  #[serde(alias = \"main-binary-name\")]\n  pub main_binary_name: Option<String>,\n  /// App version. It is a semver version number or a path to a `package.json` file containing the `version` field.\n  ///\n  /// If removed the version number from `Cargo.toml` is used.\n  /// It's recommended to manage the app versioning in the Tauri config.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS**: Translates to the bundle's CFBundleShortVersionString property and is used as the default CFBundleVersion.\n  ///    You can set an specific bundle version using [`bundle > macOS > bundleVersion`](MacConfig::bundle_version).\n  /// - **iOS**: Translates to the bundle's CFBundleShortVersionString property and is used as the default CFBundleVersion.\n  ///    You can set an specific bundle version using [`bundle > iOS > bundleVersion`](IosConfig::bundle_version).\n  ///    The `tauri ios build` CLI command has a `--build-number <number>` option that lets you append a build number to the app version.\n  /// - **Android**: By default version 1.0 is used. You can set a version code using [`bundle > android > versionCode`](AndroidConfig::version_code).\n  ///\n  /// By default version 1.0 is used on Android.\n  #[serde(deserialize_with = \"version_deserializer\", default)]\n  pub version: Option<String>,\n  /// The application identifier in reverse domain name notation (e.g. `com.tauri.example`).\n  /// This string must be unique across applications since it is used in system configurations like\n  /// the bundle ID and path to the webview data directory.\n  /// This string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-),\n  /// and periods (.).\n  pub identifier: String,\n  /// The App configuration.\n  #[serde(default)]\n  pub app: AppConfig,\n  /// The build configuration.\n  #[serde(default)]\n  pub build: BuildConfig,\n  /// The bundler configuration.\n  #[serde(default)]\n  pub bundle: BundleConfig,\n  /// The plugins config.\n  #[serde(default)]\n  pub plugins: PluginConfig,\n}\n\n/// The plugin configs holds a HashMap mapping a plugin name to its configuration object.\n///\n/// See more: <https://v2.tauri.app/reference/config/#pluginconfig>\n#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(JsonSchema))]\npub struct PluginConfig(pub HashMap<String, JsonValue>);\n\n/// Implement `ToTokens` for all config structs, allowing a literal `Config` to be built.\n///\n/// This allows for a build script to output the values in a `Config` to a `TokenStream`, which can\n/// then be consumed by another crate. Useful for passing a config to both the build script and the\n/// application using tauri while only parsing it once (in the build script).\n#[cfg(feature = \"build\")]\nmod build {\n  use super::*;\n  use crate::{literal_struct, tokens::*};\n  use proc_macro2::TokenStream;\n  use quote::{quote, ToTokens, TokenStreamExt};\n  use std::convert::identity;\n\n  impl ToTokens for WebviewUrl {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::WebviewUrl };\n\n      tokens.append_all(match self {\n        Self::App(path) => {\n          let path = path_buf_lit(path);\n          quote! { #prefix::App(#path) }\n        }\n        Self::External(url) => {\n          let url = url_lit(url);\n          quote! { #prefix::External(#url) }\n        }\n        Self::CustomProtocol(url) => {\n          let url = url_lit(url);\n          quote! { #prefix::CustomProtocol(#url) }\n        }\n      })\n    }\n  }\n\n  impl ToTokens for BackgroundThrottlingPolicy {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::BackgroundThrottlingPolicy };\n      tokens.append_all(match self {\n        Self::Disabled => quote! { #prefix::Disabled },\n        Self::Throttle => quote! { #prefix::Throttle },\n        Self::Suspend => quote! { #prefix::Suspend },\n      })\n    }\n  }\n\n  impl ToTokens for crate::Theme {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::Theme };\n\n      tokens.append_all(match self {\n        Self::Light => quote! { #prefix::Light },\n        Self::Dark => quote! { #prefix::Dark },\n      })\n    }\n  }\n\n  impl ToTokens for Color {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let Color(r, g, b, a) = self;\n      tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});\n    }\n  }\n  impl ToTokens for WindowEffectsConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let effects = vec_lit(self.effects.clone(), |d| d);\n      let state = opt_lit(self.state.as_ref());\n      let radius = opt_lit(self.radius.as_ref());\n      let color = opt_lit(self.color.as_ref());\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::config::WindowEffectsConfig,\n        effects,\n        state,\n        radius,\n        color\n      )\n    }\n  }\n\n  impl ToTokens for crate::TitleBarStyle {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::TitleBarStyle };\n\n      tokens.append_all(match self {\n        Self::Visible => quote! { #prefix::Visible },\n        Self::Transparent => quote! { #prefix::Transparent },\n        Self::Overlay => quote! { #prefix::Overlay },\n      })\n    }\n  }\n\n  impl ToTokens for LogicalPosition {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let LogicalPosition { x, y } = self;\n      literal_struct!(tokens, ::tauri::utils::config::LogicalPosition, x, y)\n    }\n  }\n\n  impl ToTokens for crate::WindowEffect {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::WindowEffect };\n\n      #[allow(deprecated)]\n      tokens.append_all(match self {\n        WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},\n        WindowEffect::Light => quote! { #prefix::Light},\n        WindowEffect::Dark => quote! { #prefix::Dark},\n        WindowEffect::MediumLight => quote! { #prefix::MediumLight},\n        WindowEffect::UltraDark => quote! { #prefix::UltraDark},\n        WindowEffect::Titlebar => quote! { #prefix::Titlebar},\n        WindowEffect::Selection => quote! { #prefix::Selection},\n        WindowEffect::Menu => quote! { #prefix::Menu},\n        WindowEffect::Popover => quote! { #prefix::Popover},\n        WindowEffect::Sidebar => quote! { #prefix::Sidebar},\n        WindowEffect::HeaderView => quote! { #prefix::HeaderView},\n        WindowEffect::Sheet => quote! { #prefix::Sheet},\n        WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},\n        WindowEffect::HudWindow => quote! { #prefix::HudWindow},\n        WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},\n        WindowEffect::Tooltip => quote! { #prefix::Tooltip},\n        WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},\n        WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},\n        WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},\n        WindowEffect::Mica => quote! { #prefix::Mica},\n        WindowEffect::MicaDark => quote! { #prefix::MicaDark},\n        WindowEffect::MicaLight => quote! { #prefix::MicaLight},\n        WindowEffect::Blur => quote! { #prefix::Blur},\n        WindowEffect::Acrylic => quote! { #prefix::Acrylic},\n        WindowEffect::Tabbed => quote! { #prefix::Tabbed },\n        WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },\n        WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },\n      })\n    }\n  }\n\n  impl ToTokens for crate::WindowEffectState {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::WindowEffectState };\n\n      #[allow(deprecated)]\n      tokens.append_all(match self {\n        WindowEffectState::Active => quote! { #prefix::Active},\n        WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},\n        WindowEffectState::Inactive => quote! { #prefix::Inactive},\n      })\n    }\n  }\n\n  impl ToTokens for PreventOverflowMargin {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let width = self.width;\n      let height = self.height;\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::config::PreventOverflowMargin,\n        width,\n        height\n      )\n    }\n  }\n\n  impl ToTokens for PreventOverflowConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::PreventOverflowConfig };\n\n      #[allow(deprecated)]\n      tokens.append_all(match self {\n        Self::Enable(enable) => quote! { #prefix::Enable(#enable) },\n        Self::Margin(margin) => quote! { #prefix::Margin(#margin) },\n      })\n    }\n  }\n\n  impl ToTokens for ScrollBarStyle {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::ScrollBarStyle };\n\n      tokens.append_all(match self {\n        Self::Default => quote! { #prefix::Default },\n        Self::FluentOverlay => quote! { #prefix::FluentOverlay },\n      })\n    }\n  }\n\n  impl ToTokens for WindowConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let label = str_lit(&self.label);\n      let create = &self.create;\n      let url = &self.url;\n      let user_agent = opt_str_lit(self.user_agent.as_ref());\n      let drag_drop_enabled = self.drag_drop_enabled;\n      let center = self.center;\n      let x = opt_lit(self.x.as_ref());\n      let y = opt_lit(self.y.as_ref());\n      let width = self.width;\n      let height = self.height;\n      let min_width = opt_lit(self.min_width.as_ref());\n      let min_height = opt_lit(self.min_height.as_ref());\n      let max_width = opt_lit(self.max_width.as_ref());\n      let max_height = opt_lit(self.max_height.as_ref());\n      let prevent_overflow = opt_lit(self.prevent_overflow.as_ref());\n      let resizable = self.resizable;\n      let maximizable = self.maximizable;\n      let minimizable = self.minimizable;\n      let closable = self.closable;\n      let title = str_lit(&self.title);\n      let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());\n      let fullscreen = self.fullscreen;\n      let focus = self.focus;\n      let focusable = self.focusable;\n      let transparent = self.transparent;\n      let maximized = self.maximized;\n      let visible = self.visible;\n      let decorations = self.decorations;\n      let always_on_bottom = self.always_on_bottom;\n      let always_on_top = self.always_on_top;\n      let visible_on_all_workspaces = self.visible_on_all_workspaces;\n      let content_protected = self.content_protected;\n      let skip_taskbar = self.skip_taskbar;\n      let window_classname = opt_str_lit(self.window_classname.as_ref());\n      let theme = opt_lit(self.theme.as_ref());\n      let title_bar_style = &self.title_bar_style;\n      let traffic_light_position = opt_lit(self.traffic_light_position.as_ref());\n      let hidden_title = self.hidden_title;\n      let accept_first_mouse = self.accept_first_mouse;\n      let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());\n      let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());\n      let shadow = self.shadow;\n      let window_effects = opt_lit(self.window_effects.as_ref());\n      let incognito = self.incognito;\n      let parent = opt_str_lit(self.parent.as_ref());\n      let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;\n      let browser_extensions_enabled = self.browser_extensions_enabled;\n      let use_https_scheme = self.use_https_scheme;\n      let devtools = opt_lit(self.devtools.as_ref());\n      let background_color = opt_lit(self.background_color.as_ref());\n      let background_throttling = opt_lit(self.background_throttling.as_ref());\n      let javascript_disabled = self.javascript_disabled;\n      let allow_link_preview = self.allow_link_preview;\n      let disable_input_accessory_view = self.disable_input_accessory_view;\n      let data_directory = opt_lit(self.data_directory.as_ref().map(path_buf_lit).as_ref());\n      let data_store_identifier = opt_vec_lit(self.data_store_identifier, identity);\n      let scroll_bar_style = &self.scroll_bar_style;\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::config::WindowConfig,\n        label,\n        url,\n        create,\n        user_agent,\n        drag_drop_enabled,\n        center,\n        x,\n        y,\n        width,\n        height,\n        min_width,\n        min_height,\n        max_width,\n        max_height,\n        prevent_overflow,\n        resizable,\n        maximizable,\n        minimizable,\n        closable,\n        title,\n        proxy_url,\n        fullscreen,\n        focus,\n        focusable,\n        transparent,\n        maximized,\n        visible,\n        decorations,\n        always_on_bottom,\n        always_on_top,\n        visible_on_all_workspaces,\n        content_protected,\n        skip_taskbar,\n        window_classname,\n        theme,\n        title_bar_style,\n        traffic_light_position,\n        hidden_title,\n        accept_first_mouse,\n        tabbing_identifier,\n        additional_browser_args,\n        shadow,\n        window_effects,\n        incognito,\n        parent,\n        zoom_hotkeys_enabled,\n        browser_extensions_enabled,\n        use_https_scheme,\n        devtools,\n        background_color,\n        background_throttling,\n        javascript_disabled,\n        allow_link_preview,\n        disable_input_accessory_view,\n        data_directory,\n        data_store_identifier,\n        scroll_bar_style\n      );\n    }\n  }\n\n  impl ToTokens for PatternKind {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::PatternKind };\n\n      tokens.append_all(match self {\n        Self::Brownfield => quote! { #prefix::Brownfield },\n        #[cfg(not(feature = \"isolation\"))]\n        Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },\n        #[cfg(feature = \"isolation\")]\n        Self::Isolation { dir } => {\n          let dir = path_buf_lit(dir);\n          quote! { #prefix::Isolation { dir: #dir } }\n        }\n      })\n    }\n  }\n\n  impl ToTokens for WebviewInstallMode {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };\n\n      tokens.append_all(match self {\n        Self::Skip => quote! { #prefix::Skip },\n        Self::DownloadBootstrapper { silent } => {\n          quote! { #prefix::DownloadBootstrapper { silent: #silent } }\n        }\n        Self::EmbedBootstrapper { silent } => {\n          quote! { #prefix::EmbedBootstrapper { silent: #silent } }\n        }\n        Self::OfflineInstaller { silent } => {\n          quote! { #prefix::OfflineInstaller { silent: #silent } }\n        }\n        Self::FixedRuntime { path } => {\n          let path = path_buf_lit(path);\n          quote! { #prefix::FixedRuntime { path: #path } }\n        }\n      })\n    }\n  }\n\n  impl ToTokens for WindowsConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let webview_install_mode = &self.webview_install_mode;\n      tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {\n        webview_install_mode: #webview_install_mode,\n        ..Default::default()\n      }})\n    }\n  }\n\n  impl ToTokens for BundleConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let publisher = quote!(None);\n      let homepage = quote!(None);\n      let icon = vec_lit(&self.icon, str_lit);\n      let active = self.active;\n      let targets = quote!(Default::default());\n      let create_updater_artifacts = quote!(Default::default());\n      let resources = quote!(None);\n      let copyright = quote!(None);\n      let category = quote!(None);\n      let file_associations = quote!(None);\n      let short_description = quote!(None);\n      let long_description = quote!(None);\n      let use_local_tools_dir = self.use_local_tools_dir;\n      let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);\n      let windows = &self.windows;\n      let license = opt_str_lit(self.license.as_ref());\n      let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());\n      let linux = quote!(Default::default());\n      let macos = quote!(Default::default());\n      let ios = quote!(Default::default());\n      let android = quote!(Default::default());\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::config::BundleConfig,\n        active,\n        publisher,\n        homepage,\n        icon,\n        targets,\n        create_updater_artifacts,\n        resources,\n        copyright,\n        category,\n        license,\n        license_file,\n        file_associations,\n        short_description,\n        long_description,\n        use_local_tools_dir,\n        external_bin,\n        windows,\n        linux,\n        macos,\n        ios,\n        android\n      );\n    }\n  }\n\n  impl ToTokens for FrontendDist {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::FrontendDist };\n\n      tokens.append_all(match self {\n        Self::Url(url) => {\n          let url = url_lit(url);\n          quote! { #prefix::Url(#url) }\n        }\n        Self::Directory(path) => {\n          let path = path_buf_lit(path);\n          quote! { #prefix::Directory(#path) }\n        }\n        Self::Files(files) => {\n          let files = vec_lit(files, path_buf_lit);\n          quote! { #prefix::Files(#files) }\n        }\n      })\n    }\n  }\n\n  impl ToTokens for RunnerConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::RunnerConfig };\n\n      tokens.append_all(match self {\n        Self::String(cmd) => {\n          let cmd = cmd.as_str();\n          quote!(#prefix::String(#cmd.into()))\n        }\n        Self::Object { cmd, cwd, args } => {\n          let cmd = cmd.as_str();\n          let cwd = opt_str_lit(cwd.as_ref());\n          let args = opt_lit(args.as_ref().map(|v| vec_lit(v, str_lit)).as_ref());\n          quote!(#prefix::Object {\n            cmd: #cmd.into(),\n            cwd: #cwd,\n            args: #args,\n          })\n        }\n      })\n    }\n  }\n\n  impl ToTokens for BuildConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());\n      let frontend_dist = opt_lit(self.frontend_dist.as_ref());\n      let runner = opt_lit(self.runner.as_ref());\n      let before_dev_command = quote!(None);\n      let before_build_command = quote!(None);\n      let before_bundle_command = quote!(None);\n      let features = quote!(None);\n      let remove_unused_commands = quote!(false);\n      let additional_watch_folders = quote!(Vec::new());\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::config::BuildConfig,\n        runner,\n        dev_url,\n        frontend_dist,\n        before_dev_command,\n        before_build_command,\n        before_bundle_command,\n        features,\n        remove_unused_commands,\n        additional_watch_folders\n      );\n    }\n  }\n\n  impl ToTokens for CspDirectiveSources {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };\n\n      tokens.append_all(match self {\n        Self::Inline(sources) => {\n          let sources = sources.as_str();\n          quote!(#prefix::Inline(#sources.into()))\n        }\n        Self::List(list) => {\n          let list = vec_lit(list, str_lit);\n          quote!(#prefix::List(#list))\n        }\n      })\n    }\n  }\n\n  impl ToTokens for Csp {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::Csp };\n\n      tokens.append_all(match self {\n        Self::Policy(policy) => {\n          let policy = policy.as_str();\n          quote!(#prefix::Policy(#policy.into()))\n        }\n        Self::DirectiveMap(list) => {\n          // Pass a sorted vec so the HashMap constructor is deterministic\n          // see: https://github.com/tauri-apps/tauri/issues/14978\n          // TODO: Remove this in v3, use a BTreeMap instead of a HashMap\n          let mut sorted: Vec<_> = list.iter().collect();\n          sorted.sort_by_key(|(k, _)| *k);\n          let map = map_lit(\n            quote! { ::std::collections::HashMap },\n            sorted,\n            str_lit,\n            identity,\n          );\n          quote!(#prefix::DirectiveMap(#map))\n        }\n      })\n    }\n  }\n\n  impl ToTokens for DisabledCspModificationKind {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };\n\n      tokens.append_all(match self {\n        Self::Flag(flag) => {\n          quote! { #prefix::Flag(#flag) }\n        }\n        Self::List(directives) => {\n          let directives = vec_lit(directives, str_lit);\n          quote! { #prefix::List(#directives) }\n        }\n      });\n    }\n  }\n\n  impl ToTokens for CapabilityEntry {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::CapabilityEntry };\n\n      tokens.append_all(match self {\n        Self::Inlined(capability) => {\n          quote! { #prefix::Inlined(#capability) }\n        }\n        Self::Reference(id) => {\n          let id = str_lit(id);\n          quote! { #prefix::Reference(#id) }\n        }\n      });\n    }\n  }\n\n  impl ToTokens for HeaderSource {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::HeaderSource };\n\n      tokens.append_all(match self {\n        Self::Inline(s) => {\n          let line = s.as_str();\n          quote!(#prefix::Inline(#line.into()))\n        }\n        Self::List(l) => {\n          let list = vec_lit(l, str_lit);\n          quote!(#prefix::List(#list))\n        }\n        Self::Map(m) => {\n          // Pass a sorted vec so the HashMap constructor is deterministic\n          // see: https://github.com/tauri-apps/tauri/issues/14978\n          // TODO: Remove this in v3, use a BTreeMap instead of a HashMap\n          let mut sorted: Vec<_> = m.iter().collect();\n          sorted.sort_by_key(|(k, _)| *k);\n          let map = map_lit(\n            quote! { ::std::collections::HashMap },\n            sorted,\n            str_lit,\n            str_lit,\n          );\n          quote!(#prefix::Map(#map))\n        }\n      })\n    }\n  }\n\n  impl ToTokens for HeaderConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let access_control_allow_credentials =\n        opt_lit(self.access_control_allow_credentials.as_ref());\n      let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());\n      let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());\n      let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());\n      let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());\n      let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());\n      let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());\n      let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());\n      let permissions_policy = opt_lit(self.permissions_policy.as_ref());\n      let service_worker_allowed = opt_lit(self.service_worker_allowed.as_ref());\n      let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());\n      let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());\n      let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::config::HeaderConfig,\n        access_control_allow_credentials,\n        access_control_allow_headers,\n        access_control_allow_methods,\n        access_control_expose_headers,\n        access_control_max_age,\n        cross_origin_embedder_policy,\n        cross_origin_opener_policy,\n        cross_origin_resource_policy,\n        permissions_policy,\n        service_worker_allowed,\n        timing_allow_origin,\n        x_content_type_options,\n        tauri_custom_header\n      );\n    }\n  }\n\n  impl ToTokens for SecurityConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let csp = opt_lit(self.csp.as_ref());\n      let dev_csp = opt_lit(self.dev_csp.as_ref());\n      let freeze_prototype = self.freeze_prototype;\n      let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;\n      let asset_protocol = &self.asset_protocol;\n      let pattern = &self.pattern;\n      let capabilities = vec_lit(&self.capabilities, identity);\n      let headers = opt_lit(self.headers.as_ref());\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::config::SecurityConfig,\n        csp,\n        dev_csp,\n        freeze_prototype,\n        dangerous_disable_asset_csp_modification,\n        asset_protocol,\n        pattern,\n        capabilities,\n        headers\n      );\n    }\n  }\n\n  impl ToTokens for TrayIconConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      // For [`Self::menu_on_left_click`]\n      tokens.append_all(quote!(#[allow(deprecated)]));\n\n      let id = opt_str_lit(self.id.as_ref());\n      let icon_as_template = self.icon_as_template;\n      #[allow(deprecated)]\n      let menu_on_left_click = self.menu_on_left_click;\n      let show_menu_on_left_click = self.show_menu_on_left_click;\n      let icon_path = path_buf_lit(&self.icon_path);\n      let title = opt_str_lit(self.title.as_ref());\n      let tooltip = opt_str_lit(self.tooltip.as_ref());\n      literal_struct!(\n        tokens,\n        ::tauri::utils::config::TrayIconConfig,\n        id,\n        icon_path,\n        icon_as_template,\n        menu_on_left_click,\n        show_menu_on_left_click,\n        title,\n        tooltip\n      );\n    }\n  }\n\n  impl ToTokens for FsScope {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::config::FsScope };\n\n      tokens.append_all(match self {\n        Self::AllowedPaths(allow) => {\n          let allowed_paths = vec_lit(allow, path_buf_lit);\n          quote! { #prefix::AllowedPaths(#allowed_paths) }\n        }\n        Self::Scope { allow, deny , require_literal_leading_dot} => {\n          let allow = vec_lit(allow, path_buf_lit);\n          let deny = vec_lit(deny, path_buf_lit);\n          let  require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());\n          quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }\n        }\n      });\n    }\n  }\n\n  impl ToTokens for AssetProtocolConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let scope = &self.scope;\n      tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })\n    }\n  }\n\n  impl ToTokens for AppConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let windows = vec_lit(&self.windows, identity);\n      let security = &self.security;\n      let tray_icon = opt_lit(self.tray_icon.as_ref());\n      let macos_private_api = self.macos_private_api;\n      let with_global_tauri = self.with_global_tauri;\n      let enable_gtk_app_id = self.enable_gtk_app_id;\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::config::AppConfig,\n        windows,\n        security,\n        tray_icon,\n        macos_private_api,\n        with_global_tauri,\n        enable_gtk_app_id\n      );\n    }\n  }\n\n  impl ToTokens for PluginConfig {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      // Pass a sorted vec so the HashMap constructor is deterministic\n      // see: https://github.com/tauri-apps/tauri/issues/14978\n      // TODO: Remove this in v3, use a BTreeMap instead of a HashMap\n      let mut sorted: Vec<_> = self.0.iter().collect();\n      sorted.sort_by_key(|(k, _)| *k);\n      let config = map_lit(\n        quote! { ::std::collections::HashMap },\n        sorted,\n        str_lit,\n        json_value_lit,\n      );\n      tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })\n    }\n  }\n\n  impl ToTokens for Config {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let schema = quote!(None);\n      let product_name = opt_str_lit(self.product_name.as_ref());\n      let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());\n      let version = opt_str_lit(self.version.as_ref());\n      let identifier = str_lit(&self.identifier);\n      let app = &self.app;\n      let build = &self.build;\n      let bundle = &self.bundle;\n      let plugins = &self.plugins;\n\n      literal_struct!(\n        tokens,\n        ::tauri::utils::config::Config,\n        schema,\n        product_name,\n        main_binary_name,\n        version,\n        identifier,\n        app,\n        build,\n        bundle,\n        plugins\n      );\n    }\n  }\n}\n\n#[cfg(test)]\nmod test {\n  use super::*;\n\n  // TODO: create a test that compares a config to a json config\n\n  #[test]\n  // test all of the default functions\n  fn test_defaults() {\n    // get default app config\n    let a_config = AppConfig::default();\n    // get default build config\n    let b_config = BuildConfig::default();\n    // get default window\n    let d_windows: Vec<WindowConfig> = vec![];\n    // get default bundle\n    let d_bundle = BundleConfig::default();\n\n    // create a tauri config.\n    let app = AppConfig {\n      windows: vec![],\n      security: SecurityConfig {\n        csp: None,\n        dev_csp: None,\n        freeze_prototype: false,\n        dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),\n        asset_protocol: AssetProtocolConfig::default(),\n        pattern: Default::default(),\n        capabilities: Vec::new(),\n        headers: None,\n      },\n      tray_icon: None,\n      macos_private_api: false,\n      with_global_tauri: false,\n      enable_gtk_app_id: false,\n    };\n\n    // create a build config\n    let build = BuildConfig {\n      runner: None,\n      dev_url: None,\n      frontend_dist: None,\n      before_dev_command: None,\n      before_build_command: None,\n      before_bundle_command: None,\n      features: None,\n      remove_unused_commands: false,\n      additional_watch_folders: Vec::new(),\n    };\n\n    // create a bundle config\n    let bundle = BundleConfig {\n      active: false,\n      targets: Default::default(),\n      create_updater_artifacts: Default::default(),\n      publisher: None,\n      homepage: None,\n      icon: Vec::new(),\n      resources: None,\n      copyright: None,\n      category: None,\n      file_associations: None,\n      short_description: None,\n      long_description: None,\n      use_local_tools_dir: false,\n      license: None,\n      license_file: None,\n      linux: Default::default(),\n      macos: Default::default(),\n      external_bin: None,\n      windows: Default::default(),\n      ios: Default::default(),\n      android: Default::default(),\n    };\n\n    // test the configs\n    assert_eq!(a_config, app);\n    assert_eq!(b_config, build);\n    assert_eq!(d_bundle, bundle);\n    assert_eq!(d_windows, app.windows);\n  }\n\n  #[test]\n  fn parse_hex_color() {\n    use super::Color;\n\n    assert_eq!(Color(255, 255, 255, 255), \"fff\".parse().unwrap());\n    assert_eq!(Color(255, 255, 255, 255), \"#fff\".parse().unwrap());\n    assert_eq!(Color(0, 0, 0, 255), \"#000000\".parse().unwrap());\n    assert_eq!(Color(0, 0, 0, 255), \"#000000ff\".parse().unwrap());\n    assert_eq!(Color(0, 255, 0, 255), \"#00ff00ff\".parse().unwrap());\n  }\n\n  #[test]\n  fn test_runner_config_string_format() {\n    use super::RunnerConfig;\n\n    // Test string format deserialization\n    let json = r#\"\"cargo\"\"#;\n    let runner: RunnerConfig = serde_json::from_str(json).unwrap();\n\n    assert_eq!(runner.cmd(), \"cargo\");\n    assert_eq!(runner.cwd(), None);\n    assert_eq!(runner.args(), None);\n\n    // Test string format serialization\n    let serialized = serde_json::to_string(&runner).unwrap();\n    assert_eq!(serialized, r#\"\"cargo\"\"#);\n  }\n\n  #[test]\n  fn test_runner_config_object_format_full() {\n    use super::RunnerConfig;\n\n    // Test object format with all fields\n    let json = r#\"{\"cmd\": \"my_runner\", \"cwd\": \"/tmp/build\", \"args\": [\"--quiet\", \"--verbose\"]}\"#;\n    let runner: RunnerConfig = serde_json::from_str(json).unwrap();\n\n    assert_eq!(runner.cmd(), \"my_runner\");\n    assert_eq!(runner.cwd(), Some(\"/tmp/build\"));\n    assert_eq!(\n      runner.args(),\n      Some(&[\"--quiet\".to_string(), \"--verbose\".to_string()][..])\n    );\n\n    // Test object format serialization\n    let serialized = serde_json::to_string(&runner).unwrap();\n    let deserialized: RunnerConfig = serde_json::from_str(&serialized).unwrap();\n    assert_eq!(runner, deserialized);\n  }\n\n  #[test]\n  fn test_runner_config_object_format_minimal() {\n    use super::RunnerConfig;\n\n    // Test object format with only cmd field\n    let json = r#\"{\"cmd\": \"cross\"}\"#;\n    let runner: RunnerConfig = serde_json::from_str(json).unwrap();\n\n    assert_eq!(runner.cmd(), \"cross\");\n    assert_eq!(runner.cwd(), None);\n    assert_eq!(runner.args(), None);\n  }\n\n  #[test]\n  fn test_runner_config_default() {\n    use super::RunnerConfig;\n\n    let default_runner = RunnerConfig::default();\n    assert_eq!(default_runner.cmd(), \"cargo\");\n    assert_eq!(default_runner.cwd(), None);\n    assert_eq!(default_runner.args(), None);\n  }\n\n  #[test]\n  fn test_runner_config_from_str() {\n    use super::RunnerConfig;\n\n    // Test From<&str> trait\n    let runner: RunnerConfig = \"my_runner\".into();\n    assert_eq!(runner.cmd(), \"my_runner\");\n    assert_eq!(runner.cwd(), None);\n    assert_eq!(runner.args(), None);\n  }\n\n  #[test]\n  fn test_runner_config_from_string() {\n    use super::RunnerConfig;\n\n    // Test From<String> trait\n    let runner: RunnerConfig = \"another_runner\".to_string().into();\n    assert_eq!(runner.cmd(), \"another_runner\");\n    assert_eq!(runner.cwd(), None);\n    assert_eq!(runner.args(), None);\n  }\n\n  #[test]\n  fn test_runner_config_from_str_parse() {\n    use super::RunnerConfig;\n    use std::str::FromStr;\n\n    // Test FromStr trait\n    let runner = RunnerConfig::from_str(\"parsed_runner\").unwrap();\n    assert_eq!(runner.cmd(), \"parsed_runner\");\n    assert_eq!(runner.cwd(), None);\n    assert_eq!(runner.args(), None);\n  }\n\n  #[test]\n  fn test_runner_config_in_build_config() {\n    use super::BuildConfig;\n\n    // Test string format in BuildConfig\n    let json = r#\"{\"runner\": \"cargo\"}\"#;\n    let build_config: BuildConfig = serde_json::from_str(json).unwrap();\n\n    let runner = build_config.runner.unwrap();\n    assert_eq!(runner.cmd(), \"cargo\");\n    assert_eq!(runner.cwd(), None);\n    assert_eq!(runner.args(), None);\n  }\n\n  #[test]\n  fn test_runner_config_in_build_config_object() {\n    use super::BuildConfig;\n\n    // Test object format in BuildConfig\n    let json = r#\"{\"runner\": {\"cmd\": \"cross\", \"cwd\": \"/workspace\", \"args\": [\"--target\", \"x86_64-unknown-linux-gnu\"]}}\"#;\n    let build_config: BuildConfig = serde_json::from_str(json).unwrap();\n\n    let runner = build_config.runner.unwrap();\n    assert_eq!(runner.cmd(), \"cross\");\n    assert_eq!(runner.cwd(), Some(\"/workspace\"));\n    assert_eq!(\n      runner.args(),\n      Some(\n        &[\n          \"--target\".to_string(),\n          \"x86_64-unknown-linux-gnu\".to_string()\n        ][..]\n      )\n    );\n  }\n\n  #[test]\n  fn test_runner_config_in_full_config() {\n    use super::Config;\n\n    // Test runner config in full Tauri config\n    let json = r#\"{\n      \"productName\": \"Test App\",\n      \"version\": \"1.0.0\",\n      \"identifier\": \"com.test.app\",\n      \"build\": {\n        \"runner\": {\n          \"cmd\": \"my_custom_cargo\",\n          \"cwd\": \"/tmp/build\",\n          \"args\": [\"--quiet\", \"--verbose\"]\n        }\n      }\n    }\"#;\n\n    let config: Config = serde_json::from_str(json).unwrap();\n    let runner = config.build.runner.unwrap();\n\n    assert_eq!(runner.cmd(), \"my_custom_cargo\");\n    assert_eq!(runner.cwd(), Some(\"/tmp/build\"));\n    assert_eq!(\n      runner.args(),\n      Some(&[\"--quiet\".to_string(), \"--verbose\".to_string()][..])\n    );\n  }\n\n  #[test]\n  fn test_runner_config_equality() {\n    use super::RunnerConfig;\n\n    let runner1 = RunnerConfig::String(\"cargo\".to_string());\n    let runner2 = RunnerConfig::String(\"cargo\".to_string());\n    let runner3 = RunnerConfig::String(\"cross\".to_string());\n\n    assert_eq!(runner1, runner2);\n    assert_ne!(runner1, runner3);\n\n    let runner4 = RunnerConfig::Object {\n      cmd: \"cargo\".to_string(),\n      cwd: Some(\"/tmp\".to_string()),\n      args: Some(vec![\"--quiet\".to_string()]),\n    };\n    let runner5 = RunnerConfig::Object {\n      cmd: \"cargo\".to_string(),\n      cwd: Some(\"/tmp\".to_string()),\n      args: Some(vec![\"--quiet\".to_string()]),\n    };\n\n    assert_eq!(runner4, runner5);\n    assert_ne!(runner1, runner4);\n  }\n\n  #[test]\n  fn test_runner_config_untagged_serialization() {\n    use super::RunnerConfig;\n\n    // Test that serde untagged works correctly - string should serialize as string, not object\n    let string_runner = RunnerConfig::String(\"cargo\".to_string());\n    let string_json = serde_json::to_string(&string_runner).unwrap();\n    assert_eq!(string_json, r#\"\"cargo\"\"#);\n\n    // Test that object serializes as object\n    let object_runner = RunnerConfig::Object {\n      cmd: \"cross\".to_string(),\n      cwd: None,\n      args: None,\n    };\n    let object_json = serde_json::to_string(&object_runner).unwrap();\n    assert!(object_json.contains(\"\\\"cmd\\\":\\\"cross\\\"\"));\n    // With skip_serializing_none, null values should not be included\n    assert!(object_json.contains(\"\\\"cwd\\\":null\") || !object_json.contains(\"cwd\"));\n    assert!(object_json.contains(\"\\\"args\\\":null\") || !object_json.contains(\"args\"));\n  }\n\n  #[test]\n  fn window_config_default_same_as_deserialize() {\n    let config_from_deserialization: WindowConfig = serde_json::from_str(\"{}\").unwrap();\n    let config_from_default: WindowConfig = WindowConfig::default();\n\n    assert_eq!(config_from_deserialization, config_from_default);\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/config_v1/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The Tauri configuration used at runtime.\n//!\n//! It is pulled from a `tauri.conf.json` file and the [`Config`] struct is generated at compile time.\n//!\n//! # Stability\n//! This is a core functionality that is not considered part of the stable API.\n//! If you use it, note that it may include breaking changes in the future.\n\nuse semver::Version;\nuse serde::{\n  de::{Deserializer, Error as DeError, Visitor},\n  Deserialize, Serialize, Serializer,\n};\nuse serde_json::Value as JsonValue;\nuse serde_with::skip_serializing_none;\nuse url::Url;\n\nuse std::{\n  collections::HashMap,\n  fmt::{self, Display},\n  fs::read_to_string,\n  path::PathBuf,\n  str::FromStr,\n};\n\n/// Items to help with parsing content into a [`Config`].\npub mod parse;\n\nfn default_true() -> bool {\n  true\n}\n\n/// An URL to open on a Tauri webview window.\n#[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n#[non_exhaustive]\npub enum WindowUrl {\n  /// An external URL.\n  External(Url),\n  /// The path portion of an app URL.\n  /// For instance, to load `tauri://localhost/users/john`,\n  /// you can simply provide `users/john` in this configuration.\n  App(PathBuf),\n}\n\nimpl fmt::Display for WindowUrl {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    match self {\n      Self::External(url) => write!(f, \"{url}\"),\n      Self::App(path) => write!(f, \"{}\", path.display()),\n    }\n  }\n}\n\nimpl Default for WindowUrl {\n  fn default() -> Self {\n    Self::App(\"index.html\".into())\n  }\n}\n\n/// A bundle referenced by tauri-bundler.\n#[derive(Debug, PartialEq, Eq, Clone)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"schema\", schemars(rename_all = \"lowercase\"))]\npub enum BundleType {\n  /// The debian bundle (.deb).\n  Deb,\n  /// The AppImage bundle (.appimage).\n  AppImage,\n  /// The Microsoft Installer bundle (.msi).\n  Msi,\n  /// The NSIS bundle (.exe).\n  Nsis,\n  /// The macOS application bundle (.app).\n  App,\n  /// The Apple Disk Image bundle (.dmg).\n  Dmg,\n  /// The Tauri updater bundle.\n  Updater,\n}\n\nimpl Display for BundleType {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    write!(\n      f,\n      \"{}\",\n      match self {\n        Self::Deb => \"deb\",\n        Self::AppImage => \"appimage\",\n        Self::Msi => \"msi\",\n        Self::Nsis => \"nsis\",\n        Self::App => \"app\",\n        Self::Dmg => \"dmg\",\n        Self::Updater => \"updater\",\n      }\n    )\n  }\n}\n\nimpl Serialize for BundleType {\n  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    serializer.serialize_str(self.to_string().as_ref())\n  }\n}\n\nimpl<'de> Deserialize<'de> for BundleType {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let s = String::deserialize(deserializer)?;\n    match s.to_lowercase().as_str() {\n      \"deb\" => Ok(Self::Deb),\n      \"appimage\" => Ok(Self::AppImage),\n      \"msi\" => Ok(Self::Msi),\n      \"nsis\" => Ok(Self::Nsis),\n      \"app\" => Ok(Self::App),\n      \"dmg\" => Ok(Self::Dmg),\n      \"updater\" => Ok(Self::Updater),\n      _ => Err(DeError::custom(format!(\"unknown bundle target '{s}'\"))),\n    }\n  }\n}\n\n/// Targets to bundle. Each value is case insensitive.\n#[derive(Debug, PartialEq, Eq, Clone, Default)]\npub enum BundleTarget {\n  /// Bundle all targets.\n  #[default]\n  All,\n  /// A list of bundle targets.\n  List(Vec<BundleType>),\n  /// A single bundle target.\n  One(BundleType),\n}\n\n#[cfg(feature = \"schemars\")]\npub(crate) trait Merge: Sized {\n  fn merge(self, other: Self) -> Self;\n}\n\n#[cfg(feature = \"schema\")]\nuse schemars::schema::{Metadata, Schema};\n\n#[cfg(feature = \"schema\")]\nimpl<T: Merge> Merge for Option<T> {\n  fn merge(self, other: Self) -> Self {\n    match (self, other) {\n      (Some(x), Some(y)) => Some(x.merge(y)),\n      (None, y) => y,\n      (x, None) => x,\n    }\n  }\n}\n\n#[cfg(feature = \"schema\")]\nimpl<T: Merge> Merge for Box<T> {\n  fn merge(mut self, other: Self) -> Self {\n    *self = (*self).merge(*other);\n    self\n  }\n}\n\n#[cfg(feature = \"schema\")]\nimpl<T> Merge for Vec<T> {\n  fn merge(mut self, other: Self) -> Self {\n    self.extend(other);\n    self\n  }\n}\n\n#[cfg(feature = \"schema\")]\nimpl Merge for Metadata {\n  fn merge(self, other: Self) -> Self {\n    Metadata {\n      id: self.id.or(other.id),\n      title: self.title.or(other.title),\n      description: self.description.or(other.description),\n      default: self.default.or(other.default),\n      deprecated: self.deprecated || other.deprecated,\n      read_only: self.read_only || other.read_only,\n      write_only: self.write_only || other.write_only,\n      examples: self.examples.merge(other.examples),\n    }\n  }\n}\n\n#[cfg(feature = \"schema\")]\nfn apply_metadata(schema: Schema, metadata: Metadata) -> Schema {\n  if metadata == Metadata::default() {\n    schema\n  } else {\n    let mut schema_obj = schema.into_object();\n    schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata);\n    Schema::Object(schema_obj)\n  }\n}\n\n#[cfg(feature = \"schema\")]\nimpl schemars::JsonSchema for BundleTarget {\n  fn schema_name() -> std::string::String {\n    \"BundleTarget\".to_owned()\n  }\n\n  fn json_schema(generator: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n    let any_of = vec![\n      schemars::schema::SchemaObject {\n        enum_values: Some(vec![\"all\".into()]),\n        metadata: Some(Box::new(schemars::schema::Metadata {\n          description: Some(\"Bundle all targets.\".to_owned()),\n          ..Default::default()\n        })),\n        ..Default::default()\n      }\n      .into(),\n      apply_metadata(\n        generator.subschema_for::<Vec<BundleType>>(),\n        schemars::schema::Metadata {\n          description: Some(\"A list of bundle targets.\".to_owned()),\n          ..Default::default()\n        },\n      ),\n      apply_metadata(\n        generator.subschema_for::<BundleType>(),\n        schemars::schema::Metadata {\n          description: Some(\"A single bundle target.\".to_owned()),\n          ..Default::default()\n        },\n      ),\n    ];\n\n    schemars::schema::SchemaObject {\n      subschemas: Some(Box::new(schemars::schema::SubschemaValidation {\n        any_of: Some(any_of),\n        ..Default::default()\n      })),\n      metadata: Some(Box::new(schemars::schema::Metadata {\n        description: Some(\"Targets to bundle. Each value is case insensitive.\".to_owned()),\n        ..Default::default()\n      })),\n      ..Default::default()\n    }\n    .into()\n  }\n}\n\nimpl Serialize for BundleTarget {\n  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    match self {\n      Self::All => serializer.serialize_str(\"all\"),\n      Self::List(l) => l.serialize(serializer),\n      Self::One(t) => serializer.serialize_str(t.to_string().as_ref()),\n    }\n  }\n}\n\nimpl<'de> Deserialize<'de> for BundleTarget {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    #[derive(Deserialize, Serialize)]\n    #[serde(untagged)]\n    pub enum BundleTargetInner {\n      List(Vec<BundleType>),\n      One(BundleType),\n      All(String),\n    }\n\n    match BundleTargetInner::deserialize(deserializer)? {\n      BundleTargetInner::All(s) if s.to_lowercase() == \"all\" => Ok(Self::All),\n      BundleTargetInner::All(t) => Err(DeError::custom(format!(\"invalid bundle type {t}\"))),\n      BundleTargetInner::List(l) => Ok(Self::List(l)),\n      BundleTargetInner::One(t) => Ok(Self::One(t)),\n    }\n  }\n}\n\n/// Configuration for AppImage bundles.\n///\n/// See more: https://tauri.app/v1/api/config#appimageconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct AppImageConfig {\n  /// Include additional gstreamer dependencies needed for audio and video playback.\n  /// This increases the bundle size by ~15-35MB depending on your build system.\n  #[serde(default, alias = \"bundle-media-framework\")]\n  pub bundle_media_framework: bool,\n}\n\n/// Configuration for Debian (.deb) bundles.\n///\n/// See more: https://tauri.app/v1/api/config#debconfig\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct DebConfig {\n  /// The list of deb dependencies your application relies on.\n  pub depends: Option<Vec<String>>,\n  /// The files to include on the package.\n  #[serde(default)]\n  pub files: HashMap<PathBuf, PathBuf>,\n  /// Path to a custom desktop file Handlebars template.\n  ///\n  /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n  pub desktop_template: Option<PathBuf>,\n  /// Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections\n  pub section: Option<String>,\n  /// Change the priority of the Debian Package. By default, it is set to `optional`.\n  /// Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\n  pub priority: Option<String>,\n  /// Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\n  /// https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes\n  pub changelog: Option<PathBuf>,\n}\n\nfn de_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>\nwhere\n  D: Deserializer<'de>,\n{\n  let version = Option::<String>::deserialize(deserializer)?;\n  match version {\n    Some(v) if v.is_empty() => Ok(minimum_system_version()),\n    e => Ok(e),\n  }\n}\n\n/// Configuration for the macOS bundles.\n///\n/// See more: https://tauri.app/v1/api/config#macconfig\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct MacConfig {\n  /// A list of strings indicating any macOS X frameworks that need to be bundled with the application.\n  ///\n  /// If a name is used, \".framework\" must be omitted and it will look for standard install locations. You may also use a path to a specific framework.\n  pub frameworks: Option<Vec<String>>,\n  /// A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.\n  ///\n  /// Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`\n  /// and the `MACOSX_DEPLOYMENT_TARGET` environment variable.\n  ///\n  /// An empty string is considered an invalid value so the default value is used.\n  #[serde(\n    deserialize_with = \"de_minimum_system_version\",\n    default = \"minimum_system_version\",\n    alias = \"minimum-system-version\"\n  )]\n  pub minimum_system_version: Option<String>,\n  /// Allows your application to communicate with the outside world.\n  /// It should be a lowercase, without port and protocol domain name.\n  #[serde(alias = \"exception-domain\")]\n  pub exception_domain: Option<String>,\n  /// The path to the license file to add to the DMG bundle.\n  pub license: Option<String>,\n  /// Identity to use for code signing.\n  #[serde(alias = \"signing-identity\")]\n  pub signing_identity: Option<String>,\n  /// Provider short name for notarization.\n  #[serde(alias = \"provider-short-name\")]\n  pub provider_short_name: Option<String>,\n  /// Path to the entitlements file.\n  pub entitlements: Option<String>,\n}\n\nimpl Default for MacConfig {\n  fn default() -> Self {\n    Self {\n      frameworks: None,\n      minimum_system_version: minimum_system_version(),\n      exception_domain: None,\n      license: None,\n      signing_identity: None,\n      provider_short_name: None,\n      entitlements: None,\n    }\n  }\n}\n\nfn minimum_system_version() -> Option<String> {\n  Some(\"10.13\".into())\n}\n\n/// Configuration for a target language for the WiX build.\n///\n/// See more: https://tauri.app/v1/api/config#wixlanguageconfig\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct WixLanguageConfig {\n  /// The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.\n  #[serde(alias = \"locale-path\")]\n  pub locale_path: Option<String>,\n}\n\n/// The languages to build using WiX.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\npub enum WixLanguage {\n  /// A single language to build, without configuration.\n  One(String),\n  /// A list of languages to build, without configuration.\n  List(Vec<String>),\n  /// A map of languages and its configuration.\n  Localized(HashMap<String, WixLanguageConfig>),\n}\n\nimpl Default for WixLanguage {\n  fn default() -> Self {\n    Self::One(\"en-US\".into())\n  }\n}\n\n/// Configuration for the MSI bundle using WiX.\n///\n/// See more: https://tauri.app/v1/api/config#wixconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct WixConfig {\n  /// The installer languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.\n  #[serde(default)]\n  pub language: WixLanguage,\n  /// A custom .wxs template to use.\n  pub template: Option<PathBuf>,\n  /// A list of paths to .wxs files with WiX fragments to use.\n  #[serde(default, alias = \"fragment-paths\")]\n  pub fragment_paths: Vec<PathBuf>,\n  /// The ComponentGroup element ids you want to reference from the fragments.\n  #[serde(default, alias = \"component-group-refs\")]\n  pub component_group_refs: Vec<String>,\n  /// The Component element ids you want to reference from the fragments.\n  #[serde(default, alias = \"component-refs\")]\n  pub component_refs: Vec<String>,\n  /// The FeatureGroup element ids you want to reference from the fragments.\n  #[serde(default, alias = \"feature-group-refs\")]\n  pub feature_group_refs: Vec<String>,\n  /// The Feature element ids you want to reference from the fragments.\n  #[serde(default, alias = \"feature-refs\")]\n  pub feature_refs: Vec<String>,\n  /// The Merge element ids you want to reference from the fragments.\n  #[serde(default, alias = \"merge-refs\")]\n  pub merge_refs: Vec<String>,\n  /// Disables the Webview2 runtime installation after app install.\n  ///\n  /// Will be removed in v2, prefer the [`WindowsConfig::webview_install_mode`] option.\n  #[serde(default, alias = \"skip-webview-install\")]\n  pub skip_webview_install: bool,\n  /// The path to the license file to render on the installer.\n  ///\n  /// Must be an RTF file, so if a different extension is provided, we convert it to the RTF format.\n  pub license: Option<PathBuf>,\n  /// Create an elevated update task within Windows Task Scheduler.\n  #[serde(default, alias = \"enable-elevated-update-task\")]\n  pub enable_elevated_update_task: bool,\n  /// Path to a bitmap file to use as the installation user interface banner.\n  /// This bitmap will appear at the top of all but the first page of the installer.\n  ///\n  /// The required dimensions are 493px × 58px.\n  #[serde(alias = \"banner-path\")]\n  pub banner_path: Option<PathBuf>,\n  /// Path to a bitmap file to use on the installation user interface dialogs.\n  /// It is used on the welcome and completion dialogs.\n  ///\n  /// The required dimensions are 493px × 312px.\n  #[serde(alias = \"dialog-image-path\")]\n  pub dialog_image_path: Option<PathBuf>,\n}\n\n/// Compression algorithms used in the NSIS installer.\n///\n/// See <https://nsis.sourceforge.io/Reference/SetCompressor>\n#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub enum NsisCompression {\n  /// ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.\n  Zlib,\n  /// BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.\n  Bzip2,\n  /// LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.\n  Lzma,\n}\n\n/// Configuration for the Installer bundle using NSIS.\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct NsisConfig {\n  /// A custom .nsi template to use.\n  pub template: Option<PathBuf>,\n  /// The path to the license file to render on the installer.\n  pub license: Option<PathBuf>,\n  /// The path to a bitmap file to display on the header of installers pages.\n  ///\n  /// The recommended dimensions are 150px x 57px.\n  #[serde(alias = \"header-image\")]\n  pub header_image: Option<PathBuf>,\n  /// The path to a bitmap file for the Welcome page and the Finish page.\n  ///\n  /// The recommended dimensions are 164px x 314px.\n  #[serde(alias = \"sidebar-image\")]\n  pub sidebar_image: Option<PathBuf>,\n  /// The path to an icon file used as the installer icon.\n  #[serde(alias = \"install-icon\")]\n  pub installer_icon: Option<PathBuf>,\n  /// Whether the installation will be for all users or just the current user.\n  #[serde(default, alias = \"install-mode\")]\n  pub install_mode: NSISInstallerMode,\n  /// A list of installer languages.\n  /// By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.\n  /// To allow the user to select the language, set `display_language_selector` to `true`.\n  ///\n  /// See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.\n  pub languages: Option<Vec<String>>,\n  /// A key-value pair where the key is the language and the\n  /// value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.\n  ///\n  /// See <https://github.com/tauri-apps/tauri/blob/dev/tooling/bundler/src/bundle/windows/templates/nsis-languages/English.nsh> for an example `.nsh` file.\n  ///\n  /// **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array,\n  pub custom_language_files: Option<HashMap<String, PathBuf>>,\n  /// Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.\n  /// By default the OS language is selected, with a fallback to the first language in the `languages` array.\n  #[serde(default, alias = \"display-language-selector\")]\n  pub display_language_selector: bool,\n  /// Set the compression algorithm used to compress files in the installer.\n  ///\n  /// See <https://nsis.sourceforge.io/Reference/SetCompressor>\n  pub compression: Option<NsisCompression>,\n}\n\n/// Install Modes for the NSIS installer.\n#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub enum NSISInstallerMode {\n  /// Default mode for the installer.\n  ///\n  /// Install the app by default in a directory that doesn't require Administrator access.\n  ///\n  /// Installer metadata will be saved under the `HKCU` registry path.\n  #[default]\n  CurrentUser,\n  /// Install the app by default in the `Program Files` folder directory requires Administrator\n  /// access for the installation.\n  ///\n  /// Installer metadata will be saved under the `HKLM` registry path.\n  PerMachine,\n  /// Combines both modes and allows the user to choose at install time\n  /// whether to install for the current user or per machine. Note that this mode\n  /// will require Administrator access even if the user wants to install it for the current user only.\n  ///\n  /// Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.\n  Both,\n}\n\n/// Install modes for the Webview2 runtime.\n/// Note that for the updater bundle [`Self::DownloadBootstrapper`] is used.\n///\n/// For more information see <https://tauri.app/v1/guides/building/windows>.\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]\n#[serde(tag = \"type\", rename_all = \"camelCase\", deny_unknown_fields)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub enum WebviewInstallMode {\n  /// Do not install the Webview2 as part of the Windows Installer.\n  Skip,\n  /// Download the bootstrapper and run it.\n  /// Requires an internet connection.\n  /// Results in a smaller installer size, but is not recommended on Windows 7.\n  DownloadBootstrapper {\n    /// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.\n    #[serde(default = \"default_true\")]\n    silent: bool,\n  },\n  /// Embed the bootstrapper and run it.\n  /// Requires an internet connection.\n  /// Increases the installer size by around 1.8MB, but offers better support on Windows 7.\n  EmbedBootstrapper {\n    /// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.\n    #[serde(default = \"default_true\")]\n    silent: bool,\n  },\n  /// Embed the offline installer and run it.\n  /// Does not require an internet connection.\n  /// Increases the installer size by around 127MB.\n  OfflineInstaller {\n    /// Instructs the installer to run the installer in silent mode. Defaults to `true`.\n    #[serde(default = \"default_true\")]\n    silent: bool,\n  },\n  /// Embed a fixed webview2 version and use it at runtime.\n  /// Increases the installer size by around 180MB.\n  FixedRuntime {\n    /// The path to the fixed runtime to use.\n    ///\n    /// The fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section).\n    /// The `.cab` file must be extracted to a folder and this folder path must be defined on this field.\n    path: PathBuf,\n  },\n}\n\nimpl Default for WebviewInstallMode {\n  fn default() -> Self {\n    Self::DownloadBootstrapper { silent: true }\n  }\n}\n\n/// Windows bundler configuration.\n///\n/// See more: https://tauri.app/v1/api/config#windowsconfig\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct WindowsConfig {\n  /// Specifies the file digest algorithm to use for creating file signatures.\n  /// Required for code signing. SHA-256 is recommended.\n  #[serde(alias = \"digest-algorithm\")]\n  pub digest_algorithm: Option<String>,\n  /// Specifies the SHA1 hash of the signing certificate.\n  #[serde(alias = \"certificate-thumbprint\")]\n  pub certificate_thumbprint: Option<String>,\n  /// Server to use during timestamping.\n  #[serde(alias = \"timestamp-url\")]\n  pub timestamp_url: Option<String>,\n  /// Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may\n  /// use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.\n  #[serde(default)]\n  pub tsp: bool,\n  /// The installation mode for the Webview2 runtime.\n  #[serde(default, alias = \"webview-install-mode\")]\n  pub webview_install_mode: WebviewInstallMode,\n  /// Path to the webview fixed runtime to use. Overwrites [`Self::webview_install_mode`] if set.\n  ///\n  /// Will be removed in v2, prefer the [`Self::webview_install_mode`] option.\n  ///\n  /// The fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section).\n  /// The `.cab` file must be extracted to a folder and this folder path must be defined on this field.\n  #[serde(alias = \"webview-fixed-runtime-path\")]\n  pub webview_fixed_runtime_path: Option<PathBuf>,\n  /// Validates a second app installation, blocking the user from installing an older version if set to `false`.\n  ///\n  /// For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\n  ///\n  /// The default value of this flag is `true`.\n  #[serde(default = \"default_true\", alias = \"allow-downgrades\")]\n  pub allow_downgrades: bool,\n  /// Configuration for the MSI generated with WiX.\n  pub wix: Option<WixConfig>,\n  /// Configuration for the installer generated with NSIS.\n  pub nsis: Option<NsisConfig>,\n}\n\nimpl Default for WindowsConfig {\n  fn default() -> Self {\n    Self {\n      digest_algorithm: None,\n      certificate_thumbprint: None,\n      timestamp_url: None,\n      tsp: false,\n      webview_install_mode: Default::default(),\n      webview_fixed_runtime_path: None,\n      allow_downgrades: true,\n      wix: None,\n      nsis: None,\n    }\n  }\n}\n\n/// Definition for bundle resources.\n/// Can be either a list of paths to include or a map of source to target paths.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields, untagged)]\npub enum BundleResources {\n  /// A list of paths to include.\n  List(Vec<String>),\n  /// A map of source to target paths.\n  Map(HashMap<String, String>),\n}\n\n/// Configuration for tauri-bundler.\n///\n/// See more: https://tauri.app/v1/api/config#bundleconfig\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct BundleConfig {\n  /// Whether Tauri should bundle your application or just output the executable.\n  #[serde(default)]\n  pub active: bool,\n  /// The bundle targets, currently supports [\"deb\", \"appimage\", \"nsis\", \"msi\", \"app\", \"dmg\", \"updater\"] or \"all\".\n  #[serde(default)]\n  pub targets: BundleTarget,\n  /// The application identifier in reverse domain name notation (e.g. `com.tauri.example`).\n  /// This string must be unique across applications since it is used in system configurations like\n  /// the bundle ID and path to the webview data directory.\n  /// This string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-),\n  /// and periods (.).\n  pub identifier: String,\n  /// The application's publisher. Defaults to the second element in the identifier string.\n  /// Currently maps to the Manufacturer property of the Windows Installer.\n  pub publisher: Option<String>,\n  /// The app's icons\n  #[serde(default)]\n  pub icon: Vec<String>,\n  /// App resources to bundle.\n  /// Each resource is a path to a file or directory.\n  /// Glob patterns are supported.\n  pub resources: Option<BundleResources>,\n  /// A copyright string associated with your application.\n  pub copyright: Option<String>,\n  /// The application kind.\n  ///\n  /// Should be one of the following:\n  /// Business, DeveloperTool, Education, Entertainment, Finance, Game, ActionGame, AdventureGame, ArcadeGame, BoardGame, CardGame, CasinoGame, DiceGame, EducationalGame, FamilyGame, KidsGame, MusicGame, PuzzleGame, RacingGame, RolePlayingGame, SimulationGame, SportsGame, StrategyGame, TriviaGame, WordGame, GraphicsAndDesign, HealthcareAndFitness, Lifestyle, Medical, Music, News, Photography, Productivity, Reference, SocialNetworking, Sports, Travel, Utility, Video, Weather.\n  pub category: Option<String>,\n  /// A short description of your application.\n  #[serde(alias = \"short-description\")]\n  pub short_description: Option<String>,\n  /// A longer, multi-line description of the application.\n  #[serde(alias = \"long-description\")]\n  pub long_description: Option<String>,\n  /// Configuration for the AppImage bundle.\n  #[serde(default)]\n  pub appimage: AppImageConfig,\n  /// Configuration for the Debian bundle.\n  #[serde(default)]\n  pub deb: DebConfig,\n  /// Configuration for the macOS bundles.\n  #[serde(rename = \"macOS\", default)]\n  pub macos: MacConfig,\n  /// A list of—either absolute or relative—paths to binaries to embed with your application.\n  ///\n  /// Note that Tauri will look for system-specific binaries following the pattern \"binary-name{-target-triple}{.system-extension}\".\n  ///\n  /// E.g. for the external binary \"my-binary\", Tauri looks for:\n  ///\n  /// - \"my-binary-x86_64-pc-windows-msvc.exe\" for Windows\n  /// - \"my-binary-x86_64-apple-darwin\" for macOS\n  /// - \"my-binary-x86_64-unknown-linux-gnu\" for Linux\n  ///\n  /// so don't forget to provide binaries for all targeted platforms.\n  #[serde(alias = \"external-bin\")]\n  pub external_bin: Option<Vec<String>>,\n  /// Configuration for the Windows bundle.\n  #[serde(default)]\n  pub windows: WindowsConfig,\n}\n\n/// A CLI argument definition.\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct CliArg {\n  /// The short version of the argument, without the preceding -.\n  ///\n  /// NOTE: Any leading `-` characters will be stripped, and only the first non-character will be used as the short version.\n  pub short: Option<char>,\n  /// The unique argument name\n  pub name: String,\n  /// The argument description which will be shown on the help information.\n  /// Typically, this is a short (one line) description of the arg.\n  pub description: Option<String>,\n  /// The argument long description which will be shown on the help information.\n  /// Typically this a more detailed (multi-line) message that describes the argument.\n  #[serde(alias = \"long-description\")]\n  pub long_description: Option<String>,\n  /// Specifies that the argument takes a value at run time.\n  ///\n  /// NOTE: values for arguments may be specified in any of the following methods\n  /// - Using a space such as -o value or --option value\n  /// - Using an equals and no space such as -o=value or --option=value\n  /// - Use a short and no space such as -ovalue\n  #[serde(default, alias = \"takes-value\")]\n  pub takes_value: bool,\n  /// Specifies that the argument may have an unknown number of multiple values. Without any other settings, this argument may appear only once.\n  ///\n  /// For example, --opt val1 val2 is allowed, but --opt val1 val2 --opt val3 is not.\n  ///\n  /// NOTE: Setting this requires `takes_value` to be set to true.\n  #[serde(default)]\n  pub multiple: bool,\n  /// Specifies that the argument may appear more than once.\n  /// For flags, this results in the number of occurrences of the flag being recorded. For example -ddd or -d -d -d would count as three occurrences.\n  /// For options or arguments that take a value, this does not affect how many values they can accept. (i.e. only one at a time is allowed)\n  ///\n  /// For example, --opt val1 --opt val2 is allowed, but --opt val1 val2 is not.\n  #[serde(default, alias = \"multiple-occurrences\")]\n  pub multiple_occurrences: bool,\n  /// Specifies how many values are required to satisfy this argument. For example, if you had a\n  /// `-f <file>` argument where you wanted exactly 3 'files' you would set\n  /// `number_of_values = 3`, and this argument wouldn't be satisfied unless the user provided\n  /// 3 and only 3 values.\n  ///\n  /// **NOTE:** Does *not* require `multiple_occurrences = true` to be set. Setting\n  /// `multiple_occurrences = true` would allow `-f <file> <file> <file> -f <file> <file> <file>` where\n  /// as *not* setting it would only allow one occurrence of this argument.\n  ///\n  /// **NOTE:** implicitly sets `takes_value = true` and `multiple_values = true`.\n  #[serde(alias = \"number-of-values\")]\n  pub number_of_values: Option<usize>,\n  /// Specifies a list of possible values for this argument.\n  /// At runtime, the CLI verifies that only one of the specified values was used, or fails with an error message.\n  #[serde(alias = \"possible-values\")]\n  pub possible_values: Option<Vec<String>>,\n  /// Specifies the minimum number of values for this argument.\n  /// For example, if you had a -f `<file>` argument where you wanted at least 2 'files',\n  /// you would set `minValues: 2`, and this argument would be satisfied if the user provided, 2 or more values.\n  #[serde(alias = \"min-values\")]\n  pub min_values: Option<usize>,\n  /// Specifies the maximum number of values are for this argument.\n  /// For example, if you had a -f `<file>` argument where you wanted up to 3 'files',\n  /// you would set .max_values(3), and this argument would be satisfied if the user provided, 1, 2, or 3 values.\n  #[serde(alias = \"max-values\")]\n  pub max_values: Option<usize>,\n  /// Sets whether or not the argument is required by default.\n  ///\n  /// - Required by default means it is required, when no other conflicting rules have been evaluated\n  /// - Conflicting rules take precedence over being required.\n  #[serde(default)]\n  pub required: bool,\n  /// Sets an arg that override this arg's required setting\n  /// i.e. this arg will be required unless this other argument is present.\n  #[serde(alias = \"required-unless-present\")]\n  pub required_unless_present: Option<String>,\n  /// Sets args that override this arg's required setting\n  /// i.e. this arg will be required unless all these other arguments are present.\n  #[serde(alias = \"required-unless-present-all\")]\n  pub required_unless_present_all: Option<Vec<String>>,\n  /// Sets args that override this arg's required setting\n  /// i.e. this arg will be required unless at least one of these other arguments are present.\n  #[serde(alias = \"required-unless-present-any\")]\n  pub required_unless_present_any: Option<Vec<String>>,\n  /// Sets a conflicting argument by name\n  /// i.e. when using this argument, the following argument can't be present and vice versa.\n  #[serde(alias = \"conflicts-with\")]\n  pub conflicts_with: Option<String>,\n  /// The same as conflictsWith but allows specifying multiple two-way conflicts per argument.\n  #[serde(alias = \"conflicts-with-all\")]\n  pub conflicts_with_all: Option<Vec<String>>,\n  /// Tets an argument by name that is required when this one is present\n  /// i.e. when using this argument, the following argument must be present.\n  pub requires: Option<String>,\n  /// Sts multiple arguments by names that are required when this one is present\n  /// i.e. when using this argument, the following arguments must be present.\n  #[serde(alias = \"requires-all\")]\n  pub requires_all: Option<Vec<String>>,\n  /// Allows a conditional requirement with the signature [arg, value]\n  /// the requirement will only become valid if `arg`'s value equals `${value}`.\n  #[serde(alias = \"requires-if\")]\n  pub requires_if: Option<Vec<String>>,\n  /// Allows specifying that an argument is required conditionally with the signature [arg, value]\n  /// the requirement will only become valid if the `arg`'s value equals `${value}`.\n  #[serde(alias = \"requires-if-eq\")]\n  pub required_if_eq: Option<Vec<String>>,\n  /// Requires that options use the --option=val syntax\n  /// i.e. an equals between the option and associated value.\n  #[serde(alias = \"requires-equals\")]\n  pub require_equals: Option<bool>,\n  /// The positional argument index, starting at 1.\n  ///\n  /// The index refers to position according to other positional argument.\n  /// It does not define position in the argument list as a whole. When utilized with multiple=true,\n  /// only the last positional argument may be defined as multiple (i.e. the one with the highest index).\n  #[cfg_attr(feature = \"schema\", validate(range(min = 1)))]\n  pub index: Option<usize>,\n}\n\n/// describes a CLI configuration\n///\n/// See more: https://tauri.app/v1/api/config#cliconfig\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct CliConfig {\n  /// Command description which will be shown on the help information.\n  pub description: Option<String>,\n  /// Command long description which will be shown on the help information.\n  #[serde(alias = \"long-description\")]\n  pub long_description: Option<String>,\n  /// Adds additional help information to be displayed in addition to auto-generated help.\n  /// This information is displayed before the auto-generated help information.\n  /// This is often used for header information.\n  #[serde(alias = \"before-help\")]\n  pub before_help: Option<String>,\n  /// Adds additional help information to be displayed in addition to auto-generated help.\n  /// This information is displayed after the auto-generated help information.\n  /// This is often used to describe how to use the arguments, or caveats to be noted.\n  #[serde(alias = \"after-help\")]\n  pub after_help: Option<String>,\n  /// List of arguments for the command\n  pub args: Option<Vec<CliArg>>,\n  /// List of subcommands of this command\n  pub subcommands: Option<HashMap<String, CliConfig>>,\n}\n\n/// The window configuration object.\n///\n/// See more: https://tauri.app/v1/api/config#windowconfig\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct WindowConfig {\n  /// The window identifier. It must be alphanumeric.\n  #[serde(default = \"default_window_label\")]\n  pub label: String,\n  /// The window webview URL.\n  #[serde(default)]\n  pub url: WindowUrl,\n  /// The user agent for the webview\n  #[serde(alias = \"user-agent\")]\n  pub user_agent: Option<String>,\n  /// Whether the file drop is enabled or not on the webview. By default it is enabled.\n  ///\n  /// Disabling it is required to use drag and drop on the frontend on Windows.\n  #[serde(default = \"default_true\", alias = \"file-drop-enabled\")]\n  pub file_drop_enabled: bool,\n  /// Whether or not the window starts centered or not.\n  #[serde(default)]\n  pub center: bool,\n  /// The horizontal position of the window's top left corner\n  pub x: Option<f64>,\n  /// The vertical position of the window's top left corner\n  pub y: Option<f64>,\n  /// The window width.\n  #[serde(default = \"default_width\")]\n  pub width: f64,\n  /// The window height.\n  #[serde(default = \"default_height\")]\n  pub height: f64,\n  /// The min window width.\n  #[serde(alias = \"min-width\")]\n  pub min_width: Option<f64>,\n  /// The min window height.\n  #[serde(alias = \"min-height\")]\n  pub min_height: Option<f64>,\n  /// The max window width.\n  #[serde(alias = \"max-width\")]\n  pub max_width: Option<f64>,\n  /// The max window height.\n  #[serde(alias = \"max-height\")]\n  pub max_height: Option<f64>,\n  /// Whether the window is resizable or not. When resizable is set to false, native window's maximize button is automatically disabled.\n  #[serde(default = \"default_true\")]\n  pub resizable: bool,\n  /// Whether the window's native maximize button is enabled or not.\n  /// If resizable is set to false, this setting is ignored.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **macOS:** Disables the \"zoom\" button in the window titlebar, which is also used to enter fullscreen mode.\n  /// - **Linux / iOS / Android:** Unsupported.\n  #[serde(default = \"default_true\")]\n  pub maximizable: bool,\n  /// Whether the window's native minimize button is enabled or not.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux / iOS / Android:** Unsupported.\n  #[serde(default = \"default_true\")]\n  pub minimizable: bool,\n  /// Whether the window's native close button is enabled or not.\n  ///\n  /// ## Platform-specific\n  ///\n  /// - **Linux:** \"GTK+ will do its best to convince the window manager not to show a close button.\n  ///   Depending on the system, this function may not have any effect when called on a window that is already visible\"\n  /// - **iOS / Android:** Unsupported.\n  #[serde(default = \"default_true\")]\n  pub closable: bool,\n  /// The window title.\n  #[serde(default = \"default_title\")]\n  pub title: String,\n  /// Whether the window starts as fullscreen or not.\n  #[serde(default)]\n  pub fullscreen: bool,\n  /// Whether the window will be initially focused or not.\n  #[serde(default = \"default_true\")]\n  pub focus: bool,\n  /// Whether the window is transparent or not.\n  ///\n  /// Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri > macOSPrivateApi`.\n  /// WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.\n  #[serde(default)]\n  pub transparent: bool,\n  /// Whether the window is maximized or not.\n  #[serde(default)]\n  pub maximized: bool,\n  /// Whether the window is visible or not.\n  #[serde(default = \"default_true\")]\n  pub visible: bool,\n  /// Whether the window should have borders and bars.\n  #[serde(default = \"default_true\")]\n  pub decorations: bool,\n  /// Whether the window should always be on top of other windows.\n  #[serde(default, alias = \"always-on-top\")]\n  pub always_on_top: bool,\n  /// Prevents the window contents from being captured by other apps.\n  #[serde(default, alias = \"content-protected\")]\n  pub content_protected: bool,\n  /// If `true`, hides the window icon from the taskbar on Windows and Linux.\n  #[serde(default, alias = \"skip-taskbar\")]\n  pub skip_taskbar: bool,\n  /// The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.\n  pub theme: Option<Theme>,\n  /// The style of the macOS title bar.\n  #[serde(default, alias = \"title-bar-style\")]\n  pub title_bar_style: TitleBarStyle,\n  /// If `true`, sets the window title to be hidden on macOS.\n  #[serde(default, alias = \"hidden-title\")]\n  pub hidden_title: bool,\n  /// Whether clicking an inactive window also clicks through to the webview on macOS.\n  #[serde(default, alias = \"accept-first-mouse\")]\n  pub accept_first_mouse: bool,\n  /// Defines the window [tabbing identifier] for macOS.\n  ///\n  /// Windows with matching tabbing identifiers will be grouped together.\n  /// If the tabbing identifier is not set, automatic tabbing will be disabled.\n  ///\n  /// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>\n  #[serde(default, alias = \"tabbing-identifier\")]\n  pub tabbing_identifier: Option<String>,\n  /// Defines additional browser arguments on Windows. By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`\n  /// so if you use this method, you also need to disable these components by yourself if you want.\n  #[serde(default, alias = \"additional-browser-args\")]\n  pub additional_browser_args: Option<String>,\n}\n\nimpl Default for WindowConfig {\n  fn default() -> Self {\n    Self {\n      label: default_window_label(),\n      url: WindowUrl::default(),\n      user_agent: None,\n      file_drop_enabled: true,\n      center: false,\n      x: None,\n      y: None,\n      width: default_width(),\n      height: default_height(),\n      min_width: None,\n      min_height: None,\n      max_width: None,\n      max_height: None,\n      resizable: true,\n      maximizable: true,\n      minimizable: true,\n      closable: true,\n      title: default_title(),\n      fullscreen: false,\n      focus: false,\n      transparent: false,\n      maximized: false,\n      visible: true,\n      decorations: true,\n      always_on_top: false,\n      content_protected: false,\n      skip_taskbar: false,\n      theme: None,\n      title_bar_style: Default::default(),\n      hidden_title: false,\n      accept_first_mouse: false,\n      tabbing_identifier: None,\n      additional_browser_args: None,\n    }\n  }\n}\n\nfn default_window_label() -> String {\n  \"main\".to_string()\n}\n\nfn default_width() -> f64 {\n  800f64\n}\n\nfn default_height() -> f64 {\n  600f64\n}\n\nfn default_title() -> String {\n  \"Tauri App\".to_string()\n}\n\n/// A Content-Security-Policy directive source list.\n/// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources>.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", untagged)]\npub enum CspDirectiveSources {\n  /// An inline list of CSP sources. Same as [`Self::List`], but concatenated with a space separator.\n  Inline(String),\n  /// A list of CSP sources. The collection will be concatenated with a space separator for the CSP string.\n  List(Vec<String>),\n}\n\nimpl Default for CspDirectiveSources {\n  fn default() -> Self {\n    Self::List(Vec::new())\n  }\n}\n\nimpl From<CspDirectiveSources> for Vec<String> {\n  fn from(sources: CspDirectiveSources) -> Self {\n    match sources {\n      CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),\n      CspDirectiveSources::List(l) => l,\n    }\n  }\n}\n\nimpl CspDirectiveSources {\n  /// Whether the given source is configured on this directive or not.\n  pub fn contains(&self, source: &str) -> bool {\n    match self {\n      Self::Inline(s) => s.contains(&format!(\"{source} \")) || s.contains(&format!(\" {source}\")),\n      Self::List(l) => l.contains(&source.into()),\n    }\n  }\n\n  /// Appends the given source to this directive.\n  pub fn push<S: AsRef<str>>(&mut self, source: S) {\n    match self {\n      Self::Inline(s) => {\n        s.push(' ');\n        s.push_str(source.as_ref());\n      }\n      Self::List(l) => {\n        l.push(source.as_ref().to_string());\n      }\n    }\n  }\n}\n\n/// A Content-Security-Policy definition.\n/// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", untagged)]\npub enum Csp {\n  /// The entire CSP policy in a single text string.\n  Policy(String),\n  /// An object mapping a directive with its sources values as a list of strings.\n  DirectiveMap(HashMap<String, CspDirectiveSources>),\n}\n\nimpl From<HashMap<String, CspDirectiveSources>> for Csp {\n  fn from(map: HashMap<String, CspDirectiveSources>) -> Self {\n    Self::DirectiveMap(map)\n  }\n}\n\nimpl From<Csp> for HashMap<String, CspDirectiveSources> {\n  fn from(csp: Csp) -> Self {\n    match csp {\n      Csp::Policy(policy) => {\n        let mut map = HashMap::new();\n        for directive in policy.split(';') {\n          let mut tokens = directive.trim().split(' ');\n          if let Some(directive) = tokens.next() {\n            let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();\n            map.insert(directive.to_string(), CspDirectiveSources::List(sources));\n          }\n        }\n        map\n      }\n      Csp::DirectiveMap(m) => m,\n    }\n  }\n}\n\nimpl Display for Csp {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    match self {\n      Self::Policy(s) => write!(f, \"{s}\"),\n      Self::DirectiveMap(m) => {\n        let len = m.len();\n        let mut i = 0;\n        for (directive, sources) in m {\n          let sources: Vec<String> = sources.clone().into();\n          write!(f, \"{} {}\", directive, sources.join(\" \"))?;\n          i += 1;\n          if i != len {\n            write!(f, \"; \")?;\n          }\n        }\n        Ok(())\n      }\n    }\n  }\n}\n\n/// The possible values for the `dangerous_disable_asset_csp_modification` config option.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[serde(untagged)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub enum DisabledCspModificationKind {\n  /// If `true`, disables all CSP modification.\n  /// `false` is the default value and it configures Tauri to control the CSP.\n  Flag(bool),\n  /// Disables the given list of CSP directives modifications.\n  List(Vec<String>),\n}\n\nimpl Default for DisabledCspModificationKind {\n  fn default() -> Self {\n    Self::Flag(false)\n  }\n}\n\n/// External command access definition.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct RemoteDomainAccessScope {\n  /// The URL scheme to allow. By default, all schemas are allowed.\n  pub scheme: Option<String>,\n  /// The domain to allow.\n  pub domain: String,\n  /// The list of window labels this scope applies to.\n  pub windows: Vec<String>,\n  /// The list of plugins that are allowed in this scope.\n  /// The names should be without the `tauri-plugin-` prefix, for example `\"store\"` for `tauri-plugin-store`.\n  #[serde(default)]\n  pub plugins: Vec<String>,\n  /// Enables access to the Tauri API.\n  #[serde(default, rename = \"enableTauriAPI\", alias = \"enable-tauri-api\")]\n  pub enable_tauri_api: bool,\n}\n\n/// Security configuration.\n///\n/// See more: https://tauri.app/v1/api/config#securityconfig\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct SecurityConfig {\n  /// The Content Security Policy that will be injected on all HTML files on the built application.\n  /// If [`dev_csp`](#SecurityConfig.devCsp) is not specified, this value is also injected on dev.\n  ///\n  /// This is a really important part of the configuration since it helps you ensure your WebView is secured.\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\n  pub csp: Option<Csp>,\n  /// The Content Security Policy that will be injected on all HTML files on development.\n  ///\n  /// This is a really important part of the configuration since it helps you ensure your WebView is secured.\n  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.\n  #[serde(alias = \"dev-csp\")]\n  pub dev_csp: Option<Csp>,\n  /// Freeze the `Object.prototype` when using the custom protocol.\n  #[serde(default, alias = \"freeze-prototype\")]\n  pub freeze_prototype: bool,\n  /// Disables the Tauri-injected CSP sources.\n  ///\n  /// At compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy\n  /// to only allow loading of your own scripts and styles by injecting nonce and hash sources.\n  /// This stricts your CSP, which may introduce issues when using along with other flexing sources.\n  ///\n  /// This configuration option allows both a boolean and a list of strings as value.\n  /// A boolean instructs Tauri to disable the injection for all CSP injections,\n  /// and a list of strings indicates the CSP directives that Tauri cannot inject.\n  ///\n  /// **WARNING:** Only disable this if you know what you are doing and have properly configured the CSP.\n  /// Your application might be vulnerable to XSS attacks without this Tauri protection.\n  #[serde(default, alias = \"dangerous-disable-asset-csp-modification\")]\n  pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,\n  /// Allow external domains to send command to Tauri.\n  ///\n  /// By default, external domains do not have access to `window.__TAURI__`, which means they cannot\n  /// communicate with the commands defined in Rust. This prevents attacks where an externally\n  /// loaded malicious or compromised sites could start executing commands on the user's device.\n  ///\n  /// This configuration allows a set of external domains to have access to the Tauri commands.\n  /// When you configure a domain to be allowed to access the IPC, all subpaths are allowed. Subdomains are not allowed.\n  ///\n  /// **WARNING:** Only use this option if you either have internal checks against malicious\n  /// external sites or you can trust the allowed external sites. You application might be\n  /// vulnerable to dangerous Tauri command related attacks otherwise.\n  #[serde(default, alias = \"dangerous-remote-domain-ipc-access\")]\n  pub dangerous_remote_domain_ipc_access: Vec<RemoteDomainAccessScope>,\n  /// Sets whether the custom protocols should use `http://<scheme>.localhost` instead of the default `https://<scheme>.localhost` on Windows.\n  ///\n  /// **WARNING:** Using a `http` scheme will allow mixed content when trying to fetch `http` endpoints and is therefore less secure but will match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.\n  #[serde(default, alias = \"dangerous-use-http-scheme\")]\n  pub dangerous_use_http_scheme: bool,\n}\n\n/// Defines an allowlist type.\npub trait Allowlist {\n  /// Returns all features associated with the allowlist struct.\n  fn all_features() -> Vec<&'static str>;\n  /// Returns the tauri features enabled on this allowlist.\n  fn to_features(&self) -> Vec<&'static str>;\n}\n\nmacro_rules! check_feature {\n  ($self:ident, $features:ident, $flag:ident, $feature_name: expr) => {\n    if $self.$flag {\n      $features.push($feature_name)\n    }\n  };\n}\n\n/// Filesystem scope definition.\n/// It is a list of glob patterns that restrict the API access from the webview.\n///\n/// Each pattern can start with a variable that resolves to a system base directory.\n/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,\n/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,\n/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,\n/// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[serde(untagged)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub enum FsAllowlistScope {\n  /// A list of paths that are allowed by this scope.\n  AllowedPaths(Vec<PathBuf>),\n  /// A complete scope configuration.\n  #[serde(rename_all = \"camelCase\")]\n  Scope {\n    /// A list of paths that are allowed by this scope.\n    #[serde(default)]\n    allow: Vec<PathBuf>,\n    /// A list of paths that are not allowed by this scope.\n    /// This gets precedence over the [`Self::Scope::allow`] list.\n    #[serde(default)]\n    deny: Vec<PathBuf>,\n    /// Whether or not paths that contain components that start with a `.`\n    /// will require that `.` appears literally in the pattern; `*`, `?`, `**`,\n    /// or `[...]` will not match. This is useful because such files are\n    /// conventionally considered hidden on Unix systems and it might be\n    /// desirable to skip them when listing files.\n    ///\n    /// Defaults to `true` on Unix systems and `false` on Windows\n    // dotfiles are not supposed to be exposed by default on unix\n    #[serde(alias = \"require-literal-leading-dot\")]\n    require_literal_leading_dot: Option<bool>,\n  },\n}\n\nimpl Default for FsAllowlistScope {\n  fn default() -> Self {\n    Self::AllowedPaths(Vec::new())\n  }\n}\n\n/// Allowlist for the file system APIs.\n///\n/// See more: https://tauri.app/v1/api/config#fsallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct FsAllowlistConfig {\n  /// The access scope for the filesystem APIs.\n  #[serde(default)]\n  pub scope: FsAllowlistScope,\n  /// Use this flag to enable all file system API features.\n  #[serde(default)]\n  pub all: bool,\n  /// Read file from local filesystem.\n  #[serde(default, alias = \"read-file\")]\n  pub read_file: bool,\n  /// Write file to local filesystem.\n  #[serde(default, alias = \"write-file\")]\n  pub write_file: bool,\n  /// Read directory from local filesystem.\n  #[serde(default, alias = \"read-dir\")]\n  pub read_dir: bool,\n  /// Copy file from local filesystem.\n  #[serde(default, alias = \"copy-file\")]\n  pub copy_file: bool,\n  /// Create directory from local filesystem.\n  #[serde(default, alias = \"create-dir\")]\n  pub create_dir: bool,\n  /// Remove directory from local filesystem.\n  #[serde(default, alias = \"remove-dir\")]\n  pub remove_dir: bool,\n  /// Remove file from local filesystem.\n  #[serde(default, alias = \"remove-file\")]\n  pub remove_file: bool,\n  /// Rename file from local filesystem.\n  #[serde(default, alias = \"rename-file\")]\n  pub rename_file: bool,\n  /// Check if path exists on the local filesystem.\n  #[serde(default)]\n  pub exists: bool,\n}\n\nimpl Allowlist for FsAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self {\n      scope: Default::default(),\n      all: false,\n      read_file: true,\n      write_file: true,\n      read_dir: true,\n      copy_file: true,\n      create_dir: true,\n      remove_dir: true,\n      remove_file: true,\n      rename_file: true,\n      exists: true,\n    };\n    let mut features = allowlist.to_features();\n    features.push(\"fs-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"fs-all\"]\n    } else {\n      let mut features = Vec::new();\n      check_feature!(self, features, read_file, \"fs-read-file\");\n      check_feature!(self, features, write_file, \"fs-write-file\");\n      check_feature!(self, features, read_dir, \"fs-read-dir\");\n      check_feature!(self, features, copy_file, \"fs-copy-file\");\n      check_feature!(self, features, create_dir, \"fs-create-dir\");\n      check_feature!(self, features, remove_dir, \"fs-remove-dir\");\n      check_feature!(self, features, remove_file, \"fs-remove-file\");\n      check_feature!(self, features, rename_file, \"fs-rename-file\");\n      check_feature!(self, features, exists, \"fs-exists\");\n      features\n    }\n  }\n}\n\n/// Allowlist for the window APIs.\n///\n/// See more: https://tauri.app/v1/api/config#windowallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct WindowAllowlistConfig {\n  /// Use this flag to enable all window API features.\n  #[serde(default)]\n  pub all: bool,\n  /// Allows dynamic window creation.\n  #[serde(default)]\n  pub create: bool,\n  /// Allows centering the window.\n  #[serde(default)]\n  pub center: bool,\n  /// Allows requesting user attention on the window.\n  #[serde(default, alias = \"request-user-attention\")]\n  pub request_user_attention: bool,\n  /// Allows setting the resizable flag of the window.\n  #[serde(default, alias = \"set-resizable\")]\n  pub set_resizable: bool,\n  /// Allows setting whether the window's native maximize button is enabled or not.\n  #[serde(default, alias = \"set-maximizable\")]\n  pub set_maximizable: bool,\n  /// Allows setting whether the window's native minimize button is enabled or not.\n  #[serde(default, alias = \"set-minimizable\")]\n  pub set_minimizable: bool,\n  /// Allows setting whether the window's native close button is enabled or not.\n  #[serde(default, alias = \"set-closable\")]\n  pub set_closable: bool,\n  /// Allows changing the window title.\n  #[serde(default, alias = \"set-title\")]\n  pub set_title: bool,\n  /// Allows maximizing the window.\n  #[serde(default)]\n  pub maximize: bool,\n  /// Allows unmaximizing the window.\n  #[serde(default)]\n  pub unmaximize: bool,\n  /// Allows minimizing the window.\n  #[serde(default)]\n  pub minimize: bool,\n  /// Allows unminimizing the window.\n  #[serde(default)]\n  pub unminimize: bool,\n  /// Allows showing the window.\n  #[serde(default)]\n  pub show: bool,\n  /// Allows hiding the window.\n  #[serde(default)]\n  pub hide: bool,\n  /// Allows closing the window.\n  #[serde(default)]\n  pub close: bool,\n  /// Allows setting the decorations flag of the window.\n  #[serde(default, alias = \"set-decorations\")]\n  pub set_decorations: bool,\n  /// Allows setting the always_on_top flag of the window.\n  #[serde(default, alias = \"set-always-on-top\")]\n  pub set_always_on_top: bool,\n  /// Allows preventing the window contents from being captured by other apps.\n  #[serde(default, alias = \"set-content-protected\")]\n  pub set_content_protected: bool,\n  /// Allows setting the window size.\n  #[serde(default, alias = \"set-size\")]\n  pub set_size: bool,\n  /// Allows setting the window minimum size.\n  #[serde(default, alias = \"set-min-size\")]\n  pub set_min_size: bool,\n  /// Allows setting the window maximum size.\n  #[serde(default, alias = \"set-max-size\")]\n  pub set_max_size: bool,\n  /// Allows changing the position of the window.\n  #[serde(default, alias = \"set-position\")]\n  pub set_position: bool,\n  /// Allows setting the fullscreen flag of the window.\n  #[serde(default, alias = \"set-fullscreen\")]\n  pub set_fullscreen: bool,\n  /// Allows focusing the window.\n  #[serde(default, alias = \"set-focus\")]\n  pub set_focus: bool,\n  /// Allows changing the window icon.\n  #[serde(default, alias = \"set-icon\")]\n  pub set_icon: bool,\n  /// Allows setting the skip_taskbar flag of the window.\n  #[serde(default, alias = \"set-skip-taskbar\")]\n  pub set_skip_taskbar: bool,\n  /// Allows grabbing the cursor.\n  #[serde(default, alias = \"set-cursor-grab\")]\n  pub set_cursor_grab: bool,\n  /// Allows setting the cursor visibility.\n  #[serde(default, alias = \"set-cursor-visible\")]\n  pub set_cursor_visible: bool,\n  /// Allows changing the cursor icon.\n  #[serde(default, alias = \"set-cursor-icon\")]\n  pub set_cursor_icon: bool,\n  /// Allows setting the cursor position.\n  #[serde(default, alias = \"set-cursor-position\")]\n  pub set_cursor_position: bool,\n  /// Allows ignoring cursor events.\n  #[serde(default, alias = \"set-ignore-cursor-events\")]\n  pub set_ignore_cursor_events: bool,\n  /// Allows start dragging on the window.\n  #[serde(default, alias = \"start-dragging\")]\n  pub start_dragging: bool,\n  /// Allows opening the system dialog to print the window content.\n  #[serde(default)]\n  pub print: bool,\n}\n\nimpl Allowlist for WindowAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self {\n      all: false,\n      create: true,\n      center: true,\n      request_user_attention: true,\n      set_resizable: true,\n      set_maximizable: true,\n      set_minimizable: true,\n      set_closable: true,\n      set_title: true,\n      maximize: true,\n      unmaximize: true,\n      minimize: true,\n      unminimize: true,\n      show: true,\n      hide: true,\n      close: true,\n      set_decorations: true,\n      set_always_on_top: true,\n      set_content_protected: false,\n      set_size: true,\n      set_min_size: true,\n      set_max_size: true,\n      set_position: true,\n      set_fullscreen: true,\n      set_focus: true,\n      set_icon: true,\n      set_skip_taskbar: true,\n      set_cursor_grab: true,\n      set_cursor_visible: true,\n      set_cursor_icon: true,\n      set_cursor_position: true,\n      set_ignore_cursor_events: true,\n      start_dragging: true,\n      print: true,\n    };\n    let mut features = allowlist.to_features();\n    features.push(\"window-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"window-all\"]\n    } else {\n      let mut features = Vec::new();\n      check_feature!(self, features, create, \"window-create\");\n      check_feature!(self, features, center, \"window-center\");\n      check_feature!(\n        self,\n        features,\n        request_user_attention,\n        \"window-request-user-attention\"\n      );\n      check_feature!(self, features, set_resizable, \"window-set-resizable\");\n      check_feature!(self, features, set_maximizable, \"window-set-maximizable\");\n      check_feature!(self, features, set_minimizable, \"window-set-minimizable\");\n      check_feature!(self, features, set_closable, \"window-set-closable\");\n      check_feature!(self, features, set_title, \"window-set-title\");\n      check_feature!(self, features, maximize, \"window-maximize\");\n      check_feature!(self, features, unmaximize, \"window-unmaximize\");\n      check_feature!(self, features, minimize, \"window-minimize\");\n      check_feature!(self, features, unminimize, \"window-unminimize\");\n      check_feature!(self, features, show, \"window-show\");\n      check_feature!(self, features, hide, \"window-hide\");\n      check_feature!(self, features, close, \"window-close\");\n      check_feature!(self, features, set_decorations, \"window-set-decorations\");\n      check_feature!(\n        self,\n        features,\n        set_always_on_top,\n        \"window-set-always-on-top\"\n      );\n      check_feature!(\n        self,\n        features,\n        set_content_protected,\n        \"window-set-content-protected\"\n      );\n      check_feature!(self, features, set_size, \"window-set-size\");\n      check_feature!(self, features, set_min_size, \"window-set-min-size\");\n      check_feature!(self, features, set_max_size, \"window-set-max-size\");\n      check_feature!(self, features, set_position, \"window-set-position\");\n      check_feature!(self, features, set_fullscreen, \"window-set-fullscreen\");\n      check_feature!(self, features, set_focus, \"window-set-focus\");\n      check_feature!(self, features, set_icon, \"window-set-icon\");\n      check_feature!(self, features, set_skip_taskbar, \"window-set-skip-taskbar\");\n      check_feature!(self, features, set_cursor_grab, \"window-set-cursor-grab\");\n      check_feature!(\n        self,\n        features,\n        set_cursor_visible,\n        \"window-set-cursor-visible\"\n      );\n      check_feature!(self, features, set_cursor_icon, \"window-set-cursor-icon\");\n      check_feature!(\n        self,\n        features,\n        set_cursor_position,\n        \"window-set-cursor-position\"\n      );\n      check_feature!(\n        self,\n        features,\n        set_ignore_cursor_events,\n        \"window-set-ignore-cursor-events\"\n      );\n      check_feature!(self, features, start_dragging, \"window-start-dragging\");\n      check_feature!(self, features, print, \"window-print\");\n      features\n    }\n  }\n}\n\n/// A command allowed to be executed by the webview API.\n#[derive(Debug, PartialEq, Eq, Clone, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub struct ShellAllowedCommand {\n  /// The name for this allowed shell command configuration.\n  ///\n  /// This name will be used inside of the webview API to call this command along with\n  /// any specified arguments.\n  pub name: String,\n\n  /// The command name.\n  /// It can start with a variable that resolves to a system base directory.\n  /// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,\n  /// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,\n  /// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,\n  /// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.\n  #[serde(rename = \"cmd\", default)] // use default just so the schema doesn't flag it as required\n  pub command: PathBuf,\n\n  /// The allowed arguments for the command execution.\n  #[serde(default)]\n  pub args: ShellAllowedArgs,\n\n  /// If this command is a sidecar command.\n  #[serde(default)]\n  pub sidecar: bool,\n}\n\nimpl<'de> Deserialize<'de> for ShellAllowedCommand {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    #[derive(Deserialize)]\n    struct InnerShellAllowedCommand {\n      name: String,\n      #[serde(rename = \"cmd\")]\n      command: Option<PathBuf>,\n      #[serde(default)]\n      args: ShellAllowedArgs,\n      #[serde(default)]\n      sidecar: bool,\n    }\n\n    let config = InnerShellAllowedCommand::deserialize(deserializer)?;\n\n    if !config.sidecar && config.command.is_none() {\n      return Err(DeError::custom(\n        \"The shell scope `command` value is required.\",\n      ));\n    }\n\n    Ok(ShellAllowedCommand {\n      name: config.name,\n      command: config.command.unwrap_or_default(),\n      args: config.args,\n      sidecar: config.sidecar,\n    })\n  }\n}\n\n/// A set of command arguments allowed to be executed by the webview API.\n///\n/// A value of `true` will allow any arguments to be passed to the command. `false` will disable all\n/// arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to\n/// be passed to the attached command configuration.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged, deny_unknown_fields)]\n#[non_exhaustive]\npub enum ShellAllowedArgs {\n  /// Use a simple boolean to allow all or disable all arguments to this command configuration.\n  Flag(bool),\n\n  /// A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.\n  List(Vec<ShellAllowedArg>),\n}\n\nimpl Default for ShellAllowedArgs {\n  fn default() -> Self {\n    Self::Flag(false)\n  }\n}\n\n/// A command argument allowed to be executed by the webview API.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged, deny_unknown_fields)]\n#[non_exhaustive]\npub enum ShellAllowedArg {\n  /// A non-configurable argument that is passed to the command in the order it was specified.\n  Fixed(String),\n\n  /// A variable that is set while calling the command from the webview API.\n  ///\n  Var {\n    /// [regex] validator to require passed values to conform to an expected input.\n    ///\n    /// This will require the argument value passed to this variable to match the `validator` regex\n    /// before it will be executed.\n    ///\n    /// [regex]: https://docs.rs/regex/latest/regex/#syntax\n    validator: String,\n  },\n}\n\n/// Shell scope definition.\n/// It is a list of command names and associated CLI arguments that restrict the API access from the webview.\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub struct ShellAllowlistScope(pub Vec<ShellAllowedCommand>);\n\n/// Defines the `shell > open` api scope.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged, deny_unknown_fields)]\n#[non_exhaustive]\npub enum ShellAllowlistOpen {\n  /// If the shell open API should be enabled.\n  ///\n  /// If enabled, the default validation regex (`^((mailto:\\w+)|(tel:\\w+)|(https?://\\w+)).+`) is used.\n  Flag(bool),\n\n  /// Enable the shell open API, with a custom regex that the opened path must match against.\n  ///\n  /// If using a custom regex to support a non-http(s) schema, care should be used to prevent values\n  /// that allow flag-like strings to pass validation. e.g. `--enable-debugging`, `-i`, `/R`.\n  Validate(String),\n}\n\nimpl Default for ShellAllowlistOpen {\n  fn default() -> Self {\n    Self::Flag(false)\n  }\n}\n\n/// Allowlist for the shell APIs.\n///\n/// See more: https://tauri.app/v1/api/config#shellallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct ShellAllowlistConfig {\n  /// Access scope for the binary execution APIs.\n  /// Sidecars are automatically enabled.\n  #[serde(default)]\n  pub scope: ShellAllowlistScope,\n  /// Use this flag to enable all shell API features.\n  #[serde(default)]\n  pub all: bool,\n  /// Enable binary execution.\n  #[serde(default)]\n  pub execute: bool,\n  /// Enable sidecar execution, allowing the JavaScript layer to spawn a sidecar command,\n  /// an executable that is shipped with the application.\n  /// For more information see <https://tauri.app/v1/guides/building/sidecar>.\n  #[serde(default)]\n  pub sidecar: bool,\n  /// Open URL with the user's default application.\n  #[serde(default)]\n  pub open: ShellAllowlistOpen,\n}\n\nimpl Allowlist for ShellAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self {\n      scope: Default::default(),\n      all: false,\n      execute: true,\n      sidecar: true,\n      open: ShellAllowlistOpen::Flag(true),\n    };\n    let mut features = allowlist.to_features();\n    features.push(\"shell-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"shell-all\"]\n    } else {\n      let mut features = Vec::new();\n      check_feature!(self, features, execute, \"shell-execute\");\n      check_feature!(self, features, sidecar, \"shell-sidecar\");\n\n      if !matches!(self.open, ShellAllowlistOpen::Flag(false)) {\n        features.push(\"shell-open\")\n      }\n\n      features\n    }\n  }\n}\n\n/// Allowlist for the dialog APIs.\n///\n/// See more: https://tauri.app/v1/api/config#dialogallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct DialogAllowlistConfig {\n  /// Use this flag to enable all dialog API features.\n  #[serde(default)]\n  pub all: bool,\n  /// Allows the API to open a dialog window to pick files.\n  #[serde(default)]\n  pub open: bool,\n  /// Allows the API to open a dialog window to pick where to save files.\n  #[serde(default)]\n  pub save: bool,\n  /// Allows the API to show a message dialog window.\n  #[serde(default)]\n  pub message: bool,\n  /// Allows the API to show a dialog window with Yes/No buttons.\n  #[serde(default)]\n  pub ask: bool,\n  /// Allows the API to show a dialog window with Ok/Cancel buttons.\n  #[serde(default)]\n  pub confirm: bool,\n}\n\nimpl Allowlist for DialogAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self {\n      all: false,\n      open: true,\n      save: true,\n      message: true,\n      ask: true,\n      confirm: true,\n    };\n    let mut features = allowlist.to_features();\n    features.push(\"dialog-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"dialog-all\"]\n    } else {\n      let mut features = Vec::new();\n      check_feature!(self, features, open, \"dialog-open\");\n      check_feature!(self, features, save, \"dialog-save\");\n      check_feature!(self, features, message, \"dialog-message\");\n      check_feature!(self, features, ask, \"dialog-ask\");\n      check_feature!(self, features, confirm, \"dialog-confirm\");\n      features\n    }\n  }\n}\n\n/// HTTP API scope definition.\n/// It is a list of URLs that can be accessed by the webview when using the HTTP APIs.\n/// The scoped URL is matched against the request URL using a glob pattern.\n///\n/// Examples:\n/// - \"https://*\": allows all HTTPS urls\n/// - \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n/// - \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"\n#[allow(rustdoc::bare_urls)]\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n// TODO: in v2, parse into a String or a custom type that perserves the\n// glob string because Url type will add a trailing slash\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub struct HttpAllowlistScope(pub Vec<Url>);\n\n/// Allowlist for the HTTP APIs.\n///\n/// See more: https://tauri.app/v1/api/config#httpallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct HttpAllowlistConfig {\n  /// The access scope for the HTTP APIs.\n  #[serde(default)]\n  pub scope: HttpAllowlistScope,\n  /// Use this flag to enable all HTTP API features.\n  #[serde(default)]\n  pub all: bool,\n  /// Allows making HTTP requests.\n  #[serde(default)]\n  pub request: bool,\n}\n\nimpl Allowlist for HttpAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self {\n      scope: Default::default(),\n      all: false,\n      request: true,\n    };\n    let mut features = allowlist.to_features();\n    features.push(\"http-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"http-all\"]\n    } else {\n      let mut features = Vec::new();\n      check_feature!(self, features, request, \"http-request\");\n      features\n    }\n  }\n}\n\n/// Allowlist for the notification APIs.\n///\n/// See more: https://tauri.app/v1/api/config#notificationallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct NotificationAllowlistConfig {\n  /// Use this flag to enable all notification API features.\n  #[serde(default)]\n  pub all: bool,\n}\n\nimpl Allowlist for NotificationAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self { all: false };\n    let mut features = allowlist.to_features();\n    features.push(\"notification-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"notification-all\"]\n    } else {\n      vec![]\n    }\n  }\n}\n\n/// Allowlist for the global shortcut APIs.\n///\n/// See more: https://tauri.app/v1/api/config#globalshortcutallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct GlobalShortcutAllowlistConfig {\n  /// Use this flag to enable all global shortcut API features.\n  #[serde(default)]\n  pub all: bool,\n}\n\nimpl Allowlist for GlobalShortcutAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self { all: false };\n    let mut features = allowlist.to_features();\n    features.push(\"global-shortcut-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"global-shortcut-all\"]\n    } else {\n      vec![]\n    }\n  }\n}\n\n/// Allowlist for the OS APIs.\n///\n/// See more: https://tauri.app/v1/api/config#osallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct OsAllowlistConfig {\n  /// Use this flag to enable all OS API features.\n  #[serde(default)]\n  pub all: bool,\n}\n\nimpl Allowlist for OsAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self { all: false };\n    let mut features = allowlist.to_features();\n    features.push(\"os-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"os-all\"]\n    } else {\n      vec![]\n    }\n  }\n}\n\n/// Allowlist for the path APIs.\n///\n/// See more: https://tauri.app/v1/api/config#pathallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct PathAllowlistConfig {\n  /// Use this flag to enable all path API features.\n  #[serde(default)]\n  pub all: bool,\n}\n\nimpl Allowlist for PathAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self { all: false };\n    let mut features = allowlist.to_features();\n    features.push(\"path-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"path-all\"]\n    } else {\n      vec![]\n    }\n  }\n}\n\n/// Allowlist for the custom protocols.\n///\n/// See more: https://tauri.app/v1/api/config#protocolallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct ProtocolAllowlistConfig {\n  /// The access scope for the asset protocol.\n  #[serde(default, alias = \"asset-scope\")]\n  pub asset_scope: FsAllowlistScope,\n  /// Use this flag to enable all custom protocols.\n  #[serde(default)]\n  pub all: bool,\n  /// Enables the asset protocol.\n  #[serde(default)]\n  pub asset: bool,\n}\n\nimpl Allowlist for ProtocolAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self {\n      asset_scope: Default::default(),\n      all: false,\n      asset: true,\n    };\n    let mut features = allowlist.to_features();\n    features.push(\"protocol-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"protocol-all\"]\n    } else {\n      let mut features = Vec::new();\n      check_feature!(self, features, asset, \"protocol-asset\");\n      features\n    }\n  }\n}\n\n/// Allowlist for the process APIs.\n///\n/// See more: https://tauri.app/v1/api/config#processallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct ProcessAllowlistConfig {\n  /// Use this flag to enable all process APIs.\n  #[serde(default)]\n  pub all: bool,\n  /// Enables the relaunch API.\n  #[serde(default)]\n  pub relaunch: bool,\n  /// Dangerous option that allows macOS to relaunch even if the binary contains a symlink.\n  ///\n  /// This is due to macOS having less symlink protection. Highly recommended to not set this flag\n  /// unless you have a very specific reason too, and understand the implications of it.\n  #[serde(\n    default,\n    alias = \"relaunchDangerousAllowSymlinkMacOS\",\n    alias = \"relaunch-dangerous-allow-symlink-macos\"\n  )]\n  pub relaunch_dangerous_allow_symlink_macos: bool,\n  /// Enables the exit API.\n  #[serde(default)]\n  pub exit: bool,\n}\n\nimpl Allowlist for ProcessAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self {\n      all: false,\n      relaunch: true,\n      relaunch_dangerous_allow_symlink_macos: false,\n      exit: true,\n    };\n    let mut features = allowlist.to_features();\n    features.push(\"process-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"process-all\"]\n    } else {\n      let mut features = Vec::new();\n      check_feature!(self, features, relaunch, \"process-relaunch\");\n      check_feature!(\n        self,\n        features,\n        relaunch_dangerous_allow_symlink_macos,\n        \"process-relaunch-dangerous-allow-symlink-macos\"\n      );\n      check_feature!(self, features, exit, \"process-exit\");\n      features\n    }\n  }\n}\n\n/// Allowlist for the clipboard APIs.\n///\n/// See more: https://tauri.app/v1/api/config#clipboardallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct ClipboardAllowlistConfig {\n  /// Use this flag to enable all clipboard APIs.\n  #[serde(default)]\n  pub all: bool,\n  /// Enables the clipboard's `writeText` API.\n  #[serde(default, alias = \"writeText\")]\n  pub write_text: bool,\n  /// Enables the clipboard's `readText` API.\n  #[serde(default, alias = \"readText\")]\n  pub read_text: bool,\n}\n\nimpl Allowlist for ClipboardAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self {\n      all: false,\n      write_text: true,\n      read_text: true,\n    };\n    let mut features = allowlist.to_features();\n    features.push(\"clipboard-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"clipboard-all\"]\n    } else {\n      let mut features = Vec::new();\n      check_feature!(self, features, write_text, \"clipboard-write-text\");\n      check_feature!(self, features, read_text, \"clipboard-read-text\");\n      features\n    }\n  }\n}\n\n/// Allowlist for the app APIs.\n///\n/// See more: https://tauri.app/v1/api/config#appallowlistconfig\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct AppAllowlistConfig {\n  /// Use this flag to enable all app APIs.\n  #[serde(default)]\n  pub all: bool,\n  /// Enables the app's `show` API.\n  #[serde(default)]\n  pub show: bool,\n  /// Enables the app's `hide` API.\n  #[serde(default)]\n  pub hide: bool,\n}\n\nimpl Allowlist for AppAllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let allowlist = Self {\n      all: false,\n      show: true,\n      hide: true,\n    };\n    let mut features = allowlist.to_features();\n    features.push(\"app-all\");\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"app-all\"]\n    } else {\n      let mut features = Vec::new();\n      check_feature!(self, features, show, \"app-show\");\n      check_feature!(self, features, hide, \"app-hide\");\n      features\n    }\n  }\n}\n\n/// Allowlist configuration. The allowlist is a translation of the [Cargo allowlist features](https://docs.rs/tauri/latest/tauri/#cargo-allowlist-features).\n///\n/// # Notes\n///\n/// - Endpoints that don't have their own allowlist option are enabled by default.\n/// - There is only \"opt-in\", no \"opt-out\". Setting an option to `false` has no effect.\n///\n/// # Examples\n///\n/// - * [`\"app-all\": true`](https://tauri.app/v1/api/config/#appallowlistconfig.all) will make the [hide](https://tauri.app/v1/api/js/app#hide) endpoint be available regardless of whether `hide` is set to `false` or `true` in the allowlist.\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct AllowlistConfig {\n  /// Use this flag to enable all API features.\n  #[serde(default)]\n  pub all: bool,\n  /// File system API allowlist.\n  #[serde(default)]\n  pub fs: FsAllowlistConfig,\n  /// Window API allowlist.\n  #[serde(default)]\n  pub window: WindowAllowlistConfig,\n  /// Shell API allowlist.\n  #[serde(default)]\n  pub shell: ShellAllowlistConfig,\n  /// Dialog API allowlist.\n  #[serde(default)]\n  pub dialog: DialogAllowlistConfig,\n  /// HTTP API allowlist.\n  #[serde(default)]\n  pub http: HttpAllowlistConfig,\n  /// Notification API allowlist.\n  #[serde(default)]\n  pub notification: NotificationAllowlistConfig,\n  /// Global shortcut API allowlist.\n  #[serde(default, alias = \"global-shortcut\")]\n  pub global_shortcut: GlobalShortcutAllowlistConfig,\n  /// OS allowlist.\n  #[serde(default)]\n  pub os: OsAllowlistConfig,\n  /// Path API allowlist.\n  #[serde(default)]\n  pub path: PathAllowlistConfig,\n  /// Custom protocol allowlist.\n  #[serde(default)]\n  pub protocol: ProtocolAllowlistConfig,\n  /// Process API allowlist.\n  #[serde(default)]\n  pub process: ProcessAllowlistConfig,\n  /// Clipboard APIs allowlist.\n  #[serde(default)]\n  pub clipboard: ClipboardAllowlistConfig,\n  /// App APIs allowlist.\n  #[serde(default)]\n  pub app: AppAllowlistConfig,\n}\n\nimpl Allowlist for AllowlistConfig {\n  fn all_features() -> Vec<&'static str> {\n    let mut features = vec![\"api-all\"];\n    features.extend(FsAllowlistConfig::all_features());\n    features.extend(WindowAllowlistConfig::all_features());\n    features.extend(ShellAllowlistConfig::all_features());\n    features.extend(DialogAllowlistConfig::all_features());\n    features.extend(HttpAllowlistConfig::all_features());\n    features.extend(NotificationAllowlistConfig::all_features());\n    features.extend(GlobalShortcutAllowlistConfig::all_features());\n    features.extend(OsAllowlistConfig::all_features());\n    features.extend(PathAllowlistConfig::all_features());\n    features.extend(ProtocolAllowlistConfig::all_features());\n    features.extend(ProcessAllowlistConfig::all_features());\n    features.extend(ClipboardAllowlistConfig::all_features());\n    features.extend(AppAllowlistConfig::all_features());\n    features\n  }\n\n  fn to_features(&self) -> Vec<&'static str> {\n    if self.all {\n      vec![\"api-all\"]\n    } else {\n      let mut features = Vec::new();\n      features.extend(self.fs.to_features());\n      features.extend(self.window.to_features());\n      features.extend(self.shell.to_features());\n      features.extend(self.dialog.to_features());\n      features.extend(self.http.to_features());\n      features.extend(self.notification.to_features());\n      features.extend(self.global_shortcut.to_features());\n      features.extend(self.os.to_features());\n      features.extend(self.path.to_features());\n      features.extend(self.protocol.to_features());\n      features.extend(self.process.to_features());\n      features.extend(self.clipboard.to_features());\n      features.extend(self.app.to_features());\n      features\n    }\n  }\n}\n\n/// The application pattern.\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]\n#[serde(rename_all = \"lowercase\", tag = \"use\", content = \"options\")]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub enum PatternKind {\n  /// Brownfield pattern.\n  #[default]\n  Brownfield,\n  /// Isolation pattern. Recommended for security purposes.\n  Isolation {\n    /// The dir containing the index.html file that contains the secure isolation application.\n    dir: PathBuf,\n  },\n}\n\n/// The Tauri configuration object.\n///\n/// See more: https://tauri.app/v1/api/config#tauriconfig\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct TauriConfig {\n  /// The pattern to use.\n  #[serde(default)]\n  pub pattern: PatternKind,\n  /// The windows configuration.\n  #[serde(default)]\n  pub windows: Vec<WindowConfig>,\n  /// The CLI configuration.\n  pub cli: Option<CliConfig>,\n  /// The bundler configuration.\n  #[serde(default)]\n  pub bundle: BundleConfig,\n  /// The allowlist configuration.\n  #[serde(default)]\n  pub allowlist: AllowlistConfig,\n  /// Security configuration.\n  #[serde(default)]\n  pub security: SecurityConfig,\n  /// The updater configuration.\n  #[serde(default)]\n  pub updater: UpdaterConfig,\n  /// Configuration for app system tray.\n  #[serde(alias = \"system-tray\")]\n  pub system_tray: Option<SystemTrayConfig>,\n  /// MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`.\n  #[serde(rename = \"macOSPrivateApi\", alias = \"macos-private-api\", default)]\n  pub macos_private_api: bool,\n}\n\n/// A URL to an updater server.\n///\n/// The URL must use the `https` scheme on production.\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub struct UpdaterEndpoint(pub Url);\n\nimpl std::fmt::Display for UpdaterEndpoint {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    write!(f, \"{}\", self.0)\n  }\n}\n\nimpl<'de> Deserialize<'de> for UpdaterEndpoint {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let url = Url::deserialize(deserializer)?;\n    #[cfg(all(not(debug_assertions), not(feature = \"schema\")))]\n    {\n      if url.scheme() != \"https\" {\n        return Err(serde::de::Error::custom(\n          \"The configured updater endpoint must use the `https` protocol.\",\n        ));\n      }\n    }\n    Ok(Self(url))\n  }\n}\n\n/// Install modes for the Windows update.\n#[derive(Debug, PartialEq, Eq, Clone, Default)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"schema\", schemars(rename_all = \"camelCase\"))]\npub enum WindowsUpdateInstallMode {\n  /// Specifies there's a basic UI during the installation process, including a final dialog box at the end.\n  BasicUi,\n  /// The quiet mode means there's no user interaction required.\n  /// Requires admin privileges if the installer does.\n  Quiet,\n  /// Specifies unattended mode, which means the installation only shows a progress bar.\n  #[default]\n  Passive,\n  // to add more modes, we need to check if the updater relaunch makes sense\n  // i.e. for a full UI mode, the user can also mark the installer to start the app\n}\n\nimpl Display for WindowsUpdateInstallMode {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    write!(\n      f,\n      \"{}\",\n      match self {\n        Self::BasicUi => \"basicUI\",\n        Self::Quiet => \"quiet\",\n        Self::Passive => \"passive\",\n      }\n    )\n  }\n}\n\nimpl Serialize for WindowsUpdateInstallMode {\n  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    serializer.serialize_str(self.to_string().as_ref())\n  }\n}\n\nimpl<'de> Deserialize<'de> for WindowsUpdateInstallMode {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let s = String::deserialize(deserializer)?;\n    match s.to_lowercase().as_str() {\n      \"basicui\" => Ok(Self::BasicUi),\n      \"quiet\" => Ok(Self::Quiet),\n      \"passive\" => Ok(Self::Passive),\n      _ => Err(DeError::custom(format!(\n        \"unknown update install mode '{s}'\"\n      ))),\n    }\n  }\n}\n\n/// The updater configuration for Windows.\n///\n/// See more: https://tauri.app/v1/api/config#updaterwindowsconfig\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct UpdaterWindowsConfig {\n  /// Additional arguments given to the NSIS or WiX installer.\n  #[serde(default, alias = \"installer-args\")]\n  pub installer_args: Vec<String>,\n  /// The installation mode for the update on Windows. Defaults to `passive`.\n  #[serde(default, alias = \"install-mode\")]\n  pub install_mode: WindowsUpdateInstallMode,\n}\n\n/// The Updater configuration object.\n///\n/// See more: https://tauri.app/v1/api/config#updaterconfig\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct UpdaterConfig {\n  /// Whether the updater is active or not.\n  #[serde(default)]\n  pub active: bool,\n  /// Display built-in dialog or use event system if disabled.\n  #[serde(default = \"default_true\")]\n  pub dialog: bool,\n  /// The updater endpoints. TLS is enforced on production.\n  ///\n  /// The updater URL can contain the following variables:\n  /// - {{current_version}}: The version of the app that is requesting the update\n  /// - {{target}}: The operating system name (one of `linux`, `windows` or `darwin`).\n  /// - {{arch}}: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`).\n  ///\n  /// # Examples\n  /// - \"https://my.cdn.com/latest.json\": a raw JSON endpoint that returns the latest version and download links for each platform.\n  /// - \"https://updates.app.dev/{{target}}?version={{current_version}}&arch={{arch}}\": a dedicated API with positional and query string arguments.\n  #[allow(rustdoc::bare_urls)]\n  pub endpoints: Option<Vec<UpdaterEndpoint>>,\n  /// Signature public key.\n  #[serde(default)] // use default just so the schema doesn't flag it as required\n  pub pubkey: String,\n  /// The Windows configuration for the updater.\n  #[serde(default)]\n  pub windows: UpdaterWindowsConfig,\n}\n\nimpl<'de> Deserialize<'de> for UpdaterConfig {\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    #[derive(Deserialize)]\n    struct InnerUpdaterConfig {\n      #[serde(default)]\n      active: bool,\n      #[serde(default = \"default_true\")]\n      dialog: bool,\n      endpoints: Option<Vec<UpdaterEndpoint>>,\n      pubkey: Option<String>,\n      #[serde(default)]\n      windows: UpdaterWindowsConfig,\n    }\n\n    let config = InnerUpdaterConfig::deserialize(deserializer)?;\n\n    if config.active && config.pubkey.is_none() {\n      return Err(DeError::custom(\n        \"The updater `pubkey` configuration is required.\",\n      ));\n    }\n\n    Ok(UpdaterConfig {\n      active: config.active,\n      dialog: config.dialog,\n      endpoints: config.endpoints,\n      pubkey: config.pubkey.unwrap_or_default(),\n      windows: config.windows,\n    })\n  }\n}\n\nimpl Default for UpdaterConfig {\n  fn default() -> Self {\n    Self {\n      active: false,\n      dialog: true,\n      endpoints: None,\n      pubkey: \"\".into(),\n      windows: Default::default(),\n    }\n  }\n}\n\n/// Configuration for application system tray icon.\n///\n/// See more: https://tauri.app/v1/api/config#systemtrayconfig\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct SystemTrayConfig {\n  /// Path to the default icon to use on the system tray.\n  #[serde(alias = \"icon-path\")]\n  pub icon_path: PathBuf,\n  /// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.\n  #[serde(default, alias = \"icon-as-template\")]\n  pub icon_as_template: bool,\n  /// A Boolean value that determines whether the menu should appear when the tray icon receives a left click on macOS.\n  #[serde(default = \"default_true\", alias = \"menu-on-left-click\")]\n  pub menu_on_left_click: bool,\n  /// Title for MacOS tray\n  pub title: Option<String>,\n}\n\n/// Defines the URL or assets to embed in the application.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged, deny_unknown_fields)]\n#[non_exhaustive]\npub enum AppUrl {\n  /// The app's external URL, or the path to the directory containing the app assets.\n  Url(WindowUrl),\n  /// An array of files to embed on the app.\n  Files(Vec<PathBuf>),\n}\n\nimpl std::fmt::Display for AppUrl {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    match self {\n      Self::Url(url) => write!(f, \"{url}\"),\n      Self::Files(files) => write!(f, \"{}\", serde_json::to_string(files).unwrap()),\n    }\n  }\n}\n\n/// Describes the shell command to run before `tauri dev`.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", untagged)]\npub enum BeforeDevCommand {\n  /// Run the given script with the default options.\n  Script(String),\n  /// Run the given script with custom options.\n  ScriptWithOptions {\n    /// The script to execute.\n    script: String,\n    /// The current working directory.\n    cwd: Option<String>,\n    /// Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`.\n    #[serde(default)]\n    wait: bool,\n  },\n}\n\n/// Describes a shell command to be executed when a CLI hook is triggered.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", untagged)]\npub enum HookCommand {\n  /// Run the given script with the default options.\n  Script(String),\n  /// Run the given script with custom options.\n  ScriptWithOptions {\n    /// The script to execute.\n    script: String,\n    /// The current working directory.\n    cwd: Option<String>,\n  },\n}\n\n/// The Build configuration object.\n///\n/// See more: https://tauri.app/v1/api/config#buildconfig\n#[skip_serializing_none]\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct BuildConfig {\n  /// The binary used to build and run the application.\n  pub runner: Option<String>,\n  /// The path to the application assets or URL to load in development.\n  ///\n  /// This is usually an URL to a dev server, which serves your application assets\n  /// with live reloading. Most modern JavaScript bundlers provides a way to start a dev server by default.\n  ///\n  /// See [vite](https://vitejs.dev/guide/), [Webpack DevServer](https://webpack.js.org/configuration/dev-server/) and [sirv](https://github.com/lukeed/sirv)\n  /// for examples on how to set up a dev server.\n  #[serde(default = \"default_dev_path\", alias = \"dev-path\")]\n  pub dev_path: AppUrl,\n  /// The path to the application assets or URL to load in production.\n  ///\n  /// When a path relative to the configuration file is provided,\n  /// it is read recursively and all files are embedded in the application binary.\n  /// Tauri then looks for an `index.html` file unless you provide a custom window URL.\n  ///\n  /// You can also provide a list of paths to be embedded, which allows granular control over what files are added to the binary.\n  /// In this case, all files are added to the root and you must reference it that way in your HTML files.\n  ///\n  /// When an URL is provided, the application won't have bundled assets\n  /// and the application will load that URL by default.\n  #[serde(default = \"default_dist_dir\", alias = \"dist-dir\")]\n  pub dist_dir: AppUrl,\n  /// A shell command to run before `tauri dev` kicks in.\n  ///\n  /// The TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation.\n  #[serde(alias = \"before-dev-command\")]\n  pub before_dev_command: Option<BeforeDevCommand>,\n  /// A shell command to run before `tauri build` kicks in.\n  ///\n  /// The TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation.\n  #[serde(alias = \"before-build-command\")]\n  pub before_build_command: Option<HookCommand>,\n  /// A shell command to run before the bundling phase in `tauri build` kicks in.\n  ///\n  /// The TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation.\n  #[serde(alias = \"before-bundle-command\")]\n  pub before_bundle_command: Option<HookCommand>,\n  /// Features passed to `cargo` commands.\n  pub features: Option<Vec<String>>,\n  /// Whether we should inject the Tauri API on `window.__TAURI__` or not.\n  #[serde(default, alias = \"with-global-tauri\")]\n  pub with_global_tauri: bool,\n}\n\nimpl Default for BuildConfig {\n  fn default() -> Self {\n    Self {\n      runner: None,\n      dev_path: default_dev_path(),\n      dist_dir: default_dist_dir(),\n      before_dev_command: None,\n      before_build_command: None,\n      before_bundle_command: None,\n      features: None,\n      with_global_tauri: false,\n    }\n  }\n}\n\nfn default_dev_path() -> AppUrl {\n  AppUrl::Url(WindowUrl::External(\n    Url::parse(\"http://localhost:8080\").unwrap(),\n  ))\n}\n\nfn default_dist_dir() -> AppUrl {\n  AppUrl::Url(WindowUrl::App(\"../dist\".into()))\n}\n\n#[derive(Debug, PartialEq, Eq)]\nstruct PackageVersion(String);\n\nimpl<'d> serde::Deserialize<'d> for PackageVersion {\n  fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<PackageVersion, D::Error> {\n    struct PackageVersionVisitor;\n\n    impl<'d> Visitor<'d> for PackageVersionVisitor {\n      type Value = PackageVersion;\n\n      fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n          formatter,\n          \"a semver string or a path to a package.json file\"\n        )\n      }\n\n      fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {\n        let path = PathBuf::from(value);\n        if path.exists() {\n          let json_str = read_to_string(&path)\n            .map_err(|e| DeError::custom(format!(\"failed to read version JSON file: {e}\")))?;\n          let package_json: serde_json::Value = serde_json::from_str(&json_str)\n            .map_err(|e| DeError::custom(format!(\"failed to read version JSON file: {e}\")))?;\n          if let Some(obj) = package_json.as_object() {\n            let version = obj\n              .get(\"version\")\n              .ok_or_else(|| DeError::custom(\"JSON must contain a `version` field\"))?\n              .as_str()\n              .ok_or_else(|| {\n                DeError::custom(format!(\"`{} > version` must be a string\", path.display()))\n              })?;\n            Ok(PackageVersion(\n              Version::from_str(version)\n                .map_err(|_| DeError::custom(\"`package > version` must be a semver string\"))?\n                .to_string(),\n            ))\n          } else {\n            Err(DeError::custom(\n              \"`package > version` value is not a path to a JSON object\",\n            ))\n          }\n        } else {\n          Ok(PackageVersion(\n            Version::from_str(value)\n              .map_err(|_| DeError::custom(\"`package > version` must be a semver string\"))?\n              .to_string(),\n          ))\n        }\n      }\n    }\n\n    deserializer.deserialize_string(PackageVersionVisitor {})\n  }\n}\n\n/// The package configuration.\n///\n/// See more: https://tauri.app/v1/api/config#packageconfig\n#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct PackageConfig {\n  /// App name.\n  #[serde(alias = \"product-name\")]\n  #[cfg_attr(feature = \"schema\", validate(regex(pattern = \"^[^/\\\\:*?\\\"<>|]+$\")))]\n  pub product_name: Option<String>,\n  /// App version. It is a semver version number or a path to a `package.json` file containing the `version` field. If removed the version number from `Cargo.toml` is used.\n  #[serde(deserialize_with = \"version_deserializer\", default)]\n  pub version: Option<String>,\n}\n\nfn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>\nwhere\n  D: Deserializer<'de>,\n{\n  Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))\n}\n\n/// The Tauri configuration object.\n/// It is read from a file where you can define your frontend assets,\n/// configure the bundler, enable the app updater, define a system tray,\n/// enable APIs via the allowlist and more.\n///\n/// The configuration file is generated by the\n/// [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in\n/// your Tauri application source directory (src-tauri).\n///\n/// Once generated, you may modify it at will to customize your Tauri application.\n///\n/// ## File Formats\n///\n/// By default, the configuration is defined as a JSON file named `tauri.conf.json`.\n///\n/// Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.\n/// The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.\n/// The TOML file name is `Tauri.toml`.\n///\n/// ## Platform-Specific Configuration\n///\n/// In addition to the default configuration file, Tauri can\n/// read a platform-specific configuration from `tauri.linux.conf.json`,\n/// `tauri.windows.conf.json`, and `tauri.macos.conf.json`\n/// (or `Tauri.linux.toml`, `Tauri.windows.toml` and `Tauri.macos.toml` if the `Tauri.toml` format is used),\n/// which gets merged with the main configuration object.\n///\n/// ## Configuration Structure\n///\n/// The configuration is composed of the following objects:\n///\n/// - [`package`](#packageconfig): Package settings\n/// - [`tauri`](#tauriconfig): The Tauri config\n/// - [`build`](#buildconfig): The build configuration\n/// - [`plugins`](#pluginconfig): The plugins config\n///\n/// ```json title=\"Example tauri.config.json file\"\n/// {\n///   \"build\": {\n///     \"beforeBuildCommand\": \"\",\n///     \"beforeDevCommand\": \"\",\n///     \"devPath\": \"../dist\",\n///     \"distDir\": \"../dist\"\n///   },\n///   \"package\": {\n///     \"productName\": \"tauri-app\",\n///     \"version\": \"0.1.0\"\n///   },\n///   \"tauri\": {\n///     \"allowlist\": {\n///       \"all\": true\n///     },\n///     \"bundle\": {},\n///     \"security\": {\n///       \"csp\": null\n///     },\n///     \"updater\": {\n///       \"active\": false\n///     },\n///     \"windows\": [\n///       {\n///         \"fullscreen\": false,\n///         \"height\": 600,\n///         \"resizable\": true,\n///         \"title\": \"Tauri App\",\n///         \"width\": 800\n///       }\n///     ]\n///   }\n/// }\n/// ```\n#[allow(rustdoc::invalid_codeblock_attributes)]\n#[skip_serializing_none]\n#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct Config {\n  /// The JSON schema for the Tauri config.\n  #[serde(rename = \"$schema\")]\n  pub schema: Option<String>,\n  /// Package settings.\n  #[serde(default)]\n  pub package: PackageConfig,\n  /// The Tauri configuration.\n  #[serde(default)]\n  pub tauri: TauriConfig,\n  /// The build configuration.\n  #[serde(default = \"default_build\")]\n  pub build: BuildConfig,\n  /// The plugins config.\n  #[serde(default)]\n  pub plugins: PluginConfig,\n}\n\n/// The plugin configs holds a HashMap mapping a plugin name to its configuration object.\n///\n/// See more: https://tauri.app/v1/api/config#pluginconfig\n#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub struct PluginConfig(pub HashMap<String, JsonValue>);\n\nfn default_build() -> BuildConfig {\n  BuildConfig {\n    runner: None,\n    dev_path: default_dev_path(),\n    dist_dir: default_dist_dir(),\n    before_dev_command: None,\n    before_build_command: None,\n    before_bundle_command: None,\n    features: None,\n    with_global_tauri: false,\n  }\n}\n\n/// How the window title bar should be displayed on macOS.\n#[derive(Debug, Clone, PartialEq, Eq, Default)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\npub enum TitleBarStyle {\n  /// A normal title bar.\n  #[default]\n  Visible,\n  /// Makes the title bar transparent, so the window background color is shown instead.\n  ///\n  /// Useful if you don't need to have actual HTML under the title bar. This lets you avoid the caveats of using `TitleBarStyle::Overlay`. Will be more useful when Tauri lets you set a custom window background color.\n  Transparent,\n  /// Shows the title bar as a transparent overlay over the window's content.\n  ///\n  /// Keep in mind:\n  /// - The height of the title bar is different on different OS versions, which can lead to window the controls and title not being where you don't expect.\n  /// - You need to define a custom drag region to make your window draggable, however due to a limitation you can't drag the window when it's not in focus <https://github.com/tauri-apps/tauri/issues/4316>.\n  /// - The color of the window title depends on the system theme.\n  Overlay,\n}\n\nimpl Serialize for TitleBarStyle {\n  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    serializer.serialize_str(self.to_string().as_ref())\n  }\n}\n\nimpl<'de> Deserialize<'de> for TitleBarStyle {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let s = String::deserialize(deserializer)?;\n    Ok(match s.to_lowercase().as_str() {\n      \"transparent\" => Self::Transparent,\n      \"overlay\" => Self::Overlay,\n      _ => Self::Visible,\n    })\n  }\n}\n\nimpl Display for TitleBarStyle {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    write!(\n      f,\n      \"{}\",\n      match self {\n        Self::Visible => \"Visible\",\n        Self::Transparent => \"Transparent\",\n        Self::Overlay => \"Overlay\",\n      }\n    )\n  }\n}\n\n/// System theme.\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[non_exhaustive]\npub enum Theme {\n  /// Light theme.\n  Light,\n  /// Dark theme.\n  Dark,\n}\n\nimpl Serialize for Theme {\n  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    serializer.serialize_str(self.to_string().as_ref())\n  }\n}\n\nimpl<'de> Deserialize<'de> for Theme {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let s = String::deserialize(deserializer)?;\n    Ok(match s.to_lowercase().as_str() {\n      \"dark\" => Self::Dark,\n      _ => Self::Light,\n    })\n  }\n}\n\nimpl Display for Theme {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    write!(\n      f,\n      \"{}\",\n      match self {\n        Self::Light => \"light\",\n        Self::Dark => \"dark\",\n      }\n    )\n  }\n}\n\n#[cfg(test)]\nmod test {\n  use super::*;\n\n  // TODO: create a test that compares a config to a json config\n\n  #[test]\n  // test all of the default functions\n  fn test_defaults() {\n    // get default tauri config\n    let t_config = TauriConfig::default();\n    // get default build config\n    let b_config = BuildConfig::default();\n    // get default dev path\n    let d_path = default_dev_path();\n    // get default window\n    let d_windows: Vec<WindowConfig> = vec![];\n    // get default bundle\n    let d_bundle = BundleConfig::default();\n    // get default updater\n    let d_updater = UpdaterConfig::default();\n\n    // create a tauri config.\n    let tauri = TauriConfig {\n      pattern: Default::default(),\n      windows: vec![],\n      bundle: BundleConfig {\n        active: false,\n        targets: Default::default(),\n        identifier: String::from(\"\"),\n        publisher: None,\n        icon: Vec::new(),\n        resources: None,\n        copyright: None,\n        category: None,\n        short_description: None,\n        long_description: None,\n        appimage: Default::default(),\n        deb: Default::default(),\n        macos: Default::default(),\n        external_bin: None,\n        windows: Default::default(),\n      },\n      cli: None,\n      updater: UpdaterConfig {\n        active: false,\n        dialog: true,\n        pubkey: \"\".into(),\n        endpoints: None,\n        windows: Default::default(),\n      },\n      security: SecurityConfig {\n        csp: None,\n        dev_csp: None,\n        freeze_prototype: false,\n        dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),\n        dangerous_remote_domain_ipc_access: Vec::new(),\n        dangerous_use_http_scheme: false,\n      },\n      allowlist: AllowlistConfig::default(),\n      system_tray: None,\n      macos_private_api: false,\n    };\n\n    // create a build config\n    let build = BuildConfig {\n      runner: None,\n      dev_path: AppUrl::Url(WindowUrl::External(\n        Url::parse(\"http://localhost:8080\").unwrap(),\n      )),\n      dist_dir: AppUrl::Url(WindowUrl::App(\"../dist\".into())),\n      before_dev_command: None,\n      before_build_command: None,\n      before_bundle_command: None,\n      features: None,\n      with_global_tauri: false,\n    };\n\n    // test the configs\n    assert_eq!(t_config, tauri);\n    assert_eq!(b_config, build);\n    assert_eq!(d_bundle, tauri.bundle);\n    assert_eq!(d_updater, tauri.updater);\n    assert_eq!(\n      d_path,\n      AppUrl::Url(WindowUrl::External(\n        Url::parse(\"http://localhost:8080\").unwrap()\n      ))\n    );\n    assert_eq!(d_windows, tauri.windows);\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/config_v1/parse.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![allow(clippy::result_large_err)]\n\nuse serde::de::DeserializeOwned;\nuse serde_json::Value;\nuse std::ffi::OsStr;\nuse std::path::{Path, PathBuf};\nuse thiserror::Error;\n\n/// All extensions that are possibly supported, but perhaps not enabled.\nconst EXTENSIONS_SUPPORTED: &[&str] = &[\"json\", \"json5\", \"toml\"];\n\n/// All configuration formats that are currently enabled.\nconst ENABLED_FORMATS: &[ConfigFormat] = &[\n  ConfigFormat::Json,\n  #[cfg(feature = \"config-json5\")]\n  ConfigFormat::Json5,\n  #[cfg(feature = \"config-toml\")]\n  ConfigFormat::Toml,\n];\n\n/// The available configuration formats.\n#[derive(Debug, Copy, Clone)]\nenum ConfigFormat {\n  /// The default JSON (tauri.conf.json) format.\n  Json,\n  /// The JSON5 (tauri.conf.json5) format.\n  Json5,\n  /// The TOML (Tauri.toml file) format.\n  Toml,\n}\n\nimpl ConfigFormat {\n  /// Maps the config format to its file name.\n  fn into_file_name(self) -> &'static str {\n    match self {\n      Self::Json => \"tauri.conf.json\",\n      Self::Json5 => \"tauri.conf.json5\",\n      Self::Toml => \"Tauri.toml\",\n    }\n  }\n\n  fn into_platform_file_name(self) -> &'static str {\n    match self {\n      Self::Json => {\n        if cfg!(target_os = \"macos\") {\n          \"tauri.macos.conf.json\"\n        } else if cfg!(windows) {\n          \"tauri.windows.conf.json\"\n        } else {\n          \"tauri.linux.conf.json\"\n        }\n      }\n      Self::Json5 => {\n        if cfg!(target_os = \"macos\") {\n          \"tauri.macos.conf.json5\"\n        } else if cfg!(windows) {\n          \"tauri.windows.conf.json5\"\n        } else {\n          \"tauri.linux.conf.json5\"\n        }\n      }\n      Self::Toml => {\n        if cfg!(target_os = \"macos\") {\n          \"Tauri.macos.toml\"\n        } else if cfg!(windows) {\n          \"Tauri.windows.toml\"\n        } else {\n          \"Tauri.linux.toml\"\n        }\n      }\n    }\n  }\n}\n\n/// Represents all the errors that can happen while reading the config.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum ConfigError {\n  /// Failed to parse from JSON.\n  #[error(\"unable to parse JSON Tauri config file at {path} because {error}\")]\n  FormatJson {\n    /// The path that failed to parse into JSON.\n    path: PathBuf,\n\n    /// The parsing [`serde_json::Error`].\n    error: serde_json::Error,\n  },\n\n  /// Failed to parse from JSON5.\n  #[cfg(feature = \"config-json5\")]\n  #[error(\"unable to parse JSON5 Tauri config file at {path} because {error}\")]\n  FormatJson5 {\n    /// The path that failed to parse into JSON5.\n    path: PathBuf,\n\n    /// The parsing [`json5::Error`].\n    error: ::json5::Error,\n  },\n\n  /// Failed to parse from TOML.\n  #[cfg(feature = \"config-toml\")]\n  #[error(\"unable to parse toml Tauri config file at {path} because {error}\")]\n  FormatToml {\n    /// The path that failed to parse into TOML.\n    path: PathBuf,\n\n    /// The parsing [`toml::Error`].\n    error: ::toml::de::Error,\n  },\n\n  /// Unknown config file name encountered.\n  #[error(\"unsupported format encountered {0}\")]\n  UnsupportedFormat(String),\n\n  /// Known file extension encountered, but corresponding parser is not enabled (cargo features).\n  #[error(\"supported (but disabled) format encountered {extension} - try enabling `{feature}` \")]\n  DisabledFormat {\n    /// The extension encountered.\n    extension: String,\n\n    /// The cargo feature to enable it.\n    feature: String,\n  },\n\n  /// A generic IO error with context of what caused it.\n  #[error(\"unable to read Tauri config file at {path} because {error}\")]\n  Io {\n    /// The path the IO error occurred on.\n    path: PathBuf,\n\n    /// The [`std::io::Error`].\n    error: std::io::Error,\n  },\n}\n\n/// See [`parse`] for specifics, returns a JSON [`Value`] instead of [`Config`].\npub fn parse_value(path: impl Into<PathBuf>) -> Result<(Value, PathBuf), ConfigError> {\n  do_parse(path.into())\n}\n\nfn do_parse<D: DeserializeOwned>(path: PathBuf) -> Result<(D, PathBuf), ConfigError> {\n  let file_name = path\n    .file_name()\n    .map(OsStr::to_string_lossy)\n    .unwrap_or_default();\n  let lookup_platform_config = ENABLED_FORMATS\n    .iter()\n    .any(|format| file_name == format.into_platform_file_name());\n\n  let json5 = path.with_file_name(if lookup_platform_config {\n    ConfigFormat::Json5.into_platform_file_name()\n  } else {\n    ConfigFormat::Json5.into_file_name()\n  });\n  let toml = path.with_file_name(if lookup_platform_config {\n    ConfigFormat::Toml.into_platform_file_name()\n  } else {\n    ConfigFormat::Toml.into_file_name()\n  });\n\n  let path_ext = path\n    .extension()\n    .map(OsStr::to_string_lossy)\n    .unwrap_or_default();\n\n  if path.exists() {\n    let raw = read_to_string(&path)?;\n\n    // to allow us to easily use the compile-time #[cfg], we always bind\n    #[allow(clippy::let_and_return)]\n    let json = do_parse_json(&raw, &path);\n\n    // we also want to support **valid** json5 in the .json extension if the feature is enabled.\n    // if the json5 is not valid the serde_json error for regular json will be returned.\n    // this could be a bit confusing, so we may want to encourage users using json5 to use the\n    // .json5 extension instead of .json\n    #[cfg(feature = \"config-json5\")]\n    let json = {\n      match do_parse_json5(&raw, &path) {\n        json5 @ Ok(_) => json5,\n\n        // assume any errors from json5 in a .json file is because it's not json5\n        Err(_) => json,\n      }\n    };\n\n    json.map(|j| (j, path))\n  } else if json5.exists() {\n    #[cfg(feature = \"config-json5\")]\n    {\n      let raw = read_to_string(&json5)?;\n      do_parse_json5(&raw, &path).map(|config| (config, json5))\n    }\n\n    #[cfg(not(feature = \"config-json5\"))]\n    Err(ConfigError::DisabledFormat {\n      extension: \".json5\".into(),\n      feature: \"config-json5\".into(),\n    })\n  } else if toml.exists() {\n    #[cfg(feature = \"config-toml\")]\n    {\n      let raw = read_to_string(&toml)?;\n      do_parse_toml(&raw, &path).map(|config| (config, toml))\n    }\n\n    #[cfg(not(feature = \"config-toml\"))]\n    Err(ConfigError::DisabledFormat {\n      extension: \".toml\".into(),\n      feature: \"config-toml\".into(),\n    })\n  } else if !EXTENSIONS_SUPPORTED.contains(&path_ext.as_ref()) {\n    Err(ConfigError::UnsupportedFormat(path_ext.to_string()))\n  } else {\n    Err(ConfigError::Io {\n      path,\n      error: std::io::ErrorKind::NotFound.into(),\n    })\n  }\n}\n\nfn do_parse_json<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {\n  serde_json::from_str(raw).map_err(|error| ConfigError::FormatJson {\n    path: path.into(),\n    error,\n  })\n}\n\n#[cfg(feature = \"config-json5\")]\nfn do_parse_json5<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {\n  ::json5::from_str(raw).map_err(|error| ConfigError::FormatJson5 {\n    path: path.into(),\n    error,\n  })\n}\n\n#[cfg(feature = \"config-toml\")]\nfn do_parse_toml<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {\n  ::toml::from_str(raw).map_err(|error| ConfigError::FormatToml {\n    path: path.into(),\n    error,\n  })\n}\n\n/// Helper function to wrap IO errors from [`std::fs::read_to_string`] into a [`ConfigError`].\nfn read_to_string(path: &Path) -> Result<String, ConfigError> {\n  std::fs::read_to_string(path).map_err(|error| ConfigError::Io {\n    path: path.into(),\n    error,\n  })\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/html.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The module to process HTML in Tauri.\n\nuse std::path::{Path, PathBuf};\n\nuse html5ever::{\n  interface::QualName,\n  namespace_url, ns,\n  serialize::{HtmlSerializer, SerializeOpts, Serializer, TraversalScope},\n  tendril::TendrilSink,\n  LocalName,\n};\npub use kuchiki::NodeRef;\nuse kuchiki::{Attribute, ExpandedName, NodeData};\nuse serde::Serialize;\n#[cfg(feature = \"isolation\")]\nuse serialize_to_javascript::DefaultTemplate;\n\n#[cfg(feature = \"isolation\")]\nuse crate::pattern::isolation::IsolationJavascriptCodegen;\nuse crate::{\n  assets::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},\n  config::{DisabledCspModificationKind, PatternKind},\n};\n\n// taken from <https://github.com/kuchiki-rs/kuchiki/blob/57ee6920d835315a498e748ba4b07a851ae5e498/src/serializer.rs#L12>\nfn serialize_node_ref_internal<S: Serializer>(\n  node: &NodeRef,\n  serializer: &mut S,\n  traversal_scope: TraversalScope,\n) -> crate::Result<()> {\n  match (traversal_scope, node.data()) {\n    (ref scope, NodeData::Element(element)) => {\n      if *scope == TraversalScope::IncludeNode {\n        let attrs = element.attributes.borrow();\n\n        // Unfortunately we need to allocate something to hold these &'a QualName\n        let attrs = attrs\n          .map\n          .iter()\n          .map(|(name, attr)| {\n            (\n              QualName::new(attr.prefix.clone(), name.ns.clone(), name.local.clone()),\n              &attr.value,\n            )\n          })\n          .collect::<Vec<_>>();\n\n        serializer.start_elem(\n          element.name.clone(),\n          attrs.iter().map(|&(ref name, value)| (name, &**value)),\n        )?\n      }\n\n      let children = match element.template_contents.as_ref() {\n        Some(template_root) => template_root.children(),\n        None => node.children(),\n      };\n      for child in children {\n        serialize_node_ref_internal(&child, serializer, TraversalScope::IncludeNode)?\n      }\n\n      if *scope == TraversalScope::IncludeNode {\n        serializer.end_elem(element.name.clone())?\n      }\n      Ok(())\n    }\n\n    (_, &NodeData::DocumentFragment) | (_, &NodeData::Document(_)) => {\n      for child in node.children() {\n        serialize_node_ref_internal(&child, serializer, TraversalScope::IncludeNode)?\n      }\n      Ok(())\n    }\n\n    (TraversalScope::ChildrenOnly(_), _) => Ok(()),\n\n    (TraversalScope::IncludeNode, NodeData::Doctype(doctype)) => {\n      serializer.write_doctype(&doctype.name).map_err(Into::into)\n    }\n    (TraversalScope::IncludeNode, NodeData::Text(text)) => {\n      serializer.write_text(&text.borrow()).map_err(Into::into)\n    }\n    (TraversalScope::IncludeNode, NodeData::Comment(text)) => {\n      serializer.write_comment(&text.borrow()).map_err(Into::into)\n    }\n    (TraversalScope::IncludeNode, NodeData::ProcessingInstruction(contents)) => {\n      let contents = contents.borrow();\n      serializer\n        .write_processing_instruction(&contents.0, &contents.1)\n        .map_err(Into::into)\n    }\n  }\n}\n\n/// Serializes the node to HTML.\npub fn serialize_node(node: &NodeRef) -> Vec<u8> {\n  let mut u8_vec = Vec::new();\n  let mut ser = HtmlSerializer::new(\n    &mut u8_vec,\n    SerializeOpts {\n      traversal_scope: TraversalScope::IncludeNode,\n      ..Default::default()\n    },\n  );\n  serialize_node_ref_internal(node, &mut ser, TraversalScope::IncludeNode).unwrap();\n  u8_vec\n}\n\n/// Parses the given HTML string.\npub fn parse(html: String) -> NodeRef {\n  kuchiki::parse_html().one(html).document_node\n}\n\nfn with_head<F: FnOnce(&NodeRef)>(document: &NodeRef, f: F) {\n  if let Ok(ref node) = document.select_first(\"head\") {\n    f(node.as_node())\n  } else {\n    let node = NodeRef::new_element(\n      QualName::new(None, ns!(html), LocalName::from(\"head\")),\n      None,\n    );\n    f(&node);\n    document.prepend(node)\n  }\n}\n\nfn inject_nonce(document: &NodeRef, selector: &str, token: &str) {\n  if let Ok(elements) = document.select(selector) {\n    for target in elements {\n      let node = target.as_node();\n      let element = node.as_element().unwrap();\n\n      let mut attrs = element.attributes.borrow_mut();\n      // if the node already has the `nonce` attribute, skip it\n      if attrs.get(\"nonce\").is_some() {\n        continue;\n      }\n      attrs.insert(\"nonce\", token.into());\n    }\n  }\n}\n\n/// Inject nonce tokens to all scripts and styles.\npub fn inject_nonce_token(\n  document: &NodeRef,\n  dangerous_disable_asset_csp_modification: &DisabledCspModificationKind,\n) {\n  if dangerous_disable_asset_csp_modification.can_modify(\"script-src\") {\n    inject_nonce(document, \"script[src^='http']\", SCRIPT_NONCE_TOKEN);\n  }\n  if dangerous_disable_asset_csp_modification.can_modify(\"style-src\") {\n    inject_nonce(document, \"style\", STYLE_NONCE_TOKEN);\n  }\n}\n\n/// Injects a content security policy to the HTML.\npub fn inject_csp(document: &NodeRef, csp: &str) {\n  with_head(document, |head| {\n    head.append(create_csp_meta_tag(csp));\n  });\n}\n\nfn create_csp_meta_tag(csp: &str) -> NodeRef {\n  NodeRef::new_element(\n    QualName::new(None, ns!(html), LocalName::from(\"meta\")),\n    vec![\n      (\n        ExpandedName::new(ns!(), LocalName::from(\"http-equiv\")),\n        Attribute {\n          prefix: None,\n          value: \"Content-Security-Policy\".into(),\n        },\n      ),\n      (\n        ExpandedName::new(ns!(), LocalName::from(\"content\")),\n        Attribute {\n          prefix: None,\n          value: csp.into(),\n        },\n      ),\n    ],\n  )\n}\n\n/// The shape of the JavaScript Pattern config\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"lowercase\", tag = \"pattern\")]\npub enum PatternObject {\n  /// Brownfield pattern.\n  Brownfield,\n  /// Isolation pattern. Recommended for security purposes.\n  Isolation {\n    /// Which `IsolationSide` this `PatternObject` is getting injected into\n    side: IsolationSide,\n  },\n}\n\nimpl From<&PatternKind> for PatternObject {\n  fn from(pattern_kind: &PatternKind) -> Self {\n    match pattern_kind {\n      PatternKind::Brownfield => Self::Brownfield,\n      PatternKind::Isolation { .. } => Self::Isolation {\n        side: IsolationSide::default(),\n      },\n    }\n  }\n}\n\n/// Where the JavaScript is injected to\n#[derive(Debug, Serialize, Default)]\n#[serde(rename_all = \"lowercase\")]\npub enum IsolationSide {\n  /// Original frame, the Brownfield application\n  #[default]\n  Original,\n  /// Secure frame, the isolation security application\n  Secure,\n}\n\n/// Injects the Isolation JavaScript to a codegen time document.\n///\n/// Note: This function is not considered part of the stable API.\n#[cfg(feature = \"isolation\")]\npub fn inject_codegen_isolation_script(document: &NodeRef) {\n  with_head(document, |head| {\n    let script = NodeRef::new_element(\n      QualName::new(None, ns!(html), \"script\".into()),\n      vec![(\n        ExpandedName::new(ns!(), LocalName::from(\"nonce\")),\n        Attribute {\n          prefix: None,\n          value: SCRIPT_NONCE_TOKEN.into(),\n        },\n      )],\n    );\n    script.append(NodeRef::new_text(\n      IsolationJavascriptCodegen {}\n        .render_default(&Default::default())\n        .expect(\"unable to render codegen isolation script template\")\n        .into_string(),\n    ));\n\n    head.prepend(script);\n  });\n}\n\n/// Temporary workaround for Windows not allowing requests\n///\n/// Note: this does not prevent path traversal due to the isolation application expectation that it\n/// is secure.\npub fn inline_isolation(document: &NodeRef, dir: &Path) {\n  for script in document\n    .select(\"script[src]\")\n    .expect(\"unable to parse document for scripts\")\n  {\n    let src = {\n      let attributes = script.attributes.borrow();\n      attributes\n        .get(LocalName::from(\"src\"))\n        .expect(\"script with src attribute has no src value\")\n        .to_string()\n    };\n\n    let mut path = PathBuf::from(src);\n    if path.has_root() {\n      path = path\n        .strip_prefix(\"/\")\n        .expect(\"Tauri \\\"Isolation\\\" Pattern only supports relative or absolute (`/`) paths.\")\n        .into();\n    }\n\n    let file = std::fs::read_to_string(dir.join(path)).expect(\"unable to find isolation file\");\n    script.as_node().append(NodeRef::new_text(file));\n\n    let mut attributes = script.attributes.borrow_mut();\n    attributes.remove(LocalName::from(\"src\"));\n  }\n}\n\n/// Normalize line endings in script content to match what the browser uses for CSP hashing.\n///\n/// According to the HTML spec, browsers normalize:\n/// - `\\r\\n` → `\\n`\n/// - `\\r`   → `\\n`\npub fn normalize_script_for_csp(input: &[u8]) -> Vec<u8> {\n  let mut output = Vec::with_capacity(input.len());\n\n  let mut i = 0;\n  while i < input.len() {\n    match input[i] {\n      b'\\r' => {\n        if i + 1 < input.len() && input[i + 1] == b'\\n' {\n          // CRLF → LF\n          output.push(b'\\n');\n          i += 2;\n        } else {\n          // Lone CR → LF\n          output.push(b'\\n');\n          i += 1;\n        }\n      }\n      _ => {\n        output.push(input[i]);\n        i += 1;\n      }\n    }\n  }\n\n  output\n}\n\n#[cfg(test)]\nmod tests {\n\n  #[test]\n  fn csp() {\n    let htmls = vec![\n      \"<html><head></head></html>\".to_string(),\n      \"<html></html>\".to_string(),\n    ];\n    for html in htmls {\n      let document = super::parse(html);\n      let csp = \"csp-string\";\n      super::inject_csp(&document, csp);\n      assert_eq!(\n        document.to_string(),\n        format!(\n          r#\"<html><head><meta http-equiv=\"Content-Security-Policy\" content=\"{csp}\"></head><body></body></html>\"#,\n        )\n      );\n    }\n  }\n\n  #[test]\n  fn normalize_script_for_csp() {\n    let js = \"// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\\r// SPDX-License-Identifier: Apache-2.0\\n// SPDX-License-Identifier: MIT\\r\\n\\r\\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\\r\\n  return payload\\r\\n}\\r\\n\";\n    let expected = \"// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\\n// SPDX-License-Identifier: Apache-2.0\\n// SPDX-License-Identifier: MIT\\n\\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\\n  return payload\\n}\\n\";\n    assert_eq!(\n      super::normalize_script_for_csp(js.as_bytes()),\n      expected.as_bytes()\n    )\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/io.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! IO helpers.\n\nuse std::io::BufRead;\n\n/// Read all bytes until a newline (the `0xA` byte) or a carriage return (`\\r`) is reached, and append them to the provided buffer.\n///\n/// Adapted from <https://doc.rust-lang.org/std/io/trait.BufRead.html#method.read_line>.\npub fn read_line<R: BufRead + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> std::io::Result<usize> {\n  let mut read = 0;\n  loop {\n    let (done, used) = {\n      let available = match r.fill_buf() {\n        Ok(n) => n,\n        Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,\n\n        Err(e) => return Err(e),\n      };\n      match memchr::memchr(b'\\n', available) {\n        Some(i) => {\n          let end = i + 1;\n          buf.extend_from_slice(&available[..end]);\n          (true, end)\n        }\n        None => match memchr::memchr(b'\\r', available) {\n          Some(i) => {\n            let end = i + 1;\n            buf.extend_from_slice(&available[..end]);\n            (true, end)\n          }\n          None => {\n            buf.extend_from_slice(available);\n            (false, available.len())\n          }\n        },\n      }\n    };\n    r.consume(used);\n    read += used;\n    if done || used == 0 {\n      return Ok(read);\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! This crate contains common code that is reused in many places and offers useful utilities like parsing configuration files, detecting platform triples, injecting the CSP, and managing assets.\n\n#![doc(\n  html_logo_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\",\n  html_favicon_url = \"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\"\n)]\n#![warn(missing_docs, rust_2018_idioms)]\n#![allow(clippy::deprecated_semver)]\n\nuse std::{\n  ffi::OsString,\n  fmt::Display,\n  path::{Path, PathBuf},\n};\n\nuse semver::Version;\nuse serde::{Deserialize, Deserializer, Serialize, Serializer};\n\npub mod acl;\npub mod assets;\npub mod config;\npub mod config_v1;\n#[cfg(feature = \"html-manipulation\")]\npub mod html;\npub mod io;\npub mod mime_type;\npub mod platform;\npub mod plugin;\n/// Prepare application resources and sidecars.\n#[cfg(feature = \"resources\")]\npub mod resources;\n#[cfg(feature = \"build\")]\npub mod tokens;\n\n#[cfg(feature = \"build\")]\npub mod build;\n\n/// Application pattern.\npub mod pattern;\n\n/// `tauri::App` package information.\n#[derive(Debug, Clone)]\npub struct PackageInfo {\n  /// App name\n  pub name: String,\n  /// App version\n  pub version: Version,\n  /// The crate authors.\n  pub authors: &'static str,\n  /// The crate description.\n  pub description: &'static str,\n  /// The crate name.\n  pub crate_name: &'static str,\n}\n\n#[allow(deprecated)]\nmod window_effects {\n  use super::*;\n\n  #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]\n  #[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n  #[serde(rename_all = \"camelCase\")]\n  /// Platform-specific window effects\n  pub enum WindowEffect {\n    /// A default material appropriate for the view's effectiveAppearance. **macOS 10.14-**\n    #[deprecated(\n      since = \"macOS 10.14\",\n      note = \"You should instead choose an appropriate semantic material.\"\n    )]\n    AppearanceBased,\n    /// **macOS 10.14-**\n    #[deprecated(since = \"macOS 10.14\", note = \"Use a semantic material instead.\")]\n    Light,\n    /// **macOS 10.14-**\n    #[deprecated(since = \"macOS 10.14\", note = \"Use a semantic material instead.\")]\n    Dark,\n    /// **macOS 10.14-**\n    #[deprecated(since = \"macOS 10.14\", note = \"Use a semantic material instead.\")]\n    MediumLight,\n    /// **macOS 10.14-**\n    #[deprecated(since = \"macOS 10.14\", note = \"Use a semantic material instead.\")]\n    UltraDark,\n    /// **macOS 10.10+**\n    Titlebar,\n    /// **macOS 10.10+**\n    Selection,\n    /// **macOS 10.11+**\n    Menu,\n    /// **macOS 10.11+**\n    Popover,\n    /// **macOS 10.11+**\n    Sidebar,\n    /// **macOS 10.14+**\n    HeaderView,\n    /// **macOS 10.14+**\n    Sheet,\n    /// **macOS 10.14+**\n    WindowBackground,\n    /// **macOS 10.14+**\n    HudWindow,\n    /// **macOS 10.14+**\n    FullScreenUI,\n    /// **macOS 10.14+**\n    Tooltip,\n    /// **macOS 10.14+**\n    ContentBackground,\n    /// **macOS 10.14+**\n    UnderWindowBackground,\n    /// **macOS 10.14+**\n    UnderPageBackground,\n    /// Mica effect that matches the system dark preference **Windows 11 Only**\n    Mica,\n    /// Mica effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**\n    MicaDark,\n    /// Mica effect with light mode **Windows 11 Only**\n    MicaLight,\n    /// Tabbed effect that matches the system dark preference **Windows 11 Only**\n    Tabbed,\n    /// Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**\n    TabbedDark,\n    /// Tabbed effect with light mode **Windows 11 Only**\n    TabbedLight,\n    /// **Windows 7/10/11(22H1) Only**\n    ///\n    /// ## Notes\n    ///\n    /// This effect has bad performance when resizing/dragging the window on Windows 11 build 22621.\n    Blur,\n    /// **Windows 10/11 Only**\n    ///\n    /// ## Notes\n    ///\n    /// This effect has bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000.\n    Acrylic,\n  }\n\n  /// Window effect state **macOS only**\n  ///\n  /// <https://developer.apple.com/documentation/appkit/nsvisualeffectview/state>\n  #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]\n  #[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n  #[serde(rename_all = \"camelCase\")]\n  pub enum WindowEffectState {\n    /// Make window effect state follow the window's active state\n    FollowsWindowActiveState,\n    /// Make window effect state always active\n    Active,\n    /// Make window effect state always inactive\n    Inactive,\n  }\n}\n\npub use window_effects::{WindowEffect, WindowEffectState};\n\n/// How the window title bar should be displayed on macOS.\n#[derive(Debug, Clone, PartialEq, Eq, Copy, Default)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[non_exhaustive]\npub enum TitleBarStyle {\n  /// A normal title bar.\n  #[default]\n  Visible,\n  /// Makes the title bar transparent, so the window background color is shown instead.\n  ///\n  /// Useful if you don't need to have actual HTML under the title bar. This lets you avoid the caveats of using `TitleBarStyle::Overlay`. Will be more useful when Tauri lets you set a custom window background color.\n  Transparent,\n  /// Shows the title bar as a transparent overlay over the window's content.\n  ///\n  /// Keep in mind:\n  /// - The height of the title bar is different on different OS versions, which can lead to window the controls and title not being where you don't expect.\n  /// - You need to define a custom drag region to make your window draggable, however due to a limitation you can't drag the window when it's not in focus <https://github.com/tauri-apps/tauri/issues/4316>.\n  /// - The color of the window title depends on the system theme.\n  Overlay,\n}\n\nimpl Serialize for TitleBarStyle {\n  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    serializer.serialize_str(self.to_string().as_ref())\n  }\n}\n\nimpl<'de> Deserialize<'de> for TitleBarStyle {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let s = String::deserialize(deserializer)?;\n    Ok(match s.to_lowercase().as_str() {\n      \"transparent\" => Self::Transparent,\n      \"overlay\" => Self::Overlay,\n      _ => Self::Visible,\n    })\n  }\n}\n\nimpl Display for TitleBarStyle {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    write!(\n      f,\n      \"{}\",\n      match self {\n        Self::Visible => \"Visible\",\n        Self::Transparent => \"Transparent\",\n        Self::Overlay => \"Overlay\",\n      }\n    )\n  }\n}\n\n/// System theme.\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[non_exhaustive]\npub enum Theme {\n  /// Light theme.\n  Light,\n  /// Dark theme.\n  Dark,\n}\n\nimpl Serialize for Theme {\n  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n  where\n    S: Serializer,\n  {\n    serializer.serialize_str(self.to_string().as_ref())\n  }\n}\n\nimpl<'de> Deserialize<'de> for Theme {\n  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n  where\n    D: Deserializer<'de>,\n  {\n    let s = String::deserialize(deserializer)?;\n    Ok(match s.to_lowercase().as_str() {\n      \"dark\" => Self::Dark,\n      _ => Self::Light,\n    })\n  }\n}\n\nimpl Display for Theme {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    write!(\n      f,\n      \"{}\",\n      match self {\n        Self::Light => \"light\",\n        Self::Dark => \"dark\",\n      }\n    )\n  }\n}\n\n/// Information about environment variables.\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct Env {\n  /// The APPIMAGE environment variable.\n  #[cfg(target_os = \"linux\")]\n  pub appimage: Option<std::ffi::OsString>,\n  /// The APPDIR environment variable.\n  #[cfg(target_os = \"linux\")]\n  pub appdir: Option<std::ffi::OsString>,\n  /// The command line arguments of the current process.\n  pub args_os: Vec<OsString>,\n}\n\n#[allow(clippy::derivable_impls)]\nimpl Default for Env {\n  fn default() -> Self {\n    let args_os = std::env::args_os().collect();\n    #[cfg(target_os = \"linux\")]\n    {\n      let env = Self {\n        #[cfg(target_os = \"linux\")]\n        appimage: std::env::var_os(\"APPIMAGE\"),\n        #[cfg(target_os = \"linux\")]\n        appdir: std::env::var_os(\"APPDIR\"),\n        args_os,\n      };\n      if env.appimage.is_some() || env.appdir.is_some() {\n        // validate that we're actually running on an AppImage\n        // an AppImage is mounted to `/$TEMPDIR/.mount_${appPrefix}${hash}`\n        // see <https://github.com/AppImage/AppImageKit/blob/1681fd84dbe09c7d9b22e13cdb16ea601aa0ec47/src/runtime.c#L501>\n        // note that it is safe to use `std::env::current_exe` here since we just loaded an AppImage.\n        let is_temp = std::env::current_exe()\n          .map(|p| {\n            p.display()\n              .to_string()\n              .starts_with(&format!(\"{}/.mount_\", std::env::temp_dir().display()))\n          })\n          .unwrap_or(true);\n\n        if !is_temp {\n          log::warn!(\"`APPDIR` or `APPIMAGE` environment variable found but this application was not detected as an AppImage; this might be a security issue.\");\n        }\n      }\n      env\n    }\n    #[cfg(not(target_os = \"linux\"))]\n    {\n      Self { args_os }\n    }\n  }\n}\n\n/// The result type of `tauri-utils`.\npub type Result<T> = std::result::Result<T, Error>;\n\n/// The error type of `tauri-utils`.\n#[derive(Debug, thiserror::Error)]\n#[non_exhaustive]\npub enum Error {\n  /// Target triple architecture error\n  #[error(\"Unable to determine target-architecture\")]\n  Architecture,\n  /// Target triple OS error\n  #[error(\"Unable to determine target-os\")]\n  Os,\n  /// Target triple environment error\n  #[error(\"Unable to determine target-environment\")]\n  Environment,\n  /// Tried to get resource on an unsupported platform\n  #[error(\"Unsupported platform for reading resources\")]\n  UnsupportedPlatform,\n  /// Get parent process error\n  #[error(\"Could not get parent process\")]\n  ParentProcess,\n  /// Get parent process PID error\n  #[error(\"Could not get parent PID\")]\n  ParentPid,\n  /// Get child process error\n  #[error(\"Could not get child process\")]\n  ChildProcess,\n  /// IO error\n  #[error(\"{0}\")]\n  Io(#[from] std::io::Error),\n  /// Invalid pattern.\n  #[error(\"invalid pattern `{0}`. Expected either `brownfield` or `isolation`.\")]\n  InvalidPattern(String),\n  /// Invalid glob pattern.\n  #[cfg(feature = \"resources\")]\n  #[error(\"{0}\")]\n  GlobPattern(#[from] glob::PatternError),\n  /// Failed to use glob pattern.\n  #[cfg(feature = \"resources\")]\n  #[error(\"`{0}`\")]\n  Glob(#[from] glob::GlobError),\n  /// Glob pattern did not find any results.\n  #[cfg(feature = \"resources\")]\n  #[error(\"glob pattern {0} path not found or didn't match any files.\")]\n  GlobPathNotFound(String),\n  /// Error walking directory.\n  #[cfg(feature = \"resources\")]\n  #[error(\"{0}\")]\n  WalkdirError(#[from] walkdir::Error),\n  /// Not allowed to walk dir.\n  #[cfg(feature = \"resources\")]\n  #[error(\"could not walk directory `{0}`, try changing `allow_walk` to true on the `ResourcePaths` constructor.\")]\n  NotAllowedToWalkDir(std::path::PathBuf),\n  /// Resource path doesn't exist\n  #[cfg(feature = \"resources\")]\n  #[error(\"resource path `{0}` doesn't exist\")]\n  ResourcePathNotFound(std::path::PathBuf),\n}\n\n/// Reconstructs a path from its components using the platform separator then converts it to String and removes UNC prefixes on Windows if it exists.\npub fn display_path<P: AsRef<Path>>(p: P) -> String {\n  dunce::simplified(&p.as_ref().components().collect::<PathBuf>())\n    .display()\n    .to_string()\n}\n\n/// Write the file only if the content of the existing file (if any) is different.\n///\n/// This will always write unless the file exists with identical content.\npub fn write_if_changed<P, C>(path: P, content: C) -> std::io::Result<()>\nwhere\n  P: AsRef<Path>,\n  C: AsRef<[u8]>,\n{\n  if let Ok(existing) = std::fs::read(&path) {\n    if existing == content.as_ref() {\n      return Ok(());\n    }\n  }\n\n  std::fs::write(path, content)\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/mime_type.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Determine a mime type from a URI or file contents.\n\nuse std::fmt;\n\nconst MIMETYPE_PLAIN: &str = \"text/plain\";\n\n/// [Web Compatible MimeTypes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#important_mime_types_for_web_developers)\n#[allow(missing_docs)]\npub enum MimeType {\n  Css,\n  Csv,\n  Html,\n  Ico,\n  Js,\n  Json,\n  Jsonld,\n  Mp4,\n  OctetStream,\n  Rtf,\n  Svg,\n  Txt,\n}\n\nimpl std::fmt::Display for MimeType {\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n    let mime = match self {\n      MimeType::Css => \"text/css\",\n      MimeType::Csv => \"text/csv\",\n      MimeType::Html => \"text/html\",\n      MimeType::Ico => \"image/vnd.microsoft.icon\",\n      MimeType::Js => \"text/javascript\",\n      MimeType::Json => \"application/json\",\n      MimeType::Jsonld => \"application/ld+json\",\n      MimeType::Mp4 => \"video/mp4\",\n      MimeType::OctetStream => \"application/octet-stream\",\n      MimeType::Rtf => \"application/rtf\",\n      MimeType::Svg => \"image/svg+xml\",\n      MimeType::Txt => MIMETYPE_PLAIN,\n    };\n    write!(f, \"{mime}\")\n  }\n}\n\nimpl MimeType {\n  /// parse a URI suffix to convert text/plain mimeType to their actual web compatible mimeType.\n  pub fn parse_from_uri(uri: &str) -> MimeType {\n    Self::parse_from_uri_with_fallback(uri, Self::Html)\n  }\n\n  /// parse a URI suffix to convert text/plain mimeType to their actual web compatible mimeType with specified fallback for unknown file extensions.\n  pub fn parse_from_uri_with_fallback(uri: &str, fallback: MimeType) -> MimeType {\n    let suffix = uri.split('.').next_back();\n    match suffix {\n      Some(\"bin\") => Self::OctetStream,\n      Some(\"css\" | \"less\" | \"sass\" | \"styl\") => Self::Css,\n      Some(\"csv\") => Self::Csv,\n      Some(\"html\") => Self::Html,\n      Some(\"ico\") => Self::Ico,\n      Some(\"js\") => Self::Js,\n      Some(\"json\") => Self::Json,\n      Some(\"jsonld\") => Self::Jsonld,\n      Some(\"mjs\") => Self::Js,\n      Some(\"mp4\") => Self::Mp4,\n      Some(\"rtf\") => Self::Rtf,\n      Some(\"svg\") => Self::Svg,\n      Some(\"txt\") => Self::Txt,\n      // Assume HTML when a TLD is found for eg. `wry:://tauri.app` | `wry://hello.com`\n      Some(_) => fallback,\n      // using octet stream according to this:\n      // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types>\n      None => Self::OctetStream,\n    }\n  }\n\n  /// infer mimetype from content (or) URI if needed.\n  pub fn parse(content: &[u8], uri: &str) -> String {\n    Self::parse_with_fallback(content, uri, Self::Html)\n  }\n  /// infer mimetype from content (or) URI if needed with specified fallback for unknown file extensions.\n  pub fn parse_with_fallback(content: &[u8], uri: &str, fallback: MimeType) -> String {\n    let mime = if uri.ends_with(\".svg\") {\n      // when reading svg, we can't use `infer`\n      None\n    } else {\n      infer::get(content).map(|info| info.mime_type())\n    };\n\n    match mime {\n      Some(mime) if mime == MIMETYPE_PLAIN => {\n        Self::parse_from_uri_with_fallback(uri, fallback).to_string()\n      }\n      None => Self::parse_from_uri_with_fallback(uri, fallback).to_string(),\n      Some(mime) => mime.to_string(),\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use super::*;\n\n  #[test]\n  fn should_parse_mimetype_from_uri() {\n    let css = MimeType::parse_from_uri(\n      \"https://unpkg.com/browse/bootstrap@4.1.0/dist/css/bootstrap-grid.css\",\n    )\n    .to_string();\n    assert_eq!(css, \"text/css\".to_string());\n\n    let csv: String = MimeType::parse_from_uri(\"https://example.com/random.csv\").to_string();\n    assert_eq!(csv, \"text/csv\".to_string());\n\n    let ico: String =\n      MimeType::parse_from_uri(\"https://icons.duckduckgo.com/ip3/microsoft.com.ico\").to_string();\n    assert_eq!(ico, String::from(\"image/vnd.microsoft.icon\"));\n\n    let html: String = MimeType::parse_from_uri(\"https://tauri.app/index.html\").to_string();\n    assert_eq!(html, String::from(\"text/html\"));\n\n    let js: String =\n      MimeType::parse_from_uri(\"https://unpkg.com/react@17.0.1/umd/react.production.min.js\")\n        .to_string();\n    assert_eq!(js, \"text/javascript\".to_string());\n\n    let json: String =\n      MimeType::parse_from_uri(\"https://unpkg.com/browse/react@17.0.1/build-info.json\").to_string();\n    assert_eq!(json, String::from(\"application/json\"));\n\n    let jsonld: String = MimeType::parse_from_uri(\"https:/example.com/hello.jsonld\").to_string();\n    assert_eq!(jsonld, String::from(\"application/ld+json\"));\n\n    let mjs: String = MimeType::parse_from_uri(\"https://example.com/bundled.mjs\").to_string();\n    assert_eq!(mjs, String::from(\"text/javascript\"));\n\n    let mp4: String = MimeType::parse_from_uri(\"https://example.com/video.mp4\").to_string();\n    assert_eq!(mp4, String::from(\"video/mp4\"));\n\n    let rtf: String = MimeType::parse_from_uri(\"https://example.com/document.rtf\").to_string();\n    assert_eq!(rtf, String::from(\"application/rtf\"));\n\n    let svg: String = MimeType::parse_from_uri(\"https://example.com/picture.svg\").to_string();\n    assert_eq!(svg, String::from(\"image/svg+xml\"));\n\n    let txt: String = MimeType::parse_from_uri(\"https://example.com/file.txt\").to_string();\n    assert_eq!(txt, String::from(\"text/plain\"));\n\n    let custom_scheme = MimeType::parse_from_uri(\"wry://tauri.app\").to_string();\n    assert_eq!(custom_scheme, String::from(\"text/html\"));\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/pattern/isolation.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/**\n * IMPORTANT: See ipc.js for the main frame implementation.\n * main frame -> isolation frame = isolation payload\n * isolation frame -> main frame = isolation message\n */\n\n;(async function () {\n  /**\n   * Sends the message to the isolation frame.\n   * @param {any} message\n   */\n  function sendMessage(message) {\n    window.parent.postMessage(message, '*')\n  }\n\n  /**\n   * @type {string} - The main frame origin.\n   */\n  const origin = __TEMPLATE_origin__\n\n  /**\n   * @type {Uint8Array} - Injected by Tauri during runtime\n   */\n  const aesGcmKeyRaw = new Uint8Array(__TEMPLATE_runtime_aes_gcm_key__)\n\n  /**\n   * @type {CryptoKey}\n   */\n  const aesGcmKey = await window.crypto.subtle.importKey(\n    'raw',\n    aesGcmKeyRaw,\n    'AES-GCM',\n    false,\n    ['encrypt']\n  )\n\n  /**\n   * @param {object} data\n   * @return {Promise<{nonce: number[], payload: number[]}>}\n   */\n  async function encrypt(payload) {\n    const algorithm = Object.create(null)\n    algorithm.name = 'AES-GCM'\n    algorithm.iv = window.crypto.getRandomValues(new Uint8Array(12))\n\n    const { contentType, data } = __RAW_process_ipc_message_fn__(payload)\n\n    const message =\n      typeof data === 'string'\n        ? new TextEncoder().encode(data)\n        : ArrayBuffer.isView(data) || data instanceof ArrayBuffer\n          ? data\n          : new Uint8Array(data)\n\n    return window.crypto.subtle\n      .encrypt(algorithm, aesGcmKey, message)\n      .then((payload) => {\n        const result = Object.create(null)\n        result.nonce = Array.from(new Uint8Array(algorithm.iv))\n        result.payload = Array.from(new Uint8Array(payload))\n        result.contentType = contentType\n        return result\n      })\n  }\n\n  /**\n   * Detects if a message event is a valid isolation message.\n   *\n   * @param {MessageEvent<object>} event - a message event that is expected to be an isolation message\n   * @return {boolean} - if the event was a valid isolation message\n   */\n  function isIsolationMessage(data) {\n    if (typeof data === 'object' && typeof data.payload === 'object') {\n      const keys = data.payload ? Object.keys(data.payload) : []\n      return (\n        keys.length > 0\n        && keys.every(\n          (key) => key === 'nonce' || key === 'payload' || key === 'contentType'\n        )\n      )\n    }\n    return false\n  }\n\n  /**\n   * Detect if a message event is a valid isolation payload.\n   *\n   * @param {MessageEvent<object>} event - a message event that is expected to be an isolation payload\n   * @return boolean\n   */\n  function isIsolationPayload(data) {\n    return (\n      typeof data === 'object'\n      && 'callback' in data\n      && 'error' in data\n      && !isIsolationMessage(data)\n    )\n  }\n\n  /**\n   * Handle incoming payload events.\n   * @param {MessageEvent<any>} event\n   */\n  async function payloadHandler(event) {\n    if (event.origin !== origin || !isIsolationPayload(event.data)) {\n      return\n    }\n\n    let data = event.data\n\n    if (typeof window.__TAURI_ISOLATION_HOOK__ === 'function') {\n      // await even if it's not async so that we can support async ones\n      data = await window.__TAURI_ISOLATION_HOOK__(data)\n    }\n\n    const message = Object.create(null)\n    message.cmd = data.cmd\n    message.callback = data.callback\n    message.error = data.error\n    message.options = data.options\n    message.payload = await encrypt(data.payload)\n    sendMessage(message)\n  }\n\n  window.addEventListener('message', payloadHandler, false)\n\n  /**\n   * @type {number} - How many milliseconds to wait between ready checks\n   */\n  const readyIntervalMs = 50\n\n  /**\n   * Wait until this Isolation context is ready to receive messages, and let the main frame know.\n   */\n  function waitUntilReady() {\n    // consider either a function or an explicitly set null value as the ready signal\n    if (\n      typeof window.__TAURI_ISOLATION_HOOK__ === 'function'\n      || window.__TAURI_ISOLATION_HOOK__ === null\n    ) {\n      sendMessage('__TAURI_ISOLATION_READY__')\n    } else {\n      setTimeout(waitUntilReady, readyIntervalMs)\n    }\n  }\n\n  setTimeout(waitUntilReady, readyIntervalMs)\n})()\n\ndocument.currentScript?.remove()\n"
  },
  {
    "path": "crates/tauri-utils/src/pattern/isolation.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::array::TryFromSliceError;\nuse std::borrow::Cow;\nuse std::fmt::{Debug, Formatter};\nuse std::string::FromUtf8Error;\n\nuse aes_gcm::aead::Aead;\nuse aes_gcm::{Aes256Gcm, KeyInit, Nonce};\nuse getrandom::Error as CsprngError;\nuse serialize_to_javascript::{default_template, Template};\n\n/// The style for the isolation iframe.\npub const IFRAME_STYLE: &str = \"#__tauri_isolation__ { display: none !important }\";\n\n/// Errors that can occur during Isolation keys generation.\n#[derive(Debug, thiserror::Error)]\n#[non_exhaustive]\npub enum Error {\n  /// Something went wrong with the CSPRNG.\n  #[error(\"CSPRNG error\")]\n  Csprng(#[from] CsprngError),\n\n  /// Something went wrong with decrypting an AES-GCM payload\n  #[error(\"AES-GCM\")]\n  Aes,\n\n  /// Nonce was not 96 bits\n  #[error(\"Nonce: {0}\")]\n  NonceSize(#[from] TryFromSliceError),\n\n  /// Payload was not valid utf8\n  #[error(\"{0}\")]\n  Utf8(#[from] FromUtf8Error),\n\n  /// Invalid json format\n  #[error(\"{0}\")]\n  Json(#[from] serde_json::Error),\n}\n\n/// A formatted AES-GCM cipher instance along with the key used to initialize it.\n#[derive(Clone)]\npub struct AesGcmPair {\n  raw: [u8; 32],\n  key: Aes256Gcm,\n}\n\nimpl Debug for AesGcmPair {\n  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n    write!(f, \"AesGcmPair(...)\")\n  }\n}\n\nimpl AesGcmPair {\n  fn new() -> Result<Self, Error> {\n    let mut raw = [0u8; 32];\n    getrandom::fill(&mut raw)?;\n    let key = aes_gcm::Key::<Aes256Gcm>::from_slice(&raw);\n    Ok(Self {\n      raw,\n      key: Aes256Gcm::new(key),\n    })\n  }\n\n  /// The raw value used to create the AES-GCM key\n  pub fn raw(&self) -> &[u8; 32] {\n    &self.raw\n  }\n\n  /// The formatted AES-GCM key\n  pub fn key(&self) -> &Aes256Gcm {\n    &self.key\n  }\n\n  #[doc(hidden)]\n  pub fn encrypt(&self, nonce: &[u8; 12], payload: &[u8]) -> Result<Vec<u8>, Error> {\n    self\n      .key\n      .encrypt(nonce.into(), payload)\n      .map_err(|_| self::Error::Aes)\n  }\n}\n\n/// All cryptographic keys required for Isolation encryption\n#[derive(Debug, Clone)]\npub struct Keys {\n  /// AES-GCM key\n  aes_gcm: AesGcmPair,\n}\n\nimpl Keys {\n  /// Securely generate required keys for Isolation encryption.\n  pub fn new() -> Result<Self, Error> {\n    AesGcmPair::new().map(|aes_gcm| Self { aes_gcm })\n  }\n\n  /// The AES-GCM data (and raw data).\n  pub fn aes_gcm(&self) -> &AesGcmPair {\n    &self.aes_gcm\n  }\n\n  /// Decrypts a message using the generated keys.\n  pub fn decrypt(&self, raw: RawIsolationPayload<'_>) -> Result<Vec<u8>, Error> {\n    let RawIsolationPayload { nonce, payload, .. } = raw;\n    let nonce: [u8; 12] = nonce.as_ref().try_into()?;\n    self\n      .aes_gcm\n      .key\n      .decrypt(Nonce::from_slice(&nonce), payload.as_ref())\n      .map_err(|_| self::Error::Aes)\n  }\n}\n\n/// Raw representation of\n#[derive(Debug, serde::Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct RawIsolationPayload<'a> {\n  nonce: Cow<'a, [u8]>,\n  payload: Cow<'a, [u8]>,\n  content_type: Cow<'a, str>,\n}\n\nimpl<'a> RawIsolationPayload<'a> {\n  /// Content type of this payload.\n  pub fn content_type(&self) -> &Cow<'a, str> {\n    &self.content_type\n  }\n}\n\nimpl<'a> TryFrom<&'a Vec<u8>> for RawIsolationPayload<'a> {\n  type Error = Error;\n\n  fn try_from(value: &'a Vec<u8>) -> Result<Self, Self::Error> {\n    serde_json::from_slice(value).map_err(Into::into)\n  }\n}\n\n/// The Isolation JavaScript template meant to be injected during codegen.\n///\n/// Note: This struct is not considered part of the stable API\n#[derive(Template)]\n#[default_template(\"isolation.js\")]\npub struct IsolationJavascriptCodegen {\n  // this template intentionally does not include the runtime field\n}\n\n/// The Isolation JavaScript template meant to be injected during runtime.\n///\n/// Note: This struct is not considered part of the stable API\n#[derive(Template)]\n#[default_template(\"isolation.js\")]\npub struct IsolationJavascriptRuntime<'a> {\n  /// The key used on the Rust backend and the Isolation Javascript\n  pub runtime_aes_gcm_key: &'a [u8; 32],\n  /// The origin the isolation application is expecting messages from.\n  pub origin: String,\n  /// The function that processes the IPC message.\n  #[raw]\n  pub process_ipc_message_fn: &'a str,\n}\n\n#[cfg(test)]\nmod test {\n  #[test]\n  fn create_keys() -> Result<(), Box<dyn std::error::Error>> {\n    let _ = super::Keys::new()?;\n    Ok(())\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/pattern/mod.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/// Handling the Tauri \"Isolation\" Pattern.\n#[cfg(feature = \"isolation\")]\npub mod isolation;\n"
  },
  {
    "path": "crates/tauri-utils/src/platform/starting_binary.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse ctor::ctor;\nuse std::{\n  io::{Error, ErrorKind, Result},\n  path::{Path, PathBuf},\n};\n\n/// A cached version of the current binary using [`ctor`] to cache it before even `main` runs.\n#[ctor]\n#[used]\npub(super) static STARTING_BINARY: StartingBinary = StartingBinary::new();\n\n/// Represents a binary path that was cached when the program was loaded.\npub(super) struct StartingBinary(std::io::Result<PathBuf>);\n\nimpl StartingBinary {\n  /// Find the starting executable as safely as possible.\n  fn new() -> Self {\n    // see notes on current_exe() for security implications\n    let dangerous_path = match std::env::current_exe() {\n      Ok(dangerous_path) => dangerous_path,\n      error @ Err(_) => return Self(error),\n    };\n\n    // note: this only checks symlinks on problematic platforms, see implementation below\n    if let Some(symlink) = Self::has_symlink(&dangerous_path) {\n      return Self(Err(Error::new(\n        ErrorKind::InvalidData,\n        format!(\"StartingBinary found current_exe() that contains a symlink on a non-allowed platform: {}\", symlink.display()),\n      )));\n    }\n\n    // we canonicalize the path to resolve any symlinks to the real exe path\n    Self(dangerous_path.canonicalize())\n  }\n\n  /// A clone of the [`PathBuf`] found to be the starting path.\n  ///\n  /// Because [`Error`] is not clone-able, it is recreated instead.\n  pub(super) fn cloned(&self) -> Result<PathBuf> {\n    // false positive\n    #[allow(clippy::useless_asref)]\n    self\n      .0\n      .as_ref()\n      .map(Clone::clone)\n      .map_err(|e| Error::new(e.kind(), e.to_string()))\n  }\n\n  /// We only care about checking this on macOS currently, as it has the least symlink protections.\n  #[cfg(any(\n    not(target_os = \"macos\"),\n    feature = \"process-relaunch-dangerous-allow-symlink-macos\"\n  ))]\n  fn has_symlink(_: &Path) -> Option<&Path> {\n    None\n  }\n\n  /// We only care about checking this on macOS currently, as it has the least symlink protections.\n  #[cfg(all(\n    target_os = \"macos\",\n    not(feature = \"process-relaunch-dangerous-allow-symlink-macos\")\n  ))]\n  fn has_symlink(path: &Path) -> Option<&Path> {\n    path.ancestors().find(|ancestor| {\n      matches!(\n        ancestor\n          .symlink_metadata()\n          .as_ref()\n          .map(std::fs::Metadata::file_type)\n          .as_ref()\n          .map(std::fs::FileType::is_symlink),\n        Ok(true)\n      )\n    })\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/platform.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Platform helper functions.\n\nuse std::{fmt::Display, path::PathBuf};\n\nuse serde::{Deserialize, Serialize};\n\nuse crate::{config::BundleType, Env, PackageInfo};\n\nmod starting_binary;\n\n/// URI prefix of a Tauri asset.\n///\n/// This is referenced in the Tauri Android library,\n/// which resolves these assets to a file descriptor.\n#[cfg(target_os = \"android\")]\npub const ANDROID_ASSET_PROTOCOL_URI_PREFIX: &str = \"asset://localhost/\";\n\n/// Platform target.\n#[derive(PartialEq, Eq, Copy, Debug, Clone, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\")]\n#[non_exhaustive]\npub enum Target {\n  /// MacOS.\n  #[serde(rename = \"macOS\")]\n  MacOS,\n  /// Windows.\n  Windows,\n  /// Linux.\n  Linux,\n  /// Android.\n  Android,\n  /// iOS.\n  #[serde(rename = \"iOS\")]\n  Ios,\n}\n\nimpl Display for Target {\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n    write!(\n      f,\n      \"{}\",\n      match self {\n        Self::MacOS => \"macOS\",\n        Self::Windows => \"windows\",\n        Self::Linux => \"linux\",\n        Self::Android => \"android\",\n        Self::Ios => \"iOS\",\n      }\n    )\n  }\n}\n\nimpl Target {\n  /// Parses the target from the given target triple.\n  pub fn from_triple(target: &str) -> Self {\n    if target.contains(\"darwin\") {\n      Self::MacOS\n    } else if target.contains(\"windows\") {\n      Self::Windows\n    } else if target.contains(\"android\") {\n      Self::Android\n    } else if target.contains(\"ios\") {\n      Self::Ios\n    } else {\n      Self::Linux\n    }\n  }\n\n  /// Gets the current build target.\n  pub fn current() -> Self {\n    if cfg!(target_os = \"macos\") {\n      Self::MacOS\n    } else if cfg!(target_os = \"windows\") {\n      Self::Windows\n    } else if cfg!(target_os = \"ios\") {\n      Self::Ios\n    } else if cfg!(target_os = \"android\") {\n      Self::Android\n    } else {\n      Self::Linux\n    }\n  }\n\n  /// Whether the target is mobile or not.\n  pub fn is_mobile(&self) -> bool {\n    matches!(self, Target::Android | Target::Ios)\n  }\n\n  /// Whether the target is desktop or not.\n  pub fn is_desktop(&self) -> bool {\n    !self.is_mobile()\n  }\n}\n\n/// Retrieves the currently running binary's path, taking into account security considerations.\n///\n/// The path is cached as soon as possible (before even `main` runs) and that value is returned\n/// repeatedly instead of fetching the path every time. It is possible for the path to not be found,\n/// or explicitly disabled (see following macOS specific behavior).\n///\n/// # Platform-specific behavior\n///\n/// On `macOS`, this function will return an error if the original path contained any symlinks\n/// due to less protection on macOS regarding symlinks. This behavior can be disabled by setting the\n/// `process-relaunch-dangerous-allow-symlink-macos` feature, although it is *highly discouraged*.\n///\n/// # Security\n///\n/// If the above platform-specific behavior does **not** take place, this function uses the\n/// following resolution.\n///\n/// We canonicalize the path we received from [`std::env::current_exe`] to resolve any soft links.\n/// This avoids the usual issue of needing the file to exist at the passed path because a valid\n/// current executable result for our purpose should always exist. Notably,\n/// [`std::env::current_exe`] also has a security section that goes over a theoretical attack using\n/// hard links. Let's cover some specific topics that relate to different ways an attacker might\n/// try to trick this function into returning the wrong binary path.\n///\n/// ## Symlinks (\"Soft Links\")\n///\n/// [`std::path::Path::canonicalize`] is used to resolve symbolic links to the original path,\n/// including nested symbolic links (`link2 -> link1 -> bin`). On macOS, any results that include\n/// a symlink are rejected by default due to lesser symlink protections. This can be disabled,\n/// **although discouraged**, with the `process-relaunch-dangerous-allow-symlink-macos` feature.\n///\n/// ## Hard Links\n///\n/// A [Hard Link] is a named entry that points to a file in the file system.\n/// On most systems, this is what you would think of as a \"file\". The term is\n/// used on filesystems that allow multiple entries to point to the same file.\n/// The linked [Hard Link] Wikipedia page provides a decent overview.\n///\n/// In short, unless the attacker was able to create the link with elevated\n/// permissions, it should generally not be possible for them to hard link\n/// to a file they do not have permissions to - with exception to possible\n/// operating system exploits.\n///\n/// There are also some platform-specific information about this below.\n///\n/// ### Windows\n///\n/// Windows requires a permission to be set for the user to create a symlink\n/// or a hard link, regardless of ownership status of the target. Elevated\n/// permissions users have the ability to create them.\n///\n/// ### macOS\n///\n/// macOS allows for the creation of symlinks and hard links to any file.\n/// Accessing through those links will fail if the user who owns the links\n/// does not have the proper permissions on the original file.\n///\n/// ### Linux\n///\n/// Linux allows for the creation of symlinks to any file. Accessing the\n/// symlink will fail if the user who owns the symlink does not have the\n/// proper permissions on the original file.\n///\n/// Linux additionally provides a kernel hardening feature since version\n/// 3.6 (30 September 2012). Most distributions since then have enabled\n/// the protection (setting `fs.protected_hardlinks = 1`) by default, which\n/// means that a vast majority of desktop Linux users should have it enabled.\n/// **The feature prevents the creation of hardlinks that the user does not own\n/// or have read/write access to.** [See the patch that enabled this].\n///\n/// [Hard Link]: https://en.wikipedia.org/wiki/Hard_link\n/// [See the patch that enabled this]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=800179c9b8a1e796e441674776d11cd4c05d61d7\npub fn current_exe() -> std::io::Result<PathBuf> {\n  self::starting_binary::STARTING_BINARY.cloned()\n}\n\n/// Try to determine the current target triple.\n///\n/// Returns a target triple (e.g. `x86_64-unknown-linux-gnu` or `i686-pc-windows-msvc`) or an\n/// `Error::Config` if the current config cannot be determined or is not some combination of the\n/// following values:\n/// `linux, mac, windows` -- `i686, x86, armv7` -- `gnu, musl, msvc`\n///\n/// * Errors:\n///     * Unexpected system config\npub fn target_triple() -> crate::Result<String> {\n  let arch = if cfg!(target_arch = \"x86\") {\n    \"i686\"\n  } else if cfg!(target_arch = \"x86_64\") {\n    \"x86_64\"\n  } else if cfg!(target_arch = \"arm\") {\n    \"armv7\"\n  } else if cfg!(target_arch = \"aarch64\") {\n    \"aarch64\"\n  } else if cfg!(target_arch = \"riscv64\") {\n    \"riscv64\"\n  } else {\n    return Err(crate::Error::Architecture);\n  };\n\n  let os = if cfg!(target_os = \"linux\") {\n    \"unknown-linux\"\n  } else if cfg!(target_os = \"macos\") {\n    \"apple-darwin\"\n  } else if cfg!(target_os = \"windows\") {\n    \"pc-windows\"\n  } else if cfg!(target_os = \"freebsd\") {\n    \"unknown-freebsd\"\n  } else {\n    return Err(crate::Error::Os);\n  };\n\n  let os = if cfg!(target_os = \"macos\") || cfg!(target_os = \"freebsd\") {\n    String::from(os)\n  } else {\n    let env = if cfg!(target_env = \"gnu\") {\n      \"gnu\"\n    } else if cfg!(target_env = \"musl\") {\n      \"musl\"\n    } else if cfg!(target_env = \"msvc\") {\n      \"msvc\"\n    } else {\n      return Err(crate::Error::Environment);\n    };\n\n    format!(\"{os}-{env}\")\n  };\n\n  Ok(format!(\"{arch}-{os}\"))\n}\n\n#[cfg(all(not(test), not(target_os = \"android\")))]\nfn is_cargo_output_directory(path: &std::path::Path) -> bool {\n  path.join(\".cargo-lock\").exists()\n}\n\n#[cfg(test)]\nconst CARGO_OUTPUT_DIRECTORIES: &[&str] = &[\"debug\", \"release\", \"custom-profile\"];\n\n#[cfg(test)]\nfn is_cargo_output_directory(path: &std::path::Path) -> bool {\n  let Some(last_component) = path.components().next_back() else {\n    return false;\n  };\n  CARGO_OUTPUT_DIRECTORIES\n    .iter()\n    .any(|dirname| &last_component.as_os_str() == dirname)\n}\n\n/// Computes the resource directory of the current environment.\n///\n/// ## Platform-specific\n///\n/// - **Windows:** Resolves to the directory that contains the main executable.\n/// - **Linux:** When running in an AppImage, the `APPDIR` variable will be set to\n///   the mounted location of the app, and the resource dir will be `${APPDIR}/usr/lib/${exe_name}`.\n///   If not running in an AppImage, the path is `/usr/lib/${exe_name}`.\n///   When running the app from `src-tauri/target/(debug|release)/`, the path is `${exe_dir}/../lib/${exe_name}`.\n/// - **macOS:** Resolves to `${exe_dir}/../Resources` (inside .app).\n/// - **iOS:** Resolves to `${exe_dir}/assets`.\n/// - **Android:** Currently the resources are stored in the APK as assets so it's not a normal file system path,\n///   we return a special URI prefix `asset://localhost/` here that can be used with the [file system plugin](https://tauri.app/plugin/file-system/),\n///   with that, you can read the files through [`FsExt::fs`](https://docs.rs/tauri-plugin-fs/latest/tauri_plugin_fs/trait.FsExt.html#tymethod.fs)\n///   like this: `app.fs().read_to_string(app.path().resource_dir().unwrap().join(\"resource\"));`\npub fn resource_dir(package_info: &PackageInfo, env: &Env) -> crate::Result<PathBuf> {\n  #[cfg(target_os = \"android\")]\n  return resource_dir_android(package_info, env);\n  #[cfg(not(target_os = \"android\"))]\n  {\n    let exe = current_exe()?;\n    resource_dir_from(exe, package_info, env)\n  }\n}\n\n#[cfg(target_os = \"android\")]\nfn resource_dir_android(_package_info: &PackageInfo, _env: &Env) -> crate::Result<PathBuf> {\n  Ok(PathBuf::from(ANDROID_ASSET_PROTOCOL_URI_PREFIX))\n}\n\n#[cfg(not(target_os = \"android\"))]\n#[allow(unused_variables)]\nfn resource_dir_from<P: AsRef<std::path::Path>>(\n  exe: P,\n  package_info: &PackageInfo,\n  env: &Env,\n) -> crate::Result<PathBuf> {\n  let exe_dir = exe.as_ref().parent().expect(\"failed to get exe directory\");\n  let curr_dir = exe_dir.display().to_string();\n\n  let parts: Vec<&str> = curr_dir.split(std::path::MAIN_SEPARATOR).collect();\n  let len = parts.len();\n\n  // Check if running from the Cargo output directory, which means it's an executable in a development machine\n  // We check if the binary is inside a `target` folder which can be either `target/$profile` or `target/$triple/$profile`\n  // and see if there's a .cargo-lock file along the executable\n  // This ensures the check is safer so it doesn't affect apps in production\n  // Windows also includes the resources in the executable folder so we check that too\n  if cfg!(target_os = \"windows\")\n    || ((len >= 2 && parts[len - 2] == \"target\") || (len >= 3 && parts[len - 3] == \"target\"))\n      && is_cargo_output_directory(exe_dir)\n  {\n    return Ok(exe_dir.to_path_buf());\n  }\n\n  #[allow(unused_mut, unused_assignments)]\n  let mut res = Err(crate::Error::UnsupportedPlatform);\n\n  #[cfg(target_os = \"linux\")]\n  {\n    // (canonicalize checks for existence, so there's no need for an extra check)\n    res = if let Ok(bundle_dir) = exe_dir\n      .join(format!(\"../lib/{}\", package_info.name))\n      .canonicalize()\n    {\n      Ok(bundle_dir)\n    } else if let Some(appdir) = &env.appdir {\n      let appdir: &std::path::Path = appdir.as_ref();\n      Ok(PathBuf::from(format!(\n        \"{}/usr/lib/{}\",\n        appdir.display(),\n        package_info.name\n      )))\n    } else {\n      // running bundle\n      Ok(PathBuf::from(format!(\"/usr/lib/{}\", package_info.name)))\n    };\n  }\n\n  #[cfg(target_os = \"macos\")]\n  {\n    res = exe_dir\n      .join(\"../Resources\")\n      .canonicalize()\n      .map_err(Into::into);\n  }\n\n  #[cfg(target_os = \"ios\")]\n  {\n    res = exe_dir.join(\"assets\").canonicalize().map_err(Into::into);\n  }\n\n  res\n}\n\n// Variable holding the type of bundle the executable is stored in. This is modified by binary\n// patching during build\n#[used]\n// Marked as `mut` because it could get optimized away without it,\n// see https://github.com/tauri-apps/tauri/pull/13812\nstatic mut __TAURI_BUNDLE_TYPE: &str = \"__TAURI_BUNDLE_TYPE_VAR_UNK\";\n\n/// Get the type of the bundle current binary is packaged in.\n/// If the bundle type is unknown, it returns [`Option::None`].\npub fn bundle_type() -> Option<BundleType> {\n  unsafe {\n    match __TAURI_BUNDLE_TYPE {\n      \"__TAURI_BUNDLE_TYPE_VAR_DEB\" => Some(BundleType::Deb),\n      \"__TAURI_BUNDLE_TYPE_VAR_RPM\" => Some(BundleType::Rpm),\n      \"__TAURI_BUNDLE_TYPE_VAR_APP\" => Some(BundleType::AppImage),\n      \"__TAURI_BUNDLE_TYPE_VAR_MSI\" => Some(BundleType::Msi),\n      \"__TAURI_BUNDLE_TYPE_VAR_NSS\" => Some(BundleType::Nsis),\n      _ => {\n        if cfg!(target_os = \"macos\") {\n          Some(BundleType::App)\n        } else {\n          None\n        }\n      }\n    }\n  }\n}\n\n#[cfg(feature = \"build\")]\nmod build {\n  use proc_macro2::TokenStream;\n  use quote::{quote, ToTokens, TokenStreamExt};\n\n  use super::*;\n\n  impl ToTokens for Target {\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n      let prefix = quote! { ::tauri::utils::platform::Target };\n\n      tokens.append_all(match self {\n        Self::MacOS => quote! { #prefix::MacOS },\n        Self::Linux => quote! { #prefix::Linux },\n        Self::Windows => quote! { #prefix::Windows },\n        Self::Android => quote! { #prefix::Android },\n        Self::Ios => quote! { #prefix::Ios },\n      });\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use std::path::PathBuf;\n\n  use crate::{Env, PackageInfo};\n\n  #[test]\n  #[cfg(not(target_os = \"android\"))]\n  fn resolve_resource_dir() {\n    let package_info = PackageInfo {\n      name: \"MyApp\".into(),\n      version: \"1.0.0\".parse().unwrap(),\n      authors: \"\",\n      description: \"\",\n      crate_name: \"my-app\",\n    };\n    let env = Env::default();\n\n    let path = PathBuf::from(\"/path/to/target/aarch64-apple-darwin/debug/app\");\n    let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();\n    assert_eq!(resource_dir, path.parent().unwrap());\n\n    let path = PathBuf::from(\"/path/to/target/custom-profile/app\");\n    let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();\n    assert_eq!(resource_dir, path.parent().unwrap());\n\n    let path = PathBuf::from(\"/path/to/target/release/app\");\n    let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();\n    assert_eq!(resource_dir, path.parent().unwrap());\n\n    let path = PathBuf::from(\"/path/to/target/unknown-profile/app\");\n    #[allow(clippy::needless_borrows_for_generic_args)]\n    let resource_dir = super::resource_dir_from(&path, &package_info, &env);\n    #[cfg(target_os = \"macos\")]\n    assert!(resource_dir.is_err());\n    #[cfg(target_os = \"linux\")]\n    assert_eq!(resource_dir.unwrap(), PathBuf::from(\"/usr/lib/MyApp\"));\n    #[cfg(windows)]\n    assert_eq!(resource_dir.unwrap(), path.parent().unwrap());\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/plugin.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Compile-time and runtime types for Tauri plugins.\n#[cfg(feature = \"build\")]\npub use build::*;\n\n#[cfg(feature = \"build\")]\nmod build {\n  use std::{\n    env::vars_os,\n    fs,\n    path::{Path, PathBuf},\n  };\n\n  const GLOBAL_API_SCRIPT_PATH_KEY: &str = \"GLOBAL_API_SCRIPT_PATH\";\n  /// Known file name of the file that contains an array with the path of all API scripts defined with [`define_global_api_script_path`].\n  pub const GLOBAL_API_SCRIPT_FILE_LIST_PATH: &str = \"__global-api-script.js\";\n\n  /// Defines the path to the global API script using Cargo instructions.\n  pub fn define_global_api_script_path(path: &Path) {\n    println!(\n      \"cargo:{GLOBAL_API_SCRIPT_PATH_KEY}={}\",\n      path\n        .canonicalize()\n        .expect(\"failed to canonicalize global API script path\")\n        .display()\n    )\n  }\n\n  /// Collects the path of all the global API scripts defined with [`define_global_api_script_path`]\n  /// and saves them to the out dir with filename [`GLOBAL_API_SCRIPT_FILE_LIST_PATH`].\n  ///\n  /// `tauri_global_scripts` is only used in Tauri's monorepo for the examples to work\n  /// since they don't have a build script to run `tauri-build` and pull in the deps env vars\n  pub fn save_global_api_scripts_paths(out_dir: &Path, mut tauri_global_scripts: Option<PathBuf>) {\n    let mut scripts = Vec::new();\n\n    for (key, value) in vars_os() {\n      let key = key.to_string_lossy();\n\n      if key == format!(\"DEP_TAURI_{GLOBAL_API_SCRIPT_PATH_KEY}\") {\n        tauri_global_scripts = Some(PathBuf::from(value));\n      } else if key.starts_with(\"DEP_\") && key.ends_with(GLOBAL_API_SCRIPT_PATH_KEY) {\n        let script_path = PathBuf::from(value);\n        scripts.push(script_path);\n      }\n    }\n\n    if let Some(tauri_global_scripts) = tauri_global_scripts {\n      scripts.insert(0, tauri_global_scripts);\n    }\n\n    fs::write(\n      out_dir.join(GLOBAL_API_SCRIPT_FILE_LIST_PATH),\n      serde_json::to_string(&scripts).expect(\"failed to serialize global API script paths\"),\n    )\n    .expect(\"failed to write global API script\");\n  }\n\n  /// Read global api scripts from [`GLOBAL_API_SCRIPT_FILE_LIST_PATH`]\n  pub fn read_global_api_scripts(out_dir: &Path) -> Option<Vec<String>> {\n    let global_scripts_path = out_dir.join(GLOBAL_API_SCRIPT_FILE_LIST_PATH);\n    if !global_scripts_path.exists() {\n      return None;\n    }\n\n    let global_scripts_str = fs::read_to_string(global_scripts_path)\n      .expect(\"failed to read plugin global API script paths\");\n    let global_scripts = serde_json::from_str::<Vec<PathBuf>>(&global_scripts_str)\n      .expect(\"failed to parse plugin global API script paths\");\n\n    Some(\n      global_scripts\n        .into_iter()\n        .map(|p| {\n          fs::read_to_string(&p).unwrap_or_else(|e| {\n            panic!(\n              \"failed to read plugin global API script {}: {e}\",\n              p.display()\n            )\n          })\n        })\n        .collect(),\n    )\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/resources.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n  collections::HashMap,\n  path::{Component, Path, PathBuf},\n};\n\nuse walkdir::WalkDir;\n\nuse crate::platform::Target as TargetPlatform;\n\n/// Given a path (absolute or relative) to a resource file, returns the\n/// relative path from the bundle resources directory where that resource\n/// should be stored.\npub fn resource_relpath(path: &Path) -> PathBuf {\n  let mut dest = PathBuf::new();\n  for component in path.components() {\n    match component {\n      Component::Prefix(_) => {}\n      Component::RootDir => dest.push(\"_root_\"),\n      Component::CurDir => {}\n      Component::ParentDir => dest.push(\"_up_\"),\n      Component::Normal(string) => dest.push(string),\n    }\n  }\n  dest\n}\n\nfn normalize(path: &Path) -> PathBuf {\n  let mut dest = PathBuf::new();\n  for component in path.components() {\n    match component {\n      Component::Prefix(_) => {}\n      Component::RootDir => dest.push(\"/\"),\n      Component::CurDir => {}\n      Component::ParentDir => dest.push(\"..\"),\n      Component::Normal(string) => dest.push(string),\n    }\n  }\n  dest\n}\n\n/// Parses the external binaries to bundle, adding the target triple suffix to each of them.\npub fn external_binaries(\n  external_binaries: &[String],\n  target_triple: &str,\n  target_platform: &TargetPlatform,\n) -> Vec<String> {\n  let mut paths = Vec::new();\n  for curr_path in external_binaries {\n    let extension = if matches!(target_platform, TargetPlatform::Windows) {\n      \".exe\"\n    } else {\n      \"\"\n    };\n    paths.push(format!(\"{curr_path}-{target_triple}{extension}\"));\n  }\n  paths\n}\n\n/// Information for a resource.\n#[derive(Debug)]\npub struct Resource {\n  path: PathBuf,\n  target: PathBuf,\n}\n\nimpl Resource {\n  /// The path of the resource.\n  pub fn path(&self) -> &Path {\n    &self.path\n  }\n\n  /// The target location of the resource.\n  pub fn target(&self) -> &Path {\n    &self.target\n  }\n}\n\n#[derive(Debug)]\nenum PatternIter<'a> {\n  Slice(std::slice::Iter<'a, String>),\n  Map(std::collections::hash_map::Iter<'a, String, String>),\n}\n\n/// A helper to iterate through resources.\npub struct ResourcePaths<'a> {\n  iter: ResourcePathsIter<'a>,\n}\n\nimpl<'a> ResourcePaths<'a> {\n  /// Creates a new ResourcePaths from a slice of patterns to iterate\n  pub fn new(patterns: &'a [String], allow_walk: bool) -> ResourcePaths<'a> {\n    ResourcePaths {\n      iter: ResourcePathsIter {\n        pattern_iter: PatternIter::Slice(patterns.iter()),\n        allow_walk,\n        current_dest: None,\n        current_iter: None,\n      },\n    }\n  }\n\n  /// Creates a new ResourcePaths from a slice of patterns to iterate\n  pub fn from_map(patterns: &'a HashMap<String, String>, allow_walk: bool) -> ResourcePaths<'a> {\n    ResourcePaths {\n      iter: ResourcePathsIter {\n        pattern_iter: PatternIter::Map(patterns.iter()),\n        allow_walk,\n        current_dest: None,\n        current_iter: None,\n      },\n    }\n  }\n\n  /// Returns the resource iterator that yields the source and target paths.\n  /// Needed when using [`Self::from_map`].\n  pub fn iter(self) -> ResourcePathsIter<'a> {\n    self.iter\n  }\n}\n\n/// Iterator of a [`ResourcePaths`].\n#[derive(Debug)]\npub struct ResourcePathsIter<'a> {\n  /// the patterns to iterate.\n  pattern_iter: PatternIter<'a>,\n  /// whether the resource paths allows directories or not.\n  allow_walk: bool,\n\n  /// The value of map when [`Self::pattern_iter`] is a [`PatternIter::Map`],\n  /// used for determining [`Resource::target`]\n  current_dest: Option<PathBuf>,\n  /// The iter for the current pattern. The cycle goes like this:\n  /// [`ResourcePaths::next`] -> [`Self::next`] -> [`Self::pattern_iter::next`] -> [`Self::current_iter::next`]\n  current_iter: Option<ResourcePathsInnerIter>,\n}\n\n#[derive(Debug)]\nenum ResourcePathsInnerIter {\n  Walk {\n    iter: walkdir::IntoIter,\n    /// The key of map when [`ResourcePathsIter::pattern_iter`] is a [`PatternIter::Map`],\n    /// used for determining [`Resource::target`]\n    current_pattern: Option<PathBuf>,\n  },\n  Glob {\n    iter: glob::Paths,\n  },\n}\n\nimpl Iterator for ResourcePathsInnerIter {\n  type Item = crate::Result<PathBuf>;\n\n  fn next(&mut self) -> Option<crate::Result<PathBuf>> {\n    match self {\n      ResourcePathsInnerIter::Walk { iter, .. } => Some(\n        iter\n          .next()?\n          .map(|entry| entry.into_path())\n          .map_err(Into::into),\n      ),\n      ResourcePathsInnerIter::Glob { iter } => Some(iter.next()?.map_err(Into::into)),\n    }\n  }\n}\n\nimpl ResourcePathsIter<'_> {\n  fn next_current_iter(&mut self) -> Option<crate::Result<Resource>> {\n    let current_iter = self.current_iter.as_mut().unwrap();\n    let entry = current_iter.next()?;\n\n    Some(match entry {\n      Ok(entry) => {\n        // Skip directories\n        if entry.is_dir() {\n          self.next_current_iter()?\n        } else {\n          self.resource_from_path(normalize(&entry))\n        }\n      }\n      Err(error) => Err(error),\n    })\n  }\n\n  fn resource_from_path(&self, path: PathBuf) -> crate::Result<Resource> {\n    if !path.exists() {\n      return Err(crate::Error::ResourcePathNotFound(path));\n    }\n\n    Ok(Resource {\n      target: if let Some(dest) = &self.current_dest {\n        match &self.current_iter {\n          Some(current_iter) => match current_iter {\n            // if processing a directory, preserve directory structure under current_dest\n            ResourcePathsInnerIter::Walk {\n              current_pattern, ..\n            } => {\n              if let Some(pattern) = current_pattern {\n                dest.join(path.strip_prefix(pattern).unwrap_or(&path))\n              } else {\n                dest.join(&path)\n              }\n            }\n            // if processing a glob and current_dest is not empty\n            // we put all globbed paths under current_dest\n            // preserving the file name as it is\n            ResourcePathsInnerIter::Glob { .. } => dest.join(path.file_name().unwrap()),\n          },\n          None => dest.clone(),\n        }\n      } else {\n        // If [`ResourcePathsIter::pattern_iter`] is a [`PatternIter::Slice`]\n        resource_relpath(&path)\n      },\n      path,\n    })\n  }\n\n  fn next_pattern(&mut self) -> Option<crate::Result<Resource>> {\n    self.current_dest = None;\n\n    let pattern = match &mut self.pattern_iter {\n      PatternIter::Slice(iter) => iter.next()?,\n      PatternIter::Map(iter) => {\n        let (pattern, dest) = iter.next()?;\n        self.current_dest = Some(resource_relpath(Path::new(dest)));\n        pattern\n      }\n    };\n\n    if pattern.contains('*') {\n      self.current_iter = match glob::glob(pattern) {\n        Ok(glob) => Some(ResourcePathsInnerIter::Glob { iter: glob }),\n        Err(error) => return Some(Err(error.into())),\n      };\n      match self.next_current_iter() {\n        Some(r) => return Some(r),\n        None => {\n          self.current_iter = None;\n          return Some(Err(crate::Error::GlobPathNotFound(pattern.clone())));\n        }\n      }\n    } else {\n      let path = normalize(Path::new(pattern));\n      if path.is_dir() {\n        if !self.allow_walk {\n          return Some(Err(crate::Error::NotAllowedToWalkDir(path)));\n        }\n        self.current_iter = Some(ResourcePathsInnerIter::Walk {\n          iter: WalkDir::new(&path).into_iter(),\n          current_pattern: if matches!(self.pattern_iter, PatternIter::Map(_)) {\n            Some(path)\n          } else {\n            None\n          },\n        });\n      } else {\n        return Some(self.resource_from_path(path));\n      }\n    }\n\n    self.next_current_iter()\n  }\n}\n\nimpl Iterator for ResourcePaths<'_> {\n  type Item = crate::Result<PathBuf>;\n\n  fn next(&mut self) -> Option<crate::Result<PathBuf>> {\n    self.iter.next().map(|r| r.map(|res| res.path))\n  }\n}\n\nimpl Iterator for ResourcePathsIter<'_> {\n  type Item = crate::Result<Resource>;\n\n  fn next(&mut self) -> Option<crate::Result<Resource>> {\n    if self.current_iter.is_some() {\n      match self.next_current_iter() {\n        Some(r) => return Some(r),\n        None => self.current_iter = None,\n      }\n    }\n\n    self.next_pattern()\n  }\n}\n\n#[cfg(test)]\nmod tests {\n\n  use super::*;\n  use std::fs;\n  use std::path::Path;\n\n  impl PartialEq for Resource {\n    fn eq(&self, other: &Self) -> bool {\n      self.path == other.path && self.target == other.target\n    }\n  }\n\n  fn expected_resources(resources: &[(&str, &str)]) -> Vec<Resource> {\n    resources\n      .iter()\n      .map(|(path, target)| Resource {\n        path: Path::new(path).components().collect(),\n        target: Path::new(target).components().collect(),\n      })\n      .collect()\n  }\n\n  fn setup_test_dirs() {\n    let mut random = [0; 1];\n    getrandom::fill(&mut random).unwrap();\n\n    let temp = std::env::temp_dir();\n    let temp = temp.join(format!(\"tauri_resource_paths_iter_test_{}\", random[0]));\n\n    let _ = fs::remove_dir_all(&temp);\n    fs::create_dir_all(&temp).unwrap();\n\n    std::env::set_current_dir(&temp).unwrap();\n\n    let paths = [\n      \"src-tauri/tauri.conf.json\",\n      \"src-tauri/some-other-json.json\",\n      \"src-tauri/Cargo.toml\",\n      \"src-tauri/Tauri.toml\",\n      \"src-tauri/build.rs\",\n      \"src-tauri/some-folder/some-file.txt\",\n      \"src/assets/javascript.svg\",\n      \"src/assets/tauri.svg\",\n      \"src/assets/rust.svg\",\n      \"src/assets/lang/en.json\",\n      \"src/assets/lang/ar.json\",\n      \"src/sounds/lang/es.wav\",\n      \"src/sounds/lang/fr.wav\",\n      \"src/textures/ground/earth.tex\",\n      \"src/textures/ground/sand.tex\",\n      \"src/textures/water.tex\",\n      \"src/textures/fire.tex\",\n      \"src/tiles/sky/grey.tile\",\n      \"src/tiles/sky/yellow.tile\",\n      \"src/tiles/grass.tile\",\n      \"src/tiles/stones.tile\",\n      \"src/index.html\",\n      \"src/style.css\",\n      \"src/script.js\",\n      \"src/dir/another-dir/file1.txt\",\n      \"src/dir/another-dir2/file2.txt\",\n    ];\n\n    for path in paths {\n      let path = Path::new(path);\n      fs::create_dir_all(path.parent().unwrap()).unwrap();\n      fs::write(path, \"\").unwrap();\n    }\n  }\n\n  fn resources_map(literal: &[(&str, &str)]) -> HashMap<String, String> {\n    literal\n      .iter()\n      .map(|(from, to)| (from.to_string(), to.to_string()))\n      .collect()\n  }\n\n  #[test]\n  #[serial_test::serial(resources)]\n  fn resource_paths_iter_slice_allow_walk() {\n    setup_test_dirs();\n\n    let dir = std::env::current_dir().unwrap().join(\"src-tauri\");\n    let _ = std::env::set_current_dir(dir);\n\n    let resources = ResourcePaths::new(\n      &[\n        \"../src/script.js\".into(),\n        \"../src/assets\".into(),\n        \"../src/index.html\".into(),\n        \"../src/sounds\".into(),\n        // Should be the same as `../src/textures/` or `../src/textures`\n        \"../src/textures/**/*\".into(),\n        \"*.toml\".into(),\n        \"*.conf.json\".into(),\n      ],\n      true,\n    )\n    .iter()\n    .flatten()\n    .collect::<Vec<_>>();\n\n    let expected = expected_resources(&[\n      // From `../src/script.js`\n      (\"../src/script.js\", \"_up_/src/script.js\"),\n      // From `../src/assets`\n      (\n        \"../src/assets/javascript.svg\",\n        \"_up_/src/assets/javascript.svg\",\n      ),\n      (\"../src/assets/tauri.svg\", \"_up_/src/assets/tauri.svg\"),\n      (\"../src/assets/rust.svg\", \"_up_/src/assets/rust.svg\"),\n      (\"../src/assets/lang/en.json\", \"_up_/src/assets/lang/en.json\"),\n      (\"../src/assets/lang/ar.json\", \"_up_/src/assets/lang/ar.json\"),\n      // From `../src/index.html`\n      (\"../src/index.html\", \"_up_/src/index.html\"),\n      // From `../src/sounds`\n      (\"../src/sounds/lang/es.wav\", \"_up_/src/sounds/lang/es.wav\"),\n      (\"../src/sounds/lang/fr.wav\", \"_up_/src/sounds/lang/fr.wav\"),\n      // From `../src/textures/**/*`\n      (\n        \"../src/textures/ground/earth.tex\",\n        \"_up_/src/textures/ground/earth.tex\",\n      ),\n      (\n        \"../src/textures/ground/sand.tex\",\n        \"_up_/src/textures/ground/sand.tex\",\n      ),\n      (\"../src/textures/water.tex\", \"_up_/src/textures/water.tex\"),\n      (\"../src/textures/fire.tex\", \"_up_/src/textures/fire.tex\"),\n      // From `*.toml`\n      (\"Cargo.toml\", \"Cargo.toml\"),\n      (\"Tauri.toml\", \"Tauri.toml\"),\n      // From `*.conf.json`\n      (\"tauri.conf.json\", \"tauri.conf.json\"),\n    ]);\n\n    assert_eq!(resources.len(), expected.len());\n    for resource in expected {\n      if !resources.contains(&resource) {\n        panic!(\"{resource:?} was expected but not found in {resources:?}\");\n      }\n    }\n  }\n\n  #[test]\n  #[serial_test::serial(resources)]\n  fn resource_paths_iter_slice_no_walk() {\n    setup_test_dirs();\n\n    let dir = std::env::current_dir().unwrap().join(\"src-tauri\");\n    let _ = std::env::set_current_dir(dir);\n\n    let resources = ResourcePaths::new(\n      &[\n        \"../src/script.js\".into(),\n        \"../src/assets\".into(),\n        \"../src/index.html\".into(),\n        \"../src/sounds\".into(),\n        \"*.toml\".into(),\n        \"*.conf.json\".into(),\n      ],\n      false,\n    )\n    .iter()\n    .flatten()\n    .collect::<Vec<_>>();\n\n    let expected = expected_resources(&[\n      (\"../src/script.js\", \"_up_/src/script.js\"),\n      (\"../src/index.html\", \"_up_/src/index.html\"),\n      (\"Cargo.toml\", \"Cargo.toml\"),\n      (\"Tauri.toml\", \"Tauri.toml\"),\n      (\"tauri.conf.json\", \"tauri.conf.json\"),\n    ]);\n\n    assert_eq!(resources.len(), expected.len());\n    for resource in expected {\n      if !resources.contains(&resource) {\n        panic!(\"{resource:?} was expected but not found in {resources:?}\");\n      }\n    }\n  }\n\n  #[test]\n  #[serial_test::serial(resources)]\n  fn resource_paths_iter_map_allow_walk() {\n    setup_test_dirs();\n\n    let dir = std::env::current_dir().unwrap().join(\"src-tauri\");\n    let _ = std::env::set_current_dir(dir);\n\n    let resources = ResourcePaths::from_map(\n      &resources_map(&[\n        (\"../src/script.js\", \"main.js\"),\n        (\"../src/assets\", \"\"),\n        (\"../src/index.html\", \"frontend/index.html\"),\n        (\"../src/sounds\", \"voices\"),\n        (\"../src/textures/*\", \"textures\"),\n        (\"../src/tiles/**/*\", \"tiles\"),\n        (\"*.toml\", \"\"),\n        (\"*.conf.json\", \"json\"),\n        (\"./some-folder/\", \"some-target-folder/\"),\n        (\"../non-existent-file\", \"asd\"), // invalid case\n        (\"../non/*\", \"asd\"),             // invalid case\n      ]),\n      true,\n    )\n    .iter()\n    .flatten()\n    .collect::<Vec<_>>();\n\n    let expected = expected_resources(&[\n      (\"../src/script.js\", \"main.js\"),\n      (\"../src/assets/javascript.svg\", \"javascript.svg\"),\n      (\"../src/assets/tauri.svg\", \"tauri.svg\"),\n      (\"../src/assets/rust.svg\", \"rust.svg\"),\n      (\"../src/assets/lang/en.json\", \"lang/en.json\"),\n      (\"../src/assets/lang/ar.json\", \"lang/ar.json\"),\n      (\"../src/index.html\", \"frontend/index.html\"),\n      (\"../src/sounds/lang/es.wav\", \"voices/lang/es.wav\"),\n      (\"../src/sounds/lang/fr.wav\", \"voices/lang/fr.wav\"),\n      (\"../src/textures/water.tex\", \"textures/water.tex\"),\n      (\"../src/textures/fire.tex\", \"textures/fire.tex\"),\n      (\"../src/tiles/grass.tile\", \"tiles/grass.tile\"),\n      (\"../src/tiles/stones.tile\", \"tiles/stones.tile\"),\n      (\"../src/tiles/sky/grey.tile\", \"tiles/grey.tile\"),\n      (\"../src/tiles/sky/yellow.tile\", \"tiles/yellow.tile\"),\n      (\"Cargo.toml\", \"Cargo.toml\"),\n      (\"Tauri.toml\", \"Tauri.toml\"),\n      (\"tauri.conf.json\", \"json/tauri.conf.json\"),\n      (\n        \"some-folder/some-file.txt\",\n        \"some-target-folder/some-file.txt\",\n      ),\n    ]);\n\n    assert_eq!(resources.len(), expected.len());\n    for resource in expected {\n      if !resources.contains(&resource) {\n        panic!(\"{resource:?} was expected but not found in {resources:?}\");\n      }\n    }\n  }\n\n  #[test]\n  #[serial_test::serial(resources)]\n  fn resource_paths_iter_map_no_walk() {\n    setup_test_dirs();\n\n    let dir = std::env::current_dir().unwrap().join(\"src-tauri\");\n    let _ = std::env::set_current_dir(dir);\n\n    let resources = ResourcePaths::from_map(\n      &resources_map(&[\n        (\"../src/script.js\", \"main.js\"),\n        (\"../src/assets\", \"\"),\n        (\"../src/index.html\", \"frontend/index.html\"),\n        (\"../src/sounds\", \"voices\"),\n        (\"*.toml\", \"\"),\n        (\"*.conf.json\", \"json\"),\n      ]),\n      false,\n    )\n    .iter()\n    .flatten()\n    .collect::<Vec<_>>();\n\n    let expected = expected_resources(&[\n      (\"../src/script.js\", \"main.js\"),\n      (\"../src/index.html\", \"frontend/index.html\"),\n      (\"Cargo.toml\", \"Cargo.toml\"),\n      (\"Tauri.toml\", \"Tauri.toml\"),\n      (\"tauri.conf.json\", \"json/tauri.conf.json\"),\n    ]);\n\n    assert_eq!(resources.len(), expected.len());\n    for resource in expected {\n      if !resources.contains(&resource) {\n        panic!(\"{resource:?} was expected but not found in {resources:?}\");\n      }\n    }\n  }\n\n  #[test]\n  #[serial_test::serial(resources)]\n  fn resource_paths_errors() {\n    setup_test_dirs();\n\n    let dir = std::env::current_dir().unwrap().join(\"src-tauri\");\n    let _ = std::env::set_current_dir(dir);\n\n    let resources = ResourcePaths::from_map(\n      &resources_map(&[\n        (\"../non-existent-file\", \"file\"),\n        (\"../non-existent-dir\", \"dir\"),\n        // exists but not allowed to walk\n        (\"../src\", \"dir2\"),\n        // doesn't exist but it is a glob and will return an error\n        (\"../non-existent-glob-dir/*\", \"glob\"),\n        // exists but only contains directories and will not produce any values\n        (\"../src/dir/*\", \"dir3\"),\n      ]),\n      false,\n    )\n    .iter()\n    .collect::<Vec<_>>();\n\n    assert_eq!(resources.len(), 5);\n\n    assert!(resources.iter().all(|r| r.is_err()));\n\n    // hashmap order is not guaranteed so we check the error variant exists and how many\n    assert!(resources\n      .iter()\n      .any(|r| matches!(r, Err(crate::Error::ResourcePathNotFound(_)))));\n    assert_eq!(\n      resources\n        .iter()\n        .filter(|r| matches!(r, Err(crate::Error::ResourcePathNotFound(_))))\n        .count(),\n      2\n    );\n    assert!(resources\n      .iter()\n      .any(|r| matches!(r, Err(crate::Error::NotAllowedToWalkDir(_)))));\n    assert_eq!(\n      resources\n        .iter()\n        .filter(|r| matches!(r, Err(crate::Error::NotAllowedToWalkDir(_))))\n        .count(),\n      1\n    );\n    assert!(resources\n      .iter()\n      .any(|r| matches!(r, Err(crate::Error::GlobPathNotFound(_)))));\n    assert_eq!(\n      resources\n        .iter()\n        .filter(|r| matches!(r, Err(crate::Error::GlobPathNotFound(_))))\n        .count(),\n      2\n    );\n  }\n}\n"
  },
  {
    "path": "crates/tauri-utils/src/tokens.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Utilities to implement [`ToTokens`] for a type.\n\nuse std::path::Path;\n\nuse proc_macro2::TokenStream;\nuse quote::{quote, ToTokens};\nuse serde_json::Value as JsonValue;\nuse url::Url;\n\n/// Write a `TokenStream` of the `$struct`'s fields to the `$tokens`.\n///\n/// All fields must represent a binding of the same name that implements `ToTokens`.\n#[macro_export]\nmacro_rules! literal_struct {\n  ($tokens:ident, $struct:path, $($field:ident),+) => {\n    $tokens.append_all(quote! {\n      $struct {\n        $($field: #$field),+\n      }\n    })\n  };\n}\n\n/// Create a `String` constructor `TokenStream`.\n///\n/// e.g. `\"Hello World\"` -> `String::from(\"Hello World\")`.\n/// This takes a `&String` to reduce casting all the `&String` -> `&str` manually.\npub fn str_lit(s: impl AsRef<str>) -> TokenStream {\n  let s = s.as_ref();\n  quote! { #s.into() }\n}\n\n/// Create an `Option` constructor `TokenStream`.\npub fn opt_lit(item: Option<&impl ToTokens>) -> TokenStream {\n  match item {\n    None => quote! { ::core::option::Option::None },\n    Some(item) => quote! { ::core::option::Option::Some(#item) },\n  }\n}\n\n/// Create an `Option` constructor `TokenStream` over an owned [`ToTokens`] impl type.\npub fn opt_lit_owned(item: Option<impl ToTokens>) -> TokenStream {\n  match item {\n    None => quote! { ::core::option::Option::None },\n    Some(item) => quote! { ::core::option::Option::Some(#item) },\n  }\n}\n\n/// Helper function to combine an `opt_lit` with `str_lit`.\npub fn opt_str_lit(item: Option<impl AsRef<str>>) -> TokenStream {\n  opt_lit(item.map(str_lit).as_ref())\n}\n\n/// Helper function to combine an `opt_lit` with a list of `str_lit`\npub fn opt_vec_lit<Raw, Tokens>(\n  item: Option<impl IntoIterator<Item = Raw>>,\n  map: impl Fn(Raw) -> Tokens,\n) -> TokenStream\nwhere\n  Tokens: ToTokens,\n{\n  opt_lit(item.map(|list| vec_lit(list, map)).as_ref())\n}\n\n/// Create a `Vec` constructor, mapping items with a function that spits out `TokenStream`s.\npub fn vec_lit<Raw, Tokens>(\n  list: impl IntoIterator<Item = Raw>,\n  map: impl Fn(Raw) -> Tokens,\n) -> TokenStream\nwhere\n  Tokens: ToTokens,\n{\n  let items = list.into_iter().map(map);\n  quote! { vec![#(#items),*] }\n}\n\n/// Create a `PathBuf` constructor `TokenStream`.\n///\n/// e.g. `\"Hello World\" -> String::from(\"Hello World\").\npub fn path_buf_lit(s: impl AsRef<Path>) -> TokenStream {\n  let s = s.as_ref().to_string_lossy().into_owned();\n  quote! { ::std::path::PathBuf::from(#s) }\n}\n\n/// Creates a `Url` constructor `TokenStream`.\npub fn url_lit(url: &Url) -> TokenStream {\n  let url = url.as_str();\n  quote! { #url.parse().unwrap() }\n}\n\n/// Create a map constructor, mapping keys and values with other `TokenStream`s.\n///\n/// This function is pretty generic because the types of keys AND values get transformed.\npub fn map_lit<Map, Key, Value, TokenStreamKey, TokenStreamValue, FuncKey, FuncValue>(\n  map_type: TokenStream,\n  map: Map,\n  map_key: FuncKey,\n  map_value: FuncValue,\n) -> TokenStream\nwhere\n  <Map as IntoIterator>::IntoIter: ExactSizeIterator,\n  Map: IntoIterator<Item = (Key, Value)>,\n  TokenStreamKey: ToTokens,\n  TokenStreamValue: ToTokens,\n  FuncKey: Fn(Key) -> TokenStreamKey,\n  FuncValue: Fn(Value) -> TokenStreamValue,\n{\n  let ident = quote::format_ident!(\"map\");\n  let map = map.into_iter();\n\n  if map.len() > 0 {\n    let items = map.map(|(key, value)| {\n      let key = map_key(key);\n      let value = map_value(value);\n      quote! { #ident.insert(#key, #value); }\n    });\n\n    quote! {{\n      let mut #ident = #map_type::new();\n      #(#items)*\n      #ident\n    }}\n  } else {\n    quote! { #map_type::new() }\n  }\n}\n\n/// Create a `serde_json::Value` variant `TokenStream` for a number\npub fn json_value_number_lit(num: &serde_json::Number) -> TokenStream {\n  // See <https://docs.rs/serde_json/1/serde_json/struct.Number.html> for guarantees\n  let prefix = quote! { ::serde_json::Value };\n  if num.is_u64() {\n    // guaranteed u64\n    let num = num.as_u64().unwrap();\n    quote! { #prefix::Number(#num.into()) }\n  } else if num.is_i64() {\n    // guaranteed i64\n    let num = num.as_i64().unwrap();\n    quote! { #prefix::Number(#num.into()) }\n  } else if num.is_f64() {\n    // guaranteed f64\n    let num = num.as_f64().unwrap();\n    quote! { #prefix::Number(::serde_json::Number::from_f64(#num).unwrap(/* safe to unwrap, guaranteed f64 */)) }\n  } else {\n    // invalid number\n    quote! { #prefix::Null }\n  }\n}\n\n/// Create a `serde_json::Value` constructor `TokenStream`\npub fn json_value_lit(jv: &JsonValue) -> TokenStream {\n  let prefix = quote! { ::serde_json::Value };\n\n  match jv {\n    JsonValue::Null => quote! { #prefix::Null },\n    JsonValue::Bool(bool) => quote! { #prefix::Bool(#bool) },\n    JsonValue::Number(number) => json_value_number_lit(number),\n    JsonValue::String(str) => {\n      let s = str_lit(str);\n      quote! { #prefix::String(#s) }\n    }\n    JsonValue::Array(vec) => {\n      let items = vec.iter().map(json_value_lit);\n      quote! { #prefix::Array(vec![#(#items),*]) }\n    }\n    JsonValue::Object(map) => {\n      let map = map_lit(quote! { ::serde_json::Map }, map, str_lit, json_value_lit);\n      quote! { #prefix::Object(#map) }\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tests/acl/Cargo.toml",
    "content": "[package]\nname = \"acl-tests\"\nversion = \"0.1.0\"\nauthors.workspace = true\nhomepage.workspace = true\nrepository.workspace = true\ncategories.workspace = true\nlicense.workspace = true\nedition.workspace = true\nrust-version.workspace = true\npublish = false\n\n[dev-dependencies]\ntauri-utils = { path = \"../../tauri-utils/\", features = [\"build\"] }\nserde_json = \"1\"\ninsta = \"1\"\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/basic-ping/cap.toml",
    "content": "identifier = \"run-app\"\ndescription = \"app capability\"\nwindows = [\"main\"]\npermissions = [\"ping:allow-ping\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/basic-ping/required-plugins.json",
    "content": "[\"ping\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/file-explorer/cap.toml",
    "content": "identifier = \"run-app\"\ndescription = \"app capability\"\nwindows = [\"main\"]\npermissions = [\"fs:read\", \"fs:allow-app\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/file-explorer/required-plugins.json",
    "content": "[\"fs\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/file-explorer-remote/cap.toml",
    "content": "identifier = \"run-app\"\ndescription = \"app capability\"\nwindows = [\"main\"]\npermissions = [\"fs:read\", \"fs:allow-app\"]\nlocal = false\n[remote]\nurls = [\"https://tauri.app\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/file-explorer-remote/required-plugins.json",
    "content": "[\"fs\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/multiwebview/cap.toml",
    "content": "identifier = \"run-app\"\ndescription = \"app capability\"\nwindows = [\"main\"]\nwebviews = [\"child1\", \"child2\"]\npermissions = [\"ping:allow-ping\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/multiwebview/required-plugins.json",
    "content": "[\"ping\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/multiwindow/cap-external.toml",
    "content": "identifier = \"run-app-external-url\"\ndescription = \"external window capability\"\nwindows = [\"external\"]\npermissions = [\"fs:read\", \"fs:deny-home\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/multiwindow/cap-main.json",
    "content": "{\n  \"identifier\": \"run-app\",\n  \"description\": \"ap capability\",\n  \"windows\": [\"main\"],\n  \"permissions\": [\n    {\n      \"identifier\": \"fs:read\",\n      \"allow\": [\n        {\n          \"path\": \"$CONFIG/*\"\n        }\n      ]\n    },\n    \"fs:allow-app\",\n    \"fs:deny-home\",\n    \"fs:allow-read-resources\",\n    \"fs:allow-move-temp\",\n    \"fs:read-download-dir\"\n  ]\n}\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/multiwindow/required-plugins.json",
    "content": "[\"fs\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/platform-specific-permissions/cap.toml",
    "content": "identifier = \"run-app\"\ndescription = \"app capability\"\nwindows = [\"main\"]\npermissions = [\n  \"os:allow-apt-linux\",\n  \"os:allow-library-folder-macos\",\n  \"os:deny-webview-folder-windows\",\n  \"os:open-browser\",\n]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/platform-specific-permissions/required-plugins.json",
    "content": "[\"os\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/scope/cap.toml",
    "content": "identifier = \"run-app\"\ndescription = \"app capability\"\nwindows = [\"main\"]\npermissions = [\n  \"fs:read\",\n  \"fs:allow-app\",\n  \"fs:deny-home\",\n  \"fs:allow-read-resources\",\n  \"fs:allow-move-temp\",\n  \"fs:read-download-dir\",\n]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/scope/required-plugins.json",
    "content": "[\"fs\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/scope-extended/cap.json",
    "content": "{\n  \"identifier\": \"run-app\",\n  \"description\": \"app capability\",\n  \"windows\": [\"main\"],\n  \"permissions\": [\n    {\n      \"identifier\": \"fs:read\",\n      \"allow\": [\n        {\n          \"path\": \"$HOME/.config/**\"\n        }\n      ]\n    },\n    \"fs:deny-home\",\n    {\n      \"identifier\": \"fs:allow-read-resources\",\n      \"deny\": [\n        {\n          \"path\": \"$RESOURCE/**/*.key\"\n        }\n      ]\n    },\n    \"fs:allow-move-temp\",\n    {\n      \"identifier\": \"fs:allow-app\",\n      \"allow\": [\n        {\n          \"path\": \"$APPDATA/**\"\n        }\n      ],\n      \"deny\": [\n        {\n          \"path\": \"$APPDATA/*.db\"\n        }\n      ]\n    },\n    \"fs:read-download-dir\"\n  ]\n}\n"
  },
  {
    "path": "crates/tests/acl/fixtures/capabilities/scope-extended/required-plugins.json",
    "content": "[\"fs\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/fs/deny-home.toml",
    "content": "\n[[permission]]\nidentifier = \"deny-home\"\ndescription = \"Denies accessing the $HOME path.\"\n[[permission.scope.deny]]\npath = \"$HOME\"\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/fs/move-tmp.toml",
    "content": "[[permission]]\nidentifier = \"allow-move-temp\"\ndescription = \"Enables the move command with the $TEMP base directory.\"\ncommands.allow = [\"move\"]\n[[permission.scope.allow]]\npath = \"$TEMP/*\"\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/fs/read-dir.toml",
    "content": "[[permission]]\nidentifier = \"allow-read-dir\"\ndescription = \"Enables the read_dir command without any pre-configured scope.\"\ncommands.allow = [\"read_dir\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/fs/read-download-dir.toml",
    "content": "[[set]]\nidentifier = \"read-download-dir\"\ndescription = \"allows all read the $DOWNLOAD dir\"\npermissions = [\"allow-read-dir\", \"allow-download-dir\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/fs/read-file.toml",
    "content": "[[permission]]\nidentifier = \"allow-read-file\"\ndescription = \"Enables the read_file command without any pre-configured scope.\"\ncommands.allow = [\"read_file\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/fs/read-resources.toml",
    "content": "[[permission]]\nidentifier = \"allow-read-resources\"\ndescription = \"Enables the read_file and read_dir command using the $RESOURCE base directory.\"\ncommands.allow = [\"read_file\", \"read_dir\"]\n[[permission.scope.allow]]\npath = \"$RESOURCE/**\"\n[[permission.scope.allow]]\npath = \"$RESOURCE\"\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/fs/read.toml",
    "content": "[[set]]\nidentifier = \"read\"\ndescription = \"allows all read APIs\"\npermissions = [\"allow-read-dir\", \"allow-read-file\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/fs/scope.toml",
    "content": "[[permission]]\nidentifier = \"allow-app\"\ndescription = \"Allows accessing the $APPDATA path.\"\n[[permission.scope.allow]]\npath = \"$APPDATA\"\n\n[[permission]]\nidentifier = \"allow-download-dir\"\ndescription = \"Allows accessing the $DOWNLOAD directory.\"\n[[permission.scope.allow]]\npath = \"$DOWNLOAD\"\n[[permission.scope.allow]]\npath = \"$DOWNLOAD/**\"\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/os/linux.toml",
    "content": "[[permission]]\nidentifier = \"allow-apt-linux\"\nplatforms = [\"linux\"]\ndescription = \"Allows spawning the apt command on Linux\"\ncommands.allow = [\"spawn\"]\n[[permission.scope.allow]]\ncommand = \"apt\"\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/os/macos.toml",
    "content": "\n[[permission]]\nidentifier = \"allow-library-folder-macos\"\nplatforms = [\"macOS\"]\ndescription = \"Allows access to the $HOME/Library folder on maOS\"\n[[permission.scope.allow]]\npath = \"$HOME/Library/**\"\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/os/open-browser.toml",
    "content": "[[permission]]\nidentifier = \"allow-servo-linux\"\nplatforms = [\"linux\"]\ndescription = \"Allows starting servo on Linux\"\ncommands.allow = [\"spawn\"]\n[[permission.scope.allow]]\ncommand = \"servo\"\n\n[[permission]]\nidentifier = \"allow-edge-windows\"\nplatforms = [\"windows\"]\ndescription = \"Allows starting edge on Windows\"\ncommands.allow = [\"spawn\"]\n[[permission.scope.allow]]\ncommand = \"edge\"\n\n[[permission]]\nidentifier = \"allow-safari-macos\"\nplatforms = [\"macOS\"]\ndescription = \"Allows starting safari on macOS\"\ncommands.allow = [\"spawn\"]\n[[permission.scope.allow]]\ncommand = \"safari\"\n\n[[set]]\nidentifier = \"open-browser\"\ndescription = \"allows opening a URL on the platform browser\"\npermissions = [\"allow-servo-linux\", \"allow-edge-windows\", \"allow-safari-macos\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/os/windows.toml",
    "content": "[[permission]]\nidentifier = \"deny-webview-folder-windows\"\nplatforms = [\"windows\"]\ndescription = \"Denies access to the webview folder on Windows\"\n[[permission.scope.deny]]\npath = \"$APPLOCALDATA/EBWebView/**\"\n"
  },
  {
    "path": "crates/tests/acl/fixtures/plugins/ping/ping.toml",
    "content": "[[permission]]\nidentifier = \"allow-ping\"\ndescription = \"Enables the ping command without any pre-configured scope.\"\ncommands.allow = [\"ping\"]\n"
  },
  {
    "path": "crates/tests/acl/fixtures/snapshots/acl_tests__tests__basic-ping.snap",
    "content": "---\nsource: crates/tests/acl/src/lib.rs\nexpression: resolved\n---\nResolved {\n    has_app_acl: false,\n    allowed_commands: {\n        \"plugin:ping|ping\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: None,\n            },\n        ],\n    },\n    denied_commands: {},\n    command_scope: {},\n    global_scope: {},\n}\n"
  },
  {
    "path": "crates/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer-remote.snap",
    "content": "---\nsource: crates/tests/acl/src/lib.rs\nexpression: resolved\n---\nResolved {\n    has_app_acl: false,\n    allowed_commands: {\n        \"plugin:fs|read_dir\": [\n            ResolvedCommand {\n                context: Remote {\n                    url: RemoteUrlPattern(\n                        UrlPattern {\n                            protocol: Component {\n                                pattern_string: \"https\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^https$\",\n                                    ),\n                                ),\n                                group_name_list: [],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: Literal {\n                                        literal: \"https\",\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            username: Component {\n                                pattern_string: \"*\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^(.*)$\",\n                                    ),\n                                ),\n                                group_name_list: [\n                                    \"0\",\n                                ],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: SingleCapture {\n                                        filter: None,\n                                        allow_empty: true,\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            password: Component {\n                                pattern_string: \"*\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^(.*)$\",\n                                    ),\n                                ),\n                                group_name_list: [\n                                    \"0\",\n                                ],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: SingleCapture {\n                                        filter: None,\n                                        allow_empty: true,\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            hostname: Component {\n                                pattern_string: \"tauri.app\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^tauri\\\\.app$\",\n                                    ),\n                                ),\n                                group_name_list: [],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: Literal {\n                                        literal: \"tauri.app\",\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            port: Component {\n                                pattern_string: \"\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^$\",\n                                    ),\n                                ),\n                                group_name_list: [],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: Literal {\n                                        literal: \"\",\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            pathname: Component {\n                                pattern_string: \"*\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^(.*)$\",\n                                    ),\n                                ),\n                                group_name_list: [\n                                    \"0\",\n                                ],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: SingleCapture {\n                                        filter: None,\n                                        allow_empty: true,\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            search: Component {\n                                pattern_string: \"*\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^(.*)$\",\n                                    ),\n                                ),\n                                group_name_list: [\n                                    \"0\",\n                                ],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: SingleCapture {\n                                        filter: None,\n                                        allow_empty: true,\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            hash: Component {\n                                pattern_string: \"*\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^(.*)$\",\n                                    ),\n                                ),\n                                group_name_list: [\n                                    \"0\",\n                                ],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: SingleCapture {\n                                        filter: None,\n                                        allow_empty: true,\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                        },\n                        \"https://tauri.app\",\n                    ),\n                },\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: None,\n            },\n        ],\n        \"plugin:fs|read_file\": [\n            ResolvedCommand {\n                context: Remote {\n                    url: RemoteUrlPattern(\n                        UrlPattern {\n                            protocol: Component {\n                                pattern_string: \"https\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^https$\",\n                                    ),\n                                ),\n                                group_name_list: [],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: Literal {\n                                        literal: \"https\",\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            username: Component {\n                                pattern_string: \"*\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^(.*)$\",\n                                    ),\n                                ),\n                                group_name_list: [\n                                    \"0\",\n                                ],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: SingleCapture {\n                                        filter: None,\n                                        allow_empty: true,\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            password: Component {\n                                pattern_string: \"*\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^(.*)$\",\n                                    ),\n                                ),\n                                group_name_list: [\n                                    \"0\",\n                                ],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: SingleCapture {\n                                        filter: None,\n                                        allow_empty: true,\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            hostname: Component {\n                                pattern_string: \"tauri.app\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^tauri\\\\.app$\",\n                                    ),\n                                ),\n                                group_name_list: [],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: Literal {\n                                        literal: \"tauri.app\",\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            port: Component {\n                                pattern_string: \"\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^$\",\n                                    ),\n                                ),\n                                group_name_list: [],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: Literal {\n                                        literal: \"\",\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            pathname: Component {\n                                pattern_string: \"*\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^(.*)$\",\n                                    ),\n                                ),\n                                group_name_list: [\n                                    \"0\",\n                                ],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: SingleCapture {\n                                        filter: None,\n                                        allow_empty: true,\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            search: Component {\n                                pattern_string: \"*\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^(.*)$\",\n                                    ),\n                                ),\n                                group_name_list: [\n                                    \"0\",\n                                ],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: SingleCapture {\n                                        filter: None,\n                                        allow_empty: true,\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                            hash: Component {\n                                pattern_string: \"*\",\n                                regexp: Ok(\n                                    Regex(\n                                        \"(?u)^(.*)$\",\n                                    ),\n                                ),\n                                group_name_list: [\n                                    \"0\",\n                                ],\n                                matcher: Matcher {\n                                    prefix: \"\",\n                                    suffix: \"\",\n                                    inner: SingleCapture {\n                                        filter: None,\n                                        allow_empty: true,\n                                    },\n                                    ignore_case: false,\n                                },\n                                has_regexp_group: false,\n                            },\n                        },\n                        \"https://tauri.app\",\n                    ),\n                },\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: None,\n            },\n        ],\n    },\n    denied_commands: {},\n    command_scope: {},\n    global_scope: {\n        \"fs\": ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$APPDATA\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n    },\n}\n"
  },
  {
    "path": "crates/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer.snap",
    "content": "---\nsource: crates/tests/acl/src/lib.rs\nexpression: resolved\n---\nResolved {\n    has_app_acl: false,\n    allowed_commands: {\n        \"plugin:fs|read_dir\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: None,\n            },\n        ],\n        \"plugin:fs|read_file\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: None,\n            },\n        ],\n    },\n    denied_commands: {},\n    command_scope: {},\n    global_scope: {\n        \"fs\": ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$APPDATA\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n    },\n}\n"
  },
  {
    "path": "crates/tests/acl/fixtures/snapshots/acl_tests__tests__multiwebview.snap",
    "content": "---\nsource: crates/tests/acl/src/lib.rs\nexpression: resolved\n---\nResolved {\n    has_app_acl: false,\n    allowed_commands: {\n        \"plugin:ping|ping\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [\n                    Pattern {\n                        original: \"child1\",\n                        tokens: [\n                            Char(\n                                'c',\n                            ),\n                            Char(\n                                'h',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'l',\n                            ),\n                            Char(\n                                'd',\n                            ),\n                            Char(\n                                '1',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                    Pattern {\n                        original: \"child2\",\n                        tokens: [\n                            Char(\n                                'c',\n                            ),\n                            Char(\n                                'h',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'l',\n                            ),\n                            Char(\n                                'd',\n                            ),\n                            Char(\n                                '2',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                scope_id: None,\n            },\n        ],\n    },\n    denied_commands: {},\n    command_scope: {},\n    global_scope: {},\n}\n"
  },
  {
    "path": "crates/tests/acl/fixtures/snapshots/acl_tests__tests__multiwindow.snap",
    "content": "---\nsource: crates/tests/acl/src/lib.rs\nexpression: resolved\n---\nResolved {\n    has_app_acl: false,\n    allowed_commands: {\n        \"plugin:fs|move\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    4,\n                ),\n            },\n        ],\n        \"plugin:fs|read_dir\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    1,\n                ),\n            },\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    3,\n                ),\n            },\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: None,\n            },\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"external\",\n                        tokens: [\n                            Char(\n                                'e',\n                            ),\n                            Char(\n                                'x',\n                            ),\n                            Char(\n                                't',\n                            ),\n                            Char(\n                                'e',\n                            ),\n                            Char(\n                                'r',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'l',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: None,\n            },\n        ],\n        \"plugin:fs|read_file\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    2,\n                ),\n            },\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    3,\n                ),\n            },\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"external\",\n                        tokens: [\n                            Char(\n                                'e',\n                            ),\n                            Char(\n                                'x',\n                            ),\n                            Char(\n                                't',\n                            ),\n                            Char(\n                                'e',\n                            ),\n                            Char(\n                                'r',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'l',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: None,\n            },\n        ],\n    },\n    denied_commands: {},\n    command_scope: {\n        1: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$CONFIG/*\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n        2: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$CONFIG/*\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n        3: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$RESOURCE/**\",\n                        ),\n                    },\n                ),\n                Map(\n                    {\n                        \"path\": String(\n                            \"$RESOURCE\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n        4: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$TEMP/*\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n    },\n    global_scope: {\n        \"fs\": ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$APPDATA\",\n                        ),\n                    },\n                ),\n                Map(\n                    {\n                        \"path\": String(\n                            \"$DOWNLOAD\",\n                        ),\n                    },\n                ),\n                Map(\n                    {\n                        \"path\": String(\n                            \"$DOWNLOAD/**\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$HOME\",\n                        ),\n                    },\n                ),\n                Map(\n                    {\n                        \"path\": String(\n                            \"$HOME\",\n                        ),\n                    },\n                ),\n            ],\n        },\n    },\n}\n"
  },
  {
    "path": "crates/tests/acl/fixtures/snapshots/acl_tests__tests__scope-extended.snap",
    "content": "---\nsource: crates/tests/acl/src/lib.rs\nexpression: resolved\n---\nResolved {\n    has_app_acl: false,\n    allowed_commands: {\n        \"plugin:fs|move\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    4,\n                ),\n            },\n        ],\n        \"plugin:fs|read_dir\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    1,\n                ),\n            },\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    3,\n                ),\n            },\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: None,\n            },\n        ],\n        \"plugin:fs|read_file\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    2,\n                ),\n            },\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    3,\n                ),\n            },\n        ],\n    },\n    denied_commands: {},\n    command_scope: {\n        1: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$HOME/.config/**\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n        2: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$HOME/.config/**\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n        3: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$RESOURCE/**\",\n                        ),\n                    },\n                ),\n                Map(\n                    {\n                        \"path\": String(\n                            \"$RESOURCE\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$RESOURCE/**/*.key\",\n                        ),\n                    },\n                ),\n            ],\n        },\n        4: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$TEMP/*\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n    },\n    global_scope: {\n        \"fs\": ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$APPDATA/**\",\n                        ),\n                    },\n                ),\n                Map(\n                    {\n                        \"path\": String(\n                            \"$APPDATA\",\n                        ),\n                    },\n                ),\n                Map(\n                    {\n                        \"path\": String(\n                            \"$DOWNLOAD\",\n                        ),\n                    },\n                ),\n                Map(\n                    {\n                        \"path\": String(\n                            \"$DOWNLOAD/**\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$HOME\",\n                        ),\n                    },\n                ),\n                Map(\n                    {\n                        \"path\": String(\n                            \"$APPDATA/*.db\",\n                        ),\n                    },\n                ),\n            ],\n        },\n    },\n}\n"
  },
  {
    "path": "crates/tests/acl/fixtures/snapshots/acl_tests__tests__scope.snap",
    "content": "---\nsource: crates/tests/acl/src/lib.rs\nexpression: resolved\n---\nResolved {\n    has_app_acl: false,\n    allowed_commands: {\n        \"plugin:fs|move\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    2,\n                ),\n            },\n        ],\n        \"plugin:fs|read_dir\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: None,\n            },\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    1,\n                ),\n            },\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: None,\n            },\n        ],\n        \"plugin:fs|read_file\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: None,\n            },\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    1,\n                ),\n            },\n        ],\n    },\n    denied_commands: {},\n    command_scope: {\n        1: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$RESOURCE/**\",\n                        ),\n                    },\n                ),\n                Map(\n                    {\n                        \"path\": String(\n                            \"$RESOURCE\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n        2: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$TEMP/*\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n    },\n    global_scope: {\n        \"fs\": ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$APPDATA\",\n                        ),\n                    },\n                ),\n                Map(\n                    {\n                        \"path\": String(\n                            \"$DOWNLOAD\",\n                        ),\n                    },\n                ),\n                Map(\n                    {\n                        \"path\": String(\n                            \"$DOWNLOAD/**\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$HOME\",\n                        ),\n                    },\n                ),\n            ],\n        },\n    },\n}\n"
  },
  {
    "path": "crates/tests/acl/fixtures/snapshots/linux/acl_tests__tests__platform-specific-permissions.snap",
    "content": "---\nsource: crates/tests/acl/src/lib.rs\nexpression: resolved\n---\nResolved {\n    has_app_acl: false,\n    allowed_commands: {\n        \"plugin:os|spawn\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    1,\n                ),\n            },\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    2,\n                ),\n            },\n        ],\n    },\n    denied_commands: {},\n    command_scope: {\n        1: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"command\": String(\n                            \"apt\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n        2: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"command\": String(\n                            \"servo\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n    },\n    global_scope: {},\n}\n"
  },
  {
    "path": "crates/tests/acl/fixtures/snapshots/macOS/acl_tests__tests__platform-specific-permissions.snap",
    "content": "---\nsource: crates/tests/acl/src/lib.rs\nexpression: resolved\n---\nResolved {\n    allowed_commands: {\n        \"plugin:os|spawn\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    1,\n                ),\n            },\n        ],\n    },\n    denied_commands: {},\n    command_scope: {\n        1: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"command\": String(\n                            \"safari\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n    },\n    global_scope: {\n        \"os\": ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$HOME/Library/**\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n    },\n}\n"
  },
  {
    "path": "crates/tests/acl/fixtures/snapshots/windows/acl_tests__tests__platform-specific-permissions.snap",
    "content": "---\nsource: crates/tests/acl/src/lib.rs\nexpression: resolved\n---\nResolved {\n    has_app_acl: false,\n    allowed_commands: {\n        \"plugin:os|spawn\": [\n            ResolvedCommand {\n                context: Local,\n                windows: [\n                    Pattern {\n                        original: \"main\",\n                        tokens: [\n                            Char(\n                                'm',\n                            ),\n                            Char(\n                                'a',\n                            ),\n                            Char(\n                                'i',\n                            ),\n                            Char(\n                                'n',\n                            ),\n                        ],\n                        is_recursive: false,\n                    },\n                ],\n                webviews: [],\n                scope_id: Some(\n                    1,\n                ),\n            },\n        ],\n    },\n    denied_commands: {},\n    command_scope: {\n        1: ResolvedScope {\n            allow: [\n                Map(\n                    {\n                        \"command\": String(\n                            \"edge\",\n                        ),\n                    },\n                ),\n            ],\n            deny: [],\n        },\n    },\n    global_scope: {\n        \"os\": ResolvedScope {\n            allow: [],\n            deny: [\n                Map(\n                    {\n                        \"path\": String(\n                            \"$APPLOCALDATA/EBWebView/**\",\n                        ),\n                    },\n                ),\n            ],\n        },\n    },\n}\n"
  },
  {
    "path": "crates/tests/acl/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#[cfg(test)]\nmod tests {\n  use std::{\n    collections::BTreeMap,\n    env::temp_dir,\n    fs::{read_dir, read_to_string},\n    path::Path,\n  };\n\n  use tauri_utils::{\n    acl::{build::parse_capabilities, manifest::Manifest, resolved::Resolved},\n    platform::Target,\n  };\n\n  fn load_plugins(plugins: &[String]) -> BTreeMap<String, Manifest> {\n    let mut manifests = BTreeMap::new();\n\n    let manifest_dir = Path::new(env!(\"CARGO_MANIFEST_DIR\"));\n    let out_dir = temp_dir();\n\n    for plugin in plugins {\n      let plugin_path = manifest_dir.join(\"fixtures\").join(\"plugins\").join(plugin);\n\n      let permission_files = tauri_utils::acl::build::define_permissions(\n        &format!(\"{}/*.toml\", plugin_path.display()),\n        plugin,\n        &out_dir,\n        |_| true,\n      )\n      .expect(\"failed to define permissions\");\n      let manifest = Manifest::new(permission_files, None);\n      manifests.insert(plugin.to_string(), manifest);\n    }\n\n    manifests\n  }\n\n  #[test]\n  fn resolve_acl() {\n    let manifest_dir = Path::new(env!(\"CARGO_MANIFEST_DIR\"));\n    let fixtures_path = manifest_dir.join(\"fixtures\").join(\"capabilities\");\n    for fixture_path in read_dir(fixtures_path).expect(\"failed to read fixtures\") {\n      let fixture_entry = fixture_path.expect(\"failed to read fixture entry\");\n\n      let mut settings = insta::Settings::clone_current();\n      settings.set_snapshot_path(\n        if fixture_entry.path().file_name().unwrap() == \"platform-specific-permissions\" {\n          Path::new(\"../fixtures/snapshots\").join(Target::current().to_string())\n        } else {\n          Path::new(\"../fixtures/snapshots\").to_path_buf()\n        },\n      );\n      let _guard = settings.bind_to_scope();\n\n      let fixture_plugins_str = read_to_string(fixture_entry.path().join(\"required-plugins.json\"))\n        .expect(\"failed to read fixture required-plugins.json file\");\n      let fixture_plugins: Vec<String> = serde_json::from_str(&fixture_plugins_str)\n        .expect(\"required-plugins.json is not a valid JSON\");\n\n      let manifests = load_plugins(&fixture_plugins);\n      let capabilities = parse_capabilities(&format!(\"{}/cap*\", fixture_entry.path().display()))\n        .expect(\"failed to parse capabilities\");\n\n      let resolved = Resolved::resolve(&manifests, capabilities, Target::current())\n        .expect(\"failed to resolve ACL\");\n\n      insta::assert_debug_snapshot!(\n        fixture_entry\n          .path()\n          .file_name()\n          .unwrap()\n          .to_string_lossy()\n          .to_string(),\n        resolved\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "crates/tests/app-updater/frameworks/test.framework/Headers",
    "content": "Versions/Current/Headers"
  },
  {
    "path": "crates/tests/app-updater/frameworks/test.framework/Modules",
    "content": "Versions/Current/Modules"
  },
  {
    "path": "crates/tests/app-updater/frameworks/test.framework/Resources",
    "content": "Versions/Current/Resources"
  },
  {
    "path": "crates/tests/app-updater/frameworks/test.framework/Versions/A/Headers/test.h",
    "content": "//\n//  test.h\n//  test\n//\n//  Created by Trey Smith on 9/15/23.\n//\n\n#import <Foundation/Foundation.h>\n\n//! Project version number for test.\nFOUNDATION_EXPORT double testVersionNumber;\n\n//! Project version string for test.\nFOUNDATION_EXPORT const unsigned char testVersionString[];\n\n// In this header, you should import all the public headers of your framework using statements like #import <test/PublicHeader.h>\n\n\n"
  },
  {
    "path": "crates/tests/app-updater/frameworks/test.framework/Versions/A/Modules/module.modulemap",
    "content": "framework module test {\n  umbrella header \"test.h\"\n\n  export *\n  module * { export * }\n}\n"
  },
  {
    "path": "crates/tests/app-updater/frameworks/test.framework/Versions/A/Resources/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>BuildMachineOSBuild</key>\n\t<string>22D68</string>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>test</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>com.tauri.test</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>test</string>\n\t<key>CFBundlePackageType</key>\n\t<string>FMWK</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleSupportedPlatforms</key>\n\t<array>\n\t\t<string>MacOSX</string>\n\t</array>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>DTCompiler</key>\n\t<string>com.apple.compilers.llvm.clang.1_0</string>\n\t<key>DTPlatformBuild</key>\n\t<string></string>\n\t<key>DTPlatformName</key>\n\t<string>macosx</string>\n\t<key>DTPlatformVersion</key>\n\t<string>13.3</string>\n\t<key>DTSDKBuild</key>\n\t<string>22E245</string>\n\t<key>DTSDKName</key>\n\t<string>macosx13.3</string>\n\t<key>DTXcode</key>\n\t<string>1431</string>\n\t<key>DTXcodeBuild</key>\n\t<string>14E300c</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>13.2</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "crates/tests/app-updater/frameworks/test.framework/Versions/A/_CodeSignature/CodeResources",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>files</key>\n\t<dict>\n\t\t<key>Resources/Info.plist</key>\n\t\t<data>\n\t\t/aPV7Q20g0elr7OiZJoUNggTOcg=\n\t\t</data>\n\t</dict>\n\t<key>files2</key>\n\t<dict>\n\t\t<key>Headers/test.h</key>\n\t\t<dict>\n\t\t\t<key>hash2</key>\n\t\t\t<data>\n\t\t\t5RA6Mnq5sNoaC4wKcFe6zymVmEL5Vb44G4BGqFjgZMM=\n\t\t\t</data>\n\t\t</dict>\n\t\t<key>Modules/module.modulemap</key>\n\t\t<dict>\n\t\t\t<key>hash2</key>\n\t\t\t<data>\n\t\t\tC6uLLSnQu9M2qLElVCkeo2JpnvWMxtArinQzmlh3v2A=\n\t\t\t</data>\n\t\t</dict>\n\t\t<key>Resources/Info.plist</key>\n\t\t<dict>\n\t\t\t<key>hash2</key>\n\t\t\t<data>\n\t\t\tnPMotNIMgvMfHtkRdpeehzfBiCZLnksfiD3nldUPzTE=\n\t\t\t</data>\n\t\t</dict>\n\t</dict>\n\t<key>rules</key>\n\t<dict>\n\t\t<key>^Resources/</key>\n\t\t<true/>\n\t\t<key>^Resources/.*\\.lproj/</key>\n\t\t<dict>\n\t\t\t<key>optional</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>1000</real>\n\t\t</dict>\n\t\t<key>^Resources/.*\\.lproj/locversion.plist$</key>\n\t\t<dict>\n\t\t\t<key>omit</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>1100</real>\n\t\t</dict>\n\t\t<key>^Resources/Base\\.lproj/</key>\n\t\t<dict>\n\t\t\t<key>weight</key>\n\t\t\t<real>1010</real>\n\t\t</dict>\n\t\t<key>^version.plist$</key>\n\t\t<true/>\n\t</dict>\n\t<key>rules2</key>\n\t<dict>\n\t\t<key>.*\\.dSYM($|/)</key>\n\t\t<dict>\n\t\t\t<key>weight</key>\n\t\t\t<real>11</real>\n\t\t</dict>\n\t\t<key>^(.*/)?\\.DS_Store$</key>\n\t\t<dict>\n\t\t\t<key>omit</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>2000</real>\n\t\t</dict>\n\t\t<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>\n\t\t<dict>\n\t\t\t<key>nested</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>10</real>\n\t\t</dict>\n\t\t<key>^.*</key>\n\t\t<true/>\n\t\t<key>^Info\\.plist$</key>\n\t\t<dict>\n\t\t\t<key>omit</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>20</real>\n\t\t</dict>\n\t\t<key>^PkgInfo$</key>\n\t\t<dict>\n\t\t\t<key>omit</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>20</real>\n\t\t</dict>\n\t\t<key>^Resources/</key>\n\t\t<dict>\n\t\t\t<key>weight</key>\n\t\t\t<real>20</real>\n\t\t</dict>\n\t\t<key>^Resources/.*\\.lproj/</key>\n\t\t<dict>\n\t\t\t<key>optional</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>1000</real>\n\t\t</dict>\n\t\t<key>^Resources/.*\\.lproj/locversion.plist$</key>\n\t\t<dict>\n\t\t\t<key>omit</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>1100</real>\n\t\t</dict>\n\t\t<key>^Resources/Base\\.lproj/</key>\n\t\t<dict>\n\t\t\t<key>weight</key>\n\t\t\t<real>1010</real>\n\t\t</dict>\n\t\t<key>^[^/]+$</key>\n\t\t<dict>\n\t\t\t<key>nested</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>10</real>\n\t\t</dict>\n\t\t<key>^embedded\\.provisionprofile$</key>\n\t\t<dict>\n\t\t\t<key>weight</key>\n\t\t\t<real>20</real>\n\t\t</dict>\n\t\t<key>^version\\.plist$</key>\n\t\t<dict>\n\t\t\t<key>weight</key>\n\t\t\t<real>20</real>\n\t\t</dict>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "crates/tests/app-updater/frameworks/test.framework/Versions/Current",
    "content": "A"
  },
  {
    "path": "crates/tests/restart/Cargo.toml",
    "content": "[package]\nname = \"restart\"\nversion = \"0.1.0\"\nauthors = [\"Tauri Programme within The Commons Conservancy\"]\nlicense = \"Apache-2.0 OR MIT\"\nedition = \"2021\"\n\n[dependencies.tauri]\npath = \"../../tauri\"\n\n[dev-dependencies]\ntempfile = \"3\"\n\n[features]\nprocess-relaunch-dangerous-allow-symlink-macos = []\n"
  },
  {
    "path": "crates/tests/restart/LICENSE.spdx",
    "content": "../../../LICENSE.spdx"
  },
  {
    "path": "crates/tests/restart/LICENSE_APACHE-2.0",
    "content": "../../../LICENSE_APACHE-2.0"
  },
  {
    "path": "crates/tests/restart/LICENSE_MIT",
    "content": "../../../LICENSE_MIT"
  },
  {
    "path": "crates/tests/restart/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nfn main() {}\n"
  },
  {
    "path": "crates/tests/restart/src/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse tauri::Env;\n\nfn main() {\n  let mut argv = std::env::args();\n  let argc = argv.len();\n  if argc == 0 || argc > 2 {\n    panic!(\"restart test binary expect either no arguments or `restart`.\")\n  }\n\n  println!(\n    \"{}\",\n    tauri::process::current_binary(&Default::default())\n      .expect(\"tauri::process::current_binary could not resolve\")\n      .display()\n  );\n\n  match argv.nth(1).as_deref() {\n    Some(\"restart\") => {\n      let mut env = Env::default();\n      env.args_os.clear();\n      tauri::process::restart(&env)\n    }\n    Some(invalid) => panic!(\"only argument `restart` is allowed, {invalid} is invalid\"),\n    None => {}\n  };\n}\n"
  },
  {
    "path": "crates/tests/restart/tests/restart.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::io;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\n/// Helper for generic catch-all errors.\ntype Result = std::result::Result<(), Box<dyn std::error::Error>>;\n\n/// <https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--1300-1699->\n#[cfg(windows)]\nconst ERROR_PRIVILEGE_NOT_HELD: i32 = 1314;\n\n/// Represents a successfully created symlink.\nenum Symlink {\n  /// Path to the created symlink\n  Created(PathBuf),\n\n  /// A symlink that failed due to missing permissions (Windows).\n  #[allow(dead_code)]\n  Privilege,\n}\n\n/// Compile the test binary, run it, and compare it with expected output.\n///\n/// Failing to create a symlink due to permissions issues is also a success\n/// for the purpose of this runner.\nfn symlink_runner(create_symlinks: impl Fn(&Path) -> io::Result<Symlink>) -> Result {\n  let mut compiled_binary = PathBuf::from(env!(\"OUT_DIR\")).join(\"../../../restart\");\n  if cfg!(windows) {\n    compiled_binary.set_extension(\"exe\");\n  }\n  println!(\"{compiled_binary:?}\");\n\n  // set up all the temporary file paths\n  let temp = tempfile::TempDir::new()?;\n  let bin = temp.path().canonicalize()?.join(\"restart.exe\");\n\n  // copy the built restart test binary to our temporary directory\n  std::fs::copy(compiled_binary, &bin)?;\n\n  if let Symlink::Created(link) = create_symlinks(&bin)? {\n    // run the command from the symlink, so that we can test if restart resolves it correctly\n    let mut cmd = Command::new(link);\n\n    // add the restart parameter so that the invocation will call tauri::process::restart\n    cmd.arg(\"restart\");\n\n    let output = cmd.output()?;\n\n    // run `TempDir` destructors to prevent resource leaking if the assertion fails\n    drop(temp);\n\n    if output.status.success() {\n      // gather the output into a string\n      let stdout = String::from_utf8_lossy(&output.stdout);\n\n      // we expect the output to be the bin path, twice\n      assert_eq!(stdout, format!(\"{bin}\\n{bin}\\n\", bin = bin.display()));\n    } else if cfg!(all(\n      target_os = \"macos\",\n      not(feature = \"process-relaunch-dangerous-allow-symlink-macos\")\n    )) {\n      // we expect this to fail on macOS without the dangerous symlink flag set\n      let stderr = String::from_utf8_lossy(&output.stderr);\n\n      // make sure it's the error that we expect\n      assert!(stderr.contains(\n        \"StartingBinary found current_exe() that contains a symlink on a non-allowed platform\"\n      ));\n    } else {\n      // we didn't expect the program to fail in this configuration, just panic\n      panic!(\"restart integration test runner failed for unknown reason\");\n    }\n  }\n\n  Ok(())\n}\n\n/// Cross-platform way to create a symlink\n///\n/// Symlinks that failed to create due to permissions issues (like on Windows)\n/// are also seen as successful for the purpose of this testing suite.\nfn create_symlink(original: &Path, link: PathBuf) -> io::Result<Symlink> {\n  #[cfg(unix)]\n  return std::os::unix::fs::symlink(original, &link).map(|()| Symlink::Created(link));\n\n  #[cfg(windows)]\n  return match std::os::windows::fs::symlink_file(original, &link) {\n    Ok(()) => Ok(Symlink::Created(link)),\n    Err(e) => match e.raw_os_error() {\n      Some(ERROR_PRIVILEGE_NOT_HELD) => Ok(Symlink::Privilege),\n      _ => Err(e),\n    },\n  };\n}\n\n/// Only use 1 test to prevent cargo from waiting on itself.\n///\n/// While not ideal, this is fine because they use the same solution for both cases.\n#[test]\nfn restart_symlinks() -> Result {\n  // single symlink\n  symlink_runner(|bin| {\n    let mut link = bin.to_owned();\n    link.set_file_name(\"symlink\");\n    link.set_extension(\"exe\");\n    create_symlink(bin, link)\n  })?;\n\n  // nested symlinks\n  symlink_runner(|bin| {\n    let mut link1 = bin.to_owned();\n    link1.set_file_name(\"symlink1\");\n    link1.set_extension(\"exe\");\n    create_symlink(bin, link1.clone())?;\n\n    let mut link2 = bin.to_owned();\n    link2.set_file_name(\"symlink2\");\n    link2.set_extension(\"exe\");\n    create_symlink(&link1, link2)\n  })\n}\n"
  },
  {
    "path": "dependabot.yml",
    "content": "# Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nversion: 2\nupdates:\n  # Crates\n  - package-ecosystem: 'cargo'\n    directory: '/crates/tauri'\n    schedule:\n      internal: 'daily'\n    labels:\n      - 'type: chore'\n    # disable version updates\n    open-pull-requests-limit: 0\n  - package-ecosystem: 'cargo'\n    directory: '/crates/tauri-build'\n    schedule:\n      internal: 'daily'\n    labels:\n      - 'type: chore'\n    # disable version updates\n    open-pull-requests-limit: 0\n  - package-ecosystem: 'cargo'\n    directory: '/crates/tauri-codegen'\n    schedule:\n      internal: 'daily'\n    labels:\n      - 'type: chore'\n    # disable version updates\n    open-pull-requests-limit: 0\n  - package-ecosystem: 'cargo'\n    directory: '/crates/tauri-macros'\n    schedule:\n      internal: 'daily'\n    labels:\n      - 'type: chore'\n    # disable version updates\n    open-pull-requests-limit: 0\n  - package-ecosystem: 'cargo'\n    directory: '/crates/tauri-runtime'\n    schedule:\n      internal: 'daily'\n    labels:\n      - 'type: chore'\n    # disable version updates\n    open-pull-requests-limit: 0\n  - package-ecosystem: 'cargo'\n    directory: '/crates/tauri-runtime-wry'\n    schedule:\n      internal: 'daily'\n    labels:\n      - 'type: chore'\n    # disable version updates\n    open-pull-requests-limit: 0\n  - package-ecosystem: 'cargo'\n    directory: '/crates/tauri-utils'\n    schedule:\n      internal: 'daily'\n    labels:\n      - 'type: chore'\n    # disable version updates\n    open-pull-requests-limit: 0\n  - package-ecosystem: 'cargo'\n    directory: '/crates/tauri-cli'\n    schedule:\n      internal: 'daily'\n    labels:\n      - 'type: chore'\n    # disable version updates\n    open-pull-requests-limit: 0\n  - package-ecosystem: 'cargo'\n    directory: '/crates/tauri-bundler'\n    schedule:\n      internal: 'daily'\n    labels:\n      - 'type: chore'\n    # disable version updates\n    open-pull-requests-limit: 0\n  - package-ecosystem: 'cargo'\n    directory: '/crates/tauri-macos-sign'\n    schedule:\n      internal: 'daily'\n    labels:\n      - 'type: chore'\n    # disable version updates\n    open-pull-requests-limit: 0\n\n  # NPM Packages\n  - package-ecosystem: 'npm'\n    directory: '/packages/api'\n    schedule:\n      internal: 'daily'\n    labels:\n      - 'type: chore'\n    # disable version updates\n    open-pull-requests-limit: 0\n  - package-ecosystem: 'npm'\n    directory: '/packages/cli'\n    schedule:\n      internal: 'daily'\n    labels:\n      - 'type: chore'\n    # disable version updates\n    open-pull-requests-limit: 0\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Examples\n\nFollowing directories contain examples of tauri app functionality.\n\nThere are different execution steps depending on the example. See each directory `README.md` for execution method.\n"
  },
  {
    "path": "examples/api/.gitignore",
    "content": "/dist/*\n!/dist/.gitkeep\n"
  },
  {
    "path": "examples/api/.setup-cross.sh",
    "content": "#!/usr/bin/env bash\n\nexport ICONS_VOLUME=\"$(realpath ../.icons)\"\nexport DIST_VOLUME=\"$(realpath dist)\"\nexport ISOLATION_VOLUME=\"$(realpath isolation-dist)\"\nexport WORKSPACE_VOLUME=\"$(realpath ../..)\"\n"
  },
  {
    "path": "examples/api/.taurignore",
    "content": "src-tauri/locales/\nsrc-tauri/Cross.toml\nsrc-tauri/.gitignore\n"
  },
  {
    "path": "examples/api/README.md",
    "content": "# API example\n\nThis example demonstrates Tauri's API capabilities using the `@tauri-apps/api` package. It's used as the main validation app, serving as the testbed of our development process.\nIn the future, this app will be used on Tauri's integration tests.\n\n![App screenshot](./screenshot.png?raw=true)\n\n## Running the example\n\n- Compile Tauri\n  go to root of the Tauri repo and run:\n\n```\npnpm i\npnpm build:debug\n```\n\n- Run the app in development mode (Run inside of this folder `examples/api/`)\n\n```bash\n$ pnpm tauri dev\n```\n\n- Build an run the release app (Run inside of this folder `examples/api/`)\n\n```bash\n$ pnpm tauri build\n$ ../../target/release/api\n```\n"
  },
  {
    "path": "examples/api/dist/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/api/index.html",
    "content": "<!doctype html>\n<html lang=\"en\" theme=\"dark\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>API Example App</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/api/isolation-dist/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Isolation Secure Script</title>\n  </head>\n  <body>\n    <script src=\"index.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/api/isolation-dist/index.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\n  return payload\n}\n"
  },
  {
    "path": "examples/api/jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"moduleResolution\": \"bundler\",\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    /**\n     * svelte-preprocess cannot figure out whether you have\n     * a value or a type, so tell TypeScript to enforce using\n     * `import type` instead of `import` for Types.\n     */\n    \"verbatimModuleSyntax\": true,\n    \"isolatedModules\": true,\n    \"resolveJsonModule\": true,\n    /**\n     * To have warnings / errors of the Svelte compiler at the\n     * correct position, enable source maps by default.\n     */\n    \"sourceMap\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    /**\n     * Typecheck JS in `.svelte` and `.js` files by default.\n     * Disable this if you'd like to use dynamic types.\n     */\n    \"checkJs\": true\n  },\n  /**\n   * Use global.d.ts instead of compilerOptions.types\n   * to avoid limiting type declarations.\n   */\n  \"include\": [\"src/**/*.d.ts\", \"src/**/*.js\", \"src/**/*.svelte\"]\n}\n"
  },
  {
    "path": "examples/api/package.json",
    "content": "{\n  \"name\": \"api\",\n  \"private\": true,\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"tauri\": \"node ../../packages/cli/tauri.js\"\n  },\n  \"dependencies\": {\n    \"@tauri-apps/api\": \"../../packages/api/dist\"\n  },\n  \"devDependencies\": {\n    \"@iconify-json/codicon\": \"^1.2.49\",\n    \"@iconify-json/ph\": \"^1.2.2\",\n    \"@sveltejs/vite-plugin-svelte\": \"^7.0.0\",\n    \"@unocss/extractor-svelte\": \"^66.6.6\",\n    \"svelte\": \"^5.53.11\",\n    \"unocss\": \"^66.6.6\",\n    \"vite\": \"^8.0.0\"\n  }\n}\n"
  },
  {
    "path": "examples/api/src/App.svelte",
    "content": "<script>\n  import { onMount, tick } from 'svelte'\n  import { writable } from 'svelte/store'\n  import { invoke } from '@tauri-apps/api/core'\n  import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'\n  import { setTheme } from '@tauri-apps/api/app'\n\n  import Welcome from './views/Welcome.svelte'\n  import Communication from './views/Communication.svelte'\n  import Window from './views/Window.svelte'\n  import WebRTC from './views/WebRTC.svelte'\n  import App from './views/App.svelte'\n  import Menu from './views/Menu.svelte'\n  import Tray from './views/Tray.svelte'\n\n  document.addEventListener('keydown', (event) => {\n    if (event.ctrlKey && event.key === 'b') {\n      invoke('plugin:app-menu|toggle')\n    }\n  })\n\n  const appWindow = getCurrentWebviewWindow()\n  appWindow.onDragDropEvent((event) => {\n    onMessage(event.payload)\n  })\n\n  const userAgent = navigator.userAgent.toLowerCase()\n  const isMobile = userAgent.includes('android') || userAgent.includes('iphone')\n\n  const desktopViews = [\n    {\n      label: 'App',\n      component: App,\n      icon: 'i-codicon-hubot'\n    },\n    {\n      label: 'Window',\n      component: Window,\n      icon: 'i-codicon-window'\n    },\n    {\n      label: 'Menu',\n      component: Menu,\n      icon: 'i-ph-list'\n    },\n    {\n      label: 'Tray',\n      component: Tray,\n      icon: 'i-ph-tray'\n    }\n  ]\n\n  const views = [\n    {\n      label: 'Welcome',\n      component: Welcome,\n      icon: 'i-ph-hand-waving'\n    },\n    {\n      label: 'Communication',\n      component: Communication,\n      icon: 'i-codicon-radio-tower'\n    },\n    ...(isMobile ? [] : desktopViews),\n    {\n      label: 'WebRTC',\n      component: WebRTC,\n      icon: 'i-ph-broadcast'\n    }\n  ]\n\n  let selected = $state.raw(views[0])\n  function select(view) {\n    selected = view\n  }\n\n  // dark/light\n  let isDark = $state()\n  onMount(() => {\n    isDark = localStorage && localStorage.getItem('theme') == 'dark'\n    applyTheme(isDark)\n  })\n  function applyTheme(isDark) {\n    const html = document.querySelector('html')\n    isDark ? html.classList.add('dark') : html.classList.remove('dark')\n    localStorage && localStorage.setItem('theme', isDark ? 'dark' : '')\n  }\n  function toggleDark() {\n    isDark = !isDark\n    applyTheme(isDark)\n    setTheme(isDark ? 'dark' : 'light')\n  }\n\n  // Console\n  let messages = writable([])\n  let consoleTextEl = $state()\n\n  // this function is renders HTML without sanitizing it so it's insecure\n  // we only use it with our own input data\n  async function insecureRenderHtml(html) {\n    messages.update((r) => [\n      ...r,\n      {\n        html: `<pre><strong class=\"text-accent dark:text-darkAccent\">[${new Date().toLocaleTimeString()}]:</strong> ${html}</pre>`\n      }\n    ])\n    await tick()\n    if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight\n  }\n\n  async function onMessage(value) {\n    const valueStr =\n      typeof value === 'string'\n        ? value\n        : JSON.stringify(\n            value instanceof ArrayBuffer\n              ? Array.from(new Uint8Array(value))\n              : value,\n            null,\n            1\n          )\n    insecureRenderHtml(valueStr)\n  }\n\n  function clear() {\n    messages.update(() => [])\n  }\n\n  let consoleEl = $state(),\n    consoleH,\n    cStartY\n  let minConsoleHeight = 50\n  function startResizingConsole(e) {\n    cStartY = e.clientY\n\n    const styles = window.getComputedStyle(consoleEl)\n    consoleH = parseInt(styles.height, 10)\n\n    const moveHandler = (e) => {\n      const dy = e.clientY - cStartY\n      const newH = consoleH - dy\n      consoleEl.style.height = `${\n        newH < minConsoleHeight ? minConsoleHeight : newH\n      }px`\n    }\n    const upHandler = () => {\n      document.removeEventListener('mouseup', upHandler)\n      document.removeEventListener('mousemove', moveHandler)\n    }\n    document.addEventListener('mouseup', upHandler)\n    document.addEventListener('mousemove', moveHandler)\n  }\n\n  // mobile\n  let isSideBarOpen = $state(false)\n  let sidebar\n  let sidebarToggle\n  let isDraggingSideBar = false\n  let draggingStartPosX = 0\n  let draggingEndPosX = 0\n  const clamp = (min, num, max) => Math.min(Math.max(num, min), max)\n\n  function toggleSidebar(sidebar, isSideBarOpen) {\n    sidebar.style.setProperty(\n      '--translate-x',\n      `${isSideBarOpen ? '0' : '-18.75'}rem`\n    )\n  }\n\n  onMount(() => {\n    sidebar = document.querySelector('#sidebar')\n    sidebarToggle = document.querySelector('#sidebarToggle')\n\n    document.addEventListener('click', (e) => {\n      if (sidebarToggle.contains(e.target)) {\n        isSideBarOpen = !isSideBarOpen\n      } else if (isSideBarOpen && !sidebar.contains(e.target)) {\n        isSideBarOpen = false\n      }\n    })\n\n    document.addEventListener('touchstart', (e) => {\n      if (sidebarToggle.contains(e.target)) return\n\n      const x = e.touches[0].clientX\n      if ((0 < x && x < 20 && !isSideBarOpen) || isSideBarOpen) {\n        isDraggingSideBar = true\n        draggingStartPosX = x\n      }\n    })\n\n    document.addEventListener('touchmove', (e) => {\n      if (isDraggingSideBar) {\n        const x = e.touches[0].clientX\n        draggingEndPosX = x\n        const delta = (x - draggingStartPosX) / 10\n        sidebar.style.setProperty(\n          '--translate-x',\n          `-${clamp(0, isSideBarOpen ? 0 - delta : 18.75 - delta, 18.75)}rem`\n        )\n      }\n    })\n\n    document.addEventListener('touchend', () => {\n      if (isDraggingSideBar) {\n        const delta = (draggingEndPosX - draggingStartPosX) / 10\n        isSideBarOpen = isSideBarOpen ? delta > -(18.75 / 2) : delta > 18.75 / 2\n      }\n\n      isDraggingSideBar = false\n    })\n  })\n\n  $effect(() => {\n    const sidebar = document.querySelector('#sidebar')\n    if (sidebar) {\n      toggleSidebar(sidebar, isSideBarOpen)\n    }\n  })\n</script>\n\n<!-- Sidebar toggle, only visible on small screens -->\n<div\n  id=\"sidebarToggle\"\n  class=\"z-2000 hidden lt-sm:flex justify-center items-center absolute top-2 left-2 w-8 h-8 rd-8\n            bg-accent dark:bg-darkAccent active:bg-accentDark dark:active:bg-darkAccentDark\"\n>\n  {#if isSideBarOpen}\n    <span class=\"i-codicon-close animate-duration-300ms animate-fade-in\"></span>\n  {:else}\n    <span class=\"i-codicon-menu animate-duration-300ms animate-fade-in\"></span>\n  {/if}\n</div>\n\n<div\n  class=\"flex h-screen w-screen overflow-hidden children-pt4 children-pb-2 text-primaryText dark:text-darkPrimaryText\"\n>\n  <aside\n    id=\"sidebar\"\n    class=\"lt-sm:h-screen lt-sm:shadow-lg lt-sm:shadow lt-sm:transition-transform lt-sm:absolute lt-sm:z-1999\n      bg-darkPrimaryLighter transition-colors-250 overflow-hidden grid grid-rows-[min-content_auto] select-none px-2\"\n  >\n    <img\n      class=\"self-center p-7 cursor-pointer\"\n      src=\"tauri_logo.png\"\n      alt=\"Tauri logo\"\n    />\n    <a href=\"##\" class=\"nv justify-between\" onclick={toggleDark}>\n      {#if isDark}\n        Switch to Light mode\n        <div class=\"i-ph-sun\"></div>\n      {:else}\n        Switch to Dark mode\n        <div class=\"i-ph-moon\"></div>\n      {/if}\n    </a>\n    <br />\n    <div class=\"bg-white/5 h-2px\"></div>\n    <br />\n\n    <a\n      class=\"nv justify-between\"\n      target=\"_blank\"\n      href=\"https://v2.tauri.app/start/\"\n    >\n      Documentation\n      <span class=\"i-codicon-link-external\"></span>\n    </a>\n    <a\n      class=\"nv justify-between\"\n      target=\"_blank\"\n      href=\"https://github.com/tauri-apps/tauri\"\n    >\n      GitHub\n      <span class=\"i-codicon-link-external\"></span>\n    </a>\n    <a\n      class=\"nv justify-between\"\n      target=\"_blank\"\n      href=\"https://github.com/tauri-apps/tauri/tree/dev/examples/api\"\n    >\n      Source\n      <span class=\"i-codicon-link-external\"></span>\n    </a>\n    <br />\n    <div class=\"bg-white/5 h-2px\"></div>\n    <br />\n    <div class=\"flex flex-col overflow-y-auto children-flex-none gap-1\">\n      {#each views as view}\n        <a\n          href=\"##\"\n          class=\"nv {selected === view ? 'nv_selected' : ''}\"\n          onclick={() => {\n            select(view)\n            isSideBarOpen = false\n          }}\n        >\n          <div class=\"{view.icon} mr-2\"></div>\n          <p>{view.label}</p>\n        </a>\n      {/each}\n    </div>\n  </aside>\n  <main\n    class=\"flex-1 bg-primary dark:bg-darkPrimary transition-transform transition-colors-250 grid grid-rows-[2fr_auto]\"\n  >\n    <div class=\"px-5 overflow-hidden grid grid-rows-[auto_1fr]\">\n      <h1>{selected.label}</h1>\n      <div class=\"overflow-y-auto\">\n        <div class=\"mr-2\">\n          <selected.component {onMessage} />\n        </div>\n      </div>\n    </div>\n\n    <div\n      bind:this={consoleEl}\n      id=\"console\"\n      class=\"select-none h-15rem grid grid-rows-[2px_2rem_1fr] gap-1 overflow-hidden\"\n    >\n      <div\n        role=\"button\"\n        tabindex=\"0\"\n        onmousedown={startResizingConsole}\n        class=\"bg-black/20 h-4px cursor-ns-resize\"\n      ></div>\n      <div class=\"flex justify-between items-center px-2\">\n        <p class=\"font-semibold\">Console</p>\n        <div\n          role=\"button\"\n          tabindex=\"0\"\n          class=\"cursor-pointer h-85% rd-1 p-1 flex justify-center items-center\n                hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay\n                active:bg-hoverOverlay/25 dark:active:bg-darkHoverOverlay/25\n          \"\n          onkeypress={(e) => (e.key === 'Enter' ? clear() : {})}\n          onclick={clear}\n        >\n          <div class=\"i-codicon-clear-all\"></div>\n        </div>\n      </div>\n      <div\n        bind:this={consoleTextEl}\n        class=\"px-2 overflow-y-auto all:font-mono code-block all:text-xs select-text mr-2\"\n      >\n        {#each $messages as r}\n          {@html r.html}\n        {/each}\n      </div>\n    </div>\n  </main>\n</div>\n"
  },
  {
    "path": "examples/api/src/app.css",
    "content": ":root {\n  line-height: 1.5;\n}\n\ninput,\nselect {\n  min-width: 0;\n}\n\n*:not(h1, h2, h3, h4, h5, h6) {\n  margin: 0;\n  padding: 0;\n}\n\n* {\n  box-sizing: border-box;\n  font-family: 'Rubik', sans-serif;\n}\n\n::-webkit-scrollbar {\n  width: 0.25rem;\n  height: 3px;\n}\n\n::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n  border-radius: 0.25rem;\n}\n\ncode {\n  padding: 0.05rem 0.25rem;\n}\n\ncode.code-block {\n  padding: 0.5rem;\n}\n\n#sidebar {\n  width: 18.75rem;\n}\n\n@media screen and (max-width: 640px) {\n  #sidebar {\n    --translate-x: -18.75rem;\n    transform: translateX(var(--translate-x));\n  }\n}\n"
  },
  {
    "path": "examples/api/src/components/MenuBuilder.svelte",
    "content": "<script>\n  import { CheckMenuItem } from '@tauri-apps/api/menu'\n  import MenuItemBuilder from './MenuItemBuilder.svelte'\n\n  let { items = $bindable([]), itemClick } = $props()\n\n  function addItem({ item, options }) {\n    items = [...items, { item, options }]\n  }\n\n  function onItemClick(detail) {\n    itemClick(detail)\n  }\n\n  function itemIcon(item) {\n    if (item.options.icon) {\n      return 'i-ph-images-square'\n    }\n    if (item.item instanceof CheckMenuItem) {\n      return item.options.checked ? 'i-ph-check-duotone' : 'i-ph-square-duotone'\n    }\n    if (item.options.item) {\n      return 'i-ph-globe-stand'\n    }\n    return 'i-ph-chat-teardrop-text'\n  }\n\n  function itemToString(item) {\n    // icon || check|normal || predefined\n    return item.options.icon || item.options.text || item.options.item\n  }\n</script>\n\n<div class=\"flex flex-col children:grow gap-2\">\n  <MenuItemBuilder newItem={addItem} itemClick={onItemClick} />\n\n  <div>\n    {#each items as item}\n      <div class=\"flex flex-row gap-1 items-center\">\n        <div class={itemIcon(item)}></div>\n        <p>{itemToString(item)}</p>\n      </div>\n    {/each}\n  </div>\n</div>\n"
  },
  {
    "path": "examples/api/src/components/MenuItemBuilder.svelte",
    "content": "<script>\n  import {\n    IconMenuItem,\n    CheckMenuItem,\n    PredefinedMenuItem,\n    MenuItem\n  } from '@tauri-apps/api/menu'\n\n  let { newItem, itemClick } = $props()\n\n  let kind = $state('Normal')\n  let text = $state('')\n  let icon = $state('')\n  /** @type {import('@tauri-apps/api/menu').PredefinedMenuItemOptions['item'] | undefined} */\n  let predefinedItem = undefined\n  let checked = $state(true)\n\n  const itemKinds = ['Normal', 'Icon', 'Check', 'Predefined']\n  const predefinedOptions = [\n    'Separator',\n    'Copy',\n    'Cut',\n    'Paste',\n    'SelectAll',\n    'Undo',\n    'Redo',\n    'Minimize',\n    'Maximize',\n    'Fullscreen',\n    'Hide',\n    'HideOthers',\n    'ShowAll',\n    'CloseWindow',\n    'Quit',\n    'Services'\n  ]\n\n  function onKindChange(event) {\n    kind = event.currentTarget.value\n  }\n\n  function onPredefinedChange(event) {\n    predefinedItem = event.currentTarget.value\n  }\n\n  async function create() {\n    let options = null\n    let item = null\n\n    const t = text\n\n    switch (kind) {\n      case 'Normal':\n        options = {\n          text,\n          action: (id) => itemClick({ id, text: t })\n        }\n        item = await MenuItem.new(options)\n        break\n      case 'Icon':\n        options = {\n          text,\n          icon,\n          action: (id) => itemClick({ id, text: t })\n        }\n        item = await IconMenuItem.new(options)\n        break\n      case 'Check':\n        options = {\n          text,\n          checked,\n          action: (id) => itemClick({ id, text: t })\n        }\n        item = await CheckMenuItem.new(options)\n        break\n      case 'Predefined':\n        options = {\n          item: predefinedItem\n        }\n        item = await PredefinedMenuItem.new(options)\n        break\n    }\n    newItem({ item, options })\n\n    text = ''\n    predefinedItem = undefined\n  }\n</script>\n\n<div class=\"flex flex-row gap-2 flex-grow-0\">\n  <div class=\"flex flex-col\">\n    {#each itemKinds as itemKind, i}\n      <div class=\"flex gap-1\">\n        <input\n          id=\"{itemKind}Input\"\n          checked={kind === itemKind}\n          onchange={onKindChange}\n          type=\"radio\"\n          name=\"kind\"\n          value={itemKinds[i]}\n        />\n        <label for=\"{itemKind}Input\">{itemKind}</label>\n      </div>\n    {/each}\n  </div>\n\n  <div class=\"bg-gray/30 dark:bg-white/5 w-1px flex-shrink-0\"></div>\n\n  <div class=\"flex flex-col gap-2\">\n    {#if kind == 'Normal' || kind == 'Icon' || kind == 'Check'}\n      <input class=\"input\" type=\"text\" placeholder=\"Text\" bind:value={text} />\n    {/if}\n    {#if kind == 'Icon'}\n      <input class=\"input\" type=\"icon\" placeholder=\"Icon\" bind:value={icon} />\n    {:else if kind == 'Check'}\n      <div class=\"flex gap-1\">\n        <input\n          id=\"checkItemCheckedInput\"\n          type=\"checkbox\"\n          class=\"checkbox\"\n          bind:checked\n        />\n        <label for=\"checkItemCheckedInput\">Enabled</label>\n      </div>\n    {:else if kind == 'Predefined'}\n      <div class=\"flex gap-2 flex-wrap\">\n        {#each predefinedOptions as predefinedOption, i}\n          <div class=\"flex gap-1\">\n            <input\n              id=\"{predefinedOption}Input\"\n              checked={kind === predefinedOption}\n              onchange={onPredefinedChange}\n              type=\"radio\"\n              name=\"predefinedKind\"\n              value={predefinedOptions[i]}\n            />\n            <label for=\"{predefinedOption}Input\">{predefinedOption}</label>\n          </div>\n        {/each}\n      </div>\n    {/if}\n  </div>\n\n  <div class=\"grow\"></div>\n\n  <div class=\"flex flex-col\">\n    <button class=\"btn\" onclick={create}>Create</button>\n  </div>\n</div>\n"
  },
  {
    "path": "examples/api/src/main.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport 'uno.css'\nimport './app.css'\nimport App from './App.svelte'\nimport { mount } from 'svelte'\n\nconst app = mount(App, {\n  target: document.querySelector('#app')\n})\n\nexport default app\n"
  },
  {
    "path": "examples/api/src/views/App.svelte",
    "content": "<script>\n  import { show, hide, setTheme, setDockVisibility } from '@tauri-apps/api/app'\n\n  let { onMessage } = $props()\n  /** @type {import('@tauri-apps/api/window').Theme | 'auto'} */\n  let theme = $state('auto')\n  let dockVisible = $state(true)\n\n  function showApp() {\n    hideApp()\n      .then(() => {\n        setTimeout(() => {\n          show()\n            .then(() => onMessage('Shown app'))\n            .catch(onMessage)\n        }, 2000)\n      })\n      .catch(onMessage)\n  }\n\n  function hideApp() {\n    return hide()\n      .then(() => onMessage('Hide app'))\n      .catch(onMessage)\n  }\n\n  async function switchTheme() {\n    switch (theme) {\n      case 'dark':\n        theme = 'light'\n        break\n      case 'light':\n        theme = 'auto'\n        break\n      case 'auto':\n        theme = 'dark'\n        break\n    }\n    setTheme(theme === 'auto' ? null : theme)\n  }\n\n  async function toggleDockVisibility() {\n    await setDockVisibility(!dockVisible)\n    dockVisible = !dockVisible\n  }\n</script>\n\n<div>\n  <button\n    class=\"btn\"\n    id=\"show\"\n    title=\"Hides and shows the app after 2 seconds\"\n    onclick={showApp}>Show</button\n  >\n  <button class=\"btn\" id=\"hide\" onclick={hideApp}>Hide</button>\n  <button class=\"btn\" id=\"switch-theme\" onclick={switchTheme}\n    >Switch Theme ({theme})</button\n  >\n  <button class=\"btn\" id=\"toggle-dock-visibility\" onclick={toggleDockVisibility}>Toggle dock visibility</button>\n</div>\n"
  },
  {
    "path": "examples/api/src/views/Communication.svelte",
    "content": "<script>\n  import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'\n  import { Channel, invoke } from '@tauri-apps/api/core'\n  import { onMount, onDestroy } from 'svelte'\n\n  let { onMessage } = $props()\n  let unlisten\n\n  const webviewWindow = getCurrentWebviewWindow()\n\n  onMount(async () => {\n    unlisten = await webviewWindow.listen('rust-event', onMessage)\n  })\n  onDestroy(() => {\n    if (unlisten) {\n      unlisten()\n    }\n  })\n\n  function log() {\n    invoke('log_operation', {\n      event: 'tauri-click',\n      payload: 'this payload is optional because we used Option in Rust'\n    })\n  }\n\n  function performRequest() {\n    invoke('perform_request', {\n      endpoint: 'dummy endpoint arg',\n      body: {\n        id: 5,\n        name: 'test'\n      }\n    })\n      .then(onMessage)\n      .catch(onMessage)\n  }\n\n  function echo() {\n    invoke('echo', {\n      message: 'Tauri JSON request!'\n    })\n      .then(onMessage)\n      .catch(onMessage)\n\n    invoke('echo', [1, 2, 3]).then(onMessage).catch(onMessage)\n  }\n\n  function spam() {\n    const channel = new Channel()\n    channel.onmessage = onMessage\n    invoke('spam', { channel })\n  }\n\n  function emitEvent() {\n    webviewWindow.emit('js-event', 'this is the payload string')\n  }\n</script>\n\n<div>\n  <button class=\"btn\" id=\"log\" onclick={log}>Call Log API</button>\n  <button class=\"btn\" id=\"request\" onclick={performRequest}>\n    Call Request (async) API\n  </button>\n  <button class=\"btn\" id=\"event\" onclick={emitEvent}>\n    Send event to Rust\n  </button>\n  <button class=\"btn\" id=\"request\" onclick={echo}> Echo </button>\n  <button class=\"btn\" id=\"request\" onclick={spam}> Spam </button>\n</div>\n"
  },
  {
    "path": "examples/api/src/views/Menu.svelte",
    "content": "<script>\n  import { Menu, Submenu, NativeIcon } from '@tauri-apps/api/menu'\n  import MenuBuilder from '../components/MenuBuilder.svelte'\n  import { defaultWindowIcon } from '@tauri-apps/api/app';\n\n  let { onMessage } = $props()\n  let items = $state([])\n  let menu = null\n  let submenu = null\n  let menuItemCount = 0\n\n  const macOS = navigator.userAgent.includes('Macintosh')\n\n  async function createSubmenu() {\n    submenu = await Submenu.new({\n      text: 'app',\n      items: items.map((i) => i.item)\n    })\n  }\n\n  async function createSubmenuWithNativeIcon() {\n    submenu = await Submenu.new({\n      text: 'Submenu with NativeIcon',\n      icon: NativeIcon.Folder,\n      items: items.map((i) => i.item)\n    })\n  }\n\n  async function createSubmenuWithImageIcon() {\n    submenu = await Submenu.new({\n      text: 'Submenu with Image',\n      icon: await defaultWindowIcon(),\n      items: items.map((i) => i.item)\n    });\n  }\n\n  async function create() {\n    await createSubmenu()\n    menuItemCount = items.length\n    menu = await Menu.new({\n      items: [submenu]\n    })\n    await (macOS ? menu.setAsAppMenu() : menu.setAsWindowMenu())\n  }\n\n  async function createWithNativeIcon() {\n    await createSubmenuWithNativeIcon()\n    menuItemCount = items.length\n    menu = await Menu.new({\n      items: [submenu]\n    })\n    await (macOS ? menu.setAsAppMenu() : menu.setAsWindowMenu())\n  }\n\n  async function createWithImageIcon() {\n    await createSubmenuWithImageIcon()\n    menuItemCount = items.length\n    menu = await Menu.new({\n      items: [submenu]\n    })\n    await (macOS ? menu.setAsAppMenu() : menu.setAsWindowMenu())\n  }\n\n  async function popup() {\n    if (!submenu || menuItemCount !== items.length) {\n      await createSubmenu()\n    }\n    // we can't popup the same menu because it's the app menu (it crashes on macOS)\n    const m = await Menu.new({ items: [submenu] })\n    m.popup()\n  }\n\n  function onItemClick(detail) {\n    onMessage(`Item ${detail.text} clicked`)\n  }\n</script>\n\n<div class=\"grid gap-4\">\n  <MenuBuilder bind:items itemClick={onItemClick} />\n  <div>\n    <button class=\"btn\" onclick={create}>Create menu</button>\n    <button class=\"btn\" onclick={popup}>Popup</button>\n    <button class=\"btn\" onclick={createWithNativeIcon}>Create menu with NativeIcon</button>\n    <button class=\"btn\" onclick={createWithImageIcon}>Create menu with Image icon</button>\n  </div>\n</div>\n"
  },
  {
    "path": "examples/api/src/views/Tray.svelte",
    "content": "<script>\n  import { TrayIcon } from '@tauri-apps/api/tray'\n  import MenuBuilder from '../components/MenuBuilder.svelte'\n  import { Menu } from '@tauri-apps/api/menu'\n\n  let { onMessage } = $props()\n\n  let icon = $state(null)\n  let tooltip = $state(null)\n  let title = $state(null)\n  let iconAsTemplate = $state(false)\n  let menuOnLeftClick = $state(true)\n  let menuItems = $state([])\n\n  function onItemClick(detail) {\n    onMessage(`Item ${detail.text} clicked`)\n  }\n\n  async function create() {\n    TrayIcon.new({\n      icon,\n      tooltip,\n      title,\n      iconAsTemplate,\n      menuOnLeftClick,\n      menu: await Menu.new({\n        items: menuItems.map((i) => i.item)\n      }),\n      action: (event) => onMessage(event)\n    }).catch(onMessage)\n  }\n</script>\n\n<div class=\"flex flex-col children:grow gap-2\">\n  <div class=\"flex gap-1\">\n    <input\n      class=\"input grow\"\n      type=\"text\"\n      placeholder=\"Title\"\n      bind:value={title}\n    />\n\n    <input\n      class=\"input grow\"\n      type=\"text\"\n      placeholder=\"Tooltip\"\n      bind:value={tooltip}\n    />\n\n    <label>\n      <input type=\"checkbox\" class=\"checkbox\" bind:checked={menuOnLeftClick} />\n      Menu on left click\n    </label>\n  </div>\n\n  <div class=\"flex gap-1\">\n    <input\n      class=\"input grow\"\n      type=\"text\"\n      placeholder=\"Icon path\"\n      bind:value={icon}\n    />\n\n    <label>\n      <input type=\"checkbox\" class=\"checkbox\" bind:checked={iconAsTemplate} />\n      Icon as template\n    </label>\n  </div>\n\n  <div class=\"flex children:grow\">\n    <MenuBuilder bind:items={menuItems} itemClick={onItemClick} />\n  </div>\n\n  <div class=\"flex\">\n    <button class=\"btn\" onclick={create} title=\"Creates the tray icon\"\n      >Create tray</button\n    >\n  </div>\n</div>\n"
  },
  {
    "path": "examples/api/src/views/WebRTC.svelte",
    "content": "<script>\n  import { onMount, onDestroy } from 'svelte'\n  let { onMessage } = $props()\n\n  const constraints = (window.constraints = {\n    audio: true,\n    video: true\n  })\n\n  function handleSuccess(stream) {\n    const video = document.querySelector('video')\n    const videoTracks = stream.getVideoTracks()\n    onMessage('Got stream with constraints:', constraints)\n    onMessage(`Using video device: ${videoTracks[0].label}`)\n    window.stream = stream // make variable available to browser console\n    video.srcObject = stream\n  }\n\n  function handleError(error) {\n    if (error.name === 'ConstraintNotSatisfiedError') {\n      const v = constraints.video\n      onMessage(\n        `The resolution ${v.width.exact}x${v.height.exact} px is not supported by your device.`\n      )\n    } else if (error.name === 'PermissionDeniedError') {\n      onMessage(\n        'Permissions have not been granted to use your camera and '\n          + 'microphone, you need to allow the page access to your devices in '\n          + 'order for the demo to work.'\n      )\n    }\n    onMessage(`getUserMedia error: ${error.name}`, error)\n  }\n\n  onMount(async () => {\n    try {\n      const stream = await navigator.mediaDevices.getUserMedia(constraints)\n      handleSuccess(stream)\n    } catch (e) {\n      handleError(e)\n    }\n  })\n\n  onDestroy(() => {\n    window.stream?.getTracks().forEach(function (track) {\n      track.stop()\n    })\n  })\n</script>\n\n<div class=\"flex flex-col gap-2\">\n  <div class=\"note-red grow\">Not available for Linux</div>\n  <video id=\"localVideo\" autoplay playsinline>\n    <track kind=\"captions\" />\n  </video>\n</div>\n"
  },
  {
    "path": "examples/api/src/views/Welcome.svelte",
    "content": "<script>\n  import { invoke } from '@tauri-apps/api/core'\n  import {\n    getName,\n    getVersion,\n    getTauriVersion,\n    getBundleType\n  } from '@tauri-apps/api/app'\n\n  let { onMessage } = $props()\n\n  let version = $state('1.0.0')\n  let tauriVersion = $state('1.0.0')\n  let appName = $state('Unknown')\n  let bundleType = $state('Unknown')\n\n  getName().then((n) => {\n    appName = n\n  })\n  getVersion().then((v) => {\n    version = v\n  })\n  getTauriVersion().then((v) => {\n    tauriVersion = v\n  })\n  getBundleType().then((b) => {\n    if (b) {\n      bundleType = b\n    }\n  })\n\n  function contextMenu() {\n    invoke('plugin:app-menu|popup')\n  }\n</script>\n\n<div class=\"grid gap-8 justify-items-start\">\n  <p>\n    This is a demo of Tauri's API capabilities using the <code\n      >@tauri-apps/api</code\n    > package. It's used as the main validation app, serving as the test bed of our\n    development process. In the future, this app will be used on Tauri's integration\n    tests.\n  </p>\n  <pre>\n    App name: <code>{appName}</code>\n    App version: <code>{version}</code>\n    Tauri version: <code>{tauriVersion}</code>\n    Bundle type: <code>{bundleType}</code>\n  </pre>\n\n  <button class=\"btn\" onclick={contextMenu}>Context menu</button>\n</div>\n"
  },
  {
    "path": "examples/api/src/views/Window.svelte",
    "content": "<script>\n  import { onDestroy } from 'svelte'\n  import {\n    LogicalSize,\n    UserAttentionType,\n    PhysicalSize,\n    PhysicalPosition,\n    Effect,\n    EffectState,\n    ProgressBarStatus\n  } from '@tauri-apps/api/window'\n  import { WebviewWindow } from '@tauri-apps/api/webviewWindow'\n\n  let { onMessage } = $props()\n\n  const webview = WebviewWindow.getCurrent()\n\n  let selectedWebview = $state(webview.label)\n  const webviewMap = $state({\n    [webview.label]: webview\n  })\n\n  let focusable = $state(true)\n\n  const cursorIconOptions = [\n    'default',\n    'crosshair',\n    'hand',\n    'arrow',\n    'move',\n    'text',\n    'wait',\n    'help',\n    'progress',\n    // something cannot be done\n    'notAllowed',\n    'contextMenu',\n    'cell',\n    'verticalText',\n    'alias',\n    'copy',\n    'noDrop',\n    // something can be grabbed\n    'grab',\n    /// something is grabbed\n    'grabbing',\n    'allScroll',\n    'zoomIn',\n    'zoomOut',\n    // edge is to be moved\n    'eResize',\n    'nResize',\n    'neResize',\n    'nwResize',\n    'sResize',\n    'seResize',\n    'swResize',\n    'wResize',\n    'ewResize',\n    'nsResize',\n    'neswResize',\n    'nwseResize',\n    'colResize',\n    'rowResize'\n  ]\n\n  const windowsEffects = [\n    'mica',\n    'blur',\n    'acrylic',\n    'tabbed',\n    'tabbedDark',\n    'tabbedLight'\n  ]\n  const isWindows = navigator.appVersion.includes('Windows')\n  const isMacOS = navigator.appVersion.includes('Macintosh')\n  let effectOptions = isWindows\n    ? windowsEffects\n    : Object.keys(Effect)\n        .map((effect) => Effect[effect])\n        .filter((e) => !windowsEffects.includes(e))\n  const effectStateOptions = Object.keys(EffectState).map(\n    (state) => EffectState[state]\n  )\n\n  const progressBarStatusOptions = Object.keys(ProgressBarStatus).map(\n    (s) => ProgressBarStatus[s]\n  )\n\n  const mainEl = document.querySelector('main')\n\n  let newWebviewLabel = $state()\n\n  let resizable = $state(true)\n  let maximizable = $state(true)\n  let minimizable = $state(true)\n  let closable = $state(true)\n  let maximized = $state(false)\n  let decorations = $state(true)\n  let alwaysOnTop = $state(false)\n  let alwaysOnBottom = $state(false)\n  let contentProtected = $state(false)\n  let fullscreen = $state(false)\n  let simpleFullscreen = $state(false)\n  let width = $state(null)\n  let height = $state(null)\n  let minWidth = $state(null)\n  let minHeight = $state(null)\n  let maxWidth = $state(null)\n  let maxHeight = $state(null)\n  let x = $state(null)\n  let y = $state(null)\n  let scaleFactor = $state(1)\n  let innerPosition = $state(new PhysicalPosition(0, 0))\n  let outerPosition = $state(new PhysicalPosition(0, 0))\n  let innerSize = $state(new PhysicalSize(0, 0))\n  let outerSize = $state(new PhysicalSize(0, 0))\n  let resizeEventUnlisten\n  let moveEventUnlisten\n  let cursorGrab = $state(false)\n  let cursorVisible = $state(true)\n  let cursorX = $state(null)\n  let cursorY = $state(null)\n  /** @type {import('@tauri-apps/api/window').CursorIcon} */\n  let cursorIcon = $state('default')\n  let cursorIgnoreEvents = $state(false)\n  let windowTitle = $state('Awesome Tauri Example!')\n\n  /** @type {import('@tauri-apps/api/window').Theme | 'auto'} */\n  let theme = $state('auto')\n\n  let effects = $state([])\n  let selectedEffect = $state()\n  let effectState = $state()\n  let effectRadius = $state()\n  let effectR = $state(),\n    effectG = $state(),\n    effectB = $state(),\n    effectA = $state()\n\n  /** @type {ProgressBarStatus} */\n  let selectedProgressBarStatus = $state(ProgressBarStatus.None)\n  let progress = $state(0)\n\n  let windowIconPath = $state()\n\n  function setTitle() {\n    webviewMap[selectedWebview].setTitle(windowTitle)\n  }\n\n  async function hide() {\n    let visible = await webviewMap[selectedWebview].isVisible()\n    onMessage('window is ' + (visible ? 'visible' : 'invisible'))\n    await webviewMap[selectedWebview].hide()\n\n    setTimeout(async () => {\n      visible = await webviewMap[selectedWebview].isVisible()\n      onMessage('window is ' + (visible ? 'visible' : 'invisible'))\n\n      await webviewMap[selectedWebview].show()\n      visible = await webviewMap[selectedWebview].isVisible()\n      onMessage('window is ' + (visible ? 'visible' : 'invisible'))\n    }, 2000)\n  }\n\n  async function disable() {\n    let enabled = await webviewMap[selectedWebview].isEnabled()\n    onMessage('window is ' + (enabled ? 'enabled' : 'disabled'))\n\n    await webviewMap[selectedWebview].setEnabled(false)\n\n    setTimeout(async () => {\n      enabled = await webviewMap[selectedWebview].isEnabled()\n      onMessage('window is ' + (enabled ? 'enabled' : 'disabled'))\n\n      await webviewMap[selectedWebview].setEnabled(true)\n      enabled = await webviewMap[selectedWebview].isEnabled()\n      onMessage('window is ' + (enabled ? 'enabled' : 'disabled'))\n    }, 2000)\n  }\n\n  function minimize() {\n    webviewMap[selectedWebview].minimize()\n    setTimeout(webviewMap[selectedWebview].unminimize, 2000)\n  }\n\n  function changeIcon() {\n    webviewMap[selectedWebview].setIcon(windowIconPath)\n  }\n\n  function createWebviewWindow() {\n    if (!newWebviewLabel) return\n\n    const label = `main-${newWebviewLabel}`\n    const webview = new WebviewWindow(label)\n    webviewMap[label] = webview\n    webview.once('tauri://error', function (e) {\n      onMessage('Error creating new webview ' + JSON.stringify(e))\n    })\n    webview.once('tauri://created', function () {\n      onMessage('webview created')\n    })\n  }\n\n  function loadWindowSize() {\n    webviewMap[selectedWebview].innerSize().then((response) => {\n      innerSize = response\n      width = innerSize.width\n      height = innerSize.height\n    })\n    webviewMap[selectedWebview].outerSize().then((response) => {\n      outerSize = response\n    })\n  }\n\n  function loadWindowPosition() {\n    webviewMap[selectedWebview].innerPosition().then((response) => {\n      innerPosition = response\n    })\n    webviewMap[selectedWebview].outerPosition().then((response) => {\n      outerPosition = response\n      x = outerPosition.x\n      y = outerPosition.y\n    })\n  }\n\n  async function addWindowEventListeners(window) {\n    if (!window) return\n    resizeEventUnlisten?.()\n    moveEventUnlisten?.()\n    moveEventUnlisten = await window.listen('tauri://move', loadWindowPosition)\n    resizeEventUnlisten = await window.listen('tauri://resize', loadWindowSize)\n  }\n\n  async function requestUserAttention() {\n    await webviewMap[selectedWebview].minimize()\n    await webviewMap[selectedWebview].requestUserAttention(\n      UserAttentionType.Critical\n    )\n    await new Promise((resolve) => setTimeout(resolve, 3000))\n    await webviewMap[selectedWebview].requestUserAttention(null)\n  }\n\n  async function switchTheme() {\n    switch (theme) {\n      case 'dark':\n        theme = 'light'\n        break\n      case 'light':\n        theme = 'auto'\n        break\n      case 'auto':\n        theme = 'dark'\n        break\n    }\n    await webviewMap[selectedWebview].setTheme(theme === 'auto' ? null : theme)\n  }\n\n  async function updateProgressBar() {\n    webviewMap[selectedWebview]?.setProgressBar({\n      status: selectedProgressBarStatus,\n      progress\n    })\n  }\n\n  async function addEffect() {\n    if (!effects.includes(selectedEffect)) {\n      effects = [...effects, selectedEffect]\n    }\n\n    const payload = {\n      effects,\n      state: effectState,\n      radius: effectRadius\n    }\n    if (\n      Number.isInteger(effectR)\n      && Number.isInteger(effectG)\n      && Number.isInteger(effectB)\n      && Number.isInteger(effectA)\n    ) {\n      payload.color = [effectR, effectG, effectB, effectA]\n    }\n\n    mainEl.classList.remove('bg-primary')\n    mainEl.classList.remove('dark:bg-darkPrimary')\n    await webviewMap[selectedWebview].clearEffects()\n    await webviewMap[selectedWebview].setEffects(payload)\n  }\n\n  async function clearEffects() {\n    effects = []\n    await webviewMap[selectedWebview].clearEffects()\n    mainEl.classList.add('bg-primary')\n    mainEl.classList.add('dark:bg-darkPrimary')\n  }\n\n  async function updatePosition() {\n    webviewMap[selectedWebview]?.setPosition(new PhysicalPosition(x, y))\n  }\n\n  async function updateSize() {\n    webviewMap[selectedWebview]?.setSize(new PhysicalSize(width, height))\n  }\n\n  $effect(() => {\n    webviewMap[selectedWebview]\n    loadWindowPosition()\n    loadWindowSize()\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setResizable(resizable)\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setMaximizable(maximizable)\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setMinimizable(minimizable)\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setClosable(closable)\n  })\n  $effect(() => {\n    maximized\n      ? webviewMap[selectedWebview]?.maximize()\n      : webviewMap[selectedWebview]?.unmaximize()\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setDecorations(decorations)\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setAlwaysOnTop(alwaysOnTop)\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setAlwaysOnBottom(alwaysOnBottom)\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setContentProtected(contentProtected)\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setFullscreen(fullscreen)\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setSimpleFullscreen(simpleFullscreen)\n  })\n\n  $effect(() => {\n    minWidth && minHeight\n      ? webviewMap[selectedWebview]?.setMinSize(\n          new LogicalSize(minWidth, minHeight)\n        )\n      : webviewMap[selectedWebview]?.setMinSize(null)\n  })\n  $effect(() => {\n    maxWidth > 800 && maxHeight > 400\n      ? webviewMap[selectedWebview]?.setMaxSize(\n          new LogicalSize(maxWidth, maxHeight)\n        )\n      : webviewMap[selectedWebview]?.setMaxSize(null)\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]\n      ?.scaleFactor()\n      .then((factor) => (scaleFactor = factor))\n  })\n  $effect(() => {\n    addWindowEventListeners(webviewMap[selectedWebview])\n  })\n\n  $effect(() => {\n    webviewMap[selectedWebview]?.setCursorGrab(cursorGrab)\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setCursorVisible(cursorVisible)\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setCursorIcon(cursorIcon)\n  })\n  $effect(() => {\n    cursorX !== null\n      && cursorY !== null\n      && webviewMap[selectedWebview]?.setCursorPosition(\n        new PhysicalPosition(cursorX, cursorY)\n      )\n  })\n  $effect(() => {\n    webviewMap[selectedWebview]?.setIgnoreCursorEvents(cursorIgnoreEvents)\n  })\n\n  onDestroy(() => {\n    resizeEventUnlisten?.()\n    moveEventUnlisten?.()\n  })\n</script>\n\n<div class=\"flex flex-col children:grow gap-8 mb-4\">\n  <div\n    class=\"flex flex-wrap items-center gap-4 pb-6 border-b-solid border-b-1 border-code\"\n  >\n    {#if Object.keys(webviewMap).length >= 1}\n      <div class=\"grid gap-1\">\n        <h4 class=\"my-2\">Selected Window</h4>\n        <select class=\"input\" bind:value={selectedWebview}>\n          <option value=\"\" disabled selected>Choose a window...</option>\n          {#each Object.keys(webviewMap) as label}\n            <option value={label}>{label}</option>\n          {/each}\n        </select>\n      </div>\n    {/if}\n    <div class=\"grid gap-1\">\n      <h4 class=\"my-2\">Create New Window</h4>\n      <form\n        class=\"flex gap-2\"\n        onsubmit={(ev) => {\n          createWebviewWindow()\n          ev.preventDefault()\n        }}\n      >\n        <input\n          class=\"input\"\n          type=\"text\"\n          placeholder=\"New window label..\"\n          bind:value={newWebviewLabel}\n        />\n        <button class=\"btn\" type=\"submit\">Create</button>\n      </form>\n    </div>\n  </div>\n  {#if webviewMap[selectedWebview]}\n    <div class=\"flex flex-wrap items-center gap-4\">\n      <div class=\"grid gap-1 grow\">\n        <h4 class=\"my-2\">Change Window Icon</h4>\n        <form\n          class=\"flex gap-2\"\n          onsubmit={(ev) => {\n            changeIcon()\n            ev.preventDefault()\n          }}\n        >\n          <input\n            class=\"input flex-1 min-w-10\"\n            placeholder=\"Window icon path\"\n            bind:value={windowIconPath}\n          />\n          <button class=\"btn\" type=\"submit\">Change</button>\n        </form>\n      </div>\n      <div class=\"grid gap-1 grow\">\n        <h4 class=\"my-2\">Set Window Title</h4>\n        <form\n          class=\"flex gap-2\"\n          onsubmit={(ev) => {\n            setTitle()\n            ev.preventDefault()\n          }}\n        >\n          <input class=\"input flex-1 min-w-10\" bind:value={windowTitle} />\n          <button class=\"btn\" type=\"submit\">Set</button>\n        </form>\n      </div>\n    </div>\n    <div class=\"flex flex-wrap gap-2\">\n      <button\n        class=\"btn\"\n        title=\"Unminimizes after 2 seconds\"\n        onclick={() => webviewMap[selectedWebview].center()}\n      >\n        Center\n      </button>\n      <button\n        class=\"btn\"\n        title=\"Unminimizes after 2 seconds\"\n        onclick={minimize}\n      >\n        Minimize\n      </button>\n      <button class=\"btn\" title=\"Visible again after 2 seconds\" onclick={hide}>\n        Hide\n      </button>\n      <button\n        class=\"btn\"\n        title=\"Enabled again after 2 seconds\"\n        onclick={disable}\n      >\n        Disable\n      </button>\n      <button\n        class=\"btn\"\n        onclick={requestUserAttention}\n        title=\"Minimizes the window, requests attention for 3s and then resets it\"\n        >Request attention</button\n      >\n      <button class=\"btn\" onclick={switchTheme}>Switch Theme ({theme})</button>\n      <button\n        class=\"btn\"\n        onclick={() => {\n          focusable = !focusable\n          webviewMap[selectedWebview].setFocusable(!focusable)\n        }}\n      >\n        Set focusable to {!focusable}\n      </button>\n    </div>\n    <div class=\"grid cols-[repeat(auto-fill,minmax(180px,1fr))]\">\n      <label>\n        <input type=\"checkbox\" class=\"checkbox\" bind:checked={resizable} />\n        Resizable\n      </label>\n      <label>\n        <input type=\"checkbox\" class=\"checkbox\" bind:checked={maximizable} />\n        Maximizable\n      </label>\n      <label>\n        <input type=\"checkbox\" class=\"checkbox\" bind:checked={minimizable} />\n        Minimizable\n      </label>\n      <label>\n        <input type=\"checkbox\" class=\"checkbox\" bind:checked={closable} />\n        Closable\n      </label>\n      <label>\n        <input type=\"checkbox\" class=\"checkbox\" bind:checked={decorations} />\n        Has decorations\n      </label>\n      <label>\n        <input type=\"checkbox\" class=\"checkbox\" bind:checked={alwaysOnTop} />\n        Always on top\n      </label>\n      <label>\n        <input type=\"checkbox\" class=\"checkbox\" bind:checked={alwaysOnBottom} />\n        Always on bottom\n      </label>\n      <label>\n        <input\n          type=\"checkbox\"\n          class=\"checkbox\"\n          bind:checked={contentProtected}\n        />\n        Content protected\n      </label>\n      <label>\n        <input type=\"checkbox\" class=\"checkbox\" bind:checked={maximized} />\n        Maximized\n      </label>\n      <label>\n        <input type=\"checkbox\" class=\"checkbox\" bind:checked={fullscreen} />\n        Fullscreen\n      </label>\n      <label>\n        <input\n          type=\"checkbox\"\n          class=\"checkbox\"\n          bind:checked={simpleFullscreen}\n        />\n        Simple fullscreen\n      </label>\n    </div>\n    <div class=\"flex flex-wrap children:flex-basis-30 gap-2\">\n      <div class=\"grid gap-1 children:grid\">\n        <label>\n          X\n          <input\n            class=\"input\"\n            type=\"number\"\n            bind:value={x}\n            onchange={updatePosition}\n            min=\"0\"\n          />\n        </label>\n        <label>\n          Y\n          <input\n            class=\"input\"\n            type=\"number\"\n            bind:value={y}\n            onchange={updatePosition}\n            min=\"0\"\n          />\n        </label>\n      </div>\n      <div class=\"grid gap-1 children:grid\">\n        <label>\n          Width\n          <input\n            class=\"input\"\n            type=\"number\"\n            bind:value={width}\n            onchange={updateSize}\n            min=\"400\"\n          />\n        </label>\n        <div>\n          Height\n          <input\n            class=\"input\"\n            type=\"number\"\n            bind:value={height}\n            onchange={updateSize}\n            min=\"400\"\n          />\n        </div>\n      </div>\n      <div class=\"grid gap-1 children:grid\">\n        <label>\n          Min width\n          <input class=\"input\" type=\"number\" bind:value={minWidth} />\n        </label>\n        <label>\n          Min height\n          <input class=\"input\" type=\"number\" bind:value={minHeight} />\n        </label>\n      </div>\n      <div class=\"grid gap-1 children:grid\">\n        <label>\n          Max width\n          <input class=\"input\" type=\"number\" bind:value={maxWidth} min=\"800\" />\n        </label>\n        <label>\n          Max height\n          <input class=\"input\" type=\"number\" bind:value={maxHeight} min=\"400\" />\n        </label>\n      </div>\n    </div>\n    <div class=\"grid grid-cols-2 gap-2 max-inline-2xl\">\n      <div>\n        <div class=\"text-accent dark:text-darkAccent font-700 m-block-1\">\n          Inner Size\n        </div>\n        <span>Width: {innerSize.width}</span>\n        <span>Height: {innerSize.height}</span>\n      </div>\n      <div>\n        <div class=\"text-accent dark:text-darkAccent font-700 m-block-1\">\n          Outer Size\n        </div>\n        <span>Width: {outerSize.width}</span>\n        <span>Height: {outerSize.height}</span>\n      </div>\n      <div>\n        <div class=\"text-accent dark:text-darkAccent font-700 m-block-1\">\n          Inner Logical Size\n        </div>\n        <span>Width: {innerSize.toLogical(scaleFactor).width.toFixed(3)}</span>\n        <span>Height: {innerSize.toLogical(scaleFactor).height.toFixed(3)}</span\n        >\n      </div>\n      <div>\n        <div class=\"text-accent dark:text-darkAccent font-700 m-block-1\">\n          Outer Logical Size\n        </div>\n        <span>Width: {outerSize.toLogical(scaleFactor).width.toFixed(3)}</span>\n        <span>Height: {outerSize.toLogical(scaleFactor).height.toFixed(3)}</span\n        >\n      </div>\n      <div>\n        <div class=\"text-accent dark:text-darkAccent font-700 m-block-1\">\n          Inner Position\n        </div>\n        <span>x: {innerPosition.x}</span>\n        <span>y: {innerPosition.y}</span>\n      </div>\n      <div>\n        <div class=\"text-accent dark:text-darkAccent font-700 m-block-1\">\n          Outer Position\n        </div>\n        <span>x: {outerPosition.x}</span>\n        <span>y: {outerPosition.y}</span>\n      </div>\n      <div>\n        <div class=\"text-accent dark:text-darkAccent font-700 m-block-1\">\n          Inner Logical Position\n        </div>\n        <span>x: {innerPosition.toLogical(scaleFactor).x.toFixed(3)}</span>\n        <span>y: {innerPosition.toLogical(scaleFactor).y.toFixed(3)}</span>\n      </div>\n      <div>\n        <div class=\"text-accent dark:text-darkAccent font-700 m-block-1\">\n          Outer Logical Position\n        </div>\n        <span>x: {outerPosition.toLogical(scaleFactor).x.toFixed(3)}</span>\n        <span>y: {outerPosition.toLogical(scaleFactor).y.toFixed(3)}</span>\n      </div>\n    </div>\n    <div class=\"grid gap-2\">\n      <h4 class=\"my-2\">Cursor</h4>\n      <div class=\"flex gap-2\">\n        <label>\n          <input type=\"checkbox\" class=\"checkbox\" bind:checked={cursorGrab} />\n          Grab\n        </label>\n        <label>\n          <input\n            type=\"checkbox\"\n            class=\"checkbox\"\n            bind:checked={cursorVisible}\n          />\n          Visible\n        </label>\n        <label>\n          <input\n            type=\"checkbox\"\n            class=\"checkbox\"\n            bind:checked={cursorIgnoreEvents}\n          />\n          Ignore events\n        </label>\n      </div>\n      <div class=\"flex gap-2\">\n        <label>\n          Icon\n          <select class=\"input\" bind:value={cursorIcon}>\n            {#each cursorIconOptions as kind}\n              <option value={kind}>{kind}</option>\n            {/each}\n          </select>\n        </label>\n        <label>\n          X position\n          <input class=\"input\" type=\"number\" bind:value={cursorX} />\n        </label>\n        <label>\n          Y position\n          <input class=\"input\" type=\"number\" bind:value={cursorY} />\n        </label>\n      </div>\n    </div>\n\n    <div class=\"flex flex-col gap-1\">\n      <div class=\"flex gap-2\">\n        <label>\n          Progress Status\n          <select\n            class=\"input\"\n            bind:value={selectedProgressBarStatus}\n            onchange={updateProgressBar}\n          >\n            {#each progressBarStatusOptions as status}\n              <option value={status}>{status}</option>\n            {/each}\n          </select>\n        </label>\n\n        <label>\n          Progress\n          <input\n            class=\"input\"\n            type=\"number\"\n            min=\"0\"\n            max=\"100\"\n            bind:value={progress}\n            onchange={updateProgressBar}\n          />\n        </label>\n      </div>\n    </div>\n\n    {#if isWindows || isMacOS}\n      <div class=\"flex flex-col gap-2\">\n        <div class=\"flex items-center gap-2\">\n          <div>\n            Applied effects: {effects.length ? effects.join(', ') : 'None'}\n          </div>\n\n          <button class=\"btn\" onclick={clearEffects}>Clear</button>\n        </div>\n\n        <div class=\"flex gap-2\">\n          <label>\n            Effect\n            <select class=\"input\" bind:value={selectedEffect}>\n              {#each effectOptions as effect}\n                <option value={effect}>{effect}</option>\n              {/each}\n            </select>\n          </label>\n\n          <label>\n            State\n            <select class=\"input\" bind:value={effectState}>\n              {#each effectStateOptions as state}\n                <option value={state}>{state}</option>\n              {/each}\n            </select>\n          </label>\n\n          <label>\n            Radius\n            <input class=\"input\" type=\"number\" bind:value={effectRadius} />\n          </label>\n        </div>\n\n        <div class=\"flex\">\n          <label>\n            Color\n            <div class=\"flex gap-2 children:flex-basis-30\">\n              <input\n                class=\"input\"\n                type=\"number\"\n                placeholder=\"R\"\n                bind:value={effectR}\n              />\n              <input\n                class=\"input\"\n                type=\"number\"\n                placeholder=\"G\"\n                bind:value={effectG}\n              />\n              <input\n                class=\"input\"\n                type=\"number\"\n                placeholder=\"B\"\n                bind:value={effectB}\n              />\n              <input\n                class=\"input\"\n                type=\"number\"\n                placeholder=\"A\"\n                bind:value={effectA}\n              />\n            </div>\n          </label>\n        </div>\n\n        <div class=\"flex\">\n          <button class=\"btn\" onclick={addEffect}>Add</button>\n        </div>\n      </div>\n    {/if}\n  {/if}\n</div>\n"
  },
  {
    "path": "examples/api/src/vite-env.d.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/// <reference types=\"svelte\" />\n/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "examples/api/src-tauri/.gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# cargo-mobile\n/gen\n"
  },
  {
    "path": "examples/api/src-tauri/.taurignore",
    "content": "tauri-plugin-sample/"
  },
  {
    "path": "examples/api/src-tauri/Cargo.toml",
    "content": "[package]\nname = \"api\"\nversion = \"0.1.0\"\ndescription = \"An example Tauri Application showcasing the api\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\nlicense = \"Apache-2.0 OR MIT\"\n\n[lib]\nname = \"api_lib\"\ncrate-type = [\"staticlib\", \"cdylib\", \"rlib\"]\n\n[build-dependencies]\ntauri-build = { path = \"../../../crates/tauri-build\", features = [\n  \"codegen\",\n  \"isolation\",\n] }\n\n[dependencies]\nserde_json = \"1\"\nserde = { version = \"1\", features = [\"derive\"] }\ntiny_http = \"0.11\"\nlog = \"0.4.21\"\ntauri-plugin-sample = { path = \"./tauri-plugin-sample/\" }\ntauri-plugin-log = \"2\"\n\n[dependencies.tauri]\npath = \"../../../crates/tauri\"\nfeatures = [\n  \"protocol-asset\",\n  \"image-ico\",\n  \"image-png\",\n  \"isolation\",\n  \"macos-private-api\",\n  \"tray-icon\",\n]\n\n[dev-dependencies.tauri]\npath = \"../../../crates/tauri\"\nfeatures = [\"test\"]\n\n[features]\nprod = [\"tauri/custom-protocol\"]\n"
  },
  {
    "path": "examples/api/src-tauri/Cross.toml",
    "content": "[build.env]\n# must set ICONS_VOLUME, DIST_VOLUME, ISOLATION_VOLUME and WORKSPACE_VOLUME environment variables\n# ICONS_VOLUME: absolute path to the .icons folder\n# DIST_VOLUME: absolute path to the dist folder\n# ISOLATION_VOLUME: absolute path to the isolation dist folder\n# WORKSPACE_VOLUME: absolute path to the workspace\n# this can be done running `$ . .setup-cross.sh` in the examples/api folder\nvolumes = [\n  \"ICONS_VOLUME\",\n  \"DIST_VOLUME\",\n  \"ISOLATION_VOLUME\",\n  \"WORKSPACE_VOLUME\",\n]\n\n[target.aarch64-unknown-linux-gnu]\nimage = \"aarch64-unknown-linux-gnu:latest\"\n#image = \"ghcr.io/tauri-apps/tauri/aarch64-unknown-linux-gnu:latest\"\n"
  },
  {
    "path": "examples/api/src-tauri/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSCameraUsageDescription</key>\n\t<string>Request camera access for WebRTC</string>\n\t<key>NSMicrophoneUsageDescription</key>\n\t<string>Request microphone access for WebRTC</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/api/src-tauri/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse tauri_build::WindowsAttributes;\n\nfn main() {\n  tauri_build::try_build(\n    tauri_build::Attributes::new()\n      .codegen(tauri_build::CodegenContext::new())\n      .windows_attributes(WindowsAttributes::new_without_app_manifest())\n      .plugin(\n        \"app-menu\",\n        tauri_build::InlinedPlugin::new().commands(&[\"toggle\", \"popup\"]),\n      )\n      .app_manifest(tauri_build::AppManifest::new().commands(&[\n        \"log_operation\",\n        \"perform_request\",\n        \"echo\",\n        \"spam\",\n      ])),\n  )\n  .expect(\"failed to run tauri-build\");\n\n  #[cfg(windows)]\n  {\n    // workaround needed to prevent `STATUS_ENTRYPOINT_NOT_FOUND` error in tests\n    // see https://github.com/tauri-apps/tauri/pull/4383#issuecomment-1212221864\n    let target_os = std::env::var(\"CARGO_CFG_TARGET_OS\").unwrap();\n    let target_env = std::env::var(\"CARGO_CFG_TARGET_ENV\");\n    let is_tauri_workspace = std::env::var(\"__TAURI_WORKSPACE__\").is_ok_and(|v| v == \"true\");\n    if is_tauri_workspace && target_os == \"windows\" && Ok(\"msvc\") == target_env.as_deref() {\n      embed_manifest_for_tests();\n    }\n  }\n}\n\n#[cfg(windows)]\nfn embed_manifest_for_tests() {\n  static WINDOWS_MANIFEST_FILE: &str = \"windows-app-manifest.xml\";\n\n  let manifest = std::env::current_dir()\n    .unwrap()\n    .join(\"../../../crates/tauri-build/src\")\n    .join(WINDOWS_MANIFEST_FILE);\n\n  println!(\"cargo:rerun-if-changed={}\", manifest.display());\n  // Embed the Windows application manifest file.\n  println!(\"cargo:rustc-link-arg=/MANIFEST:EMBED\");\n  println!(\n    \"cargo:rustc-link-arg=/MANIFESTINPUT:{}\",\n    manifest.to_str().unwrap()\n  );\n  // Turn linker warnings into errors.\n  println!(\"cargo:rustc-link-arg=/WX\");\n}\n"
  },
  {
    "path": "examples/api/src-tauri/capabilities/.gitignore",
    "content": "schemas/\n"
  },
  {
    "path": "examples/api/src-tauri/capabilities/main.json",
    "content": "{\n  \"$schema\": \"../gen/schemas/desktop-schema.json\",\n  \"identifier\": \"secondary-window\",\n  \"description\": \"capability for secondary window\",\n  \"windows\": [\"main-*\"],\n  \"permissions\": [\n    {\n      \"identifier\": \"sample:allow-ping\",\n      \"deny\": [\n        {\n          \"path\": \"tauri.app\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "examples/api/src-tauri/capabilities/run-app.json",
    "content": "{\n  \"$schema\": \"../gen/schemas/desktop-schema.json\",\n  \"identifier\": \"run-app\",\n  \"description\": \"permissions to run the app\",\n  \"windows\": [\"main\", \"main-*\"],\n  \"permissions\": [\n    \"core:window:allow-is-enabled\",\n    \"core:window:allow-set-enabled\",\n    {\n      \"identifier\": \"allow-log-operation\",\n      \"allow\": [\n        {\n          \"event\": \"tauri-click\"\n        }\n      ]\n    },\n    \"allow-perform-request\",\n    \"allow-echo\",\n    \"allow-spam\",\n    \"app-menu:default\",\n    \"sample:allow-ping-scoped\",\n    \"sample:global-scope\",\n    \"core:default\",\n    \"core:app:allow-app-hide\",\n    \"core:app:allow-app-show\",\n    \"core:app:allow-set-app-theme\",\n    \"core:app:allow-set-dock-visibility\",\n    \"core:app:allow-default-window-icon\",\n    \"core:window:allow-set-theme\",\n    \"core:window:allow-center\",\n    \"core:window:allow-request-user-attention\",\n    \"core:window:allow-set-resizable\",\n    \"core:window:allow-set-maximizable\",\n    \"core:window:allow-set-minimizable\",\n    \"core:window:allow-set-closable\",\n    \"core:window:allow-set-title\",\n    \"core:window:allow-maximize\",\n    \"core:window:allow-unmaximize\",\n    \"core:window:allow-minimize\",\n    \"core:window:allow-unminimize\",\n    \"core:window:allow-show\",\n    \"core:window:allow-hide\",\n    \"core:window:allow-close\",\n    \"core:window:allow-set-decorations\",\n    \"core:window:allow-set-shadow\",\n    \"core:window:allow-set-effects\",\n    \"core:window:allow-set-always-on-top\",\n    \"core:window:allow-set-always-on-bottom\",\n    \"core:window:allow-set-content-protected\",\n    \"core:window:allow-set-size\",\n    \"core:window:allow-set-min-size\",\n    \"core:window:allow-set-max-size\",\n    \"core:window:allow-set-position\",\n    \"core:window:allow-set-fullscreen\",\n    \"core:window:allow-set-simple-fullscreen\",\n    \"core:window:allow-set-focus\",\n    \"core:window:allow-set-focusable\",\n    \"core:window:allow-set-skip-taskbar\",\n    \"core:window:allow-set-cursor-grab\",\n    \"core:window:allow-set-cursor-visible\",\n    \"core:window:allow-set-cursor-icon\",\n    \"core:window:allow-set-cursor-position\",\n    \"core:window:allow-set-ignore-cursor-events\",\n    \"core:window:allow-start-dragging\",\n    \"core:window:allow-set-progress-bar\",\n    \"core:window:allow-set-icon\",\n    \"core:window:allow-toggle-maximize\",\n    \"core:webview:allow-create-webview-window\",\n    \"core:webview:allow-print\"\n  ]\n}\n"
  },
  {
    "path": "examples/api/src-tauri/locales/pt-BR.wxl",
    "content": "<WixLocalization Culture=\"pt-BR\" xmlns=\"http://schemas.microsoft.com/wix/2006/localization\">\n    <String Id=\"LaunchApp\">Executar Tauri API</String>\n    <String Id=\"DowngradeErrorMessage\">Uma versão mais recente de Tauri API está instalada.</String>\n    <String Id=\"PathEnvVarFeature\">Adiciona o caminho do executável de Tauri API para a variável de ambiente PATH. Isso permite Tauri API ser executado pela linha de comando.</String>\n    <String Id=\"InstallAppFeature\">Instala Tauri API.</String>\n</WixLocalization>\n"
  },
  {
    "path": "examples/api/src-tauri/permissions/app-menu/default.toml",
    "content": "[default]\ndescription = \"Default permissions for the plugin\"\npermissions = [\"allow-toggle\", \"allow-popup\"]\n"
  },
  {
    "path": "examples/api/src-tauri/permissions/autogenerated/echo.toml",
    "content": "# Automatically generated - DO NOT EDIT!\n\n[[permission]]\nidentifier = \"allow-echo\"\ndescription = \"Enables the echo command without any pre-configured scope.\"\ncommands.allow = [\"echo\"]\n\n[[permission]]\nidentifier = \"deny-echo\"\ndescription = \"Denies the echo command without any pre-configured scope.\"\ncommands.deny = [\"echo\"]\n"
  },
  {
    "path": "examples/api/src-tauri/permissions/autogenerated/log_operation.toml",
    "content": "# Automatically generated - DO NOT EDIT!\n\n[[permission]]\nidentifier = \"allow-log-operation\"\ndescription = \"Enables the log_operation command without any pre-configured scope.\"\ncommands.allow = [\"log_operation\"]\n\n[[permission]]\nidentifier = \"deny-log-operation\"\ndescription = \"Denies the log_operation command without any pre-configured scope.\"\ncommands.deny = [\"log_operation\"]\n"
  },
  {
    "path": "examples/api/src-tauri/permissions/autogenerated/perform_request.toml",
    "content": "# Automatically generated - DO NOT EDIT!\n\n[[permission]]\nidentifier = \"allow-perform-request\"\ndescription = \"Enables the perform_request command without any pre-configured scope.\"\ncommands.allow = [\"perform_request\"]\n\n[[permission]]\nidentifier = \"deny-perform-request\"\ndescription = \"Denies the perform_request command without any pre-configured scope.\"\ncommands.deny = [\"perform_request\"]\n"
  },
  {
    "path": "examples/api/src-tauri/permissions/autogenerated/spam.toml",
    "content": "# Automatically generated - DO NOT EDIT!\n\n[[permission]]\nidentifier = \"allow-spam\"\ndescription = \"Enables the spam command without any pre-configured scope.\"\ncommands.allow = [\"spam\"]\n\n[[permission]]\nidentifier = \"deny-spam\"\ndescription = \"Denies the spam command without any pre-configured scope.\"\ncommands.deny = [\"spam\"]\n"
  },
  {
    "path": "examples/api/src-tauri/src/cmd.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::{Deserialize, Serialize};\nuse tauri::{\n  command,\n  ipc::{Channel, CommandScope},\n};\n\n#[derive(Debug, Deserialize)]\n#[allow(unused)]\npub struct RequestBody {\n  id: i32,\n  name: String,\n}\n\n#[derive(Debug, Deserialize)]\npub struct LogScope {\n  event: String,\n}\n\n#[command]\npub fn log_operation(\n  event: String,\n  payload: Option<String>,\n  command_scope: CommandScope<LogScope>,\n) -> Result<(), &'static str> {\n  if command_scope.denies().iter().any(|s| s.event == event) {\n    Err(\"denied\")\n  } else if !command_scope.allows().iter().any(|s| s.event == event) {\n    Err(\"not allowed\")\n  } else {\n    log::info!(\"{event} {payload:?}\");\n    Ok(())\n  }\n}\n\n#[derive(Serialize)]\npub struct ApiResponse {\n  message: String,\n}\n\n#[command]\npub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse {\n  println!(\"{endpoint} {body:?}\");\n  ApiResponse {\n    message: \"message response\".into(),\n  }\n}\n\n#[command]\npub fn echo(request: tauri::ipc::Request<'_>) -> tauri::ipc::Response {\n  tauri::ipc::Response::new(request.body().clone())\n}\n\n#[command]\npub fn spam(channel: Channel<i32>) -> tauri::Result<()> {\n  for i in 1..=1_000 {\n    channel.send(i)?;\n  }\n  Ok(())\n}\n"
  },
  {
    "path": "examples/api/src-tauri/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nmod cmd;\n#[cfg(desktop)]\nmod menu_plugin;\n#[cfg(desktop)]\nmod tray;\n\nuse serde::Serialize;\nuse tauri::{\n  ipc::Channel,\n  webview::{PageLoadEvent, WebviewWindowBuilder},\n  App, Emitter, Listener, Runtime, WebviewUrl,\n};\n#[allow(unused)]\nuse tauri::{Manager, RunEvent};\nuse tauri_plugin_sample::{PingRequest, SampleExt};\n\n#[derive(Clone, Serialize)]\nstruct Reply {\n  data: String,\n}\n\n#[cfg(target_os = \"macos\")]\npub struct AppMenu<R: Runtime>(pub std::sync::Mutex<Option<tauri::menu::Menu<R>>>);\n\n#[cfg(all(desktop, not(test)))]\npub struct PopupMenu<R: Runtime>(tauri::menu::Menu<R>);\n\n#[cfg_attr(mobile, tauri::mobile_entry_point)]\npub fn run() {\n  run_app(tauri::Builder::default(), |_app| {})\n}\n\npub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(\n  builder: tauri::Builder<R>,\n  setup: F,\n) {\n  #[allow(unused_mut)]\n  let mut builder = builder\n    .plugin(\n      tauri_plugin_log::Builder::default()\n        .level(log::LevelFilter::Info)\n        .build(),\n    )\n    .plugin(tauri_plugin_sample::init())\n    .setup(move |app| {\n      #[cfg(all(desktop, not(test)))]\n      {\n        let handle = app.handle();\n        tray::create_tray(handle)?;\n        handle.plugin(menu_plugin::init())?;\n      }\n\n      #[cfg(target_os = \"macos\")]\n      app.manage(AppMenu::<R>(Default::default()));\n\n      #[cfg(all(desktop, not(test)))]\n      app.manage(PopupMenu(\n        tauri::menu::MenuBuilder::new(app)\n          .check(\"check\", \"Tauri is awesome!\")\n          .text(\"text\", \"Do something\")\n          .copy()\n          .build()?,\n      ));\n\n      let mut window_builder = WebviewWindowBuilder::new(app, \"main\", WebviewUrl::default())\n        .on_document_title_changed(|_window, title| {\n          println!(\"document title changed: {title}\");\n        });\n\n      #[cfg(all(desktop, not(test)))]\n      {\n        let app_ = app.handle().clone();\n        let mut created_window_count = std::sync::atomic::AtomicUsize::new(0);\n\n        window_builder = window_builder\n          .title(\"Tauri API Validation\")\n          .inner_size(1000., 800.)\n          .min_inner_size(600., 400.)\n          .menu(tauri::menu::Menu::default(app.handle())?)\n          .on_new_window(move |url, features| {\n            println!(\"new window requested: {url:?} {features:?}\");\n\n            let number = created_window_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);\n\n            let builder = tauri::WebviewWindowBuilder::new(\n              &app_,\n              format!(\"new-{number}\"),\n              tauri::WebviewUrl::External(\"about:blank\".parse().unwrap()),\n            )\n            .window_features(features)\n            .on_document_title_changed(|window, title| {\n              window.set_title(&title).unwrap();\n            })\n            .title(url.as_str());\n\n            let window = builder.build().unwrap();\n            tauri::webview::NewWindowResponse::Create { window }\n          });\n      }\n\n      let webview = window_builder.build()?;\n\n      #[cfg(debug_assertions)]\n      webview.open_devtools();\n\n      let value = Some(\"test\".to_string());\n      let response = app.sample().ping(PingRequest {\n        value: value.clone(),\n        on_event: Channel::new(|event| {\n          println!(\"got channel event: {event:?}\");\n          Ok(())\n        }),\n      });\n      log::info!(\"got response: {:?}\", response);\n      // when #[cfg(desktop)], Rust will detect pattern as irrefutable\n      #[allow(irrefutable_let_patterns)]\n      if let Ok(res) = response {\n        assert_eq!(res.value, value);\n      }\n\n      #[cfg(desktop)]\n      std::thread::spawn(|| {\n        let server = match tiny_http::Server::http(\"localhost:3003\") {\n          Ok(s) => s,\n          Err(e) => {\n            eprintln!(\"{e}\");\n            std::process::exit(1);\n          }\n        };\n        loop {\n          if let Ok(mut request) = server.recv() {\n            let mut body = Vec::new();\n            let _ = request.as_reader().read_to_end(&mut body);\n            let response = tiny_http::Response::new(\n              tiny_http::StatusCode(200),\n              request.headers().to_vec(),\n              std::io::Cursor::new(body),\n              request.body_length(),\n              None,\n            );\n            let _ = request.respond(response);\n          }\n        }\n      });\n\n      setup(app);\n\n      Ok(())\n    })\n    .on_page_load(|webview, payload| {\n      if payload.event() == PageLoadEvent::Finished {\n        let webview_ = webview.clone();\n        webview.listen(\"js-event\", move |event| {\n          println!(\"got js-event with message '{:?}'\", event.payload());\n          let reply = Reply {\n            data: \"something else\".to_string(),\n          };\n\n          webview_\n            .emit(\"rust-event\", Some(reply))\n            .expect(\"failed to emit\");\n        });\n      }\n    });\n\n  #[allow(unused_mut)]\n  let mut app = builder\n    .invoke_handler(tauri::generate_handler![\n      cmd::log_operation,\n      cmd::perform_request,\n      cmd::echo,\n      cmd::spam,\n    ])\n    .build(tauri::tauri_build_context!())\n    .expect(\"error while building tauri application\");\n\n  #[cfg(target_os = \"macos\")]\n  app.set_activation_policy(tauri::ActivationPolicy::Regular);\n\n  app.run(move |_app_handle, _event| {\n    #[cfg(all(desktop, not(test)))]\n    match &_event {\n      RunEvent::ExitRequested { api, code, .. } => {\n        // Keep the event loop running even if all windows are closed\n        // This allow us to catch tray icon events when there is no window\n        // if we manually requested an exit (code is Some(_)) we will let it go through\n        if code.is_none() {\n          api.prevent_exit();\n        }\n      }\n      RunEvent::WindowEvent {\n        event: tauri::WindowEvent::CloseRequested { api, .. },\n        label,\n        ..\n      } => {\n        println!(\"closing window...\");\n        // run the window destroy manually just for fun :)\n        // usually you'd show a dialog here to ask for confirmation or whatever\n        api.prevent_close();\n        _app_handle\n          .get_webview_window(label)\n          .unwrap()\n          .destroy()\n          .unwrap();\n      }\n      _ => (),\n    }\n  })\n}\n\n#[cfg(test)]\nmod tests {\n  use tauri::Manager;\n\n  #[test]\n  fn run_app() {\n    super::run_app(tauri::test::mock_builder(), |app| {\n      let window = app.get_webview_window(\"main\").unwrap();\n      std::thread::spawn(move || {\n        std::thread::sleep(std::time::Duration::from_secs(1));\n        window.close().unwrap();\n      });\n    })\n  }\n}\n"
  },
  {
    "path": "examples/api/src-tauri/src/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// Prevents additional console window on Windows in release, DO NOT REMOVE!!\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nfn main() {\n  api_lib::run();\n}\n"
  },
  {
    "path": "examples/api/src-tauri/src/menu_plugin.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n#![cfg(all(desktop, not(test)))]\n\nuse tauri::{\n  command,\n  plugin::{Builder, TauriPlugin},\n  Runtime,\n};\n\n#[cfg(not(target_os = \"macos\"))]\n#[command]\npub fn toggle<R: tauri::Runtime>(window: tauri::Window<R>) {\n  if window.is_menu_visible().unwrap_or_default() {\n    let _ = window.hide_menu();\n  } else {\n    let _ = window.show_menu();\n  }\n}\n\n#[cfg(target_os = \"macos\")]\n#[command]\npub fn toggle<R: tauri::Runtime>(\n  app: tauri::AppHandle<R>,\n  app_menu: tauri::State<'_, crate::AppMenu<R>>,\n) {\n  if let Some(menu) = app.remove_menu().unwrap() {\n    app_menu.0.lock().unwrap().replace(menu);\n  } else {\n    app\n      .set_menu(app_menu.0.lock().unwrap().clone().expect(\"no app menu\"))\n      .unwrap();\n  }\n}\n\n#[command]\npub fn popup<R: tauri::Runtime>(\n  window: tauri::Window<R>,\n  popup_menu: tauri::State<'_, crate::PopupMenu<R>>,\n) {\n  window.popup_menu(&popup_menu.0).unwrap();\n}\n\npub fn init<R: Runtime>() -> TauriPlugin<R> {\n  Builder::new(\"app-menu\")\n    .invoke_handler(tauri::generate_handler![\n      #![plugin(app_menu)]\n      popup, toggle\n    ])\n    .build()\n}\n"
  },
  {
    "path": "examples/api/src-tauri/src/tray.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg(all(desktop, not(test)))]\n\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse tauri::{\n  include_image,\n  menu::{Menu, MenuItem},\n  tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},\n  Manager, Runtime, WebviewUrl,\n};\n\npub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {\n  let toggle_i = MenuItem::with_id(app, \"toggle\", \"Toggle\", true, None::<&str>)?;\n  let new_window_i = MenuItem::with_id(app, \"new-window\", \"New window\", true, None::<&str>)?;\n  let icon_i_1 = MenuItem::with_id(app, \"icon-1\", \"Icon 1\", true, None::<&str>)?;\n  let icon_i_2 = MenuItem::with_id(app, \"icon-2\", \"Icon 2\", true, None::<&str>)?;\n  #[cfg(target_os = \"macos\")]\n  let set_title_i = MenuItem::with_id(app, \"set-title\", \"Set Title\", true, None::<&str>)?;\n  let switch_i = MenuItem::with_id(app, \"switch-menu\", \"Switch Menu\", true, None::<&str>)?;\n  let quit_i = MenuItem::with_id(app, \"quit\", \"Quit\", true, None::<&str>)?;\n  let remove_tray_i =\n    MenuItem::with_id(app, \"remove-tray\", \"Remove Tray icon\", true, None::<&str>)?;\n  let menu1 = Menu::with_items(\n    app,\n    &[\n      &toggle_i,\n      &new_window_i,\n      &icon_i_1,\n      &icon_i_2,\n      #[cfg(target_os = \"macos\")]\n      &set_title_i,\n      &switch_i,\n      &quit_i,\n      &remove_tray_i,\n    ],\n  )?;\n  let menu2 = Menu::with_items(\n    app,\n    &[&toggle_i, &new_window_i, &switch_i, &quit_i, &remove_tray_i],\n  )?;\n\n  let is_menu1 = AtomicBool::new(true);\n\n  let _ = TrayIconBuilder::with_id(\"tray-1\")\n    .tooltip(\"Tauri\")\n    .icon(app.default_window_icon().unwrap().clone())\n    .menu(&menu1)\n    .show_menu_on_left_click(false)\n    .on_menu_event(move |app, event| match event.id.as_ref() {\n      \"quit\" => {\n        app.exit(0);\n      }\n      \"remove-tray\" => {\n        app.remove_tray_by_id(\"tray-1\");\n      }\n      \"toggle\" => {\n        if let Some(window) = app.get_webview_window(\"main\") {\n          let new_title = if window.is_visible().unwrap_or_default() {\n            let _ = window.hide();\n            \"Show\"\n          } else {\n            let _ = window.show();\n            let _ = window.set_focus();\n            \"Hide\"\n          };\n          toggle_i.set_text(new_title).unwrap();\n        }\n      }\n      \"new-window\" => {\n        let _webview =\n          tauri::WebviewWindowBuilder::new(app, \"new\", WebviewUrl::App(\"index.html\".into()))\n            .title(\"Tauri\")\n            .build()\n            .unwrap();\n      }\n      #[cfg(target_os = \"macos\")]\n      \"set-title\" => {\n        if let Some(tray) = app.tray_by_id(\"tray-1\") {\n          let _ = tray.set_title(Some(\"Tauri\"));\n        }\n      }\n      i @ \"icon-1\" | i @ \"icon-2\" => {\n        if let Some(tray) = app.tray_by_id(\"tray-1\") {\n          let icon = if i == \"icon-1\" {\n            include_image!(\"../../.icons/icon.ico\")\n          } else {\n            include_image!(\"../../.icons/tray_icon_with_transparency.png\")\n          };\n          let _ = tray.set_icon(Some(icon));\n        }\n      }\n      \"switch-menu\" => {\n        let flag = is_menu1.load(Ordering::Relaxed);\n        let (menu, tooltip) = if flag {\n          (menu2.clone(), \"Menu 2\")\n        } else {\n          (menu1.clone(), \"Tauri\")\n        };\n        if let Some(tray) = app.tray_by_id(\"tray-1\") {\n          let _ = tray.set_menu(Some(menu));\n          let _ = tray.set_tooltip(Some(tooltip));\n        }\n        is_menu1.store(!flag, Ordering::Relaxed);\n      }\n\n      _ => {}\n    })\n    .on_tray_icon_event(|tray, event| {\n      if let TrayIconEvent::Click {\n        button: MouseButton::Left,\n        button_state: MouseButtonState::Up,\n        ..\n      } = event\n      {\n        let app = tray.app_handle();\n        if let Some(window) = app.get_webview_window(\"main\") {\n          let _ = window.unminimize();\n          let _ = window.show();\n          let _ = window.set_focus();\n        }\n      }\n    })\n    .build(app);\n\n  Ok(())\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/.gitignore",
    "content": ".tauri\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/Cargo.toml",
    "content": "[package]\nname = \"tauri-plugin-sample\"\nversion = \"0.1.0\"\nedition = \"2021\"\nlinks = \"tauri-plugin-sample\"\n\n[dependencies]\ntauri = { path = \"../../../../crates/tauri\" }\nlog = \"0.4\"\nserde = \"1\"\nthiserror = \"2\"\n\n[build-dependencies]\ntauri-plugin = { path = \"../../../../crates/tauri-plugin\", features = [\n  \"build\",\n] }\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/android/.gitignore",
    "content": "/build\n.tauri\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/android/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n    id(\"org.jetbrains.kotlin.android\")\n}\n\nandroid {\n    namespace = \"com.plugin.sample\"\n    compileSdk = 36\n\n    defaultConfig {\n        minSdk = 21\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles(\"consumer-rules.pro\")\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = false\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n}\n\ndependencies {\n\n    implementation(\"androidx.core:core-ktx:1.9.0\")\n    implementation(\"androidx.appcompat:appcompat:1.6.0\")\n    implementation(\"com.google.android.material:material:1.7.0\")\n    testImplementation(\"junit:junit:4.13.2\")\n    androidTestImplementation(\"androidx.test.ext:junit:1.1.5\")\n    androidTestImplementation(\"androidx.test.espresso:espresso-core:3.5.1\")\n    implementation(project(\":tauri-android\"))\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/android/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/android/settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        mavenCentral()\n        gradlePluginPortal()\n        google()\n    }\n    resolutionStrategy {\n        eachPlugin {\n            switch (requested.id.id) {\n                case \"com.android.library\":\n                    useVersion(\"8.0.2\")\n                    break\n                case \"org.jetbrains.kotlin.android\":\n                    useVersion(\"1.8.20\")\n                    break\n            }\n        }\n    }\n}\n\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        mavenCentral()\n        google()\n\n    }\n}\n\ninclude ':tauri-android'\nproject(':tauri-android').projectDir = new File('./.tauri/tauri-api')\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/android/src/androidTest/java/com/plugin/sample/ExampleInstrumentedTest.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage com.plugin.sample\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.runners.AndroidJUnit4\n\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\nimport org.junit.Assert.*\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"com.plugin.sample\", appContext.packageName)\n    }\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/android/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n</manifest>"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/android/src/main/java/com/plugin/sample/Example.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage com.plugin.sample\n\nimport android.util.Log\n\nclass Example {\n    fun pong(value: String): String {\n        Log.i(\"Pong\", value)\n        return value\n    }\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/android/src/main/java/com/plugin/sample/ExamplePlugin.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage com.plugin.sample\n\nimport android.app.Activity\nimport app.tauri.annotation.Command\nimport app.tauri.annotation.InvokeArg\nimport app.tauri.annotation.TauriPlugin\nimport app.tauri.plugin.Channel\nimport app.tauri.plugin.JSObject\nimport app.tauri.plugin.Plugin\nimport app.tauri.plugin.Invoke\n\n@InvokeArg\nclass PingArgs {\n  var value: String? = null\n  var onEvent: Channel? = null\n}\n\n@TauriPlugin\nclass ExamplePlugin(private val activity: Activity): Plugin(activity) {\n    private val implementation = Example()\n\n    @Command\n    fun ping(invoke: Invoke) {\n        val args = invoke.parseArgs(PingArgs::class.java)\n\n        val event = JSObject()\n        event.put(\"kind\", \"ping\")\n        args.onEvent?.send(event)\n\n        val ret = JSObject()\n        ret.put(\"value\", implementation.pong(args.value ?: \"default value :(\"))\n        invoke.resolve(ret)\n    }\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/android/src/test/java/com/plugin/sample/ExampleUnitTest.kt",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\npackage com.plugin.sample\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/api-iife.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nif ('__TAURI__' in window) {\n  window.__TAURI__.sample = {}\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nconst COMMANDS: &[&str] = &[\"ping\"];\n\nfn main() {\n  tauri_plugin::Builder::new(COMMANDS)\n    .android_path(\"android\")\n    .ios_path(\"ios\")\n    .global_api_script_path(\"./api-iife.js\")\n    .build();\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/ios/.gitignore",
    "content": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\nDerivedData/\n.swiftpm/config/registries.json\n.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata\n.netrc\nPackage.resolved\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/ios/Package.swift",
    "content": "// swift-tools-version:5.3\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"tauri-plugin-sample\",\n    platforms: [\n        .macOS(.v10_13),\n        .iOS(.v13),\n    ],\n    products: [\n        // Products define the executables and libraries a package produces, and make them visible to other packages.\n        .library(\n            name: \"tauri-plugin-sample\",\n            type: .static,\n            targets: [\"tauri-plugin-sample\"]),\n    ],\n    dependencies: [\n        // Dependencies declare other packages that this package depends on.\n        .package(name: \"Tauri\", path: \"../../../../../crates/tauri/mobile/ios-api\")\n    ],\n    targets: [\n        // Targets are the basic building blocks of a package. A target can define a module or a test suite.\n        // Targets can depend on other targets in this package, and on products in packages this package depends on.\n        .target(\n            name: \"tauri-plugin-sample\",\n            dependencies: [\n                .byName(name: \"Tauri\")\n            ],\n            path: \"Sources\")\n    ]\n)\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/ios/README.md",
    "content": "# Tauri Plugin sample\n\nA description of this package.\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/ios/Sources/ExamplePlugin.swift",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport SwiftRs\nimport Tauri\nimport UIKit\nimport WebKit\n\nclass PingArgs: Decodable {\n  let value: String?\n  let onEvent: Channel?\n}\n\nclass ExamplePlugin: Plugin {\n  @objc public func ping(_ invoke: Invoke) throws {\n    let args = try invoke.parseArgs(PingArgs.self)\n    try args.onEvent?.send([\"kind\": \"ping\"])\n    invoke.resolve([\"value\": args.value ?? \"\"])\n  }\n}\n\n@_cdecl(\"init_plugin_sample\")\nfunc initPlugin() -> Plugin {\n  return ExamplePlugin()\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/ios/Tests/PluginTests/PluginTests.swift",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport XCTest\n@testable import ExamplePlugin\n\nfinal class ExamplePluginTests: XCTestCase {\n    func testExample() throws {\n        let plugin = ExamplePlugin()\n    }\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/permissions/.gitignore",
    "content": "schemas/"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/permissions/autogenerated/commands/ping.toml",
    "content": "# Automatically generated - DO NOT EDIT!\n\n\"$schema\" = \"../../schemas/schema.json\"\n\n[[permission]]\nidentifier = \"allow-ping\"\ndescription = \"Enables the ping command without any pre-configured scope.\"\ncommands.allow = [\"ping\"]\n\n[[permission]]\nidentifier = \"deny-ping\"\ndescription = \"Denies the ping command without any pre-configured scope.\"\ncommands.deny = [\"ping\"]\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/permissions/autogenerated/reference.md",
    "content": "## Permission Table\n\n<table>\n<tr>\n<th>Identifier</th>\n<th>Description</th>\n</tr>\n\n\n<tr>\n<td>\n\n`sample:allow-ping`\n\n</td>\n<td>\n\nEnables the ping command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`sample:deny-ping`\n\n</td>\n<td>\n\nDenies the ping command without any pre-configured scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`sample:global-scope`\n\n</td>\n<td>\n\nSets a global scope.\n\n</td>\n</tr>\n\n<tr>\n<td>\n\n`sample:allow-ping-scoped`\n\n</td>\n<td>\n\nEnables the ping command with a test scope.\n\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/permissions/global-scope.toml",
    "content": "\"$schema\" = \"schemas/schema.json\"\n\n[[permission]]\nidentifier = \"global-scope\"\ndescription = \"Sets a global scope.\"\n[[permission.scope.allow]]\npath = \"global\"\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/permissions/ping-scoped.toml",
    "content": "\"$schema\" = \"schemas/schema.json\"\n\n[[permission]]\nidentifier = \"allow-ping-scoped\"\ndescription = \"Enables the ping command with a test scope.\"\ncommands.allow = [\"ping\"]\n[[permission.scope.allow]]\npath = \"x\"\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/src/desktop.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::de::DeserializeOwned;\nuse tauri::{plugin::PluginApi, AppHandle, Runtime};\n\nuse crate::models::*;\n\npub fn init<R: Runtime, C: DeserializeOwned>(\n  app: &AppHandle<R>,\n  _api: PluginApi<R, C>,\n) -> crate::Result<Sample<R>> {\n  Ok(Sample(app.clone()))\n}\n\n/// A helper class to access the sample APIs.\npub struct Sample<R: Runtime>(AppHandle<R>);\n\nimpl<R: Runtime> Sample<R> {\n  pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {\n    payload.on_event.send(Event {\n      kind: \"ping\".to_string(),\n      value: payload.value.clone(),\n    })?;\n    Ok(PingResponse {\n      value: payload.value,\n    })\n  }\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/src/error.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#[derive(Debug, thiserror::Error)]\npub enum Error {\n  #[cfg(mobile)]\n  #[error(transparent)]\n  PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),\n  #[error(transparent)]\n  Tauri(#[from] tauri::Error),\n}\n\npub type Result<T> = std::result::Result<T, Error>;\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::Deserialize;\nuse std::path::PathBuf;\nuse tauri::{\n  plugin::{Builder, TauriPlugin},\n  Manager, Runtime,\n};\n\npub use models::*;\n\n#[cfg(desktop)]\nmod desktop;\n#[cfg(mobile)]\nmod mobile;\n\nmod error;\nmod models;\n\n#[cfg(desktop)]\nuse desktop::Sample;\n#[cfg(mobile)]\nuse mobile::Sample;\n\npub use error::*;\n\n/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the sample APIs.\npub trait SampleExt<R: Runtime> {\n  fn sample(&self) -> &Sample<R>;\n}\n\nimpl<R: Runtime, T: Manager<R>> crate::SampleExt<R> for T {\n  fn sample(&self) -> &Sample<R> {\n    self.state::<Sample<R>>().inner()\n  }\n}\n\n#[allow(dead_code)]\n#[derive(Debug, Deserialize)]\nstruct PingScope {\n  path: PathBuf,\n}\n\n#[allow(dead_code)]\n#[derive(Debug, Deserialize)]\nstruct SampleScope {\n  path: PathBuf,\n}\n\n#[tauri::command]\nfn ping<R: tauri::Runtime>(\n  app: tauri::AppHandle<R>,\n  value: Option<String>,\n  scope: tauri::ipc::CommandScope<PingScope>,\n  global_scope: tauri::ipc::GlobalScope<SampleScope>,\n) -> std::result::Result<PingResponse, String> {\n  println!(\"local scope {scope:?}\");\n  println!(\"global scope {global_scope:?}\");\n  app\n    .sample()\n    .ping(PingRequest {\n      value,\n      on_event: tauri::ipc::Channel::new(|_| Ok(())),\n    })\n    .map_err(|e| e.to_string())\n}\n\npub fn init<R: Runtime>() -> TauriPlugin<R> {\n  Builder::new(\"sample\")\n    .setup(|app, api| {\n      #[cfg(mobile)]\n      let sample = mobile::init(app, api)?;\n      #[cfg(desktop)]\n      let sample = desktop::init(app, api)?;\n      app.manage(sample);\n\n      Ok(())\n    })\n    .invoke_handler(tauri::generate_handler![ping])\n    .on_navigation(|window, url| {\n      println!(\"navigation {} {url}\", window.label());\n      true\n    })\n    .build()\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/src/mobile.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::de::DeserializeOwned;\nuse tauri::{\n  plugin::{PluginApi, PluginHandle},\n  AppHandle, Runtime,\n};\n\nuse crate::models::*;\n\n#[cfg(target_os = \"android\")]\nconst PLUGIN_IDENTIFIER: &str = \"com.plugin.sample\";\n\n#[cfg(target_os = \"ios\")]\ntauri::ios_plugin_binding!(init_plugin_sample);\n\n// initializes the Kotlin or Swift plugin classes\npub fn init<R: Runtime, C: DeserializeOwned>(\n  _app: &AppHandle<R>,\n  api: PluginApi<R, C>,\n) -> crate::Result<Sample<R>> {\n  #[cfg(target_os = \"android\")]\n  let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, \"ExamplePlugin\")?;\n  #[cfg(target_os = \"ios\")]\n  let handle = api.register_ios_plugin(init_plugin_sample)?;\n  Ok(Sample(handle))\n}\n\n/// A helper class to access the sample APIs.\npub struct Sample<R: Runtime>(PluginHandle<R>);\n\nimpl<R: Runtime> Sample<R> {\n  pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {\n    self\n      .0\n      .run_mobile_plugin(\"ping\", payload)\n      .map_err(Into::into)\n  }\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri-plugin-sample/src/models.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse serde::{Deserialize, Serialize};\nuse tauri::ipc::Channel;\n\n#[derive(Serialize)]\npub struct Event {\n  pub kind: String,\n  pub value: Option<String>,\n}\n\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct PingRequest {\n  pub value: Option<String>,\n  pub on_event: Channel<Event>,\n}\n\n#[derive(Debug, Clone, Default, Deserialize, Serialize)]\npub struct PingResponse {\n  pub value: Option<String>,\n}\n"
  },
  {
    "path": "examples/api/src-tauri/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"productName\": \"Tauri API\",\n  \"version\": \"1.0.0\",\n  \"identifier\": \"com.tauri.api\",\n  \"build\": {\n    \"frontendDist\": \"../dist\",\n    \"devUrl\": \"http://localhost:1420\",\n    \"beforeDevCommand\": \"pnpm dev\",\n    \"beforeBuildCommand\": \"pnpm build\",\n    \"removeUnusedCommands\": true\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n    \"macOSPrivateApi\": true,\n    \"security\": {\n      \"pattern\": {\n        \"use\": \"isolation\",\n        \"options\": {\n          \"dir\": \"../isolation-dist/\"\n        }\n      },\n      \"csp\": {\n        \"default-src\": \"'self' customprotocol: asset:\",\n        \"connect-src\": \"ipc: http://ipc.localhost\",\n        \"font-src\": [\"https://fonts.gstatic.com\"],\n        \"img-src\": \"'self' asset: http://asset.localhost blob: data:\",\n        \"style-src\": \"'unsafe-inline' 'self' https://fonts.googleapis.com\"\n      },\n      \"freezePrototype\": true,\n      \"assetProtocol\": {\n        \"enable\": true,\n        \"scope\": {\n          \"allow\": [\"$APPDATA/db/**\", \"$RESOURCE/**\"],\n          \"deny\": [\"$APPDATA/db/*.stronghold\"]\n        }\n      }\n    }\n  },\n  \"plugins\": {\n    \"cli\": {\n      \"description\": \"Tauri API example\",\n      \"args\": [\n        {\n          \"short\": \"c\",\n          \"name\": \"config\",\n          \"takesValue\": true,\n          \"description\": \"Config path\"\n        },\n        {\n          \"short\": \"t\",\n          \"name\": \"theme\",\n          \"takesValue\": true,\n          \"description\": \"App theme\",\n          \"possibleValues\": [\"light\", \"dark\", \"system\"]\n        },\n        {\n          \"short\": \"v\",\n          \"name\": \"verbose\",\n          \"description\": \"Verbosity level\"\n        }\n      ],\n      \"subcommands\": {\n        \"update\": {\n          \"description\": \"Updates the app\",\n          \"args\": [\n            {\n              \"short\": \"b\",\n              \"name\": \"background\",\n              \"description\": \"Update in background\"\n            }\n          ]\n        }\n      }\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"icon\": [\n      \"../../.icons/32x32.png\",\n      \"../../.icons/128x128.png\",\n      \"../../.icons/128x128@2x.png\",\n      \"../../.icons/icon.icns\",\n      \"../../.icons/icon.ico\"\n    ],\n    \"windows\": {\n      \"wix\": {\n        \"language\": {\n          \"en-US\": {},\n          \"pt-BR\": {\n            \"localePath\": \"locales/pt-BR.wxl\"\n          }\n        }\n      },\n      \"nsis\": {\n        \"compression\": \"none\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "examples/api/svelte.config.js",
    "content": "import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'\n\nexport default {\n  // Consult https://svelte.dev/docs#compile-time-svelte-preprocess\n  // for more information about preprocessors\n  preprocess: vitePreprocess()\n}\n"
  },
  {
    "path": "examples/api/unocss.config.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport { defineConfig, presetIcons, presetWind3, presetWebFonts } from 'unocss'\nimport extractorSvelte from '@unocss/extractor-svelte'\n\nexport default defineConfig({\n  theme: {\n    colors: {\n      primary: '#FFFFFF',\n      primaryLighter: '#e9ecef',\n      darkPrimary: '#1B1B1D',\n      darkPrimaryLighter: '#242526',\n      primaryText: '#1C1E21',\n      darkPrimaryText: '#E3E3E3',\n      secondaryText: '#858A91',\n      darkSecondaryText: '#C2C5CA',\n      accent: '#3578E5',\n      accentDark: '#306cce',\n      accentDarker: '#2d66c3',\n      accentDarkest: '#2554a0',\n      accentLight: '#538ce9',\n      accentLighter: '#72a1ed',\n      accentLightest: '#9abcf2',\n      accentText: '#FFFFFF',\n      darkAccent: '#67d6ed',\n      darkAccentDark: '#49cee9',\n      darkAccentDarker: '#39cae8',\n      darkAccentDarkest: '#19b5d5',\n      darkAccentLight: '#85def1',\n      darkAccentLighter: '#95e2f2',\n      darkAccentLightest: '#c2eff8',\n      darkAccentText: '#1C1E21',\n      code: '#d6d8da',\n      codeDark: '#282a2e',\n      hoverOverlay: 'rgba(0,0,0,.05)',\n      hoverOverlayDarker: 'rgba(0,0,0,.1)',\n      darkHoverOverlay: 'hsla(0,0%,100%,.05)',\n      darkHoverOverlayDarker: 'hsla(0,0%,100%,.1)'\n    }\n  },\n  preflights: [\n    {\n      getCSS: ({ theme }) => `\n    ::-webkit-scrollbar-thumb {\n      background-color: ${theme.colors.accent};\n    }\n\n    .dark ::-webkit-scrollbar-thumb {\n      background-color: ${theme.colors.darkAccent};\n    }\n\n    code {\n      font-size: ${theme.fontSize.xs[0]};\n      font-family: ${theme.fontFamily.mono};\n      border-radius: ${theme.borderRadius['DEFAULT']};\n      background-color: ${theme.colors.code};\n    }\n\n    .code-block {\n      font-family: ${theme.fontFamily.mono};\n      font-size: ${theme.fontSize.sm[0]};\n    }\n\n    .dark code {\n      background-color: ${theme.colors.codeDark};\n    }\n    `\n    }\n  ],\n  shortcuts: {\n    btn: `select-none outline-none shadow-md p-2 rd-1 text-primaryText border-none font-400 dark:font-600\n            bg-accent hover:bg-accentDarker active:bg-accentDarkest text-accentText\n            dark:bg-darkAccent dark:hover:bg-darkAccentDarker dark:active:bg-darkAccentDarkest dark:text-darkAccentText`,\n    nv: `decoration-none flex items-center relative p-2 rd-1 transition-all-125 ease\n            text-darkSecondaryText\n            hover:text-accent dark:hover:text-darkAccent\n            hover:bg-darkHoverOverlay hover:border-l-4`,\n    nv_selected: `nv bg-darkHoverOverlay text-accent dark:text-darkAccent border-l-4`,\n    note: `decoration-none flex-inline items-center relative p-2 rd-1\n             border-l-4 border-accent dark:border-darkAccent\n             bg-accent/10 dark:bg-darkAccent/10`,\n    'note-red':\n      'note bg-red-700/10 dark:bg-red-700/10 after:bg-red-700 dark:after:bg-red-700',\n    input:\n      'flex items-center outline-none border-none py-3 px-2 rd-1 shadow-md bg-primaryLighter dark:bg-darkPrimaryLighter text-primaryText dark:text-darkPrimaryText',\n    checkbox: 'accent-accent'\n  },\n  presets: [\n    presetWind3(),\n    presetIcons(),\n    presetWebFonts({\n      fonts: {\n        sans: 'Rubik',\n        mono: ['Fira Code', 'Fira Mono:400,700']\n      }\n    })\n  ],\n  extractors: [extractorSvelte]\n})\n"
  },
  {
    "path": "examples/api/vite.config.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport { defineConfig } from 'vite'\nimport Unocss from 'unocss/vite'\nimport { svelte } from '@sveltejs/vite-plugin-svelte'\n\nconst host = process.env.TAURI_DEV_HOST\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [Unocss(), svelte()],\n  build: {\n    emptyOutDir: false,\n    rollupOptions: {\n      output: {\n        entryFileNames: `assets/[name].js`,\n        chunkFileNames: `assets/[name].js`,\n        assetFileNames: `assets/[name].[ext]`\n      }\n    }\n  },\n\n  // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`\n  // prevent Vite from obscuring rust errors\n  clearScreen: false,\n  // tauri expects a fixed port, fail if that port is not available\n  server: {\n    host: host ?? false,\n    port: 1420,\n    strictPort: true,\n    hmr: host\n      ? {\n          protocol: 'ws',\n          host: host,\n          port: 1430\n        }\n      : undefined,\n    fs: {\n      allow: ['.', '../../packages/api/dist']\n    }\n  }\n})\n"
  },
  {
    "path": "examples/commands/README.md",
    "content": "# Commands Example\n\nA simple Tauri Application showcasing the command API.\n\nTo execute run the following on the root directory of the repository: `cargo run --example commands`.\n"
  },
  {
    "path": "examples/commands/commands.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse tauri::{command, State};\n\n#[command]\npub fn cmd(_argument: String) {}\n\n#[command]\npub fn invoke(_argument: String) {}\n\n#[command]\npub fn message(_argument: String) {}\n\n#[command]\npub fn resolver(_argument: String) {}\n\n#[command]\npub fn simple_command(the_argument: String) {\n  println!(\"{the_argument}\");\n}\n\n#[command]\npub fn stateful_command(the_argument: Option<String>, state: State<'_, super::MyState>) {\n  println!(\"{:?} {:?}\", the_argument, state.inner());\n}\n"
  },
  {
    "path": "examples/commands/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Tauri</title>\n  </head>\n\n  <body>\n    <h1>Tauri Commands</h1>\n    <div>Response: <span id=\"response\"></span></div>\n    <div>Without Args: <span id=\"response-optional\"></span></div>\n    <div id=\"container\"></div>\n    <script>\n      function runCommand(commandName, args, optional) {\n        const id = optional ? '#response-optional' : '#response'\n        const result = document.querySelector(id)\n        window.__TAURI__.core\n          .invoke(commandName, args)\n          .then((response) => {\n            const val =\n              response instanceof ArrayBuffer\n                ? new TextDecoder().decode(response)\n                : response\n            result.innerText = `Ok(${val})`\n          })\n          .catch((error) => {\n            result.innerText = `Err(${error})`\n          })\n      }\n\n      const container = document.querySelector('#container')\n      const commands = [\n        { name: 'borrow_cmd' },\n        { name: 'raw_request' },\n        { name: 'window_label' },\n        { name: 'simple_command' },\n        { name: 'stateful_command' },\n        { name: 'async_simple_command' },\n        { name: 'async_simple_command_snake' },\n        { name: 'future_simple_command' },\n        { name: 'async_stateful_command' },\n        { name: 'simple_command_with_result' },\n        // snake\n        { name: 'future_simple_command_snake' },\n        { name: 'future_simple_command_with_return_snake' },\n        { name: 'future_simple_command_with_result_snake' },\n        { name: 'force_async_snake' },\n        { name: 'force_async_with_result_snake' },\n        { name: 'simple_command_with_result_snake' },\n        { name: 'stateful_command_with_result_snake' },\n        // state\n        { name: 'stateful_command_with_result' },\n        { name: 'async_simple_command_with_result' },\n        { name: 'future_simple_command_with_return' },\n        { name: 'future_simple_command_with_result' },\n        { name: 'async_stateful_command_with_result' },\n        { name: 'command_arguments_wild' },\n        {\n          name: 'command_arguments_struct',\n          args: { person: { name: 'ferris', age: 6 } }\n        },\n        {\n          name: 'command_arguments_tuple_struct',\n          args: { inlinePerson: ['ferris', 6] }\n        }\n      ]\n\n      for (const command of commands) {\n        const { name } = command\n        const args = command.args ?? {\n          [name.endsWith('snake') ? 'the_argument' : 'theArgument']: 'value'\n        }\n        const button = document.createElement('button')\n        button.innerHTML = `Run ${name}`\n        button.addEventListener('click', function () {\n          runCommand(name, args, false)\n          runCommand(name, Object.create(null), true)\n        })\n        container.appendChild(button)\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/commands/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\n// we move some basic commands to a separate module just to show it works\nmod commands;\nuse commands::{cmd, invoke, message, resolver};\n\nuse serde::Deserialize;\nuse tauri::{\n  command,\n  ipc::{Request, Response},\n  State, Window,\n};\n\n#[derive(Debug)]\npub struct MyState {\n  #[allow(dead_code)]\n  value: u64,\n  #[allow(dead_code)]\n  label: String,\n}\n\n#[derive(Debug, serde::Serialize)]\nenum MyError {\n  FooError,\n}\n\n// ------------------------ Commands using Window ------------------------\n#[command]\nfn window_label(window: Window) {\n  println!(\"window label: {}\", window.label());\n}\n\n// Async commands\n\n#[command]\nasync fn async_simple_command(the_argument: String) {\n  println!(\"{the_argument}\");\n}\n\n#[command(rename_all = \"snake_case\")]\nasync fn async_simple_command_snake(the_argument: String) {\n  println!(\"{the_argument}\");\n}\n\n#[command]\nasync fn async_stateful_command(\n  the_argument: Option<String>,\n  state: State<'_, MyState>,\n) -> Result<(), ()> {\n  println!(\"{:?} {:?}\", the_argument, state.inner());\n  Ok(())\n}\n// ------------------------ Raw future commands ------------------------\n\n#[command(async)]\nfn future_simple_command(the_argument: String) -> impl std::future::Future<Output = ()> {\n  println!(\"{the_argument}\");\n  std::future::ready(())\n}\n\n#[command(async)]\nfn future_simple_command_with_return(\n  the_argument: String,\n) -> impl std::future::Future<Output = String> {\n  println!(\"{the_argument}\");\n  std::future::ready(the_argument)\n}\n\n#[command(async)]\nfn future_simple_command_with_result(\n  the_argument: String,\n) -> impl std::future::Future<Output = Result<String, ()>> {\n  println!(\"{the_argument}\");\n  std::future::ready(Ok(the_argument))\n}\n\n#[command(async)]\nfn force_async(the_argument: String) -> String {\n  the_argument\n}\n\n#[command(async)]\nfn force_async_with_result(the_argument: &str) -> Result<&str, MyError> {\n  (!the_argument.is_empty())\n    .then_some(the_argument)\n    .ok_or(MyError::FooError)\n}\n\n// ------------------------ Raw future commands - snake_case ------------------------\n\n#[command(async, rename_all = \"snake_case\")]\nfn future_simple_command_snake(the_argument: String) -> impl std::future::Future<Output = ()> {\n  println!(\"{the_argument}\");\n  std::future::ready(())\n}\n\n#[command(async, rename_all = \"snake_case\")]\nfn future_simple_command_with_return_snake(\n  the_argument: String,\n) -> impl std::future::Future<Output = String> {\n  println!(\"{the_argument}\");\n  std::future::ready(the_argument)\n}\n\n#[command(async, rename_all = \"snake_case\")]\nfn future_simple_command_with_result_snake(\n  the_argument: String,\n) -> impl std::future::Future<Output = Result<String, ()>> {\n  println!(\"{the_argument}\");\n  std::future::ready(Ok(the_argument))\n}\n\n#[command(async, rename_all = \"snake_case\")]\nfn force_async_snake(the_argument: String) -> String {\n  the_argument\n}\n\n#[command(rename_all = \"snake_case\", async)]\nfn force_async_with_result_snake(the_argument: &str) -> Result<&str, MyError> {\n  (!the_argument.is_empty())\n    .then_some(the_argument)\n    .ok_or(MyError::FooError)\n}\n\n// ------------------------ Commands returning Result ------------------------\n\n#[command]\nfn simple_command_with_result(the_argument: String) -> Result<String, MyError> {\n  println!(\"{the_argument}\");\n  (!the_argument.is_empty())\n    .then_some(the_argument)\n    .ok_or(MyError::FooError)\n}\n\n#[command]\nfn stateful_command_with_result(\n  the_argument: Option<String>,\n  state: State<'_, MyState>,\n) -> Result<String, MyError> {\n  println!(\"{:?} {:?}\", the_argument, state.inner());\n  dbg!(the_argument.ok_or(MyError::FooError))\n}\n\n// ------------------------ Commands returning Result - snake_case ------------------------\n\n#[command(rename_all = \"snake_case\")]\nfn simple_command_with_result_snake(the_argument: String) -> Result<String, MyError> {\n  println!(\"{the_argument}\");\n  (!the_argument.is_empty())\n    .then_some(the_argument)\n    .ok_or(MyError::FooError)\n}\n\n#[command(rename_all = \"snake_case\")]\nfn stateful_command_with_result_snake(\n  the_argument: Option<String>,\n  state: State<'_, MyState>,\n) -> Result<String, MyError> {\n  println!(\"{:?} {:?}\", the_argument, state.inner());\n  dbg!(the_argument.ok_or(MyError::FooError))\n}\n\n// Async commands\n\n#[command]\nasync fn async_simple_command_with_result(the_argument: String) -> Result<String, MyError> {\n  println!(\"{the_argument}\");\n  Ok(the_argument)\n}\n\n#[command]\nasync fn async_stateful_command_with_result(\n  the_argument: Option<String>,\n  state: State<'_, MyState>,\n) -> Result<String, MyError> {\n  println!(\"{:?} {:?}\", the_argument, state.inner());\n  Ok(the_argument.unwrap_or_default())\n}\n\n// Non-Ident command function arguments\n\n#[command]\nfn command_arguments_wild(_: Window) {\n  println!(\"we saw the wildcard!\")\n}\n\n#[derive(Deserialize)]\nstruct Person<'a> {\n  name: &'a str,\n  age: u8,\n}\n\n#[command]\nfn command_arguments_struct(Person { name, age }: Person<'_>) {\n  println!(\"received person struct with name: {name} | age: {age}\")\n}\n\n#[derive(Deserialize)]\nstruct InlinePerson<'a>(&'a str, u8);\n\n#[command]\nfn command_arguments_tuple_struct(InlinePerson(name, age): InlinePerson<'_>) {\n  println!(\"received person tuple with name: {name} | age: {age}\")\n}\n\n#[command]\nfn borrow_cmd(the_argument: &str) -> &str {\n  the_argument\n}\n\n#[command]\nfn borrow_cmd_async(the_argument: &str) -> &str {\n  the_argument\n}\n\n#[command]\nfn raw_request(request: Request<'_>) -> Response {\n  println!(\"{request:?}\");\n  Response::new(include_bytes!(\"./README.md\").to_vec())\n}\n\nfn main() {\n  tauri::Builder::default()\n    .manage(MyState {\n      value: 0,\n      label: \"Tauri!\".into(),\n    })\n    .invoke_handler(tauri::generate_handler![\n      borrow_cmd,\n      borrow_cmd_async,\n      raw_request,\n      window_label,\n      force_async,\n      force_async_with_result,\n      commands::simple_command,\n      commands::stateful_command,\n      cmd,\n      invoke,\n      message,\n      resolver,\n      async_simple_command,\n      future_simple_command,\n      async_stateful_command,\n      command_arguments_wild,\n      command_arguments_struct,\n      simple_command_with_result,\n      async_simple_command_snake,\n      future_simple_command_snake,\n      future_simple_command_with_return_snake,\n      future_simple_command_with_result_snake,\n      force_async_snake,\n      force_async_with_result_snake,\n      simple_command_with_result_snake,\n      stateful_command_with_result_snake,\n      stateful_command_with_result,\n      command_arguments_tuple_struct,\n      async_simple_command_with_result,\n      future_simple_command_with_return,\n      future_simple_command_with_result,\n      async_stateful_command_with_result,\n    ])\n    .run(tauri::generate_context!(\n      \"../../examples/commands/tauri.conf.json\"\n    ))\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "examples/commands/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"productName\": \"Commands\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"frontendDist\": [\"index.html\"]\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n    \"windows\": [\n      {\n        \"title\": \"Welcome to Tauri!\",\n        \"width\": 800,\n        \"height\": 600,\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src 'self'; connect-src ipc: http://ipc.localhost\"\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../.icons/32x32.png\",\n      \"../.icons/128x128.png\",\n      \"../.icons/128x128@2x.png\",\n      \"../.icons/icon.icns\",\n      \"../.icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/file-associations/README.md",
    "content": "# File Associations Example\n\nThis example demonstrates how to make associations between the application and certain file types.\n\nThis feature is commonly used for functionality such as previewing or editing files.\n\n## Running the example\n\n1. Run the following inside `examples/file-associations/src-tauri`\n\n   ```\n   cargo build --features tauri/protocol-asset\n   ```\n\n## Associations\n\nThis example creates associations with PNG, JPG, JPEG and GIF files.\n\nAdditionally, it defines two new extensions - `taurid` (derives from a raw data file) and `taurijson` (derives from JSON). They have special treatment on macOS (see `exportedType` in `src-tauri/tauri.conf.json`).\n"
  },
  {
    "path": "examples/file-associations/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>File Associations</title>\n  </head>\n\n  <body>\n    <h1>File Associations</h1>\n    <pre id=\"files\"></pre>\n\n    <script>\n      const filesView = document.getElementById('files')\n      filesView.textContent = window.openedFiles.join('\\n')\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/file-associations/package.json",
    "content": "{\n  \"name\": \"file-associations\",\n  \"version\": \"1.0.0\",\n  \"pkg\": {\n    \"assets\": [\n      \"src/**/*\"\n    ]\n  },\n  \"scripts\": {\n    \"tauri\": \"node ../../packages/cli/tauri.js\"\n  },\n  \"devDependencies\": {}\n}\n"
  },
  {
    "path": "examples/file-associations/src-tauri/.license_template",
    "content": "// Copyright {20\\d{2}(-20\\d{2})?} Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT"
  },
  {
    "path": "examples/file-associations/src-tauri/Cargo.toml",
    "content": "[package]\nname = \"tauri-file-associations-demo\"\nversion = \"0.1.0\"\ndescription = \"A Tauri application that associate file types\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\n\n[build-dependencies]\ntauri-build = { path = \"../../../crates/tauri-build\", features = [\"codegen\"] }\n\n[dependencies]\nserde_json = \"1\"\nserde = { version = \"1\", features = [\"derive\"] }\ntauri = { path = \"../../../crates/tauri\", features = [\"protocol-asset\"] }\nurl = \"2\"\n"
  },
  {
    "path": "examples/file-associations/src-tauri/Info.plist",
    "content": "<!-- Add this file next to your tauri.conf.json file -->\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n    <dict>\n        <key>CFBundleURLTypes</key>\n        <array>\n            <dict>\n                <key>CFBundleURLName</key>\n                <!-- Obviously needs to be replaced with your app's bundle identifier -->\n                <string>com.tauri.dev-file-associations-demo</string>\n                <key>CFBundleURLSchemes</key>\n                <array>\n                    <!-- register the myapp:// and myscheme:// schemes -->\n                    <string>myapp</string>\n                    <string>myscheme</string>\n                </array>\n            </dict>\n        </array>\n    </dict>\n</plist>\n"
  },
  {
    "path": "examples/file-associations/src-tauri/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nfn main() {\n  tauri_build::build()\n}\n"
  },
  {
    "path": "examples/file-associations/src-tauri/src/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(\n  all(not(debug_assertions), target_os = \"windows\"),\n  windows_subsystem = \"windows\"\n)]\n\nuse std::path::PathBuf;\nuse tauri::{AppHandle, Manager};\n\nfn handle_file_associations(app: AppHandle, files: Vec<PathBuf>) {\n  // -- Scope handling start --\n\n  // You can remove this block if you only want to know about the paths, but not actually \"use\" them in the frontend.\n\n  // This requires the `fs` tauri plugin and is required to make the plugin's frontend work:\n  // use tauri_plugin_fs::FsExt;\n  // let fs_scope = app.fs_scope();\n\n  // This is for the `asset:` protocol to work:\n  let asset_protocol_scope = app.asset_protocol_scope();\n\n  for file in &files {\n    // This requires the `fs` plugin:\n    // let _ = fs_scope.allow_file(file);\n\n    // This is for the `asset:` protocol:\n    let _ = asset_protocol_scope.allow_file(file);\n  }\n\n  // -- Scope handling end --\n\n  let files = files\n    .into_iter()\n    .map(|f| {\n      let file = f.to_string_lossy().replace('\\\\', \"\\\\\\\\\"); // escape backslash\n      format!(\"\\\"{file}\\\"\",) // wrap in quotes for JS array\n    })\n    .collect::<Vec<_>>()\n    .join(\",\");\n\n  tauri::WebviewWindowBuilder::new(&app, \"main\", Default::default())\n    .initialization_script(format!(\"window.openedFiles = [{files}]\"))\n    .build()\n    .unwrap();\n}\n\nfn main() {\n  tauri::Builder::default()\n    .setup(|#[allow(unused_variables)] app| {\n      #[cfg(any(windows, target_os = \"linux\"))]\n      {\n        let mut files = Vec::new();\n\n        // NOTICE: `args` may include URL protocol (`your-app-protocol://`)\n        // or arguments (`--`) if your app supports them.\n        // files may also be passed as `file://path/to/file`\n        for maybe_file in std::env::args().skip(1) {\n          // skip flags like -f or --flag\n          if maybe_file.starts_with('-') {\n            continue;\n          }\n\n          // handle `file://` path urls and skip other urls\n          if let Ok(url) = url::Url::parse(&maybe_file) {\n            if let Ok(path) = url.to_file_path() {\n              files.push(path);\n            }\n          } else {\n            files.push(PathBuf::from(maybe_file))\n          }\n        }\n\n        handle_file_associations(app.handle().clone(), files);\n      }\n\n      Ok(())\n    })\n    .build(tauri::generate_context!())\n    .expect(\"error while running tauri application\")\n    .run(\n      #[allow(unused_variables)]\n      |app, event| {\n        #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n        if let tauri::RunEvent::Opened { urls } = event {\n          let files = urls\n            .into_iter()\n            .filter_map(|url| url.to_file_path().ok())\n            .collect::<Vec<_>>();\n\n          handle_file_associations(app.clone(), files);\n        }\n      },\n    );\n}\n"
  },
  {
    "path": "examples/file-associations/src-tauri/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../../crates/tauri-cli/config.schema.json\",\n  \"identifier\": \"com.tauri.dev-file-associations-demo\",\n  \"build\": {\n    \"frontendDist\": [\"../index.html\"]\n  },\n  \"app\": {\n    \"security\": {\n      \"csp\": \"default-src 'self'\",\n      \"assetProtocol\": {\n        \"enable\": true\n      }\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../../.icons/32x32.png\",\n      \"../../.icons/128x128.png\",\n      \"../../.icons/128x128@2x.png\",\n      \"../../.icons/icon.icns\",\n      \"../../.icons/icon.ico\"\n    ],\n    \"fileAssociations\": [\n      {\n        \"ext\": [\"png\"],\n        \"mimeType\": \"image/png\",\n        \"rank\": \"Default\"\n      },\n      {\n        \"ext\": [\"jpg\", \"jpeg\"],\n        \"mimeType\": \"image/jpeg\",\n        \"rank\": \"Alternate\"\n      },\n      {\n        \"ext\": [\"gif\"],\n        \"mimeType\": \"image/gif\",\n        \"rank\": \"Owner\"\n      },\n      {\n        \"ext\": [\"taurijson\"],\n        \"exportedType\": {\n          \"identifier\": \"com.tauri.dev-file-associations-demo.taurijson\",\n          \"conformsTo\": [\"public.json\"]\n        }\n      },\n      {\n        \"ext\": [\"taurid\"],\n        \"exportedType\": {\n          \"identifier\": \"com.tauri.dev-file-associations-demo.tauridata\",\n          \"conformsTo\": [\"public.data\"]\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/helloworld/README.md",
    "content": "# Hello World Example\n\nTo execute run the following on the root directory of the repository: `cargo run --example helloworld`.\n"
  },
  {
    "path": "examples/helloworld/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Welcome to Tauri!</title>\n  </head>\n  <body>\n    <h1>Welcome to Tauri!</h1>\n\n    <form id=\"form\">\n      <input id=\"name\" placeholder=\"Enter a name...\" />\n      <button>Greet</button>\n    </form>\n\n    <p id=\"message\"></p>\n\n    <script>\n      const { invoke } = window.__TAURI__.core\n\n      const form = document.querySelector('#form')\n      const nameEl = document.querySelector('#name')\n      const messageEl = document.querySelector('#message')\n\n      form.addEventListener('submit', async (e) => {\n        e.preventDefault()\n\n        const name = nameEl.value\n        const newMessage = await invoke('greet', { name })\n        messageEl.textContent = newMessage\n      })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/helloworld/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\n#[tauri::command]\nfn greet(name: &str) -> String {\n  format!(\"Hello {name}, You have been greeted from Rust!\")\n}\n\nfn main() {\n  tauri::Builder::default()\n    .invoke_handler(tauri::generate_handler![greet])\n    .run(tauri::generate_context!(\n      \"../../examples/helloworld/tauri.conf.json\"\n    ))\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "examples/helloworld/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"productName\": \"Hello World\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"frontendDist\": [\"index.html\"]\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n    \"windows\": [\n      {\n        \"title\": \"Welcome to Tauri!\",\n        \"width\": 800,\n        \"height\": 600,\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src 'self'; connect-src ipc: http://ipc.localhost\"\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../.icons/32x32.png\",\n      \"../.icons/128x128.png\",\n      \"../.icons/128x128@2x.png\",\n      \"../.icons/icon.icns\",\n      \"../.icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/isolation/README.md",
    "content": "# Isolation Example\n\nTo execute run the following on the root directory of the repository: `cargo run --example isolation --features isolation`.\n"
  },
  {
    "path": "examples/isolation/dist/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-US\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Hello Tauri!</title>\n</head>\n\n<body>\n  <div>\n    <h1>Hello, Tauri!</h1>\n    <div>\n      <button id=\"ping\">ping</button>\n      <span id=\"pong\"></span>\n    </div>\n  </div>\n\n  <script>\n    const { invoke } = window.__TAURI__.core\n\n    const ping = document.querySelector(\"#ping\")\n    const pong = document.querySelector('#pong')\n\n    ping.addEventListener(\"click\", () => {\n      invoke(\"ping\")\n        .then(() => {\n          pong.innerText = `ok: ${Date.now()}`\n        })\n        .catch(() => {\n          pong.innerText = `error: ${Date.now()}`\n        })\n    })\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/isolation/dist/linked.js",
    "content": "console.log(\"linked\", window.__TAURI_INTERNALS__.__TAURI_PATTERN__);\n"
  },
  {
    "path": "examples/isolation/isolation-dist/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Isolation Secure Script</title>\n  </head>\n  <body>\n    <script src=\"index.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/isolation/isolation-dist/index.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\n  console.log('hook', payload, options)\n  return payload\n}\n"
  },
  {
    "path": "examples/isolation/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\n#[tauri::command]\nfn ping() {\n  println!(\"ping: {:?}\", std::time::Instant::now());\n}\n\nfn main() {\n  tauri::Builder::default()\n    .invoke_handler(tauri::generate_handler![ping])\n    .run(tauri::generate_context!(\n      \"../../examples/isolation/tauri.conf.json\"\n    ))\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "examples/isolation/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"productName\": \"Isolation\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"frontendDist\": \"dist\",\n    \"beforeDevCommand\": \"\",\n    \"beforeBuildCommand\": \"\"\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n    \"windows\": [\n      {\n        \"title\": \"Isolation\",\n        \"width\": 800,\n        \"height\": 600,\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'; connect-src ipc: http://ipc.localhost\",\n      \"pattern\": {\n        \"use\": \"isolation\",\n        \"options\": {\n          \"dir\": \"isolation-dist\"\n        }\n      }\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../.icons/32x32.png\",\n      \"../.icons/128x128.png\",\n      \"../.icons/128x128@2x.png\",\n      \"../.icons/icon.icns\",\n      \"../.icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/multiwebview/README.md",
    "content": "# Hello World Example\n\nTo execute run the following on the root directory of the repository: `cargo run --example multiwebview --features unstable`.\n"
  },
  {
    "path": "examples/multiwebview/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Welcome to Tauri!</title>\n  </head>\n  <body>\n    <h1>Welcome to Tauri!</h1>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/multiwebview/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nuse tauri::{LogicalPosition, LogicalSize, WebviewUrl};\n\nfn main() {\n  tauri::Builder::default()\n    .setup(|app| {\n      let width = 800.;\n      let height = 600.;\n\n      let window = tauri::window::WindowBuilder::new(app, \"main\")\n        .inner_size(width, height)\n        .build()?;\n\n      let _webview1 = window.add_child(\n        tauri::webview::WebviewBuilder::new(\"main1\", WebviewUrl::App(Default::default()))\n          .auto_resize(),\n        LogicalPosition::new(0., 0.),\n        LogicalSize::new(width / 2., height / 2.),\n      )?;\n\n      let _webview2 = window.add_child(\n        tauri::webview::WebviewBuilder::new(\n          \"main2\",\n          WebviewUrl::External(\"https://github.com/tauri-apps/tauri\".parse().unwrap()),\n        )\n        .auto_resize(),\n        LogicalPosition::new(width / 2., 0.),\n        LogicalSize::new(width / 2., height / 2.),\n      )?;\n\n      let _webview3 = window.add_child(\n        tauri::webview::WebviewBuilder::new(\n          \"main3\",\n          WebviewUrl::External(\"https://tauri.app\".parse().unwrap()),\n        )\n        .auto_resize(),\n        LogicalPosition::new(0., height / 2.),\n        LogicalSize::new(width / 2., height / 2.),\n      )?;\n\n      let _webview4 = window.add_child(\n        tauri::webview::WebviewBuilder::new(\n          \"main4\",\n          WebviewUrl::External(\"https://twitter.com/TauriApps\".parse().unwrap()),\n        )\n        .auto_resize(),\n        LogicalPosition::new(width / 2., height / 2.),\n        LogicalSize::new(width / 2., height / 2.),\n      )?;\n\n      Ok(())\n    })\n    .run(tauri::generate_context!(\n      \"../../examples/multiwebview/tauri.conf.json\"\n    ))\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "examples/multiwebview/tauri.conf.json",
    "content": "{\n  \"productName\": \"MultiWebview\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.dev\",\n  \"$schema\": \"../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"build\": {\n    \"frontendDist\": [\"index.html\"]\n  },\n  \"app\": {\n    \"security\": {\n      \"csp\": \"default-src 'self'; connect-src ipc: http://ipc.localhost\"\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../.icons/32x32.png\",\n      \"../.icons/128x128.png\",\n      \"../.icons/128x128@2x.png\",\n      \"../.icons/icon.icns\",\n      \"../.icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/multiwindow/README.md",
    "content": "# Multi-Window Example\n\nAn example Tauri Multi-Window Application.\n\nTo execute run the following on the root directory of the repository: `cargo run --example multiwindow`.\n"
  },
  {
    "path": "examples/multiwindow/index.html",
    "content": "<!doctype html>\n<html>\n  <h1>Send a message to a window using its label:</h1>\n  <form id=\"send-message-form\">\n    <input id=\"send-message\" placeholder=\"message\" />\n    <input id=\"send-label\" placeholder=\"Secondary\" />\n    <button type=\"submit\">Send</button>\n  </form>\n\n  <br />\n\n  <h1>Create new window</h1>\n  <form id=\"new-window-form\">\n    <input id=\"new-label\" placeholder=\"newLabel\" />\n    <input id=\"new-title\" placeholder=\"New window\" />\n    <button type=\"submit\">Create</button>\n  </form>\n\n  <br />\n\n  <h1>Messages received from other windows:</h1>\n  <pre id=\"messages-view\"></pre>\n\n  <body>\n    <script>\n      const { WebviewWindow } = window.__TAURI__.webviewWindow\n      const { getCurrentWebviewWindow } = window.__TAURI__.webviewWindow\n      const { emitTo } = window.__TAURI__.event\n\n      const sendMessageForm = document.querySelector('#send-message-form')\n      const sendMessageEl = document.querySelector('#send-message')\n      const sendLabelEl = document.querySelector('#send-label')\n      sendMessageForm.addEventListener('submit', (e) => {\n        e.preventDefault()\n        console.log(sendLabelEl.value)\n        console.log(sendMessageEl.value)\n\n        emitTo(sendLabelEl.value, 'message', sendMessageEl.value)\n      })\n\n      const newWindowForm = document.querySelector('#new-window-form')\n      const newLabelEl = document.querySelector('#new-label')\n      const newTitleEl = document.querySelector('#new-title')\n      newWindowForm.addEventListener('submit', (e) => {\n        e.preventDefault()\n\n        new WebviewWindow(newLabelEl.value, {\n          title: newTitleEl.value\n        })\n      })\n\n      const currentWindow = getCurrentWebviewWindow()\n      const messagesView = document.querySelector('#messages-view')\n      window.addEventListener('DOMContentLoaded', () => {\n        currentWindow.listen('message', (event) => {\n          const time = new Date().toLocaleTimeString()\n          messagesView.textContent = `${messagesView.textContent}\\n[${time}] ${event.payload}`\n        })\n      })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/multiwindow/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nuse tauri::WebviewWindowBuilder;\n\nfn main() {\n  tauri::Builder::default()\n    .setup(|app| {\n      WebviewWindowBuilder::new(app, \"Third\", tauri::WebviewUrl::default())\n        .title(\"Tauri - Third\")\n        .build()?;\n\n      Ok(())\n    })\n    .run(generate_context())\n    .expect(\"failed to run tauri application\");\n}\n\nfn generate_context() -> tauri::Context {\n  let mut context = tauri::generate_context!(\"../../examples/multiwindow/tauri.conf.json\");\n  for cmd in [\n    \"plugin:event|listen\",\n    \"plugin:event|emit\",\n    \"plugin:event|emit_to\",\n    \"plugin:webview|create_webview_window\",\n  ] {\n    context\n      .runtime_authority_mut()\n      .__allow_command(cmd.to_string(), tauri_utils::acl::ExecutionContext::Local);\n  }\n  context\n}\n"
  },
  {
    "path": "examples/multiwindow/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"productName\": \"Multi Window\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"frontendDist\": [\"index.html\"]\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n    \"windows\": [\n      {\n        \"label\": \"Main\",\n        \"title\": \"Tauri - Main\",\n        \"tabbingIdentifier\": \"Main\",\n        \"width\": 800,\n        \"height\": 600\n      },\n      {\n        \"label\": \"Secondary\",\n        \"title\": \"Tauri - Secondary\",\n        \"tabbingIdentifier\": \"Secondary\",\n        \"width\": 600,\n        \"height\": 400\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src 'self'; connect-src ipc: http://ipc.localhost\"\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../.icons/32x32.png\",\n      \"../.icons/128x128.png\",\n      \"../.icons/128x128@2x.png\",\n      \"../.icons/icon.icns\",\n      \"../.icons/icon.ico\"\n    ],\n    \"resources\": [],\n    \"externalBin\": [],\n    \"copyright\": \"\",\n    \"category\": \"DeveloperTool\"\n  }\n}\n"
  },
  {
    "path": "examples/resources/README.md",
    "content": "# Resource example\n\nThis example demonstrates the Tauri bundle resources functionality. The example adds `src-tauri/assets/index.js` as a resource (defined on `tauri.conf.json > bundle > resources`) and executes it using `Node.js`, locating the JavaScript file using the `tauri::App::path_resolver` APIs.\n\n## Running the example\n\n- Compile Tauri\n  go to root of the Tauri repo and run:\n  Linux / Mac:\n\n```\n# choose to install node cli (1)\nbash .scripts/setup.sh\n```\n\nWindows:\n\n```\n./.scripts/setup.ps1\n```\n\n- Install dependencies (Run inside of this folder `examples/resources/`)\n\n```bash\n$ pnpm i\n```\n\n- Run the app in development mode (Run inside of this folder `examples/resources/`)\n\n```bash\n$ pnpm tauri dev\n```\n\n- Build an run the release app (Run inside of this folder `examples/resources/`)\n\n```bash\n$ pnpm tauri build\n$ ./src-tauri/target/release/app\n```\n"
  },
  {
    "path": "examples/resources/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  </head>\n\n  <body>\n    <div>\n      <strong>Resource `assets/index.js` path:</strong>\n      <span id=\"path\"></span>\n    </div>\n    <strong>Resource `assets/index.js` content:</strong>\n    <pre id=\"content\"></pre>\n\n    <script>\n      const { invoke } = window.__TAURI__.core\n      const { resolveResource } = window.__TAURI__.path\n\n      const pathEl = document.querySelector('#path')\n      const contentEl = document.querySelector('#content')\n\n      window.addEventListener('DOMContentLoaded', async () => {\n        const path = await resolveResource('assets/index.js')\n        pathEl.textContent = path\n\n        const content = await invoke('read_to_string', { path })\n        contentEl.textContent = content\n      })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/resources/package.json",
    "content": "{\n  \"name\": \"resources\",\n  \"version\": \"0.1.0\",\n  \"scripts\": {\n    \"tauri\": \"node ../../packages/cli/tauri.js\"\n  }\n}\n"
  },
  {
    "path": "examples/resources/src-tauri/Cargo.toml",
    "content": "[package]\nname = \"resources\"\nversion = \"0.1.0\"\ndescription = \"A Tauri application that uses Node.js with app resources\"\nedition = \"2021\"\nrust-version = \"1.77.2\"\n\n[build-dependencies]\ntauri-build = { path = \"../../../crates/tauri-build\", features = [\"codegen\"] }\n\n[dependencies]\nserde_json = \"1\"\nserde = { version = \"1\", features = [\"derive\"] }\ntauri = { path = \"../../../crates/tauri\", features = [] }\n"
  },
  {
    "path": "examples/resources/src-tauri/assets/index.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nconsole.log('hello world')\n"
  },
  {
    "path": "examples/resources/src-tauri/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nfn main() {\n  tauri_build::build()\n}\n"
  },
  {
    "path": "examples/resources/src-tauri/capabilities/app.json",
    "content": "{\n  \"$schema\": \"../gen/schemas/desktop-schema.json\",\n  \"identifier\": \"app\",\n  \"permissions\": [\"core:path:default\"],\n  \"windows\": [\"main\"]\n}\n"
  },
  {
    "path": "examples/resources/src-tauri/src/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nuse tauri::Manager;\n\n#[tauri::command]\nfn read_to_string(path: &str) -> String {\n  std::fs::read_to_string(path).unwrap_or_default()\n}\n\nfn main() {\n  tauri::Builder::default()\n    .setup(move |app| {\n      let path = app\n        .path()\n        .resolve(\"assets/index.js\", tauri::path::BaseDirectory::Resource)\n        .unwrap();\n\n      let content = std::fs::read_to_string(&path).unwrap();\n\n      println!(\"Resource `assets/index.js` path: {}\", path.display());\n      println!(\"Resource `assets/index.js` content:\\n{content}\\n\");\n\n      Ok(())\n    })\n    .invoke_handler(tauri::generate_handler![read_to_string])\n    .run(tauri::generate_context!())\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "examples/resources/src-tauri/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"productName\": \"Resources\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.resources\",\n  \"build\": {\n    \"frontendDist\": [\"../index.html\"]\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n\n    \"windows\": [\n      {\n        \"title\": \"Welcome to Tauri!\",\n        \"width\": 800,\n        \"height\": 600,\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src 'self'; connect-src ipc: http://ipc.localhost\"\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../../.icons/32x32.png\",\n      \"../../.icons/128x128.png\",\n      \"../../.icons/128x128@2x.png\",\n      \"../../.icons/icon.icns\",\n      \"../../.icons/icon.ico\"\n    ],\n    \"resources\": [\"assets/*\"]\n  }\n}\n"
  },
  {
    "path": "examples/run-return/README.md",
    "content": "# Run Return Example\n\nTo execute run the following on the root directory of the repository: `cargo run --example run-return`.\n"
  },
  {
    "path": "examples/run-return/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Welcome to Tauri!</title>\n  </head>\n  <body>\n    <h1>Welcome to Tauri!</h1>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/run-return/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nfn main() {\n  let app = tauri::Builder::default()\n    .build(tauri::generate_context!(\n      \"../../examples/run-return/tauri.conf.json\"\n    ))\n    .expect(\"error while building tauri application\");\n\n  let exit_code = app.run_return(|_app, _event| {\n    //println!(\"{:?}\", _event);\n  });\n\n  println!(\"I run after exit\");\n\n  std::process::exit(exit_code);\n}\n"
  },
  {
    "path": "examples/run-return/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"productName\": \"RunReturn\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"frontendDist\": [\"index.html\"]\n  },\n  \"app\": {\n    \"windows\": [\n      {\n        \"title\": \"Welcome to Tauri!\",\n        \"width\": 800,\n        \"height\": 600,\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src 'self'; connect-src ipc: http://ipc.localhost\"\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../.icons/32x32.png\",\n      \"../.icons/128x128.png\",\n      \"../.icons/128x128@2x.png\",\n      \"../.icons/icon.icns\",\n      \"../.icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/splashscreen/README.md",
    "content": "# Splashscreen example\n\nThis example demonstrates how a splashscreen can be implemented when waiting on an initialization code on Rust or on the UI.\n\n## Running the example\n\nRun the following scripts on the root directory of the repository:\n\n```bash\n$ cargo run --example splashscreen\n```\n"
  },
  {
    "path": "examples/splashscreen/dist/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <body>\n    <h1>This is the main window!</h1>\n\n    <script>\n      const { invoke } = window.__TAURI__.core;\n      document.addEventListener('DOMContentLoaded', () => {\n        // we delay here just so we can see the splashscreen for a while\n        setTimeout(() => invoke('close_splashscreen'), 2000)\n      })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/splashscreen/dist/splashscreen.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Splashscreen</title>\n    <style>\n      body {\n        margin: 0;\n      }\n\n      .splashscreen-image {\n        background-color: #444;\n        background-image: url('icon.png');\n        background-position: center;\n        background-repeat: no-repeat;\n        background-size: 50%;\n        width: 100vw !important;\n        height: 100vh !important;\n        overflow: hidden;\n      }\n    </style>\n  </head>\n\n  <body>\n    <div class=\"splashscreen-image\" />\n  </body>\n</html>\n"
  },
  {
    "path": "examples/splashscreen/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nuse tauri::{AppHandle, Manager};\n\n#[tauri::command]\nfn close_splashscreen(app: AppHandle) {\n  // Close splashscreen\n  app\n    .get_webview_window(\"splashscreen\")\n    .unwrap()\n    .close()\n    .unwrap();\n  // Show main window\n  app.get_webview_window(\"main\").unwrap().show().unwrap();\n}\n\nfn main() {\n  tauri::Builder::default()\n    .menu(tauri::menu::Menu::default)\n    .invoke_handler(tauri::generate_handler![close_splashscreen])\n    .run(tauri::generate_context!(\n      \"../../examples/splashscreen/tauri.conf.json\"\n    ))\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "examples/splashscreen/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"productName\": \"Splashscreen\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"frontendDist\": \"dist\"\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n    \"windows\": [\n      {\n        \"label\": \"main\",\n        \"title\": \"Tauri\",\n        \"width\": 800,\n        \"height\": 600,\n        \"visible\": false\n      },\n      {\n        \"label\": \"splashscreen\",\n        \"width\": 400,\n        \"height\": 200,\n        \"decorations\": false,\n        \"resizable\": false,\n        \"url\": \"splashscreen.html\"\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src 'self'; connect-src ipc: http://ipc.localhost\"\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../.icons/32x32.png\",\n      \"../.icons/128x128.png\",\n      \"../.icons/128x128@2x.png\",\n      \"../.icons/icon.icns\",\n      \"../.icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/state/README.md",
    "content": "# State example\n\nA simple Tauri Application showcase the application State usage.\n\nTo execute run the following on the root directory of the repository: `cargo run --example state`\n"
  },
  {
    "path": "examples/state/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Tauri</title>\n  </head>\n\n  <body>\n    <h3>Counter: <span id=\"counter\"></span></h3>\n    <div>\n      <button id=\"increment-btn\">Increment</button>\n      <button id=\"decrement-btn\">Decrement</button>\n      <button id=\"reset-btn\">Reset</button>\n    </div>\n\n    <p>Press Ctrl+R to reload and see the state persist.</p>\n\n    <script>\n      const { invoke } = window.__TAURI__.core\n\n      const incrementBtn = document.querySelector('#increment-btn')\n      const decrementBtn = document.querySelector('#decrement-btn')\n      const resetBtn = document.querySelector('#reset-btn')\n      const counterContainer = document.querySelector('#counter')\n\n      document.addEventListener('DOMContentLoaded', async () => {\n        let currentCount = await invoke('get')\n        counterContainer.innerText = currentCount\n        console.log('loaded')\n      })\n\n      incrementBtn.addEventListener('click', async () => {\n        let newCount = await invoke('increment')\n        counterContainer.innerText = newCount\n      })\n\n      decrementBtn.addEventListener('click', async () => {\n        let newCount = await invoke('decrement')\n        counterContainer.innerText = newCount\n      })\n\n      resetBtn.addEventListener('click', async () => {\n        let newCount = await invoke('reset')\n        counterContainer.innerText = newCount\n      })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/state/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nuse std::sync::Mutex;\n\nuse tauri::State;\n\nstruct Counter(Mutex<isize>);\n\n#[tauri::command]\nfn increment(counter: State<'_, Counter>) -> isize {\n  let mut c = counter.0.lock().unwrap();\n  *c += 1;\n  *c\n}\n\n#[tauri::command]\nfn decrement(counter: State<'_, Counter>) -> isize {\n  let mut c = counter.0.lock().unwrap();\n  *c -= 1;\n  *c\n}\n\n#[tauri::command]\nfn reset(counter: State<'_, Counter>) -> isize {\n  let mut c = counter.0.lock().unwrap();\n  *c = 0;\n  *c\n}\n\n#[tauri::command]\nfn get(counter: State<'_, Counter>) -> isize {\n  *counter.0.lock().unwrap()\n}\n\nfn main() {\n  tauri::Builder::default()\n    .manage(Counter(Mutex::new(0)))\n    .invoke_handler(tauri::generate_handler![increment, decrement, reset, get])\n    .run(tauri::generate_context!(\n      \"../../examples/state/tauri.conf.json\"\n    ))\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "examples/state/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"productName\": \"State\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"frontendDist\": [\"index.html\"]\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n    \"windows\": [\n      {\n        \"title\": \"Welcome to Tauri!\",\n        \"width\": 800,\n        \"height\": 600,\n        \"resizable\": true,\n        \"fullscreen\": false\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src 'self'; connect-src ipc: http://ipc.localhost\"\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../.icons/32x32.png\",\n      \"../.icons/128x128.png\",\n      \"../.icons/128x128@2x.png\",\n      \"../.icons/icon.icns\",\n      \"../.icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/streaming/README.md",
    "content": "# Streaming example\n\nA simple Tauri Application showcase how to stream video through custom protocol but can be adapted to stream any type of files.\n\nTo execute run the following on the root directory of the repository: `cargo run --example streaming`.\n\n### Note\n\nTauri has a built-in `asset` protocol that implements this streaming functionality so you don't need to. This example just exists as a reference.\n"
  },
  {
    "path": "examples/streaming/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <style>\n      body {\n        margin: unset;\n        overflow: hidden;\n      }\n      video {\n        width: 100vw;\n        height: 100vh;\n      }\n    </style>\n  </head>\n\n  <body>\n    <video id=\"video_source\" controls=\"\" autoplay=\"\" name=\"media\">\n      <source type=\"video/mp4\" />\n    </video>\n\n    <script>\n      const { invoke, convertFileSrc } = window.__TAURI__.core\n      const video = document.getElementById('video_source')\n      const source = document.createElement('source')\n      source.type = 'video/mp4'\n      source.src = convertFileSrc('streaming_example_test_video.mp4', 'stream')\n      video.appendChild(source)\n      video.load()\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/streaming/main.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nuse http::{header::*, response::Builder as ResponseBuilder, status::StatusCode};\nuse http_range::HttpRange;\nuse std::{\n  io::{Read, Seek, SeekFrom, Write},\n  path::PathBuf,\n  process::{Command, Stdio},\n};\n\nfn get_stream_response(\n  request: http::Request<Vec<u8>>,\n) -> Result<http::Response<Vec<u8>>, Box<dyn std::error::Error>> {\n  // skip leading `/`\n  let path = percent_encoding::percent_decode(&request.uri().path().as_bytes()[1..])\n    .decode_utf8_lossy()\n    .to_string();\n\n  // return error 404 if it's not our video\n  if path != \"streaming_example_test_video.mp4\" {\n    return Ok(ResponseBuilder::new().status(404).body(Vec::new())?);\n  }\n\n  let mut file = std::fs::File::open(&path)?;\n\n  // get file length\n  let len = {\n    let old_pos = file.stream_position()?;\n    let len = file.seek(SeekFrom::End(0))?;\n    file.seek(SeekFrom::Start(old_pos))?;\n    len\n  };\n\n  let mut resp = ResponseBuilder::new().header(CONTENT_TYPE, \"video/mp4\");\n\n  // if the webview sent a range header, we need to send a 206 in return\n  let http_response = if let Some(range_header) = request.headers().get(\"range\") {\n    let not_satisfiable = || {\n      ResponseBuilder::new()\n        .status(StatusCode::RANGE_NOT_SATISFIABLE)\n        .header(CONTENT_RANGE, format!(\"bytes */{len}\"))\n        .body(vec![])\n    };\n\n    // parse range header\n    let ranges = if let Ok(ranges) = HttpRange::parse(range_header.to_str()?, len) {\n      ranges\n        .iter()\n        // map the output back to spec range <start-end>, example: 0-499\n        .map(|r| (r.start, r.start + r.length - 1))\n        .collect::<Vec<_>>()\n    } else {\n      return Ok(not_satisfiable()?);\n    };\n\n    /// The Maximum bytes we send in one range\n    const MAX_LEN: u64 = 1000 * 1024;\n\n    if ranges.len() == 1 {\n      let &(start, mut end) = ranges.first().unwrap();\n\n      // check if a range is not satisfiable\n      //\n      // this should be already taken care of by HttpRange::parse\n      // but checking here again for extra assurance\n      if start >= len || end >= len || end < start {\n        return Ok(not_satisfiable()?);\n      }\n\n      // adjust end byte for MAX_LEN\n      end = start + (end - start).min(len - start).min(MAX_LEN - 1);\n\n      // calculate number of bytes needed to be read\n      let bytes_to_read = end + 1 - start;\n\n      // allocate a buf with a suitable capacity\n      let mut buf = Vec::with_capacity(bytes_to_read as usize);\n      // seek the file to the starting byte\n      file.seek(SeekFrom::Start(start))?;\n      // read the needed bytes\n      file.take(bytes_to_read).read_to_end(&mut buf)?;\n\n      resp = resp.header(CONTENT_RANGE, format!(\"bytes {start}-{end}/{len}\"));\n      resp = resp.header(CONTENT_LENGTH, end + 1 - start);\n      resp = resp.status(StatusCode::PARTIAL_CONTENT);\n      resp.body(buf)\n    } else {\n      let mut buf = Vec::new();\n      let ranges = ranges\n        .iter()\n        .filter_map(|&(start, mut end)| {\n          // filter out unsatisfiable ranges\n          //\n          // this should be already taken care of by HttpRange::parse\n          // but checking here again for extra assurance\n          if start >= len || end >= len || end < start {\n            None\n          } else {\n            // adjust end byte for MAX_LEN\n            end = start + (end - start).min(len - start).min(MAX_LEN - 1);\n            Some((start, end))\n          }\n        })\n        .collect::<Vec<_>>();\n\n      let boundary = random_boundary();\n      let boundary_sep = format!(\"\\r\\n--{boundary}\\r\\n\");\n      let boundary_closer = format!(\"\\r\\n--{boundary}\\r\\n\");\n\n      resp = resp.header(\n        CONTENT_TYPE,\n        format!(\"multipart/byteranges; boundary={boundary}\"),\n      );\n\n      for (start, end) in ranges {\n        // a new range is being written, write the range boundary\n        buf.write_all(boundary_sep.as_bytes())?;\n\n        // write the needed headers `Content-Type` and `Content-Range`\n        buf.write_all(format!(\"{CONTENT_TYPE}: video/mp4\\r\\n\").as_bytes())?;\n        buf.write_all(format!(\"{CONTENT_RANGE}: bytes {start}-{end}/{len}\\r\\n\").as_bytes())?;\n\n        // write the separator to indicate the start of the range body\n        buf.write_all(\"\\r\\n\".as_bytes())?;\n\n        // calculate number of bytes needed to be read\n        let bytes_to_read = end + 1 - start;\n\n        let mut local_buf = vec![0_u8; bytes_to_read as usize];\n        file.seek(SeekFrom::Start(start))?;\n        file.read_exact(&mut local_buf)?;\n        buf.extend_from_slice(&local_buf);\n      }\n      // all ranges have been written, write the closing boundary\n      buf.write_all(boundary_closer.as_bytes())?;\n\n      resp.body(buf)\n    }\n  } else {\n    resp = resp.header(CONTENT_LENGTH, len);\n    let mut buf = Vec::with_capacity(len as usize);\n    file.read_to_end(&mut buf)?;\n    resp.body(buf)\n  };\n\n  http_response.map_err(Into::into)\n}\n\nfn random_boundary() -> String {\n  let mut x = [0_u8; 30];\n  getrandom::fill(&mut x).expect(\"failed to get random bytes\");\n  (x[..])\n    .iter()\n    .map(|&x| format!(\"{x:x}\"))\n    .fold(String::new(), |mut a, x| {\n      a.push_str(x.as_str());\n      a\n    })\n}\n\nfn download_video() {\n  let video_file = PathBuf::from(\"streaming_example_test_video.mp4\");\n  if !video_file.exists() {\n    let video_url =\n      \"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\";\n\n    // Downloading with curl this saves us from adding\n    // a Rust HTTP client dependency.\n    println!(\"Downloading {video_url}\");\n    let status = Command::new(\"curl\")\n      .arg(\"-L\")\n      .arg(\"-o\")\n      .arg(&video_file)\n      .arg(video_url)\n      .stdout(Stdio::inherit())\n      .stderr(Stdio::inherit())\n      .output()\n      .unwrap();\n\n    assert!(status.status.success());\n    assert!(video_file.exists());\n  }\n}\n\nfn main() {\n  download_video();\n\n  tauri::Builder::default()\n    .register_asynchronous_uri_scheme_protocol(\"stream\", move |_ctx, request, responder| {\n      match get_stream_response(request) {\n        Ok(http_response) => responder.respond(http_response),\n        Err(e) => responder.respond(\n          ResponseBuilder::new()\n            .status(StatusCode::INTERNAL_SERVER_ERROR)\n            .header(CONTENT_TYPE, \"text/plain\")\n            .body(e.to_string().as_bytes().to_vec())\n            .unwrap(),\n        ),\n      }\n    })\n    .run(tauri::generate_context!(\n      \"../../examples/streaming/tauri.conf.json\"\n    ))\n    .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "examples/streaming/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../crates/tauri-schema-generator/schemas/config.schema.json\",\n  \"productName\": \"Streaming\",\n  \"version\": \"0.1.0\",\n  \"identifier\": \"com.tauri.dev\",\n  \"build\": {\n    \"frontendDist\": [\"index.html\"]\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n    \"windows\": [\n      {\n        \"title\": \"Stream video using custom protocol\",\n        \"width\": 800,\n        \"height\": 600\n      }\n    ],\n    \"security\": {\n      \"csp\": \"default-src 'self'; connect-src ipc: http://ipc.localhost; media-src stream: http://stream.localhost\",\n      \"assetProtocol\": {\n        \"scope\": [\"**/streaming_example_test_video.mp4\"]\n      }\n    }\n  },\n  \"bundle\": {\n    \"active\": true,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"../.icons/32x32.png\",\n      \"../.icons/128x128.png\",\n      \"../.icons/128x128@2x.png\",\n      \"../.icons/icon.icns\",\n      \"../.icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"tauri-workspace\",\n  \"version\": \"0.0.0\",\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"private\": true,\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/tauri-apps/tauri.git\"\n  },\n  \"scripts\": {\n    \"format\": \"prettier --write .\",\n    \"format:check\": \"prettier --check .\",\n    \"eslint:check\": \"pnpm run -r eslint:check\",\n    \"ts:check\": \"pnpm run -r ts:check\",\n    \"build\": \"pnpm run -F !api build && pnpm run -F api build\",\n    \"build:debug\": \"pnpm run -F !api build:debug && pnpm run -F api build:debug\",\n    \"build:api\": \"pnpm run --filter \\\"@tauri-apps/api\\\" build\",\n    \"build:api:debug\": \"pnpm run --filter \\\"@tauri-apps/api\\\" build:debug\",\n    \"build:cli\": \"pnpm run --filter \\\"@tauri-apps/cli\\\" build\",\n    \"build:cli:debug\": \"pnpm run --filter \\\"@tauri-apps/cli\\\" build:debug\",\n    \"test\": \"pnpm run -r test\",\n    \"example:api:dev\": \"pnpm run --filter \\\"api\\\" tauri dev\"\n  },\n  \"devDependencies\": {\n    \"prettier\": \"^3.8.1\"\n  },\n  \"minimumReleaseAge\": 4320,\n  \"packageManager\": \"pnpm@10.30.3\"\n}\n"
  },
  {
    "path": "packages/api/.gitignore",
    "content": "# Build output\n/dist/\n"
  },
  {
    "path": "packages/api/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.10.1]\n\n### Bug Fixes\n\n- Re-release of `2.10.0` with working assets.\n\n## \\[2.10.0]\n\n### Dependencies\n\n- [`517b81e97`](https://www.github.com/tauri-apps/tauri/commit/517b81e97005d087ca5fc4538d954982fce0f4ac) ([#14876](https://www.github.com/tauri-apps/tauri/pull/14876)) Upgraded to `tauri@2.10`\n\n## \\[2.9.1]\n\n### Bug Fixes\n\n- [`ad1dec2e2`](https://www.github.com/tauri-apps/tauri/commit/ad1dec2e2488fe5c0a004b69f1bd290dfc593bf8) ([#14464](https://www.github.com/tauri-apps/tauri/pull/14464) by [@funnydino](https://www.github.com/tauri-apps/tauri/../../funnydino)) Fix `addPluginListener` fallback added in https://github.com/tauri-apps/tauri/pull/14132 didn't work properly\n\n## \\[2.9.0]\n\n### New Features\n\n- [`f5851ee00`](https://www.github.com/tauri-apps/tauri/commit/f5851ee00d6d1f4d560a220ca5a728fedd525092) ([#14089](https://www.github.com/tauri-apps/tauri/pull/14089)) Adds the `scrollBarStyle` option to the Webview and WebviewBuilder constructors.\n- [`3397fd9bf`](https://www.github.com/tauri-apps/tauri/commit/3397fd9bfe5f6b1337110149f6c34731b8a44bb3) ([#14133](https://www.github.com/tauri-apps/tauri/pull/14133)) Added `app > onBackButtonPress` for Android back button handling.\n\n### Enhancements\n\n- [`59089723f`](https://www.github.com/tauri-apps/tauri/commit/59089723fc20d66f3f305f2008adeb279bf87462) ([#14091](https://www.github.com/tauri-apps/tauri/pull/14091)) Added a config to set a data_directory relative to the app-specific data dir in JavaScript and `tauri.conf.json`.\n\n### Bug Fixes\n\n- [`08bda64c2`](https://www.github.com/tauri-apps/tauri/commit/08bda64c25008bd45c5b58d06ff14649081a2f5d) ([#14132](https://www.github.com/tauri-apps/tauri/pull/14132)) Fix `core > addPluginListener` failing on command permission check.\n\n## \\[2.8.0]\n\n### New Features\n\n- [`68874c68c`](https://www.github.com/tauri-apps/tauri/commit/68874c68c566638b4c21a3aa67844d1bdaeb6dab) ([#13564](https://www.github.com/tauri-apps/tauri/pull/13564) by [@robertrpf](https://www.github.com/tauri-apps/tauri/../../robertrpf)) Add window focusable attribute and set_focusable API.\n- [`5110a762e`](https://www.github.com/tauri-apps/tauri/commit/5110a762e9db978a28a15400bf76e3c864da2a86) ([#13830](https://www.github.com/tauri-apps/tauri/pull/13830) by [@Sky-walkerX](https://www.github.com/tauri-apps/tauri/../../Sky-walkerX)) Added `Window::setSimpleFullscreen`.\n\n### Enhancements\n\n- [`5ba1c3faa`](https://www.github.com/tauri-apps/tauri/commit/5ba1c3faa468073512bdb5035a01f7f99720fcf0) ([#13722](https://www.github.com/tauri-apps/tauri/pull/13722) by [@s00d](https://www.github.com/tauri-apps/tauri/../../s00d)) Added icon (icon and nativeIcon) support for Submenu:\n\n  - In the Rust API (`tauri`), you can now set an icon for submenus via the builder and dedicated methods.\n  - In the JS/TS API (`@tauri-apps/api`), `SubmenuOptions` now has an `icon` field, and the `Submenu` class provides `setIcon` and `setNativeIcon` methods.\n  - Usage examples are added to the documentation and demo app.\n\n  This is a backwards-compatible feature. Submenus can now display icons just like regular menu items.\n\n## \\[2.7.0]\n\n### New Features\n\n- [`232265c70`](https://www.github.com/tauri-apps/tauri/commit/232265c70e1c213bbb3f84b5541ddc07d330fce1) ([#13209](https://www.github.com/tauri-apps/tauri/pull/13209) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Added `getBundleType` to the app module.\n\n### Enhancements\n\n- [`96391467e`](https://www.github.com/tauri-apps/tauri/commit/96391467e967c1e3c6475ce75166c58a326116a3) ([#13783](https://www.github.com/tauri-apps/tauri/pull/13783) by [@JosephBrooksbank](https://www.github.com/tauri-apps/tauri/../../JosephBrooksbank)) Allow events emitted with `emit` to be handled correctly by `listen` callbacks when in a mocked environment\n\n### Bug Fixes\n\n- [`152d971bc`](https://www.github.com/tauri-apps/tauri/commit/152d971bcd6c1fdc5716f7d5417dd4df5ce7479f) ([#13744](https://www.github.com/tauri-apps/tauri/pull/13744) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Expose `unregisterCallback`, `runCallback`, `callbacks` in `mockIPC`\n- [`b821796ad`](https://www.github.com/tauri-apps/tauri/commit/b821796add33cca4de72f48882684af487936d02) ([#13810](https://www.github.com/tauri-apps/tauri/pull/13810) by [@asdolo](https://www.github.com/tauri-apps/tauri/../../asdolo)) Add missing `trafficLightPosition` TypeScript type definition\n\n## \\[2.6.0]\n\n### New Features\n\n- [`50ebddaa2`](https://www.github.com/tauri-apps/tauri/commit/50ebddaa2d83033a393a176ba07ef28352b98210) ([#13319](https://www.github.com/tauri-apps/tauri/pull/13319) by [@kingsword09](https://www.github.com/tauri-apps/tauri/../../kingsword09)) Expose the `setAutoResize` API for webviews in `@tauri-apps/api`.\n- [`267368fd4`](https://www.github.com/tauri-apps/tauri/commit/267368fd4f83e0a71dfb1b72a66d56592a2066bc) ([#13276](https://www.github.com/tauri-apps/tauri/pull/13276) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `Monitor.workArea` field.\n\n### Bug Fixes\n\n- [`23b9da75b`](https://www.github.com/tauri-apps/tauri/commit/23b9da75b91379cca9520bc53b10fdf39ebae241) ([#13324](https://www.github.com/tauri-apps/tauri/pull/13324) by [@kingsword09](https://www.github.com/tauri-apps/tauri/../../kingsword09)) Fixed path joining behavior where `path.join('', 'a')` incorrectly returns \"/a\" instead of \"a\".\n- [`b985eaf0a`](https://www.github.com/tauri-apps/tauri/commit/b985eaf0a231ea570e36d686c665cddbc76ab4f6) ([#13306](https://www.github.com/tauri-apps/tauri/pull/13306) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Immediately unregister event listener when the unlisten function is called.\n\n### What's Changed\n\n- [`b5c549d18`](https://www.github.com/tauri-apps/tauri/commit/b5c549d1898ecdb712822c02dc665cc6771fbd07) ([#13325](https://www.github.com/tauri-apps/tauri/pull/13325) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) `transformCallback` now registers the callbacks inside `window.__TAURI_INTERNALS__.callbacks` instead of directly on `window['_{id}']`\n\n## \\[2.5.0]\n\n### New Features\n\n- [`66e6325f4`](https://www.github.com/tauri-apps/tauri/commit/66e6325f43efa49ec2165c45afec911a1a14ecfb) ([#13136](https://www.github.com/tauri-apps/tauri/pull/13136)) Allow passing the callback as the parameter of constructor of `Channel` so you can use it like this `new Channel((message) => console.log(message))`\n- [`ea36294cb`](https://www.github.com/tauri-apps/tauri/commit/ea36294cbca98f7725c91d1464fd92e77c89698a) ([#13208](https://www.github.com/tauri-apps/tauri/pull/13208)) Added `disableInputAccessoryView: bool` config for iOS.\n- [`c1cd0a2dd`](https://www.github.com/tauri-apps/tauri/commit/c1cd0a2ddb5bc3e99451cbe399b5fc9f0035f571) ([#13090](https://www.github.com/tauri-apps/tauri/pull/13090)) macOS/iOS: add option to disable or enable link previews when building a webview (the webkit api has it enabled by default)\n\n  - `WindowOptions::allowLinkPreview`\n  - `WebviewOptions::allowLinkPreview`\n- [`b072e2b29`](https://www.github.com/tauri-apps/tauri/commit/b072e2b2967640ae4fa1af466ae878c156551edd) ([#9687](https://www.github.com/tauri-apps/tauri/pull/9687)) Add `preventOverflow` config option to prevent the window from overflowing the monitor size on creation\n- [`dd4f13ce4`](https://www.github.com/tauri-apps/tauri/commit/dd4f13ce4b3cd89cde2fa3f18a063c272f215621) ([#13185](https://www.github.com/tauri-apps/tauri/pull/13185)) Added `app.setDockVisibility` for macOS.\n\n### Enhancements\n\n- [`b8f86669a`](https://www.github.com/tauri-apps/tauri/commit/b8f86669ab05f7dbdd15839a20999e63dc43bda6) ([#13145](https://www.github.com/tauri-apps/tauri/pull/13145)) `core.isTauri` now leverages `globalThis` instead of `window` in order to be used in unit tests.\n\n### Bug Fixes\n\n- [`66e6325f4`](https://www.github.com/tauri-apps/tauri/commit/66e6325f43efa49ec2165c45afec911a1a14ecfb) ([#13136](https://www.github.com/tauri-apps/tauri/pull/13136)) Fix `Channel`'s callback attached to `window` never cleaned up\n\n## \\[2.4.1]\n\n### Enhancements\n\n- [`dd1372833`](https://www.github.com/tauri-apps/tauri/commit/dd137283341ce0e6aabfd158d07d77c6feeb920e) ([#13066](https://www.github.com/tauri-apps/tauri/pull/13066) by [@ahaoboy](https://www.github.com/tauri-apps/tauri/../../ahaoboy)) Add a generic to `emit` and `emitTo` functions for the `payload` instead of the previously used type (`unknown`).\n\n## \\[2.4.0]\n\n### New Features\n\n- [`d8059bad3`](https://www.github.com/tauri-apps/tauri/commit/d8059bad3cc922dc369c39ca1cfa49aaec31322e) ([#12900](https://www.github.com/tauri-apps/tauri/pull/12900) by [@Simon-Laux](https://www.github.com/tauri-apps/tauri/../../Simon-Laux)) add `AppHandle.fetch_data_store_identifiers` and `AppHandle.remove_data_store` (macOS and iOS only)\n- [`20c190691`](https://www.github.com/tauri-apps/tauri/commit/20c19069125c89b2d45a2127278c9ffc2df35fc2) ([#12821](https://www.github.com/tauri-apps/tauri/pull/12821) by [@Simon-Laux](https://www.github.com/tauri-apps/tauri/../../Simon-Laux)) Added `WindowOptions::javascriptDisabled` and `WebviewOptions::javascriptDisabled`.\n- [`060de5bbd`](https://www.github.com/tauri-apps/tauri/commit/060de5bbdddca384e3965a8938d89840f27c581d) ([#12837](https://www.github.com/tauri-apps/tauri/pull/12837) by [@niladrix719](https://www.github.com/tauri-apps/tauri/../../niladrix719)) Added `getIdentifier()` function to get the application identifier configured in tauri.conf.json\n- [`be2e6b85f`](https://www.github.com/tauri-apps/tauri/commit/be2e6b85fed226732b4a98f68cc5d72b4f8f5a13) ([#12944](https://www.github.com/tauri-apps/tauri/pull/12944) by [@Simon-Laux](https://www.github.com/tauri-apps/tauri/../../Simon-Laux)) Added `Window#isAlwaysOnTop` and `WebviewWindow#isAlwaysOnTop` methods.\n- [`bcdd51025`](https://www.github.com/tauri-apps/tauri/commit/bcdd510254ebe37827e22a5ffeb944321361e97c) ([#13012](https://www.github.com/tauri-apps/tauri/pull/13012) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) The `path` basename and extname APIs now accept Android content URIs, such as the paths returned by the dialog plugin.\n\n### Bug Fixes\n\n- [`3a74dc8f3`](https://www.github.com/tauri-apps/tauri/commit/3a74dc8f3421112b1d0a32b6a432606b1f33cc25) ([#12935](https://www.github.com/tauri-apps/tauri/pull/12935) by [@tk103331](https://www.github.com/tauri-apps/tauri/../../tk103331)) Fix `Webview.close` always fail with command not found\n\n## \\[2.3.0]\n\n### Enhancements\n\n- [`a2d36b8c3`](https://www.github.com/tauri-apps/tauri/commit/a2d36b8c34a8dcfc6736797ca5cd4665faf75e7e) ([#12181](https://www.github.com/tauri-apps/tauri/pull/12181) by [@bastiankistner](https://www.github.com/tauri-apps/tauri/../../bastiankistner)) Add an option to change the default background throttling policy (currently for WebKit only).\n\n## \\[2.2.0]\n\n### New Features\n\n- [`020ea0556`](https://www.github.com/tauri-apps/tauri/commit/020ea05561348dcd6d2a7df358f8a5190f661ba2) ([#11661](https://www.github.com/tauri-apps/tauri/pull/11661) by [@ahqsoftwares](https://www.github.com/tauri-apps/tauri/../../ahqsoftwares)) Add badging APIs:\n\n  - `Window/WebviewWindow::set_badge_count` for Linux, macOS and IOS.\n  - `Window/WebviewWindow::set_overlay_icon` for Windows Only.\n  - `Window/WebviewWindow::set_badge_label`for macOS Only.\n- [`fc30b20be`](https://www.github.com/tauri-apps/tauri/commit/fc30b20bea125f647db00ca824663f8e1da4d61f) ([#11726](https://www.github.com/tauri-apps/tauri/pull/11726) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `TrayIcon.setShowMenuOnLeftClick` method and deprecate `TrayIcon.setMenuOnLeftClick` to match the Rust API.\n- [`fc30b20be`](https://www.github.com/tauri-apps/tauri/commit/fc30b20bea125f647db00ca824663f8e1da4d61f) ([#11726](https://www.github.com/tauri-apps/tauri/pull/11726) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `TrayIconOptions.showMenuOnLeftClick` field and deprecate `TrayIconOptions.menuOnLeftClick` to match the Rust API.\n\n### Enhancements\n\n- [`fc30b20be`](https://www.github.com/tauri-apps/tauri/commit/fc30b20bea125f647db00ca824663f8e1da4d61f) ([#11726](https://www.github.com/tauri-apps/tauri/pull/11726) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add support for `TrayIconOptions.menuOnLeftClick` option and `TrayIcon.setMenuOnLeftClick` on Windows.\n\n### Bug Fixes\n\n- [`a16796a55`](https://www.github.com/tauri-apps/tauri/commit/a16796a55592cf5be80043edfbb630dd2e32efab) ([#12069](https://www.github.com/tauri-apps/tauri/pull/12069) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `Channel` never calls `onmessage` in some cases\n- [`12a48d1e2`](https://www.github.com/tauri-apps/tauri/commit/12a48d1e26a83c3915eaa0687b196fbc8f2d457a) ([#11741](https://www.github.com/tauri-apps/tauri/pull/11741) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix error when calling `PredefinedMenuItem.new` to create an `About` menu item that uses an `Image` instance for the about icon.\n- [`12a48d1e2`](https://www.github.com/tauri-apps/tauri/commit/12a48d1e26a83c3915eaa0687b196fbc8f2d457a) ([#11741](https://www.github.com/tauri-apps/tauri/pull/11741) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix error when calling `IconMenuItem.new` using an `Image` instance for the icon.\n- [`b63262cd4`](https://www.github.com/tauri-apps/tauri/commit/b63262cd4d6a3667ca1664607a0a5444ad79fe0e) ([#11724](https://www.github.com/tauri-apps/tauri/pull/11724) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Removed the generic in the type of the callback function argument in `mockIPC` which prevented its proper use in tests using TypeScript.\n- [`a6e84f7d2`](https://www.github.com/tauri-apps/tauri/commit/a6e84f7d2c1d5fdc65901fce683502be3f47833f) ([#11835](https://www.github.com/tauri-apps/tauri/pull/11835) by [@ilittlebig](https://www.github.com/tauri-apps/tauri/../../ilittlebig)) Fix error where using `isAbsolute` would return `Command not found`.\n\n## \\[2.1.1]\n\n### Bug Fixes\n\n- [`7f81f0523`](https://www.github.com/tauri-apps/tauri/commit/7f81f052365675721312aafba297a7b67fb872d2) Fix regression in `toLogical` and `toPhysical` for position types in `dpi` module returning incorrect `y` value.\n- [`e8a50f6d7`](https://www.github.com/tauri-apps/tauri/commit/e8a50f6d760fad4529e7abb400302a1b487f11dd) ([#11645](https://www.github.com/tauri-apps/tauri/pull/11645)) Fix integer values of `BasDirectory.Home` and `BaseDirectory.Font` regression which broke path APIs in JS.\n\n## \\[2.1.0]\n\n### New Features\n\n- [`5c4b83084`](https://www.github.com/tauri-apps/tauri/commit/5c4b830843ab085f8ff9db9e08d832223b027e4e) ([#11191](https://www.github.com/tauri-apps/tauri/pull/11191) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Improved support for `dpi` module types to allow these types to be used without manual conversions with `invoke`:\n\n  - Added `SERIALIZE_TO_IPC_FN` const in `core` module which can be used to implement custom IPC serialization for types passed to `invoke`.\n  - Added `Size` and `Position` classes in `dpi` module.\n  - Implementd `SERIALIZE_TO_IPC_FN` method on `PhysicalSize`, `PhysicalPosition`, `LogicalSize` and `LogicalPosition` to convert it into a valid IPC-compatible value that can be deserialized correctly on the Rust side into its equivalent struct.\n- [`4d545ab3c`](https://www.github.com/tauri-apps/tauri/commit/4d545ab3ca228c8a21b966b709f84a0da2864479) ([#11486](https://www.github.com/tauri-apps/tauri/pull/11486) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Webview::setBackgroundColor`, `WebviewWindow::setBackgroundColor` APIs to set the window background color dynamically\n  and a `backgroundColor` window option to set the background color on window creation.\n- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > devtools` config option and when creating the webview from JS, to enable or disable devtools for a specific webview.\n- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `windowClassname` option, when constructing a `Webview` or `WebviewWindow`, to specify the name of the window class on Windows.\n\n### Bug Fixes\n\n- [`54cbf59b5`](https://www.github.com/tauri-apps/tauri/commit/54cbf59b5a572570a47237a3b5e6505f2a9e5d5d) ([#11441](https://www.github.com/tauri-apps/tauri/pull/11441) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix submenu created as a menu item instead of a submenu when created by using an object in the `items` field in the options object passed to `Menu.new` or `Submenu.new`.\n\n## \\[2.0.3]\n\n### Bug Fixes\n\n- [`fbb45c674`](https://www.github.com/tauri-apps/tauri/commit/fbb45c674ca92fbbe04f1a8360e5f2e477dd4297) ([#11423](https://www.github.com/tauri-apps/tauri/pull/11423) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes `addPluginListener` not working.\n\n### What's Changed\n\n- [`2e88633ba`](https://www.github.com/tauri-apps/tauri/commit/2e88633ba4da8fc289c6d8a29c36f3327f9b576e) ([#11369](https://www.github.com/tauri-apps/tauri/pull/11369) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Remove references to no longer used `__TAURI_INTERNALS__.metadata.windows` and `__TAURI_INTERNALS__.metadata.webviews`.\n\n## \\[2.0.2]\n\n### What's Changed\n\n- [`e968b3d25`](https://www.github.com/tauri-apps/tauri/commit/e968b3d2527b8edf7653e6cf7284dc4a8889b5fe) ([#11219](https://www.github.com/tauri-apps/tauri/pull/11219) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Actually publish package with the latest tag.\n\n## \\[2.0.1]\n\n### What's Changed\n\n- [`be683e2ac`](https://www.github.com/tauri-apps/tauri/commit/be683e2ac36df9c51a5c050d9d500247bd019090) ([#11199](https://www.github.com/tauri-apps/tauri/pull/11199) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Publish package with the latest NPM tag.\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n## \\[2.0.0-rc.6]\n\n### New Features\n\n- [`9014a3f17`](https://www.github.com/tauri-apps/tauri/commit/9014a3f1765ca406ea5c3e5224267a79c52cd53d) ([#11066](https://www.github.com/tauri-apps/tauri/pull/11066) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindow.clearAllBrowsingData` and `Webview.clearAllBrowsingData` to clear the webview browsing data.\n- [`95df53a2e`](https://www.github.com/tauri-apps/tauri/commit/95df53a2ed96873cd35a4b14a5e312d07e4e3004) ([#11143](https://www.github.com/tauri-apps/tauri/pull/11143) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Add the ability to set theme dynamically using `Window.setTheme` or `setTheme` function from the `app` module\n- [`d9d2502b4`](https://www.github.com/tauri-apps/tauri/commit/d9d2502b41e39efde679e30c8955006e2ba9ea64) ([#11140](https://www.github.com/tauri-apps/tauri/pull/11140) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `Webview.hide` and `Webview.show` methods.\n- [`de7414aab`](https://www.github.com/tauri-apps/tauri/commit/de7414aab935e45540594ea930eb60bae4dbc979) ([#11154](https://www.github.com/tauri-apps/tauri/pull/11154) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `Window::setEnabled` and `Window::isEnabled` methods\n\n### Bug Fixes\n\n- [`948772a65`](https://www.github.com/tauri-apps/tauri/commit/948772a657eb3caf20843628abac9109e3b67d41) ([#11114](https://www.github.com/tauri-apps/tauri/pull/11114) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Change the `button_state` tray event field to camelCase `buttonState`.\n\n### Breaking Changes\n\n- [`0b4495996`](https://www.github.com/tauri-apps/tauri/commit/0b4495996d3131a5ee80fbb2c71a28203e491ee7) ([#11121](https://www.github.com/tauri-apps/tauri/pull/11121) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Simplified emitted tray event JS value and updated `TrayIconEvent` type definition to match it.\n\n## \\[2.0.0-rc.5]\n\n### New Features\n\n- [`ddf69157b`](https://www.github.com/tauri-apps/tauri/commit/ddf69157b54249f3321ca72db6703812019f1ab9) ([#11031](https://www.github.com/tauri-apps/tauri/pull/11031) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `toPhysical` method on `LogicalPositon` and `LogicalSize` classes.\n\n## \\[2.0.0-rc.4]\n\n### Enhancements\n\n- [`f81929e25`](https://www.github.com/tauri-apps/tauri/commit/f81929e25104aa1091e464bd012c80649dedf9e5) ([#10799](https://www.github.com/tauri-apps/tauri/pull/10799) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `PermissionState`, `checkPermissions` and `requestPermissions` base APIs to the core module, designed for plugin authors to extend.\n\n### Bug Fixes\n\n- [`fbe76a955`](https://www.github.com/tauri-apps/tauri/commit/fbe76a955a63af9fb33f66d5f747caf858cf179b) ([#10797](https://www.github.com/tauri-apps/tauri/pull/10797) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Uint8Arrays and ArrayBuffers are now properly serialized as an array of numbers.\n\n## \\[2.0.0-rc.3]\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n## \\[2.0.0-rc.2]\n\n### Bug Fixes\n\n- [`c689521a7`](https://www.github.com/tauri-apps/tauri/commit/c689521a7674b6562b5dfd4f5cacd12138d99d85) ([#10681](https://www.github.com/tauri-apps/tauri/pull/10681) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix tslib path in dist.\n\n## \\[2.0.0-rc.1]\n\n### Breaking Changes\n\n- [`b6dca99ff`](https://www.github.com/tauri-apps/tauri/commit/b6dca99fff73816a39380b288c299b47b493cfdb) ([#10630](https://www.github.com/tauri-apps/tauri/pull/10630) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Changed `WebviewWindow.getAll`, `WebviewWindow.getByLabel`, `getAllWebviewWindows`,\n  `Window.getAll`, `Window.getByLabel`, `getAllWindows`,\n  `Webview.getAll`, `Webview.getByLabel`, `getAllWebviews`\n  to be async so their return value are synchronized with the state from the Rust side,\n  meaning new and destroyed windows are reflected.\n\n## \\[2.0.0-rc.0]\n\n### Changes\n\n- Promoted to RC!\n\n## \\[2.0.0-beta.16]\n\n### New Features\n\n- [`da25f7353`](https://www.github.com/tauri-apps/tauri/commit/da25f7353070477ba969851e974379d7666d6806) ([#10242](https://www.github.com/tauri-apps/tauri/pull/10242) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add APIs to enable setting window size constraints separately:\n\n  - Added `WindowSizeConstraints` interface in `window` and `webviewWindow` modules.\n  - Added `Window.setSizeConstraints` and `WebviewWindow.setSizeConstraints`\n\n### Bug Fixes\n\n- [`3c17fb64f`](https://www.github.com/tauri-apps/tauri/commit/3c17fb64fd822597d5cc16ee7e7b3f9e1023637b) ([#10277](https://www.github.com/tauri-apps/tauri/pull/10277) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `Webview.reparent` pointing to `set_webview_focus` instead of `reparent` Rust API\n- [`da25f7353`](https://www.github.com/tauri-apps/tauri/commit/da25f7353070477ba969851e974379d7666d6806) ([#10242](https://www.github.com/tauri-apps/tauri/pull/10242) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Apply `minWidth`, `minHieght`, `maxWidth` and `maxHeight` constraints separately, which fixes a long standing bug where these constraints were never applied unless width and height were constrained together.\n\n## \\[2.0.0-beta.15]\n\n### New Features\n\n- [`7bc6a2a1d`](https://www.github.com/tauri-apps/tauri/commit/7bc6a2a1d6d2c5406d91cac94d33bce76443c28f) ([#9788](https://www.github.com/tauri-apps/tauri/pull/9788) by [@pewsheen](https://www.github.com/tauri-apps/tauri/../../pewsheen)) Add a new method to set title bar style dynamically on macOS.\n\n### Enhancements\n\n- [`080b6e127`](https://www.github.com/tauri-apps/tauri/commit/080b6e12720b89d839c686d7067cc94d276ed7e4) ([#10246](https://www.github.com/tauri-apps/tauri/pull/10246) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Use `EventName` on `Window`, `Webview` and `WebviewWindow`'s `once` so you can get auto complete for tauri's built-in events\n\n### Bug Fixes\n\n- [`080b6e127`](https://www.github.com/tauri-apps/tauri/commit/080b6e12720b89d839c686d7067cc94d276ed7e4) ([#10246](https://www.github.com/tauri-apps/tauri/pull/10246) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `once` doesn't detached after one callback if event handler throws\n\n### Breaking Changes\n\n- [`261c9f942`](https://www.github.com/tauri-apps/tauri/commit/261c9f942de9a598b5c6cc504de6bddd1306113b) ([#10170](https://www.github.com/tauri-apps/tauri/pull/10170) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Renamed drag and drop events in `TauriEvent` enum to better convey when they are triggered:\n\n  - `TauriEvent.DRAG` -> `TauriEvent.DRAG_ENTER`\n  - `TauriEvent.DROP_OVER` -> `TauriEvent.DRAG_OVER`\n  - `TauriEvent.DROP` -> `TauriEvent.DRAG_DROP`\n  - `TauriEvent.DROP_CANCELLED` -> `TauriEvent::DRAG_LEAVE`\n\n  Also the `type` field values in `Window/Webview/WebviewWindow.onDropEvent` and `DragDropEvent` have changed:\n\n  - `dragged` -> `enter`\n  - `dragOver` -> `over`\n  - `dropped` -> `drop`\n  - `cancelled` -> `leave`\n- [`2b1ceb40d`](https://www.github.com/tauri-apps/tauri/commit/2b1ceb40d345aef42dd79438fa69ca7989ee0194) ([#10229](https://www.github.com/tauri-apps/tauri/pull/10229) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Renamed the JS `getCurrent` and `getAll` functions to a clearer name to avoid ambiguity:\n\n  - `getCurrent` in `window` module has been renamed to `getCurrentWindow`\n  - `getCurrent` in `webview` module has been renamed to `getCurrentWebview`\n  - `getCurrent` in `webviewWindow` module has been renamed to `getCurrentWebviewWindow`\n  - `getAll` in `window` module has been renamed to `getAllWindows`\n  - `getAll` in `webview` module has been renamed to `getAllWebviews`\n  - `getAll` in `webviewWindow` module has been renamed to `getAllWebviewWindows`\n\n## \\[2.0.0-beta.14]\n\n### New Features\n\n- [`148f04887`](https://www.github.com/tauri-apps/tauri/commit/148f048871caee21498b236c058b8890f2b66cc7) ([#9979](https://www.github.com/tauri-apps/tauri/pull/9979)) Add `defaultWindowIcon` to the JS `app` module to retrieve the default window icon in JS.\n\n### Bug Fixes\n\n- [`c98f385cb`](https://www.github.com/tauri-apps/tauri/commit/c98f385cb5da4d72968df24b1fc0b58212d59653) ([#10044](https://www.github.com/tauri-apps/tauri/pull/10044)) Export `mocks` module in `@tauri-apps/api` npm package.\n\n## \\[2.0.0-beta.13]\n\n### Breaking Changes\n\n- [`c4410daa8`](https://www.github.com/tauri-apps/tauri/commit/c4410daa85616340e911c8243fdaa69e6906fd49)([#9777](https://www.github.com/tauri-apps/tauri/pull/9777)) This release contains breaking changes to the tray event structure because of newly added events:\n\n  - Changed `TrayIconEvent` to be an enum instead of a struct.\n  - Added `MouseButtonState` and `MouseButton` enums.\n  - Removed `ClickType` enum and replaced it with `MouseButton` enum.\n  - Added `MouseButtonState` enum.\n\n## \\[2.0.0-beta.12]\n\n### New Features\n\n- [`ec0e092ec`](https://www.github.com/tauri-apps/tauri/commit/ec0e092ecd23b547c756c7476f23a0d95be6db80)([#9770](https://www.github.com/tauri-apps/tauri/pull/9770)) Add `monitorFromPoint` function in `window` module to get the monitor from a given point.\n\n## \\[2.0.0-beta.11]\n\n### Bug Fixes\n\n- [`aa080696e`](https://www.github.com/tauri-apps/tauri/commit/aa080696e0952abff416dd9088d519eaf2587a3a)([#9618](https://www.github.com/tauri-apps/tauri/pull/9618)) Fix `isTauri` incorrect return type.\n\n## \\[2.0.0-beta.10]\n\n### New Features\n\n- [`477bb8cd4`](https://www.github.com/tauri-apps/tauri/commit/477bb8cd4ea88ade3f6c1f268ad1701a68150161)([#9297](https://www.github.com/tauri-apps/tauri/pull/9297)) Add `cursorPosition` function in `window` module to get the current cursor position.\n\n## \\[2.0.0-beta.9]\n\n### New Features\n\n- [`70c51371e`](https://www.github.com/tauri-apps/tauri/commit/70c51371e01184223312de3dba8030394a5a9406)([#9539](https://www.github.com/tauri-apps/tauri/pull/9539)) Add `isTauri` function in `core` module to check whether running inside tauri or not.\n\n### Bug Fixes\n\n- [`be7eab209`](https://www.github.com/tauri-apps/tauri/commit/be7eab209c60c45e140f7bcb4bab1037d62d4c03)([#9486](https://www.github.com/tauri-apps/tauri/pull/9486)) Set the `exports > types` package.json field.\n- [`cf615e8e4`](https://www.github.com/tauri-apps/tauri/commit/cf615e8e4d5008ee1ac3f77e530ba26fb91e8977)([#9463](https://www.github.com/tauri-apps/tauri/pull/9463)) Fixes a bug when processing channel messages out of order.\n- [`35b25f7e5`](https://www.github.com/tauri-apps/tauri/commit/35b25f7e5c0fe03af4ed3582e22a626863f035f0)([#9530](https://www.github.com/tauri-apps/tauri/pull/9530)) Do not use JS optional chaining to prevent script errors on older webviews such as macOS 10.14.\n\n## \\[2.0.0-beta.8]\n\n### New Features\n\n- [`58a7a552d`](https://www.github.com/tauri-apps/tauri/commit/58a7a552d739b77b71d61af11c53f7f2dc7a6e7e)([#9378](https://www.github.com/tauri-apps/tauri/pull/9378)) Added the `set_zoom` function to the webview API.\n- [`58a7a552d`](https://www.github.com/tauri-apps/tauri/commit/58a7a552d739b77b71d61af11c53f7f2dc7a6e7e)([#9378](https://www.github.com/tauri-apps/tauri/pull/9378)) Add `zoom_hotkeys_enabled` to enable browser native zoom controls on creating webviews.\n\n### Bug Fixes\n\n- [`48a7a78f8`](https://www.github.com/tauri-apps/tauri/commit/48a7a78f8094d08e5e403e88050391642d29151b)([#9376](https://www.github.com/tauri-apps/tauri/pull/9376)) Fix `Window/Webview/WebviewWindow.setSize`, `Window/Webview/WebviewWindow.setPostion`, `Window/WebviewWindow.setMinSize`, `Window/WebviewWindow.setMaxSize`, `Window/WebviewWindow.setCursorPosition` and `Menu/Submenu.popup` methods failing with invalid args.\n\n## \\[2.0.0-beta.7]\n\n### Bug Fixes\n\n- [`c33f6e6cf`](https://www.github.com/tauri-apps/tauri/commit/c33f6e6cf35a0d34b5598875a2e5b642a01c8b38)([#9211](https://www.github.com/tauri-apps/tauri/pull/9211)) Re-added the `TauriEvent.WINDOW_CREATED` (`tauri://window-created`) event.\n\n### Breaking Changes\n\n- [`06833f4fa`](https://www.github.com/tauri-apps/tauri/commit/06833f4fa8e63ecc55fe3fc874a9e397e77a5709)([#9100](https://www.github.com/tauri-apps/tauri/pull/9100)) Rename `FileDrop` to `DragDrop` on structs, enums and enum variants. Also renamed `file_drop` to `drag_drop` on fields and function names.\n\n## \\[2.0.0-beta.6]\n\n### New Features\n\n- [`acdd76833`](https://www.github.com/tauri-apps/tauri/commit/acdd76833db6d81f4012418133d0042220de100b)([#9155](https://www.github.com/tauri-apps/tauri/pull/9155)) Add `TrayIcon.getById` and `TrayIcon.removeById` static methods.\n\n### Enhancements\n\n- [`ea0242db4`](https://www.github.com/tauri-apps/tauri/commit/ea0242db4aa6c127d2bb4a2e275000ba47c9e68c)([#9179](https://www.github.com/tauri-apps/tauri/pull/9179)) The `Image` constructor is now public (for internal use only).\n\n### Bug Fixes\n\n- [`379cc2b35`](https://www.github.com/tauri-apps/tauri/commit/379cc2b3547395474d4b66b4222679cf4538428d)([#9165](https://www.github.com/tauri-apps/tauri/pull/9165)) Fix `basename(path, 'ext')` JS API when removing all occurances of `ext` where it should only remove the last one.\n\n### Breaking Changes\n\n- [`ea0242db4`](https://www.github.com/tauri-apps/tauri/commit/ea0242db4aa6c127d2bb4a2e275000ba47c9e68c)([#9179](https://www.github.com/tauri-apps/tauri/pull/9179)) `Image::rgba()` now returns `Promise<Uint8Array>`.\n- [`ea0242db4`](https://www.github.com/tauri-apps/tauri/commit/ea0242db4aa6c127d2bb4a2e275000ba47c9e68c)([#9179](https://www.github.com/tauri-apps/tauri/pull/9179)) Removed `width` and `height` methods on the JS `Image` class, use `size` instead.\n\n## \\[2.0.0-beta.5]\n\n### Breaking Changes\n\n- [`db0a24a97`](https://www.github.com/tauri-apps/tauri/commit/db0a24a973191752aeecfbd556faa254b0f17e79)([#9132](https://www.github.com/tauri-apps/tauri/pull/9132)) Remove the `Image.fromPngBytes` and `Image.fromIcoBytes` APIs. Use `Image.fromBytes` instead.\n\n## \\[2.0.0-beta.4]\n\n### New Features\n\n- [`d1e77acd8`](https://www.github.com/tauri-apps/tauri/commit/d1e77acd8dfdf554b90b542513a58a2de1ef2360)([#9011](https://www.github.com/tauri-apps/tauri/pull/9011)) Add a new `Image` type in Rust and JS.\n\n### Enhancements\n\n- [`e62ca4ee9`](https://www.github.com/tauri-apps/tauri/commit/e62ca4ee95f4308a6ad128d0f100c85634e28223)([#9070](https://www.github.com/tauri-apps/tauri/pull/9070)) Added a mechanism to preserve channel message order.\n\n## \\[2.0.0-beta.3]\n\n### New Features\n\n- [`fdcaf935`](https://www.github.com/tauri-apps/tauri/commit/fdcaf935fa75ecfa2806939c4faad4fe9e880386)([#8939](https://www.github.com/tauri-apps/tauri/pull/8939)) Added the `reparent` function to the webview API.\n\n## \\[2.0.0-beta.2]\n\n### Breaking Changes\n\n- [`361ec37f`](https://www.github.com/tauri-apps/tauri/commit/361ec37fd4a5caa5b6630b9563ef079f53c6c336)([#8932](https://www.github.com/tauri-apps/tauri/pull/8932)) Removed the `unityUri` option from the progress bar state, no longer required.\n\n## \\[2.0.0-beta.1]\n\n### New Features\n\n- [`16e550ec`](https://www.github.com/tauri-apps/tauri/commit/16e550ec1503765158cdc3bb2a20e70ec710e981)([#8844](https://www.github.com/tauri-apps/tauri/pull/8844)) Add a new `webviewWindow` module that exports `WebviewWindow` class and related methods such as `getCurrent` and `getAll`.\n- [`16e550ec`](https://www.github.com/tauri-apps/tauri/commit/16e550ec1503765158cdc3bb2a20e70ec710e981)([#8844](https://www.github.com/tauri-apps/tauri/pull/8844)) Add `Window.onFileDropEvent` method.\n\n### Breaking Changes\n\n- [`16e550ec`](https://www.github.com/tauri-apps/tauri/commit/16e550ec1503765158cdc3bb2a20e70ec710e981)([#8844](https://www.github.com/tauri-apps/tauri/pull/8844)) Renamed the following enum variants of `TauriEvent` enum:\n\n  - `TauriEvent.WEBVIEW_FILE_DROP` -> `TauriEvent.FILE_DROP`\n  - `TauriEvent.WEBVIEW_FILE_DROP_HOVER` -> `TauriEvent.FILE_DROP_HOVER`\n  - `TauriEvent.WEBVIEW_FILE_DROP_CANCELLED` -> `TauriEvent.FILE_DROP_CANCELLED`\n- [`16e550ec`](https://www.github.com/tauri-apps/tauri/commit/16e550ec1503765158cdc3bb2a20e70ec710e981)([#8844](https://www.github.com/tauri-apps/tauri/pull/8844)) Move `WebviewWindow` class from `webview` module to a new `webviewWindow` module.\n\n## \\[2.0.0-beta.0]\n\n### New Features\n\n- [`74a2a603`](https://www.github.com/tauri-apps/tauri/commit/74a2a6036a5e57462f161d728cbd8a6f121028ca)([#8661](https://www.github.com/tauri-apps/tauri/pull/8661)) Implement access control list for IPC usage.\n- [`a093682d`](https://www.github.com/tauri-apps/tauri/commit/a093682d2df7169b024bb4f736c7f1fd2ea8b327)([#8621](https://www.github.com/tauri-apps/tauri/pull/8621)) Added `emitTo` api to `event` module which is equivalent to the rust `emit_to` method. Also added `emitTo` method on `Window`, `Webivew` and `WebviewWindow` classes.\n- [`a2fc3a63`](https://www.github.com/tauri-apps/tauri/commit/a2fc3a63579ca739646d696870cbecbb3a169d33)([#8657](https://www.github.com/tauri-apps/tauri/pull/8657)) Add `visibleOnAllWorkspaces` option when creating the window in JS and `Window.setVisibleOnAllWorkspaces` method.\n- [`7f033f6d`](https://www.github.com/tauri-apps/tauri/commit/7f033f6dcd54c69a4193765a5c1584755ba92c61)([#8537](https://www.github.com/tauri-apps/tauri/pull/8537)) Add `Window.startResizeDragging`.\n- [`9eaeb5a8`](https://www.github.com/tauri-apps/tauri/commit/9eaeb5a8cd95ae24b5e66205bdc2763cb7f965ce)([#8622](https://www.github.com/tauri-apps/tauri/pull/8622)) Add `parent` option when creating a window.\n- [`af610232`](https://www.github.com/tauri-apps/tauri/commit/af6102327376884364b2075b468bdf08ee0d02aa)([#8710](https://www.github.com/tauri-apps/tauri/pull/8710)) Added `Window::destroy` to force close a window.\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Added support to multiwebview via the new `window` and `webview` modules.\n\n### Breaking Changes\n\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Removed event callback's `windowLabel`.\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) The event target is now an object so you can target either a window or a webview.\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Moved webview-specific APIs from the `Window` class to the `Webview` class.\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Renamed `TauriEvent.WINDOW_FILE_DROP` to `TauriEvent.WEBVIEW_FILE_DROP`, `TauriEvent.WINDOW_FILE_DROP_HOVER` to `TauriEvent.WEBVIEW_FILE_DROP_HOVER` and `TauriEvent.WINDOW_FILE_DROP_CANCELLED` to `TauriEvent.WEBVIEW_FILE_DROP_CANCELLED`.\n- [`c77b4032`](https://www.github.com/tauri-apps/tauri/commit/c77b40324ea9bf580871fc11aed69ba0c9b6b8cf)([#8280](https://www.github.com/tauri-apps/tauri/pull/8280)) Added back the `WebviewWindow` API that exposes functionality of a window that hosts a single webview. The dedicated `Window` and `Webview` types are exposed for multiwebview features.\n- [`af610232`](https://www.github.com/tauri-apps/tauri/commit/af6102327376884364b2075b468bdf08ee0d02aa)([#8710](https://www.github.com/tauri-apps/tauri/pull/8710)) `Window::close` now triggers a close requested event instead of forcing the window to be closed.\n\n## \\[2.0.0-alpha.14]\n\n- [`97e33412`](https://www.github.com/tauri-apps/tauri/commit/97e334129956159bbd60e1c531b6acd3bc6139a6)([#8534](https://www.github.com/tauri-apps/tauri/pull/8534)) `mockIPC` and `mockWindows` no longer crash if `window.__TAURI_INTERNALS__` is undefined.\n\n## \\[2.0.0-alpha.13]\n\n### New Features\n\n- [`428ea652`](https://www.github.com/tauri-apps/tauri/commit/428ea6524c70545be33aac96d7c22b21f25caa4c)([#8370](https://www.github.com/tauri-apps/tauri/pull/8370)) Exposed `Resource` class which should be extended for Rust-backed resources created through `tauri::Manager::resources_table`.\n\n### Bug Fixes\n\n- [`ef21b681`](https://www.github.com/tauri-apps/tauri/commit/ef21b681e237a80592c9118b9c023c1d57231bac)([#8391](https://www.github.com/tauri-apps/tauri/pull/8391)) Fix a regression where typescript could not find types when using `\"moduleResolution\": \"node\"`\n- [`46451aee`](https://www.github.com/tauri-apps/tauri/commit/46451aee1318f63a6cd861a12b63929b38c64eb6)([#8268](https://www.github.com/tauri-apps/tauri/pull/8268)) Add top-level `main`, `module` and `types` fields in `package.json` to be compliant with typescripts's `\"moduleResolution\": \"node\"`\n\n### Breaking Changes\n\n- [`c2ad4d28`](https://www.github.com/tauri-apps/tauri/commit/c2ad4d28c481b2d7ed643458db56210cd44a2e0c)([#8273](https://www.github.com/tauri-apps/tauri/pull/8273)) Changed former `tauri` module from `primitives` to `core`.\n\n## \\[2.0.0-alpha.12]\n\n### New Features\n\n- [`f93148ea`](https://www.github.com/tauri-apps/tauri/commit/f93148eac05a1428e038bd9351a8149b2464ff4c)([#7709](https://www.github.com/tauri-apps/tauri/pull/7709)) Add `tray` and `menu` modules to create and manage tray icons and menus from Javascript.\n\n### Enhancements\n\n- [`b7add750`](https://www.github.com/tauri-apps/tauri/commit/b7add750ef9f32d959de613ab35063ff240281c2)([#8204](https://www.github.com/tauri-apps/tauri/pull/8204)) Added `position` field to the `FileDropEvent` payload.\n\n## \\[2.0.0-alpha.11]\n\n### Bug Fixes\n\n- [`822bf15d`](https://www.github.com/tauri-apps/tauri/commit/822bf15d6b258556b689ca55ac2ac224897e913a)([#8130](https://www.github.com/tauri-apps/tauri/pull/8130)) Fix tslib missing in the distributed api package.\n\n## \\[2.0.0-alpha.10]\n\n### Enhancements\n\n- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release.\n\n### Bug Fixes\n\n- [`287066b2`](https://www.github.com/tauri-apps/tauri/commit/287066b279f503dd09bfd43d5da37d1f471451fb)([#8071](https://www.github.com/tauri-apps/tauri/pull/8071)) No longer crashing in tests without mocks when `clearMocks` is defined in `afterEach` hook.\n\n## \\[2.0.0-alpha.9]\n\n### New Features\n\n- [`c1ec0f15`](https://www.github.com/tauri-apps/tauri/commit/c1ec0f155118527361dd5645d920becbc8afd569)([#7933](https://www.github.com/tauri-apps/tauri/pull/7933)) Added `setAlwaysOnBottom` function on `Window` and the `alwaysOnBottom` option when creating a window.\n- [`fb10b879`](https://www.github.com/tauri-apps/tauri/commit/fb10b87970a43320ef4d14564f45e7579b774eaf)([#8039](https://www.github.com/tauri-apps/tauri/pull/8039)) Add the `app` module back.\n- [`ed32257d`](https://www.github.com/tauri-apps/tauri/commit/ed32257d044f90b5eb15053efd1667125def2d2b)([#7794](https://www.github.com/tauri-apps/tauri/pull/7794)) On Windows, add `Effect.Tabbed`,`Effect.TabbedDark` and `Effect.TabbedLight` effects.\n- [`c9a9246c`](https://www.github.com/tauri-apps/tauri/commit/c9a9246c37bdf190661355c8ee406dac6c427344)([#8007](https://www.github.com/tauri-apps/tauri/pull/8007)) Add the `window` module back.\n- [`c085adda`](https://www.github.com/tauri-apps/tauri/commit/c085addab58ba851398373c6fd13f9cb026d71e8)([#8009](https://www.github.com/tauri-apps/tauri/pull/8009)) Added the `setProgressBar` API on the `Window` class.\n\n### What's Changed\n\n- [`5c0eeb40`](https://www.github.com/tauri-apps/tauri/commit/5c0eeb40c1003583290ff3aebfa02e2b5f5b9c41)([#7638](https://www.github.com/tauri-apps/tauri/pull/7638)) Updated minimum Node.js version to 18.\n\n### Breaking Changes\n\n- [`a63e71f9`](https://www.github.com/tauri-apps/tauri/commit/a63e71f9799e9bbc82521d2f17b5238fbf690e89)([#7942](https://www.github.com/tauri-apps/tauri/pull/7942)) Changed `tauri` module to `primitives` and removed the undocumented `invoke` export from the root module.\n\n## \\[2.0.0-alpha.8]\n\n### Breaking Changes\n\n- [`d5074af5`](https://www.github.com/tauri-apps/tauri/commit/d5074af562b2b5cb6c5711442097c4058af32db6)([#7801](https://www.github.com/tauri-apps/tauri/pull/7801)) The custom protocol on Android now uses the `http` scheme instead of `https`.\n\n## \\[2.0.0-alpha.7]\n\n### Breaking Changes\n\n- [`4cb51a2d`](https://www.github.com/tauri-apps/tauri/commit/4cb51a2d56cfcae0749062c79ede5236bd8c02c2)([#7779](https://www.github.com/tauri-apps/tauri/pull/7779)) The custom protocol on Windows now uses the `http` scheme instead of `https`.\n\n## \\[2.0.0-alpha.6]\n\n### New Features\n\n- [`4af5c5a8`](https://www.github.com/tauri-apps/tauri/commit/4af5c5a8293263c16f8a65e8d232f2de52f41701)([#7170](https://www.github.com/tauri-apps/tauri/pull/7170)) Change the IPC call to align with the new format for the custom protocol based API.\n\n## \\[2.0.0-alpha.5]\n\n### New Features\n\n- [`e0f0dce2`](https://www.github.com/tauri-apps/tauri/commit/e0f0dce220730e2822fc202463aedf0166145de7)([#6442](https://www.github.com/tauri-apps/tauri/pull/6442)) Added the `windowEffects` option when creating a window and `setWindowEffects` method to change it at runtime.\n\n### Enhancements\n\n- [`9e3a18e0`](https://www.github.com/tauri-apps/tauri/commit/9e3a18e04672edad15d0ec654bd8632544871967)([#7132](https://www.github.com/tauri-apps/tauri/pull/7132)) Expose the window target option on event APIs.\n- [`6d3f3138`](https://www.github.com/tauri-apps/tauri/commit/6d3f3138b9e2f41cda712c7d9caba0f0e65dfd3c)([#7160](https://www.github.com/tauri-apps/tauri/pull/7160)) Changed `sep` and `delimiter` from `path` module into functions to fix import in frameworks like `next.js`\n- [`4652c446`](https://www.github.com/tauri-apps/tauri/commit/4652c446b361a801252bcf45e9da39813bf85482)([#7144](https://www.github.com/tauri-apps/tauri/pull/7144)) Add `tempDir` function to `path` module\n\n## \\[2.0.0-alpha.4]\n\n- [`0ab5f40d`](https://www.github.com/tauri-apps/tauri/commit/0ab5f40d3a4207f20e4440587b41c4e78f91d233)([#6813](https://www.github.com/tauri-apps/tauri/pull/6813)) Add channel API for sending data across the IPC.\n- [`3245d14b`](https://www.github.com/tauri-apps/tauri/commit/3245d14b9eb256a5c5675c7030bac7082855df47)([#6895](https://www.github.com/tauri-apps/tauri/pull/6895)) Moved the `app` feature to its own plugin in the plugins-workspace repository.\n- [`09376af5`](https://www.github.com/tauri-apps/tauri/commit/09376af59424cc27803fa2820d2ac0d4cdc90a6d)([#6704](https://www.github.com/tauri-apps/tauri/pull/6704)) Moved the `cli` feature to its own plugin in the plugins-workspace repository.\n- [`2d5378bf`](https://www.github.com/tauri-apps/tauri/commit/2d5378bfc1ba817ee2f331b41738a90e5997e5e8)([#6717](https://www.github.com/tauri-apps/tauri/pull/6717)) Moved the dialog APIs to its own plugin in the plugins-workspace repository.\n- [`39f1b04f`](https://www.github.com/tauri-apps/tauri/commit/39f1b04f7be4966488484829cd54c8ce72a04200)([#6943](https://www.github.com/tauri-apps/tauri/pull/6943)) Moved the `event` JS APIs to a plugin.\n- [`fc4d687e`](https://www.github.com/tauri-apps/tauri/commit/fc4d687ef0ef2ea069ed73c40916da733b5dcb8f)([#6716](https://www.github.com/tauri-apps/tauri/pull/6716)) Moved the file system APIs to its own plugin in the plugins-workspace repository.\n- [`f78a3783`](https://www.github.com/tauri-apps/tauri/commit/f78a378344bbec48533641661d865920a8f46f8f)([#6742](https://www.github.com/tauri-apps/tauri/pull/6742)) Moved the `http` feature to its own plugin in the plugins-workspace repository.\n- [`29ce9ce2`](https://www.github.com/tauri-apps/tauri/commit/29ce9ce2ce7dfb260d556d5cffd075e8fe06660c)([#6902](https://www.github.com/tauri-apps/tauri/pull/6902)) Moved the `os` feature to its own plugin in the plugins-workspace repository.\n- [`60cf9ed2`](https://www.github.com/tauri-apps/tauri/commit/60cf9ed2fcd7be4df41e86cf18735efe9b6cb254)([#6905](https://www.github.com/tauri-apps/tauri/pull/6905)) Moved the `process` feature to its own plugin in the plugins-workspace repository.\n- [`96639ca2`](https://www.github.com/tauri-apps/tauri/commit/96639ca239c9e4f75142fc07868ac46822111cff)([#6749](https://www.github.com/tauri-apps/tauri/pull/6749)) Moved the `shell` functionality to its own plugin in the plugins-workspace repository.\n- [`b072daa3`](https://www.github.com/tauri-apps/tauri/commit/b072daa3bd3e38b808466666619ddb885052c5b2)([#6919](https://www.github.com/tauri-apps/tauri/pull/6919)) Moved the `updater` feature to its own plugin in the plugins-workspace repository.\n- [`cebd7526`](https://www.github.com/tauri-apps/tauri/commit/cebd75261ac71b98976314a450cb292eeeec1515)([#6728](https://www.github.com/tauri-apps/tauri/pull/6728)) Moved the `clipboard` feature to its own plugin in the plugins-workspace repository.\n- [`3f17ee82`](https://www.github.com/tauri-apps/tauri/commit/3f17ee82f6ff21108806edb7b00500b8512b8dc7)([#6737](https://www.github.com/tauri-apps/tauri/pull/6737)) Moved the `global-shortcut` feature to its own plugin in the plugins-workspace repository.\n- [`9a79dc08`](https://www.github.com/tauri-apps/tauri/commit/9a79dc085870e0c1a5df13481ff271b8c6cc3b78)([#6947](https://www.github.com/tauri-apps/tauri/pull/6947)) Moved the `window` JS APIs to its own plugin in the plugins-workspace repository.\n\n## \\[2.0.0-alpha.3]\n\n- Overload the dialog `open` function to have better TS result types.\n  - [1eacd51d](https://www.github.com/tauri-apps/tauri/commit/1eacd51d185ba69a3c3cb2cc93c792e2d5929843) overloaded the open function for convenient type inference ([#5619](https://www.github.com/tauri-apps/tauri/pull/5619)) on 2023-04-07\n\n## \\[2.0.0-alpha.2]\n\n- Added `raw` encoding option to read stdout and stderr raw bytes.\n  - [f992e7f5](https://www.github.com/tauri-apps/tauri/commit/f992e7f58bf975c654a3daf36780b31a32bac064) chore(changes): readd change file on 2023-04-03\n- Removed shell's `Command` constructor and added the `Command.create` static function instead.\n  - [509d4678](https://www.github.com/tauri-apps/tauri/commit/509d4678b12816c1dd08a9a5efa71ba556d91c27) Support sending raw byte data to the \"data\" event for child command's stdout and stderr ([#5789](https://www.github.com/tauri-apps/tauri/pull/5789)) on 2023-03-31\n\n## \\[2.0.0-alpha.1]\n\n- Added the `shadow` option when creating a window and `setShadow` function.\n  - [a81750d7](https://www.github.com/tauri-apps/tauri/commit/a81750d779bc72f0fdb7de90b7fbddfd8049b328) feat(core): add shadow APIs ([#6206](https://www.github.com/tauri-apps/tauri/pull/6206)) on 2023-02-08\n\n## \\[2.0.0-alpha.0]\n\n- First mobile alpha release!\n  - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08\n\n## \\[1.5.3]\n\n### Bug Fixes\n\n- [`1c582a94`](https://www.github.com/tauri-apps/tauri/commit/1c582a942e345a066b65620e4db9f688ec142bb9)([#8392](https://www.github.com/tauri-apps/tauri/pull/8392)) Fix a regression where typescript could not find types when using `\"moduleResolution\": \"node\"`\n\n## \\[1.5.2]\n\n### Bug Fixes\n\n- [`50462702`](https://www.github.com/tauri-apps/tauri/commit/504627027303ef5a0e855aab2abea64c6964223b)([#8267](https://www.github.com/tauri-apps/tauri/pull/8267)) Add top-level `main`, `module` and `types` fields in `package.json` to be compliant with typescripts's `\"moduleResolution\": \"node\"`\n- [`14544e4b`](https://www.github.com/tauri-apps/tauri/commit/14544e4b87269c06c89fed3647d80f492e0a1d34)([#8219](https://www.github.com/tauri-apps/tauri/pull/8219)) Avoid crashing in `clearMocks`\n\n## \\[1.5.1]\n\n### New Features\n\n- [`2b0212af`](https://www.github.com/tauri-apps/tauri/commit/2b0212af49c386e52bb2357381813d6d435ec4af)([#7961](https://www.github.com/tauri-apps/tauri/pull/7961)) Add `mockConvertFileSrc` in `mocks` module, to mock `convertFileSrc` function.\n\n## \\[1.5.0]\n\n### New Features\n\n- [`6c408b73`](https://www.github.com/tauri-apps/tauri/commit/6c408b736c7aa2a0a91f0a40d45a2b7a7dedfe78)([#7269](https://www.github.com/tauri-apps/tauri/pull/7269)) Add option to specify notification sound.\n\n### Enhancements\n\n- [`58d6b899`](https://www.github.com/tauri-apps/tauri/commit/58d6b899e21d37bb42810890d289deb57f2273bd)([#7636](https://www.github.com/tauri-apps/tauri/pull/7636)) Add `append` option to `FsOptions` in the `fs` JS module, used in `writeTextFile` and `writeBinaryFile`, to be able to append to existing files instead of overwriting it.\n\n### Bug Fixes\n\n- [`2eab1505`](https://www.github.com/tauri-apps/tauri/commit/2eab1505632ff71431d4c31c49b5afc78fa5b9dd)([#7394](https://www.github.com/tauri-apps/tauri/pull/7394)) Fix `Body.form` static not reading and sending entries of type `Blob` (including subclasses such as `File`)\n\n## \\[1.4.0]\n\n### New Features\n\n- [`359058ce`](https://www.github.com/tauri-apps/tauri/commit/359058cecca44a9c30b65140c44a8bb3a6dd3be8)([#5939](https://www.github.com/tauri-apps/tauri/pull/5939)) Add `locale` function in the `os` module to get the system locale.\n- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `maximizable`, `minimizable` and `closable` fields on `WindowOptions`.\n- [`c4d6fb4b`](https://www.github.com/tauri-apps/tauri/commit/c4d6fb4b1ea8acf02707a9fe5dcab47c1c5bae7b)([#2353](https://www.github.com/tauri-apps/tauri/pull/2353)) Added the `setMaximizable`, `setMinimizable`, `setClosable`, `isMaximizable`, `isMinimizable` and `isClosable` methods.\n- [`000104bc`](https://www.github.com/tauri-apps/tauri/commit/000104bc3bc0c9ff3d20558ab9cf2080f126e9e0)([#6472](https://www.github.com/tauri-apps/tauri/pull/6472)) Add `WebviewWindow.is_focused` and `WebviewWindow.getFocusedWindow` getters.\n\n## \\[1.3.0]\n\n- Return correct type for `event.payload` in `onResized` and `onMoved` window event handlers.\n  - [0b46637e](https://www.github.com/tauri-apps/tauri/commit/0b46637ebaba54403afa32a1cb466f09df2db999) fix(api): construct correct object for onResized and onMoved, closes [#6507](https://www.github.com/tauri-apps/tauri/pull/6507) ([#6509](https://www.github.com/tauri-apps/tauri/pull/6509)) on 2023-04-03\n- Added the `WindowOptions::contentProtected` option and `WebviewWindow#setContentProtected` to change it at runtime.\n  - [4ab5545b](https://www.github.com/tauri-apps/tauri/commit/4ab5545b7a831c549f3c65e74de487ede3ab7ce5) feat: add content protection api, closes [#5132](https://www.github.com/tauri-apps/tauri/pull/5132) ([#5513](https://www.github.com/tauri-apps/tauri/pull/5513)) on 2022-12-13\n- Allow setting the text of the dialog buttons.\n  - [00e1efaa](https://www.github.com/tauri-apps/tauri/commit/00e1efaa9b33876d41dd360624b69971e70d3856) feat: customize button texts of message dialog ([#4383](https://www.github.com/tauri-apps/tauri/pull/4383)) on 2022-12-28\n- Add `is_minimized()` window method.\n  - [62144ef3](https://www.github.com/tauri-apps/tauri/commit/62144ef3be63b237869e511826edfb938e2c7174) feat: add is_minimized (fix [#3878](https://www.github.com/tauri-apps/tauri/pull/3878)) ([#5618](https://www.github.com/tauri-apps/tauri/pull/5618)) on 2022-12-13\n- Add `title` getter on window.\n  - [233e43b0](https://www.github.com/tauri-apps/tauri/commit/233e43b0c34fada1ca025378533a0b76931a6540) feat: add `title` getter on window, closes [#5023](https://www.github.com/tauri-apps/tauri/pull/5023) ([#5515](https://www.github.com/tauri-apps/tauri/pull/5515)) on 2022-12-13\n\n## \\[1.2.0]\n\n- Added the `acceptFirstMouse` window option.\n  - [95f467ad](https://www.github.com/tauri-apps/tauri/commit/95f467add51448319983c54e2f382c7c09fb72d6) feat(core): add window `accept_first_mouse` option, closes [#5347](https://www.github.com/tauri-apps/tauri/pull/5347) ([#5374](https://www.github.com/tauri-apps/tauri/pull/5374)) on 2022-10-17\n- Fix incorrect return type on `fs/exists`\n  - [ca3cd8b3](https://www.github.com/tauri-apps/tauri/commit/ca3cd8b3d11beb9b6102da40b7d27f6dbe6cd2d0) fix(api): fs/exists return type previously set to void when it should be boolean ([#5252](https://www.github.com/tauri-apps/tauri/pull/5252)) on 2022-09-29\n- Initialize `Monitor` instances with the correct classes for `position` and `size` fields instead of plain object.\n  - [6f41a271](https://www.github.com/tauri-apps/tauri/commit/6f41a2712445ac41a5ed84bbcd40af3b76c8b1d8) fix(api.js): fix `Monitor` initialization, closes [#4672](https://www.github.com/tauri-apps/tauri/pull/4672) ([#5314](https://www.github.com/tauri-apps/tauri/pull/5314)) on 2022-09-30\n- **Breaking change:** Node.js v12 is no longer supported.\n  - [1129f4f5](https://www.github.com/tauri-apps/tauri/commit/1129f4f575dd02f746abe8e66472c88c8f9fe63d) refactor: simplify api.js bundling ([#4277](https://www.github.com/tauri-apps/tauri/pull/4277)) on 2022-10-04\n- Add new app-specific `BaseDirectory` enum variants `AppConfig`, `AppData`, `AppLocalData`, `AppCache` and `AppLog` along with equivalent functions in `path` module and deprecated ambiguous variants `Log` and `App` along with their equivalent functions in `path` module.\n  - [5d89905e](https://www.github.com/tauri-apps/tauri/commit/5d89905e39ce0e6eaaec50a693679335449edb32) feat(api): add app-specific directory APIs, closes [#5263](https://www.github.com/tauri-apps/tauri/pull/5263) ([#5272](https://www.github.com/tauri-apps/tauri/pull/5272)) on 2022-09-28\n- Fix `dialog.save` return type\n  - [8357ce5b](https://www.github.com/tauri-apps/tauri/commit/8357ce5b2efdd6f92c7944822542e48ba0e303ce) Fix dialog.save return type ([#5373](https://www.github.com/tauri-apps/tauri/pull/5373)) on 2022-10-08\n- Added support to `FormData` on the `Body.form` function.\n  - [aa119f28](https://www.github.com/tauri-apps/tauri/commit/aa119f28364f8ffbc64c6bcdfc77483613076a20) feat(api): add FormData support on Body.form, closes [#5545](https://www.github.com/tauri-apps/tauri/pull/5545) ([#5546](https://www.github.com/tauri-apps/tauri/pull/5546)) on 2022-11-04\n- Added `show` and `hide` methods on the `app` module.\n  - [39bf895b](https://www.github.com/tauri-apps/tauri/commit/39bf895b73ec6b53f5758815396ba85dda6b9c67) feat(macOS): Add application `show` and `hide` methods ([#3689](https://www.github.com/tauri-apps/tauri/pull/3689)) on 2022-10-03\n- Added `tabbingIdentifier` window option for macOS.\n  - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19\n- Added `tabbing_identifier` to the window builder on macOS.\n  - [4137ab44](https://www.github.com/tauri-apps/tauri/commit/4137ab44a81d739556cbc7583485887e78952bf1) feat(macos): add `tabbing_identifier` option, closes [#2804](https://www.github.com/tauri-apps/tauri/pull/2804), [#3912](https://www.github.com/tauri-apps/tauri/pull/3912) ([#5399](https://www.github.com/tauri-apps/tauri/pull/5399)) on 2022-10-19\n- Added the `user_agent` option when creating a window.\n  - [a6c94119](https://www.github.com/tauri-apps/tauri/commit/a6c94119d8545d509723b147c273ca5edfe3729f) feat(core): expose user_agent to window config ([#5317](https://www.github.com/tauri-apps/tauri/pull/5317)) on 2022-10-02\n\n## \\[1.1.0]\n\n- Update `mockIPC()` handler signature to allow async handler functions.\n  - [4fa968dc](https://www.github.com/tauri-apps/tauri/commit/4fa968dc0e74b5206bfcd54e704d180c16b67b08) fix(api): add async `mockIPC()` handler signature ([#5056](https://www.github.com/tauri-apps/tauri/pull/5056)) on 2022-08-26\n- Improve shell's `Command`, `Command.stdout` and `Command.stderr` events with new `once`, `off`, `listenerCount`, `prependListener`, `prependOnceListener` and `removeAllListeners` functions.\n  - [aa9f1243](https://www.github.com/tauri-apps/tauri/commit/aa9f1243e6c1629972a82e469f20c8399741740e) Improved EventEmitter for tauri api shell ([#4697](https://www.github.com/tauri-apps/tauri/pull/4697)) on 2022-07-26\n- Added the `encoding` option to the `Command` options.\n  - [d8cf9f9f](https://www.github.com/tauri-apps/tauri/commit/d8cf9f9fcd617ac24fa418952fd4a32c08804f5c) Command support for specified character encoding, closes [#4644](https://www.github.com/tauri-apps/tauri/pull/4644) ([#4772](https://www.github.com/tauri-apps/tauri/pull/4772)) on 2022-07-28\n- Add `exists` function to the fs module.\n  - [3c62dbc9](https://www.github.com/tauri-apps/tauri/commit/3c62dbc902c904d35a7472ce72a969084c95fbbe) feat(api): Add `exists` function to the fs module. ([#5060](https://www.github.com/tauri-apps/tauri/pull/5060)) on 2022-09-15\n\n## \\[1.0.2]\n\n- Added helper functions to listen to updater and window events.\n  - [b02fc90f](https://www.github.com/tauri-apps/tauri/commit/b02fc90f450ff9e9d8a35ee55dc1beced4957869) feat(api): add abstractions to updater and window event listeners ([#4569](https://www.github.com/tauri-apps/tauri/pull/4569)) on 2022-07-05\n- Add support to `ArrayBuffer` in `Body.bytes` and `writeBinaryFile`.\n  - [92aca55a](https://www.github.com/tauri-apps/tauri/commit/92aca55a6f1f899d5c0c3a6aae9ac9cb0a7e9a86) feat(api): add support to ArrayBuffer ([#4579](https://www.github.com/tauri-apps/tauri/pull/4579)) on 2022-07-05\n- Use `toString()` on message/confirm/ask dialogs title and message values.\n  - [b8cd2a79](https://www.github.com/tauri-apps/tauri/commit/b8cd2a7993cd2aa5b71b30c545b3307245d254bf) feat(api): call `toString()` on dialog title and message, closes [#4583](https://www.github.com/tauri-apps/tauri/pull/4583) ([#4588](https://www.github.com/tauri-apps/tauri/pull/4588)) on 2022-07-04\n- Remove the `type-fest` dependency, changing the OS types to the specific enum instead of allowing any string.\n  - [d5e910eb](https://www.github.com/tauri-apps/tauri/commit/d5e910ebcc6c8d7f055ab0691286722b140ffcd4) chore(api): remove `type-fest` ([#4605](https://www.github.com/tauri-apps/tauri/pull/4605)) on 2022-07-06\n\n## \\[1.0.1]\n\n- Fixes the `writeBinaryFile` sending an empty file contents when only the first argument is passed.\n  - [ea43cf52](https://www.github.com/tauri-apps/tauri/commit/ea43cf52db8541d20a6397ef3ecd40f0f2bd6113) fix(api): `writeBinaryFile` sends an empty contents with only one arg ([#4368](https://www.github.com/tauri-apps/tauri/pull/4368)) on 2022-06-16\n\n## \\[1.0.0]\n\n- Allow choosing multiple folders in `dialog.open`.\n  - [4e51dce6](https://www.github.com/tauri-apps/tauri/commit/4e51dce6ca21c7664de779bc78a04be1051371f7) fix: dialog open supports multiple dirs, fixes [#4091](https://www.github.com/tauri-apps/tauri/pull/4091) ([#4354](https://www.github.com/tauri-apps/tauri/pull/4354)) on 2022-06-15\n- Upgrade to `stable`!\n  - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15\n\n## \\[1.0.0-rc.7]\n\n- Fix `FilePart` usage in `http.Body.form` by renaming the `value` property to `file`.\n  - [55f89d5f](https://www.github.com/tauri-apps/tauri/commit/55f89d5f9d429252ad3fd557b1d6233b256495e0) fix(api): Rename FormPart `value` to `file` to match docs and endpoint ([#4307](https://www.github.com/tauri-apps/tauri/pull/4307)) on 2022-06-09\n- Fixes a memory leak in the command system.\n  - [f72cace3](https://www.github.com/tauri-apps/tauri/commit/f72cace36821dc675a6d25268ae85a21bdbd6296) fix: never remove ipc callback & mem never be released ([#4274](https://www.github.com/tauri-apps/tauri/pull/4274)) on 2022-06-05\n- The notification's `isPermissionGranted` function now returns `boolean` instead of `boolean | null`. The response is never `null` because we won't check the permission for now, always returning `true` instead.\n  - [f482b094](https://www.github.com/tauri-apps/tauri/commit/f482b0942276e9402ab3725957535039bacb4fef) fix: remove notification permission prompt ([#4302](https://www.github.com/tauri-apps/tauri/pull/4302)) on 2022-06-09\n- Added the `resolveResource` API to the path module.\n  - [7bba8db8](https://www.github.com/tauri-apps/tauri/commit/7bba8db83ead92e9bd9c4be7863742e71ac47513) feat(api): add `resolveResource` API to the path module ([#4234](https://www.github.com/tauri-apps/tauri/pull/4234)) on 2022-05-29\n- Renamed `writeFile` to `writeTextFile` but kept the original function for backwards compatibility.\n  - [3f998ca2](https://www.github.com/tauri-apps/tauri/commit/3f998ca29445a349489078a74dd068e157a4d68e) feat(api): add `writeTextFile` and `(path, contents, options)` overload ([#4228](https://www.github.com/tauri-apps/tauri/pull/4228)) on 2022-05-29\n- Added `(path, contents[, options])` overload to the `writeTextFile` and `writeBinaryFile` APIs.\n  - [3f998ca2](https://www.github.com/tauri-apps/tauri/commit/3f998ca29445a349489078a74dd068e157a4d68e) feat(api): add `writeTextFile` and `(path, contents, options)` overload ([#4228](https://www.github.com/tauri-apps/tauri/pull/4228)) on 2022-05-29\n\n## \\[1.0.0-rc.6]\n\n- Expose option to set the dialog type.\n  - [f46175d5](https://www.github.com/tauri-apps/tauri/commit/f46175d5d46fa3eae66ad2415a0eb1efb7d31da2) feat(core): expose option to set dialog type, closes [#4183](https://www.github.com/tauri-apps/tauri/pull/4183) ([#4187](https://www.github.com/tauri-apps/tauri/pull/4187)) on 2022-05-21\n- Expose `title` option in the message dialog API.\n  - [ae99f991](https://www.github.com/tauri-apps/tauri/commit/ae99f991674d77c322a2240d10ed4b78ed2f4d4b) feat(core): expose message dialog's title option, ref [#4183](https://www.github.com/tauri-apps/tauri/pull/4183) ([#4186](https://www.github.com/tauri-apps/tauri/pull/4186)) on 2022-05-21\n\n## \\[1.0.0-rc.5]\n\n- Fixes the type of `http > connectTimeout`.\n  - [f3c5ca89](https://www.github.com/tauri-apps/tauri/commit/f3c5ca89e79d429183c4e15a9e7cebada2b493a0) fix(core): http api `connect_timeout` deserialization, closes [#4004](https://www.github.com/tauri-apps/tauri/pull/4004) ([#4006](https://www.github.com/tauri-apps/tauri/pull/4006)) on 2022-04-29\n\n## \\[1.0.0-rc.4]\n\n- Encode the file path in the `convertFileSrc` function.\n  - [42e8d9cf](https://www.github.com/tauri-apps/tauri/commit/42e8d9cf925089e9ad591198ee04b0cc0a0eed48) fix(api): encode file path in `convertFileSrc` function, closes [#3841](https://www.github.com/tauri-apps/tauri/pull/3841) ([#3846](https://www.github.com/tauri-apps/tauri/pull/3846)) on 2022-04-02\n- Added `theme` getter to `WebviewWindow`.\n  - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21\n- Added `theme` field to `WindowOptions`.\n  - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21\n- Added the `setCursorGrab`, `setCursorVisible`, `setCursorIcon` and `setCursorPosition` methods to the `WebviewWindow` class.\n  - [c54ddfe9](https://www.github.com/tauri-apps/tauri/commit/c54ddfe9338e7eb90b4d5b02dfde687d432d5bc1) feat: expose window cursor APIs, closes [#3888](https://www.github.com/tauri-apps/tauri/pull/3888) [#3890](https://www.github.com/tauri-apps/tauri/pull/3890) ([#3935](https://www.github.com/tauri-apps/tauri/pull/3935)) on 2022-04-21\n- **Breaking change:** The process Command API stdio lines now includes the trailing `\\r`.\n  - [b5622882](https://www.github.com/tauri-apps/tauri/commit/b5622882cf3748e1e5a90915f415c0cd922aaaf8) fix(cli): exit on non-compilation Cargo errors, closes [#3930](https://www.github.com/tauri-apps/tauri/pull/3930) ([#3942](https://www.github.com/tauri-apps/tauri/pull/3942)) on 2022-04-22\n- Added the `tauri://theme-changed` event.\n  - [4cebcf6d](https://www.github.com/tauri-apps/tauri/commit/4cebcf6da7cad1953e0f01b426afac3b5ef1f81e) feat: expose theme APIs, closes [#3903](https://www.github.com/tauri-apps/tauri/pull/3903) ([#3937](https://www.github.com/tauri-apps/tauri/pull/3937)) on 2022-04-21\n\n## \\[1.0.0-rc.3]\n\n- Properly define the `appWindow` type.\n  - [1deeb03e](https://www.github.com/tauri-apps/tauri/commit/1deeb03ef6c7cbea8cf585864424a3d66f184a02) fix(api.js): appWindow shown as type `any`, fixes [#3747](https://www.github.com/tauri-apps/tauri/pull/3747) ([#3772](https://www.github.com/tauri-apps/tauri/pull/3772)) on 2022-03-24\n- Added `Temp` to the `BaseDirectory` enum.\n  - [266156a0](https://www.github.com/tauri-apps/tauri/commit/266156a0b08150b21140dd552c8bc252fe413cdd) feat(core): add `BaseDirectory::Temp` and `$TEMP` variable ([#3763](https://www.github.com/tauri-apps/tauri/pull/3763)) on 2022-03-24\n\n## \\[1.0.0-rc.2]\n\n- Do not crash if `__TAURI_METADATA__` is not set, log an error instead.\n  - [9cb1059a](https://www.github.com/tauri-apps/tauri/commit/9cb1059aa3f81521ccc6da655243acfe0327cd98) fix(api): do not throw an exception if **TAURI_METADATA** is not set, fixes [#3554](https://www.github.com/tauri-apps/tauri/pull/3554) ([#3572](https://www.github.com/tauri-apps/tauri/pull/3572)) on 2022-03-03\n- Reimplement endpoint to read file as string for performance.\n  - [834ccc51](https://www.github.com/tauri-apps/tauri/commit/834ccc51539401d36a7dfa1c0982623c9c446a4c) feat(core): reimplement `readTextFile` for performance ([#3631](https://www.github.com/tauri-apps/tauri/pull/3631)) on 2022-03-07\n- Fixes a regression on the `unlisten` command.\n  - [76c791bd](https://www.github.com/tauri-apps/tauri/commit/76c791bd2b836d2055410e37e71716172a3f81ef) fix(core): regression on the unlisten function ([#3623](https://www.github.com/tauri-apps/tauri/pull/3623)) on 2022-03-06\n\n## \\[1.0.0-rc.1]\n\n- Provide functions to mock IPC calls during testing and static site generation.\n  - [7e04c072](https://www.github.com/tauri-apps/tauri/commit/7e04c072c4ee2278c648f44575c6c4710ac047f3) feat: add mock functions for testing and SSG ([#3437](https://www.github.com/tauri-apps/tauri/pull/3437)) on 2022-02-14\n  - [6f5ed2e6](https://www.github.com/tauri-apps/tauri/commit/6f5ed2e69cb7ffa0d5c8eb5a744fbf94ed6010d4) fix: change file on 2022-02-14\n\n## \\[1.0.0-rc.0]\n\n- Add `fileDropEnabled` property to `WindowOptions` so you can now disable it when creating windows from js.\n\n  - [1bfc32a3](https://www.github.com/tauri-apps/tauri/commit/1bfc32a3b2f31b962ce8a5c611b60cb008360923) fix(api.js): add `fileDropEnabled` to `WindowOptions`, closes [#2968](https://www.github.com/tauri-apps/tauri/pull/2968) ([#2989](https://www.github.com/tauri-apps/tauri/pull/2989)) on 2021-12-09\n\n- Add `logDir` function to the `path` module to access the suggested log directory.\n  Add `BaseDirectory.Log` to the `fs` module.\n\n  - [acbb3ae7](https://www.github.com/tauri-apps/tauri/commit/acbb3ae7bb0165846b9456aea103269f027fc548) feat: add Log directory ([#2736](https://www.github.com/tauri-apps/tauri/pull/2736)) on 2021-10-16\n  - [62c7a8ad](https://www.github.com/tauri-apps/tauri/commit/62c7a8ad30fd3031b8679960590e5ef3eef8e4da) chore(covector): prepare for `rc` release ([#3376](https://www.github.com/tauri-apps/tauri/pull/3376)) on 2022-02-10\n\n- Expose `ask`, `message` and `confirm` APIs on the dialog module.\n\n  - [e98c1af4](https://www.github.com/tauri-apps/tauri/commit/e98c1af44279a5ff6c8a6f0a506ecc219c9f77af) feat(core): expose message dialog APIs, fix window.confirm, implement HasRawWindowHandle for Window, closes [#2535](https://www.github.com/tauri-apps/tauri/pull/2535) ([#2700](https://www.github.com/tauri-apps/tauri/pull/2700)) on 2021-10-02\n\n- Event `emit` now automatically serialize non-string types.\n\n  - [06000996](https://www.github.com/tauri-apps/tauri/commit/060009969627890fa9018e2f1105bad13299394c) feat(api): support unknown types for event emit payload, closes [#2929](https://www.github.com/tauri-apps/tauri/pull/2929) ([#2964](https://www.github.com/tauri-apps/tauri/pull/2964)) on 2022-01-07\n\n- Fix `http.fetch` throwing error if the response is successful but the body is empty.\n\n  - [50c63900](https://www.github.com/tauri-apps/tauri/commit/50c63900c7313064037e2ceb798a6432fcd1bcda) fix(api.js): fix `http.fetch` throwing error if response body is empty, closes [#2831](https://www.github.com/tauri-apps/tauri/pull/2831) ([#3008](https://www.github.com/tauri-apps/tauri/pull/3008)) on 2021-12-09\n\n- Add `title` option to file open/save dialogs.\n\n  - [e1d6a6e6](https://www.github.com/tauri-apps/tauri/commit/e1d6a6e6445637723e2331ca799a662e720e15a8) Create api-file-dialog-title.md ([#3235](https://www.github.com/tauri-apps/tauri/pull/3235)) on 2022-01-16\n  - [62c7a8ad](https://www.github.com/tauri-apps/tauri/commit/62c7a8ad30fd3031b8679960590e5ef3eef8e4da) chore(covector): prepare for `rc` release ([#3376](https://www.github.com/tauri-apps/tauri/pull/3376)) on 2022-02-10\n\n- Fix `os.platform` returning `macos` and `windows` instead of `darwin` and `win32`.\n\n  - [3924c3d8](https://www.github.com/tauri-apps/tauri/commit/3924c3d85365df30b376a1ec6c2d933460d66af0) fix(api.js): fix `os.platform` return on macos and windows, closes [#2698](https://www.github.com/tauri-apps/tauri/pull/2698) ([#2699](https://www.github.com/tauri-apps/tauri/pull/2699)) on 2021-10-02\n\n- The `formatCallback` helper function now returns a number instead of a string.\n\n  - [a48b8b18](https://www.github.com/tauri-apps/tauri/commit/a48b8b18d428bcc404d489daa690bbefe1f57311) feat(core): validate callbacks and event names \\[TRI-038] \\[TRI-020] ([#21](https://www.github.com/tauri-apps/tauri/pull/21)) on 2022-01-09\n\n- Added `rawHeaders` to `http > Response`.\n\n  - [b7a2345b](https://www.github.com/tauri-apps/tauri/commit/b7a2345b06ca0306988b4ba3d3deadd449e65af9) feat(core): add raw headers to HTTP API, closes [#2695](https://www.github.com/tauri-apps/tauri/pull/2695) ([#3053](https://www.github.com/tauri-apps/tauri/pull/3053)) on 2022-01-07\n\n- Removed the `currentDir` API from the `path` module.\n\n  - [a08509c6](https://www.github.com/tauri-apps/tauri/commit/a08509c641f43695e25944a2dd47697b18cd83e2) fix(api): remove `currentDir` API from the `path` module on 2022-02-04\n\n- Remove `.ts` files on the published package.\n\n  - [0f321ac0](https://www.github.com/tauri-apps/tauri/commit/0f321ac08d56412edd5bc9d166201fbc95d887d8) fix(api): do not ship TS files, closes [#2598](https://www.github.com/tauri-apps/tauri/pull/2598) ([#2645](https://www.github.com/tauri-apps/tauri/pull/2645)) on 2021-09-23\n\n- **Breaking change:** Replaces all usages of `number[]` with `Uint8Array` to be closer aligned with the wider JS ecosystem.\n\n  - [9b19a805](https://www.github.com/tauri-apps/tauri/commit/9b19a805aa8efa64b22f2dfef193a144b8e0cee3) fix(api.js) Replace `number[]`with `Uint8Array`. fixes [#3306](https://www.github.com/tauri-apps/tauri/pull/3306) ([#3305](https://www.github.com/tauri-apps/tauri/pull/3305)) on 2022-02-05\n\n- `WindowManager` methods `innerPosition` `outerPosition` now correctly return instance of `PhysicalPosition`.\n  `WindowManager` methods `innerSize` `outerSize` now correctly return instance of `PhysicalSize`.\n\n  - [cc8b1468](https://www.github.com/tauri-apps/tauri/commit/cc8b1468c821df53ceb771061c919409a9c80978) Fix(api): Window size and position returning wrong class (fix: [#2599](https://www.github.com/tauri-apps/tauri/pull/2599)) ([#2621](https://www.github.com/tauri-apps/tauri/pull/2621)) on 2021-09-22\n\n- Change the `event` field of the `Event` interface to type `EventName` instead of `string`.\n\n  - [b5d9bcb4](https://www.github.com/tauri-apps/tauri/commit/b5d9bcb402380abc86ae1fa1a77c629af2275f9d) Consistent event name usage ([#3228](https://www.github.com/tauri-apps/tauri/pull/3228)) on 2022-01-15\n  - [62c7a8ad](https://www.github.com/tauri-apps/tauri/commit/62c7a8ad30fd3031b8679960590e5ef3eef8e4da) chore(covector): prepare for `rc` release ([#3376](https://www.github.com/tauri-apps/tauri/pull/3376)) on 2022-02-10\n\n- Now `resolve()`, `join()` and `normalize()` from the `path` module, won't throw errors if the path doesn't exist, which matches NodeJS behavior.\n\n  - [fe381a0b](https://www.github.com/tauri-apps/tauri/commit/fe381a0bde86ebf4014007f6e21af4c1a9e58cef) fix: `join` no longer cares if path doesn't exist, closes [#2499](https://www.github.com/tauri-apps/tauri/pull/2499) ([#2548](https://www.github.com/tauri-apps/tauri/pull/2548)) on 2021-09-21\n\n- Fixes the dialog `defaultPath` usage on Linux.\n\n  - [2212bd5d](https://www.github.com/tauri-apps/tauri/commit/2212bd5d75146f5a2df27cc2157a057642f626da) fix: dialog default path on Linux, closes [#3091](https://www.github.com/tauri-apps/tauri/pull/3091) ([#3123](https://www.github.com/tauri-apps/tauri/pull/3123)) on 2021-12-27\n\n- Fixes `window.label` property returning null instead of the actual label.\n\n  - [f5109e0c](https://www.github.com/tauri-apps/tauri/commit/f5109e0c962e3d25404995194968bade1be33b16) fix(api): window label null instead of actual value, closes [#3295](https://www.github.com/tauri-apps/tauri/pull/3295) ([#3332](https://www.github.com/tauri-apps/tauri/pull/3332)) on 2022-02-04\n\n- Remove the `BaseDirectory::Current` enum variant for security reasons.\n\n  - [696dca58](https://www.github.com/tauri-apps/tauri/commit/696dca58a9f8ee127a1cf857eb848e09f5845d18) refactor(core): remove `BaseDirectory::Current` variant on 2022-01-26\n\n- Change `WindowLabel` type to `string`.\n\n  - [f68603ae](https://www.github.com/tauri-apps/tauri/commit/f68603aee4e16500dff9e385b217f5dd8b1b39e8) chore(docs): simplify event system documentation on 2021-09-27\n\n- When building Universal macOS Binaries through the virtual target `universal-apple-darwin`:\n\n- Expect a universal binary to be created by the user\n\n- Ensure that binary is bundled and accessed correctly at runtime\n\n- [3035e458](https://www.github.com/tauri-apps/tauri/commit/3035e4581c161ec7f0bd6d9b42e9015cf1dd1d77) Remove target triple from sidecar bin paths, closes [#3355](https://www.github.com/tauri-apps/tauri/pull/3355) ([#3356](https://www.github.com/tauri-apps/tauri/pull/3356)) on 2022-02-07\n\n## \\[1.0.0-beta.8]\n\n- Revert target back to ES5.\n  - [657c7dac](https://www.github.com/tauri-apps/tauri/commit/657c7dac734661956b87d021ff531ba530dd92a3) fix(api): revert ES2021 target on 2021-08-23\n\n## \\[1.0.0-beta.7]\n\n- Fix missing asset protocol path.Now the protocol is `https://asset.localhost/path/to/file` on Windows. Linux and macOS\n  is still `asset://path/to/file`.\n  - [994b5325](https://www.github.com/tauri-apps/tauri/commit/994b5325dd385f564b37fe1530c5d798dc925fff) fix: missing asset protocol path ([#2484](https://www.github.com/tauri-apps/tauri/pull/2484)) on 2021-08-23\n\n## \\[1.0.0-beta.6]\n\n- `bundle` now exports `clipboard` module so you can `import { clipboard } from \"@tauri-apps/api\"`.\n  - [4f88c3fb](https://www.github.com/tauri-apps/tauri/commit/4f88c3fb94286f3daafb906e3513c9210ecfa76b) fix(api.js): `bundle` now exports `clipboard` mod, closes [#2243](https://www.github.com/tauri-apps/tauri/pull/2243) ([#2244](https://www.github.com/tauri-apps/tauri/pull/2244)) on 2021-07-19\n- Fix double window creation\n  - [9fbcc024](https://www.github.com/tauri-apps/tauri/commit/9fbcc024542d87f71afd364acdcf2302cf82912c) fix(api.js): fix double window creation, closes [#2284](https://www.github.com/tauri-apps/tauri/pull/2284) ([#2285](https://www.github.com/tauri-apps/tauri/pull/2285)) on 2021-07-23\n- Add `os` module which exports `EOL`, `platform()`, `version()`, `type()`, `arch()`, `tempdir()`\n  - [05e679a6](https://www.github.com/tauri-apps/tauri/commit/05e679a6d2aca5642c780052bcf1384c49a462de) feat(api.js): add `os` module ([#2299](https://www.github.com/tauri-apps/tauri/pull/2299)) on 2021-07-28\n- - Add new nodejs-inspired functions which are `join`, `resolve`, `normalize`, `dirname`, `basename` and `extname`.\n- Add `sep` and `delimiter` constants.\n- Removed `resolvePath` API, use `resolve` instead.\n- [05b9d81e](https://www.github.com/tauri-apps/tauri/commit/05b9d81ee6bcc920defca76cff00178b301fffe8) feat(api.js): add nodejs-inspired functions in `path` module ([#2310](https://www.github.com/tauri-apps/tauri/pull/2310)) on 2021-08-02\n- Change target to ES2021.\n  - [97bc52ee](https://www.github.com/tauri-apps/tauri/commit/97bc52ee03dec0b67cc1cced23305a4c53e9eb62) Tooling: \\[API] Changed target in tsconfig to es6 ([#2362](https://www.github.com/tauri-apps/tauri/pull/2362)) on 2021-08-09\n- Add `toggleMaximize()` function to the `WebviewWindow` class.\n  - [1a510066](https://www.github.com/tauri-apps/tauri/commit/1a510066732d5f61c88c0ceed1c5f5cc559faf7d) fix(core): `data-tauri-drag-region` didn't respect resizable, closes [#2314](https://www.github.com/tauri-apps/tauri/pull/2314) ([#2316](https://www.github.com/tauri-apps/tauri/pull/2316)) on 2021-08-02\n- Fix `@ts-expect` error usage\n  - [dd52e738](https://www.github.com/tauri-apps/tauri/commit/dd52e738f1fd323bd8d185d6e650f412eb031200) fix(api.js): fix `@ts-expect-error` usage, closes [#2249](https://www.github.com/tauri-apps/tauri/pull/2249) ([#2250](https://www.github.com/tauri-apps/tauri/pull/2250)) on 2021-07-20\n- Fixes file drop events being swapped (`file-drop-hover` on drop and `file-drop` on hover).\n  - [c2b0fe1c](https://www.github.com/tauri-apps/tauri/commit/c2b0fe1ce58e54dbcfdb63162ad17d7e6d8774d9) fix(core): fix wrong file drop events ([#2300](https://www.github.com/tauri-apps/tauri/pull/2300)) on 2021-07-31\n- Fixes the global bundle UMD code.\n  - [268450b1](https://www.github.com/tauri-apps/tauri/commit/268450b1329a4b55f2043890c565a8563f890c3a) fix(api): global bundle broken code, closes [#2289](https://www.github.com/tauri-apps/tauri/pull/2289) ([#2297](https://www.github.com/tauri-apps/tauri/pull/2297)) on 2021-07-26\n- - Fixes monitor api not working.\n- Fixes window.print() not working on macOS.\n- [0f63f5e7](https://www.github.com/tauri-apps/tauri/commit/0f63f5e757873f1787a1ae07ca531340d0d45ec3) fix(api): Fix monitor functions, closes [#2294](https://www.github.com/tauri-apps/tauri/pull/2294) ([#2301](https://www.github.com/tauri-apps/tauri/pull/2301)) on 2021-07-29\n- Improve `EventName` type using `type-fest`'s `LiteralUnion`.\n  - [8e480297](https://www.github.com/tauri-apps/tauri/commit/8e48029790857b38988da4d291aa7458f51bb265) feat(api): improve `EventName` type definition ([#2379](https://www.github.com/tauri-apps/tauri/pull/2379)) on 2021-08-10\n- Update protocol url path with wry 0.12.1 on Windows.\n  - [88382fe1](https://www.github.com/tauri-apps/tauri/commit/88382fe147ebcb3f59308cc529e5562a04970876) chore(api): update protocol url path with wry 0.12.1 on Windows ([#2409](https://www.github.com/tauri-apps/tauri/pull/2409)) on 2021-08-13\n\n## \\[1.0.0-beta.5]\n\n- Adds `convertFileSrc` helper to the `tauri` module, simplifying the process of using file paths as webview source (`img`, `video`, etc).\n  - [51a5cfe4](https://www.github.com/tauri-apps/tauri/commit/51a5cfe4b5e9890fb6f639c9c929657fd747a595) feat(api): add `convertFileSrc` helper ([#2138](https://www.github.com/tauri-apps/tauri/pull/2138)) on 2021-07-02\n- You can now use `emit`, `listen` and `once` using the `appWindow` exported by the window module.\n  - [5d7626f8](https://www.github.com/tauri-apps/tauri/commit/5d7626f89781a6ebccceb9ab3b2e8335aa7a0392) feat(api): WindowManager extends WebviewWindowHandle, add events docs ([#2146](https://www.github.com/tauri-apps/tauri/pull/2146)) on 2021-07-03\n- Allow manipulating a spawned window directly using `WebviewWindow`, which now extends `WindowManager`.\n  - [d69b1cf6](https://www.github.com/tauri-apps/tauri/commit/d69b1cf6d7c13297073073d753e30fe1a22a09cb) feat(api): allow managing windows created on JS ([#2154](https://www.github.com/tauri-apps/tauri/pull/2154)) on 2021-07-05\n\n## \\[1.0.0-beta.4]\n\n- Add asset custom protocol to access local file system.\n  - [ee60e424](https://www.github.com/tauri-apps/tauri/commit/ee60e424221559d3d725716b0003c5566ef2b5cd) feat: asset custom protocol to access local file system ([#2104](https://www.github.com/tauri-apps/tauri/pull/2104)) on 2021-06-28\n\n## \\[1.0.0-beta.3]\n\n- Export `Response` and `ResponseType` as value instead of type.\n  - [394b6e05](https://www.github.com/tauri-apps/tauri/commit/394b6e0572e7a0a92e103e462a7f603f7d569319) fix(api): http `ResponseType` export type error ([#2065](https://www.github.com/tauri-apps/tauri/pull/2065)) on 2021-06-24\n\n## \\[1.0.0-beta.2]\n\n- Export `BaseDirectory` in `path` module\n  - [277f5ca5](https://www.github.com/tauri-apps/tauri/commit/277f5ca5a8ae227bbdccee1ad52bdd88b4a5b11b) feat(api): export `BaseDirectory` in `path` module ([#1885](https://www.github.com/tauri-apps/tauri/pull/1885)) on 2021-05-30\n- Use `export type` to export TS types, enums and interfaces.\n  - [9a662d26](https://www.github.com/tauri-apps/tauri/commit/9a662d2601b01d712c6bd205f8db1b674f56dfa7) fix: Monitor if --isolatedModules is enabled ([#1825](https://www.github.com/tauri-apps/tauri/pull/1825)) on 2021-05-13\n  - [612cd8ec](https://www.github.com/tauri-apps/tauri/commit/612cd8ecb8e02954f3696b9e138cbc7d2c228fad) feat(api): finalize `export type` usage ([#1847](https://www.github.com/tauri-apps/tauri/pull/1847)) on 2021-05-17\n- Adds `focus?: boolean` to the WindowOptions interface.\n  - [5f351622](https://www.github.com/tauri-apps/tauri/commit/5f351622c7812ad1bb56ddb37364ccaa4124c24b) feat(core): add focus API to the WindowBuilder and WindowOptions, [#1737](https://www.github.com/tauri-apps/tauri/pull/1737) on 2021-05-30\n- Adds `isDecorated` getter on the window API.\n  - [f58a2114](https://www.github.com/tauri-apps/tauri/commit/f58a2114fbfd5307c349f05c88f2e08fd8baa8aa) feat(core): add `is_decorated` Window getter on 2021-05-30\n- Adds `isResizable` getter on the window API.\n  - [1e8af280](https://www.github.com/tauri-apps/tauri/commit/1e8af280c27f381828d6209722b10e889082fa00) feat(core): add `is_resizable` Window getter on 2021-05-30\n- Adds `isVisible` getter on the window API.\n  - [36506c96](https://www.github.com/tauri-apps/tauri/commit/36506c967de82bc7ff453d11e6104ecf66d7a588) feat(core): add `is_visible` API on 2021-05-30\n- Adds `requestUserAttention` API to the `window` module.\n  - [7dcca6e9](https://www.github.com/tauri-apps/tauri/commit/7dcca6e9281182b11ad3d4a79871f09b30b9b419) feat(core): add `request_user_attention` API, closes [#2023](https://www.github.com/tauri-apps/tauri/pull/2023) ([#2026](https://www.github.com/tauri-apps/tauri/pull/2026)) on 2021-06-20\n- Adds `setFocus` to the window API.\n  - [bb6992f8](https://www.github.com/tauri-apps/tauri/commit/bb6992f888196ca7c87bb2fe74ad2bd8bf393e05) feat(core): add `set_focus` window API, fixes [#1737](https://www.github.com/tauri-apps/tauri/pull/1737) on 2021-05-30\n- Adds `setSkipTaskbar` to the window API.\n  - [e06aa277](https://www.github.com/tauri-apps/tauri/commit/e06aa277384450cfef617c0e57b0d5d403bb1e7f) feat(core): add `set_skip_taskbar` API on 2021-05-30\n- Adds `skipTaskbar?: boolean` to the WindowOptions interface.\n  - [5525b03a](https://www.github.com/tauri-apps/tauri/commit/5525b03a78a2232c650043fbd9894ce1553cad41) feat(core): add `skip_taskbar` API to the WindowBuilder/WindowOptions on 2021-05-30\n- Adds `center?: boolean` to `WindowOptions` and `center()` API to the `appWindow`.\n  - [5cba6eb4](https://www.github.com/tauri-apps/tauri/commit/5cba6eb4d28d53f06855d60d4d0eae6b95233ccf) feat(core): add window `center` API, closes [#1822](https://www.github.com/tauri-apps/tauri/pull/1822) ([#1954](https://www.github.com/tauri-apps/tauri/pull/1954)) on 2021-06-05\n- Adds `clipboard` APIs (write and read text).\n  - [285bf64b](https://www.github.com/tauri-apps/tauri/commit/285bf64bf9569efb2df904c69c6df405ff0d62e2) feat(core): add clipboard writeText and readText APIs ([#2035](https://www.github.com/tauri-apps/tauri/pull/2035)) on 2021-06-21\n  - [dee71ad5](https://www.github.com/tauri-apps/tauri/commit/dee71ad58349f699995cc9077b79032bacc6afcb) fix(workflows): update docs workflow syntax ([#2054](https://www.github.com/tauri-apps/tauri/pull/2054)) on 2021-06-23\n- The `http` APIs now resolve the returned promise when the API call finishes with an error status code.\n  - [47f75584](https://www.github.com/tauri-apps/tauri/commit/47f7558417cc654bdb1d018127e8900bc4eac622) fix(core): resolve HTTP API on non-ok status code, fix binary response, closes [#2046](https://www.github.com/tauri-apps/tauri/pull/2046) ([#2053](https://www.github.com/tauri-apps/tauri/pull/2053)) on 2021-06-23\n- Improve RPC security by requiring a numeric code to invoke commands. The codes are generated by the Rust side and injected into the app's code using a closure, so external scripts can't access the backend. This change doesn't protect `withGlobalTauri` (`window.__TAURI__`) usage.\n  - [160fb052](https://www.github.com/tauri-apps/tauri/commit/160fb0529fd31d755574ae30fbdf01fa221a2acb) feat(core): improve RPC security, closes [#814](https://www.github.com/tauri-apps/tauri/pull/814) ([#2047](https://www.github.com/tauri-apps/tauri/pull/2047)) on 2021-06-22\n- Mark the `WebviewWindow` constructor as public.\n  - [4aeb936e](https://www.github.com/tauri-apps/tauri/commit/4aeb936e9b60b895d383597dc698ee5d638436f9) fix(api): `WebviewWindow` constructor is public ([#1888](https://www.github.com/tauri-apps/tauri/pull/1888)) on 2021-05-21\n- Validate arguments on the window `setLocation`, `setSize`, `setMinSize` and `setMaxSize` API.\n  - [7616e6cc](https://www.github.com/tauri-apps/tauri/commit/7616e6cc7bcd49f688b0d00fdc33c94b7b93713d) feat(api): validate window API `size` and `location` arguments ([#1846](https://www.github.com/tauri-apps/tauri/pull/1846)) on 2021-05-17\n\n## \\[1.0.0-beta.1]\n\n- Adds `package.json` to the `exports` object.\n  - [ab1ea96](https://www.github.com/tauri-apps/tauri/commit/ab1ea964786e1781c922582b059c555b6072f1a0) chore(api): add `package.json` to the `exports` field ([#1807](https://www.github.com/tauri-apps/tauri/pull/1807)) on 2021-05-12\n\n## \\[1.0.0-beta.0]\n\n- CommonJS chunks are now properly exported with `.cjs` extension\n  - [ddcd923](https://www.github.com/tauri-apps/tauri/commit/ddcd9233bd6f499aa7f22484d6c151b01778bc1b) fix(api): export commonjs chunks with `.cjs` extension, fix [#1625](https://www.github.com/tauri-apps/tauri/pull/1625) ([#1627](https://www.github.com/tauri-apps/tauri/pull/1627)) on 2021-04-26\n- Adds `transparent?: boolean` to the `WindowOptions` interface.\n  - [08c1c5c](https://www.github.com/tauri-apps/tauri/commit/08c1c5ca5c0ebe17ea98689a5fe3b7e47a98e955) fix(api): missing `transparent` flag on `WindowOptions` ([#1764](https://www.github.com/tauri-apps/tauri/pull/1764)) on 2021-05-10\n- Adds `options` argument to the shell command API (`env` and `cwd` configuration).\n  - [721e98f](https://www.github.com/tauri-apps/tauri/commit/721e98f175567b360c86f30565ab1b9d08e7cf85) feat(core): add env, cwd to the command API, closes [#1634](https://www.github.com/tauri-apps/tauri/pull/1634) ([#1635](https://www.github.com/tauri-apps/tauri/pull/1635)) on 2021-04-28\n- Adds `startDragging` API on the window module.\n  - [c31f097](https://www.github.com/tauri-apps/tauri/commit/c31f0978c535f794fffb75a121e69a323e70b06e) refactor: update to wry 0.9 ([#1630](https://www.github.com/tauri-apps/tauri/pull/1630)) on 2021-04-28\n- Move `exit` and `relaunch` APIs from `app` to `process` module.\n  - [b0bb796](https://www.github.com/tauri-apps/tauri/commit/b0bb796a42e2560233aea47ce6ced54ac238eb53) refactor: rename `command` mod to `process`, move restart_application ([#1667](https://www.github.com/tauri-apps/tauri/pull/1667)) on 2021-04-30\n- The window management API was refactored: removed `setX`, `setY`, `setWidth`, `setHeight` APIs, renamed `resize` to `setSize` and the size and position APIs now allow defining both logical and physical values.\n  - [6bfac86](https://www.github.com/tauri-apps/tauri/commit/6bfac866a703f1499a64237fb29b2625703f4e22) refactor(core): add window getters, physical & logical sizes/positions ([#1723](https://www.github.com/tauri-apps/tauri/pull/1723)) on 2021-05-05\n- Adds window getters.\n  - [6bfac86](https://www.github.com/tauri-apps/tauri/commit/6bfac866a703f1499a64237fb29b2625703f4e22) refactor(core): add window getters, physical & logical sizes/positions ([#1723](https://www.github.com/tauri-apps/tauri/pull/1723)) on 2021-05-05\n\n## \\[1.0.0-beta-rc.3]\n\n- Fixes distribution of the `@tauri-apps/api` package for older bundlers.\n  - [7f998d0](https://www.github.com/tauri-apps/tauri/commit/7f998d08e3ab8823c99190fa283bdfa2c4f2749b) fix(api): distribution ([#1582](https://www.github.com/tauri-apps/tauri/pull/1582)) on 2021-04-22\n- Update minimum Node.js version to v12.13.0\n  - [1f089fb](https://www.github.com/tauri-apps/tauri/commit/1f089fb4f964c673dcab5784bdf1da2833487a7c) chore: update minimum nodejs version to 12.13.0 ([#1562](https://www.github.com/tauri-apps/tauri/pull/1562)) on 2021-04-21\n\n## \\[1.0.0-beta-rc.2]\n\n- TS was wrongly re-exporting the module.\n  - [fcb3b48](https://www.github.com/tauri-apps/tauri/commit/fcb3b4857efa17d2a3717f32457e88b24520cc9b) fix: [#1512](https://www.github.com/tauri-apps/tauri/pull/1512) ([#1517](https://www.github.com/tauri-apps/tauri/pull/1517)) on 2021-04-19\n  - [ae14a3f](https://www.github.com/tauri-apps/tauri/commit/ae14a3ff51a742b6ab6f76bbfc21f385310f1dc6) fix: [#1517](https://www.github.com/tauri-apps/tauri/pull/1517) had the wrong package reference in the changefile ([#1538](https://www.github.com/tauri-apps/tauri/pull/1538)) on 2021-04-19\n\n## \\[1.0.0-beta-rc.1]\n\n- Missing the `files` property in the package.json which mean that the `dist` directory was not published and used.\n  - [b2569a7](https://www.github.com/tauri-apps/tauri/commit/b2569a729a3caa88bdba62abc31f0665e1323aaa) fix(js-api): dist ([#1498](https://www.github.com/tauri-apps/tauri/pull/1498)) on 2021-04-15\n\n## \\[1.0.0-beta-rc.0]\n\n- Add current working directory to the path api module.\n  - [52c2baf](https://www.github.com/tauri-apps/tauri/commit/52c2baf940773cf7c51647fb6f20d0f7df126115) feat: add current working directory to path api module ([#1375](https://www.github.com/tauri-apps/tauri/pull/1375)) on 2021-03-23\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- The shell process spawning API was rewritten and now includes stream access.\n  - [3713066](https://www.github.com/tauri-apps/tauri/commit/3713066e451bd30d0cc6f57bb437f08276f4c4ad) refactor(core): rewrite shell execute API, closes [#1229](https://www.github.com/tauri-apps/tauri/pull/1229) ([#1408](https://www.github.com/tauri-apps/tauri/pull/1408)) on 2021-03-31\n- The file dialog API now uses [rfd](https://github.com/PolyMeilex/rfd). The filter option is now an array of `{ name: string, extensions: string[] }`.\n  - [2326bcd](https://www.github.com/tauri-apps/tauri/commit/2326bcd399411f7f0eabdb7ade910be473adadae) refactor(core): use `nfd` for file dialogs, closes [#1251](https://www.github.com/tauri-apps/tauri/pull/1251) ([#1257](https://www.github.com/tauri-apps/tauri/pull/1257)) on 2021-02-18\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- The HTTP API was improved with client caching and better payload and response types.\n  - [a7bc472](https://www.github.com/tauri-apps/tauri/commit/a7bc472e994730071f960d09a12ac85296a080ae) refactor(core): improve HTTP API, closes [#1098](https://www.github.com/tauri-apps/tauri/pull/1098) ([#1237](https://www.github.com/tauri-apps/tauri/pull/1237)) on 2021-02-15\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- Update all code files to have our license header.\n  - [bf82136](https://www.github.com/tauri-apps/tauri/commit/bf8213646689175f8a158b956911f3a43e360690) feat(license): SPDX Headers ([#1449](https://www.github.com/tauri-apps/tauri/pull/1449)) on 2021-04-11\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n  - [aea6145](https://www.github.com/tauri-apps/tauri/commit/aea614587bddab930d552512b54e18624fbf573e) refactor(repo): add /tooling folder ([#1457](https://www.github.com/tauri-apps/tauri/pull/1457)) on 2021-04-12\n- Use secure RNG on callback function names.\n  - [c8992bb](https://www.github.com/tauri-apps/tauri/commit/c8992bb0bfb8eaeae8ebed444719f9c9372d39d4) refactor(api): use secure RNG, closes [#1356](https://www.github.com/tauri-apps/tauri/pull/1356) ([#1398](https://www.github.com/tauri-apps/tauri/pull/1398)) on 2021-03-30\n- The invoke function can now be called with the cmd as the first parameter and the args as the second.\n  - [427d170](https://www.github.com/tauri-apps/tauri/commit/427d170930ab711fd0ca82f7a73b524d6fdc222f) feat(api/invoke): separate cmd arg ([#1321](https://www.github.com/tauri-apps/tauri/pull/1321)) on 2021-03-04\n- Adds a global shortcut API.\n  - [855effa](https://www.github.com/tauri-apps/tauri/commit/855effadd9ebfb6bc1a3555ac7fc733f6f766b7a) feat(core): globalShortcut API ([#1232](https://www.github.com/tauri-apps/tauri/pull/1232)) on 2021-02-14\n  - [a6def70](https://www.github.com/tauri-apps/tauri/commit/a6def7066eec19c889b0f14cc1e475bf209a332e) Refactor(tauri): move tauri-api and tauri-updater to tauri ([#1455](https://www.github.com/tauri-apps/tauri/pull/1455)) on 2021-04-11\n- Added window management and window creation APIs.\n  - [a3d6dff](https://www.github.com/tauri-apps/tauri/commit/a3d6dff2163c7a45842253edd81dbc62248dc65d) feat(core): window API ([#1225](https://www.github.com/tauri-apps/tauri/pull/1225)) on 2021-02-13\n  - [641374b](https://www.github.com/tauri-apps/tauri/commit/641374b15343518cd835bd5ada811941c65dcf2e) feat(core): window creation at runtime ([#1249](https://www.github.com/tauri-apps/tauri/pull/1249)) on 2021-02-17\n"
  },
  {
    "path": "packages/api/LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "packages/api/LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2017 - Present Tauri Apps Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/api/README.md",
    "content": "# @tauri-apps/api\n\n <img align=\"right\" src=\"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\" height=\"128\" width=\"128\">\n\n[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev)\n[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri)\n[![lint js](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/lint-js.yml?label=lint%20js&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/lint-js.yml)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield)\n[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S)\n[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app)\n[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)\n[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri)\n\n| Component       | Version                                               |\n| --------------- | ----------------------------------------------------- |\n| @tauri-apps/api | ![](https://img.shields.io/npm/v/@tauri-apps/api.svg) |\n\n## About Tauri\n\nTauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.\n\nTauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.\n\n## This module\n\nThis is a typescript library that creates `cjs` and `esm` JavaScript endpoints for you to import into your Frontend framework so that the Webview can call and listen to backend activity. We also ship the pure typescript, because for some frameworks this is more optimal. It uses the message passing of webviews to their hosts.\n\nTo learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.\n\n## Installation\n\nThe preferred method is to install this module locally as a dependency:\n\n```\n$ pnpm add @tauri-apps/api\n$ yarn add @tauri-apps/api\n$ npm add @tauri-apps/api\n```\n\n## Semver\n\n**tauri** is following [Semantic Versioning 2.0](https://semver.org/).\n\n## Licenses\n\nCode: (c) 2019 - 2021 - The Tauri Programme within The Commons Conservancy.\n\nMIT or MIT/Apache 2.0 where applicable.\n\nLogo: CC-BY-NC-ND\n\n- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)\n"
  },
  {
    "path": "packages/api/eslint.config.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport globals from 'globals'\nimport eslint from '@eslint/js'\nimport prettierConfig from 'eslint-config-prettier'\nimport securityPlugin from 'eslint-plugin-security'\nimport tseslint from 'typescript-eslint'\n\n/** @type {import('eslint').Linter.Config} */\nexport default [\n  eslint.configs.recommended,\n  prettierConfig,\n  securityPlugin.configs.recommended,\n  ...tseslint.configs.recommendedTypeChecked,\n  {\n    languageOptions: {\n      globals: {\n        ...globals.node,\n        ...globals.jest,\n        __statics: true,\n        process: true\n      },\n      parserOptions: {\n        project: true,\n        tsconfigRootDir: import.meta.dirname\n      }\n    },\n    rules: {\n      'no-console': 'error',\n      'no-debugger': 'error',\n      'no-process-exit': 'off',\n      'security/detect-non-literal-fs-filename': 'warn',\n      'security/detect-unsafe-regex': 'error',\n      'security/detect-buffer-noassert': 'error',\n      'security/detect-child-process': 'warn',\n      'security/detect-disable-mustache-escape': 'error',\n      'security/detect-eval-with-expression': 'error',\n      'security/detect-no-csrf-before-method-override': 'error',\n      'security/detect-non-literal-regexp': 'error',\n      'security/detect-non-literal-require': 'warn',\n      'security/detect-object-injection': 'warn',\n      'security/detect-possible-timing-attacks': 'error',\n      'security/detect-pseudoRandomBytes': 'error',\n      'space-before-function-paren': 'off',\n      '@typescript-eslint/default-param-last': 'off',\n      '@typescript-eslint/strict-boolean-expressions': 0,\n      'no-return-await': 'warn',\n      '@typescript-eslint/return-await': 'off',\n      '@typescript-eslint/no-unused-vars': [\n        'error',\n        {\n          argsIgnorePattern: '^_',\n          varsIgnorePattern: '^_',\n          caughtErrorsIgnorePattern: '^_'\n        }\n      ]\n    }\n  }\n]\n"
  },
  {
    "path": "packages/api/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/api\",\n  \"version\": \"2.10.1\",\n  \"description\": \"Tauri API definitions\",\n  \"funding\": {\n    \"type\": \"opencollective\",\n    \"url\": \"https://opencollective.com/tauri\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"type\": \"module\",\n  \"main\": \"./index.cjs\",\n  \"module\": \"./index.js\",\n  \"types\": \"./index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./index.js\",\n      \"require\": \"./index.cjs\",\n      \"types\": \"./index.d.ts\"\n    },\n    \"./*\": {\n      \"import\": \"./*.js\",\n      \"require\": \"./*.cjs\",\n      \"types\": \"./*.d.ts\"\n    },\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"build\": \"rollup -c --configPlugin typescript\",\n    \"build:debug\": \"rollup -c --configPlugin typescript\",\n    \"npm-pack\": \"pnpm build && cd ./dist && npm pack\",\n    \"npm-publish\": \"pnpm build && cd ./dist && pnpm publish --access public --loglevel silly --no-git-checks\",\n    \"ts:check\": \"tsc --noEmit\",\n    \"eslint:check\": \"eslint src/**/*.ts\",\n    \"eslint:fix\": \"eslint src/**/*.ts --fix\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^10.0.1\",\n    \"@rollup/plugin-terser\": \"1.0.0\",\n    \"@rollup/plugin-typescript\": \"12.3.0\",\n    \"@types/eslint\": \"^9.6.1\",\n    \"@types/node\": \"^24.11.0\",\n    \"eslint\": \"^10.0.2\",\n    \"eslint-config-prettier\": \"10.1.8\",\n    \"eslint-plugin-security\": \"4.0.0\",\n    \"fast-glob\": \"3.3.3\",\n    \"globals\": \"^17.4.0\",\n    \"rollup\": \"4.59.0\",\n    \"tslib\": \"^2.8.1\",\n    \"typescript\": \"^5.9.3\",\n    \"typescript-eslint\": \"^8.56.1\"\n  }\n}\n"
  },
  {
    "path": "packages/api/rollup.config.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport { defineConfig, Plugin, RollupLog } from 'rollup'\nimport typescript from '@rollup/plugin-typescript'\nimport terser from '@rollup/plugin-terser'\nimport fg from 'fast-glob'\nimport { basename, dirname, join } from 'path'\nimport { copyFileSync, opendirSync, rmSync, Dir } from 'fs'\nimport { fileURLToPath } from 'url'\n\n// cleanup dist dir\nconst __dirname = fileURLToPath(new URL('.', import.meta.url))\ncleanDir(join(__dirname, './dist'))\n\nconst modules = fg.sync(['!./src/*.d.ts', './src/*.ts'])\n\nexport default defineConfig([\n  {\n    input: Object.fromEntries(modules.map((p) => [basename(p, '.ts'), p])),\n    output: [\n      {\n        format: 'esm',\n        dir: './dist',\n        preserveModules: true,\n        preserveModulesRoot: 'src',\n        entryFileNames: (chunkInfo) => {\n          if (chunkInfo.name.includes('node_modules')) {\n            return externalLibPath(chunkInfo.name) + '.js'\n          }\n\n          return '[name].js'\n        }\n      },\n      {\n        format: 'cjs',\n        dir: './dist',\n        preserveModules: true,\n        preserveModulesRoot: 'src',\n        entryFileNames: (chunkInfo) => {\n          if (chunkInfo.name.includes('node_modules')) {\n            return externalLibPath(chunkInfo.name) + '.cjs'\n          }\n\n          return '[name].cjs'\n        }\n      }\n    ],\n    plugins: [\n      typescript({\n        declaration: true,\n        declarationDir: './dist',\n        rootDir: 'src'\n      }),\n      makeFlatPackageInDist()\n    ],\n    onwarn\n  },\n\n  {\n    input: 'src/index.ts',\n    output: {\n      format: 'iife',\n      name: '__TAURI_IIFE__',\n      footer: 'window.__TAURI__ = __TAURI_IIFE__',\n      file: '../../crates/tauri/scripts/bundle.global.js'\n    },\n    plugins: [typescript(), terser()],\n    onwarn\n  }\n])\n\nfunction externalLibPath(path: string) {\n  return `external/${basename(dirname(path))}/${basename(path)}`\n}\n\nfunction onwarn(warning: RollupLog) {\n  // deny warnings by default\n  throw Object.assign(new Error(), warning)\n}\n\nfunction makeFlatPackageInDist(): Plugin {\n  return {\n    name: 'makeFlatPackageInDist',\n    writeBundle() {\n      // copy necessary files like `CHANGELOG.md` , `README.md` and Licenses to `./dist`\n      fg.sync('(LICENSE*|*.md|package.json)').forEach((f) =>\n        copyFileSync(f, `dist/${f}`)\n      )\n    }\n  }\n}\n\nfunction cleanDir(path: string) {\n  let dir: Dir\n  try {\n    dir = opendirSync(path)\n  } catch (err: any) {\n    switch (err.code) {\n      case 'ENOENT':\n        return // Noop when directory don't exists.\n      case 'ENOTDIR':\n        throw new Error(`'${path}' is not a directory.`)\n      default:\n        throw err\n    }\n  }\n\n  let file = dir.readSync()\n  while (file) {\n    const filePath = join(path, file.name)\n    rmSync(filePath, { recursive: true })\n    file = dir.readSync()\n  }\n  dir.closeSync()\n}\n"
  },
  {
    "path": "packages/api/src/app.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport { addPluginListener, invoke, PluginListener } from './core'\nimport { Image } from './image'\nimport { Theme } from './window'\n\n/**\n * Identifier type used for data stores on macOS and iOS.\n *\n * Represents a 128-bit identifier, commonly expressed as a 16-byte UUID.\n */\nexport type DataStoreIdentifier = [\n  number,\n  number,\n  number,\n  number,\n  number,\n  number,\n  number,\n  number,\n  number,\n  number,\n  number,\n  number,\n  number,\n  number,\n  number,\n  number\n]\n\n/**\n * Bundle type of the current application.\n */\nexport enum BundleType {\n  /** Windows NSIS */\n  Nsis = 'nsis',\n  /** Windows MSI */\n  Msi = 'msi',\n  /** Linux Debian package */\n  Deb = 'deb',\n  /** Linux RPM */\n  Rpm = 'rpm',\n  /** Linux AppImage */\n  AppImage = 'appimage',\n  /** macOS app bundle */\n  App = 'app'\n}\n\n/**\n * Application metadata and related APIs.\n *\n * @module\n */\n\n/**\n * Gets the application version.\n * @example\n * ```typescript\n * import { getVersion } from '@tauri-apps/api/app';\n * const appVersion = await getVersion();\n * ```\n *\n * @since 1.0.0\n */\nasync function getVersion(): Promise<string> {\n  return invoke('plugin:app|version')\n}\n\n/**\n * Gets the application name.\n * @example\n * ```typescript\n * import { getName } from '@tauri-apps/api/app';\n * const appName = await getName();\n * ```\n *\n * @since 1.0.0\n */\nasync function getName(): Promise<string> {\n  return invoke('plugin:app|name')\n}\n\n/**\n * Gets the Tauri framework version used by this application.\n *\n * @example\n * ```typescript\n * import { getTauriVersion } from '@tauri-apps/api/app';\n * const tauriVersion = await getTauriVersion();\n * ```\n *\n * @since 1.0.0\n */\nasync function getTauriVersion(): Promise<string> {\n  return invoke('plugin:app|tauri_version')\n}\n\n/**\n * Gets the application identifier.\n * @example\n * ```typescript\n * import { getIdentifier } from '@tauri-apps/api/app';\n * const identifier = await getIdentifier();\n * ```\n *\n * @returns The application identifier as configured in `tauri.conf.json`.\n *\n * @since 2.4.0\n */\nasync function getIdentifier(): Promise<string> {\n  return invoke('plugin:app|identifier')\n}\n\n/**\n * Shows the application on macOS. This function does not automatically\n * focus any specific app window.\n *\n * @example\n * ```typescript\n * import { show } from '@tauri-apps/api/app';\n * await show();\n * ```\n *\n * @since 1.2.0\n */\nasync function show(): Promise<void> {\n  return invoke('plugin:app|app_show')\n}\n\n/**\n * Hides the application on macOS.\n *\n * @example\n * ```typescript\n * import { hide } from '@tauri-apps/api/app';\n * await hide();\n * ```\n *\n * @since 1.2.0\n */\nasync function hide(): Promise<void> {\n  return invoke('plugin:app|app_hide')\n}\n\n/**\n * Fetches the data store identifiers on macOS and iOS.\n *\n * See https://developer.apple.com/documentation/webkit/wkwebsitedatastore for more information.\n *\n * @example\n * ```typescript\n * import { fetchDataStoreIdentifiers } from '@tauri-apps/api/app';\n * const ids = await fetchDataStoreIdentifiers();\n * ```\n *\n * @since 2.4.0\n */\nasync function fetchDataStoreIdentifiers(): Promise<DataStoreIdentifier[]> {\n  return invoke('plugin:app|fetch_data_store_identifiers')\n}\n\n/**\n * Removes the data store with the given identifier.\n *\n * Note that any webview using this data store should be closed before running this API.\n *\n * See https://developer.apple.com/documentation/webkit/wkwebsitedatastore for more information.\n *\n * @example\n * ```typescript\n * import { fetchDataStoreIdentifiers, removeDataStore } from '@tauri-apps/api/app';\n * for (const id of (await fetchDataStoreIdentifiers())) {\n *   await removeDataStore(id);\n * }\n * ```\n *\n * @since 2.4.0\n */\nasync function removeDataStore(uuid: DataStoreIdentifier): Promise<void> {\n  return invoke('plugin:app|remove_data_store', { uuid })\n}\n\n/**\n * Gets the default window icon.\n *\n * @example\n * ```typescript\n * import { defaultWindowIcon } from '@tauri-apps/api/app';\n * const icon = await defaultWindowIcon();\n * ```\n *\n * @since 2.0.0\n */\n\nasync function defaultWindowIcon(): Promise<Image | null> {\n  return invoke<number | null>('plugin:app|default_window_icon').then((rid) =>\n    rid ? new Image(rid) : null\n  )\n}\n\n/**\n * Sets the application's theme. Pass in `null` or `undefined` to follow\n * the system theme.\n *\n * @example\n * ```typescript\n * import { setTheme } from '@tauri-apps/api/app';\n * await setTheme('dark');\n * ```\n *\n * #### Platform-specific\n *\n * - **iOS / Android:** Unsupported.\n *\n * @since 2.0.0\n */\nasync function setTheme(theme?: Theme | null): Promise<void> {\n  return invoke('plugin:app|set_app_theme', { theme })\n}\n\n/**\n * Sets the dock visibility for the application on macOS.\n *\n * @param visible - Whether the dock should be visible or not.\n *\n * @example\n * ```typescript\n * import { setDockVisibility } from '@tauri-apps/api/app';\n * await setDockVisibility(false);\n * ```\n *\n * @since 2.5.0\n */\nasync function setDockVisibility(visible: boolean): Promise<void> {\n  return invoke('plugin:app|set_dock_visibility', { visible })\n}\n\n/**\n * Gets the application bundle type.\n *\n * @example\n * ```typescript\n * import { getBundleType } from '@tauri-apps/api/app';\n * const type = await getBundleType();\n * ```\n *\n * @since 2.5.0\n */\nasync function getBundleType(): Promise<BundleType> {\n  return invoke('plugin:app|bundle_type')\n}\n\n/**\n * Payload for the onBackButtonPress event.\n */\ntype OnBackButtonPressPayload = {\n  /** Whether the webview canGoBack property is true. */\n  canGoBack: boolean\n}\n\n/**\n * Listens to the backButton event on Android.\n * @param handler\n */\nasync function onBackButtonPress(\n  handler: (payload: OnBackButtonPressPayload) => void\n): Promise<PluginListener> {\n  return addPluginListener<OnBackButtonPressPayload>(\n    'app',\n    'back-button',\n    handler\n  )\n}\n\nexport {\n  getName,\n  getVersion,\n  getTauriVersion,\n  getIdentifier,\n  show,\n  hide,\n  defaultWindowIcon,\n  setTheme,\n  fetchDataStoreIdentifiers,\n  removeDataStore,\n  setDockVisibility,\n  getBundleType,\n  type OnBackButtonPressPayload,\n  onBackButtonPress\n}\n"
  },
  {
    "path": "packages/api/src/core.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/**\n * Invoke your custom commands.\n *\n * This package is also accessible with `window.__TAURI__.core` when [`app.withGlobalTauri`](https://v2.tauri.app/reference/config/#withglobaltauri) in `tauri.conf.json` is set to `true`.\n * @module\n */\n\n/**\n * A key to be used to implement a special function\n * on your types that define how your type should be serialized\n * when passing across the IPC.\n * @example\n * Given a type in Rust that looks like this\n * ```rs\n * #[derive(serde::Serialize, serde::Deserialize)\n * enum UserId {\n *   String(String),\n *   Number(u32),\n * }\n * ```\n * `UserId::String(\"id\")` would be serialized into `{ String: \"id\" }`\n * and so we need to pass the same structure back to Rust\n * ```ts\n * import { SERIALIZE_TO_IPC_FN } from \"@tauri-apps/api/core\"\n *\n * class UserIdString {\n *   id\n *   constructor(id) {\n *     this.id = id\n *   }\n *\n *   [SERIALIZE_TO_IPC_FN]() {\n *     return { String: this.id }\n *   }\n * }\n *\n * class UserIdNumber {\n *   id\n *   constructor(id) {\n *     this.id = id\n *   }\n *\n *   [SERIALIZE_TO_IPC_FN]() {\n *     return { Number: this.id }\n *   }\n * }\n *\n * type UserId = UserIdString | UserIdNumber\n * ```\n *\n */\n// if this value changes, make sure to update it in:\n// 1. ipc.js\n// 2. process-ipc-message-fn.js\nexport const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__'\n\n/**\n * Stores the callback in a known location, and returns an identifier that can be passed to the backend.\n * The backend uses the identifier to `eval()` the callback.\n *\n * @return An unique identifier associated with the callback function.\n *\n * @since 1.0.0\n */\nfunction transformCallback<T = unknown>(\n  // TODO: Make this not optional in v3\n  callback?: (response: T) => void,\n  once = false\n): number {\n  return window.__TAURI_INTERNALS__.transformCallback(callback, once)\n}\n\nclass Channel<T = unknown> {\n  /** The callback id returned from {@linkcode transformCallback} */\n  id: number\n  #onmessage: (response: T) => void\n\n  // the index is used as a mechanism to preserve message order\n  #nextMessageIndex = 0\n  #pendingMessages: T[] = []\n  #messageEndIndex: number | undefined\n\n  constructor(onmessage?: (response: T) => void) {\n    this.#onmessage = onmessage || (() => {})\n\n    this.id = transformCallback<\n      // Normal message\n      | { message: T; index: number }\n      // Message when the channel gets dropped in the rust side\n      | { end: true; index: number }\n    >((rawMessage) => {\n      const index = rawMessage.index\n\n      if ('end' in rawMessage) {\n        if (index == this.#nextMessageIndex) {\n          this.cleanupCallback()\n        } else {\n          this.#messageEndIndex = index\n        }\n        return\n      }\n\n      const message = rawMessage.message\n      // Process the message if we're at the right order\n      if (index == this.#nextMessageIndex) {\n        this.#onmessage(message)\n        this.#nextMessageIndex += 1\n\n        // process pending messages\n        while (this.#nextMessageIndex in this.#pendingMessages) {\n          const message = this.#pendingMessages[this.#nextMessageIndex]\n          this.#onmessage(message)\n          // eslint-disable-next-line @typescript-eslint/no-array-delete\n          delete this.#pendingMessages[this.#nextMessageIndex]\n          this.#nextMessageIndex += 1\n        }\n\n        if (this.#nextMessageIndex === this.#messageEndIndex) {\n          this.cleanupCallback()\n        }\n      }\n      // Queue the message if we're not\n      else {\n        // eslint-disable-next-line security/detect-object-injection\n        this.#pendingMessages[index] = message\n      }\n    })\n  }\n\n  private cleanupCallback() {\n    window.__TAURI_INTERNALS__.unregisterCallback(this.id)\n  }\n\n  set onmessage(handler: (response: T) => void) {\n    this.#onmessage = handler\n  }\n\n  get onmessage(): (response: T) => void {\n    return this.#onmessage\n  }\n\n  [SERIALIZE_TO_IPC_FN]() {\n    return `__CHANNEL__:${this.id}`\n  }\n\n  toJSON(): string {\n    // eslint-disable-next-line security/detect-object-injection\n    return this[SERIALIZE_TO_IPC_FN]()\n  }\n}\n\nclass PluginListener {\n  plugin: string\n  event: string\n  channelId: number\n\n  constructor(plugin: string, event: string, channelId: number) {\n    this.plugin = plugin\n    this.event = event\n    this.channelId = channelId\n  }\n\n  async unregister(): Promise<void> {\n    return invoke(`plugin:${this.plugin}|remove_listener`, {\n      event: this.event,\n      channelId: this.channelId\n    })\n  }\n}\n\n/**\n * Adds a listener to a plugin event.\n *\n * @returns The listener object to stop listening to the events.\n *\n * @since 2.0.0\n */\nasync function addPluginListener<T>(\n  plugin: string,\n  event: string,\n  cb: (payload: T) => void\n): Promise<PluginListener> {\n  const handler = new Channel<T>(cb)\n  try {\n    await invoke(`plugin:${plugin}|register_listener`, {\n      event,\n      handler\n    })\n    return new PluginListener(plugin, event, handler.id)\n  } catch {\n    // TODO(v3): remove this fallback\n    // note: we must try with camelCase here for backwards compatibility\n    await invoke(`plugin:${plugin}|registerListener`, { event, handler })\n    return new PluginListener(plugin, event, handler.id)\n  }\n}\n\ntype PermissionState = 'granted' | 'denied' | 'prompt' | 'prompt-with-rationale'\n\n/**\n * Get permission state for a plugin.\n *\n * This should be used by plugin authors to wrap their actual implementation.\n */\nasync function checkPermissions<T>(plugin: string): Promise<T> {\n  return invoke(`plugin:${plugin}|check_permissions`)\n}\n\n/**\n * Request permissions.\n *\n * This should be used by plugin authors to wrap their actual implementation.\n */\nasync function requestPermissions<T>(plugin: string): Promise<T> {\n  return invoke(`plugin:${plugin}|request_permissions`)\n}\n\n/**\n * Command arguments.\n *\n * @since 1.0.0\n */\ntype InvokeArgs = Record<string, unknown> | number[] | ArrayBuffer | Uint8Array\n\n/**\n * @since 2.0.0\n */\ninterface InvokeOptions {\n  headers: HeadersInit\n}\n\n/**\n * Sends a message to the backend.\n * @example\n * ```typescript\n * import { invoke } from '@tauri-apps/api/core';\n * await invoke('login', { user: 'tauri', password: 'poiwe3h4r5ip3yrhtew9ty' });\n * ```\n *\n * @param cmd The command name.\n * @param args The optional arguments to pass to the command.\n * @param options The request options.\n * @return A promise resolving or rejecting to the backend response.\n *\n * @since 1.0.0\n */\nasync function invoke<T>(\n  cmd: string,\n  args: InvokeArgs = {},\n  options?: InvokeOptions\n): Promise<T> {\n  return window.__TAURI_INTERNALS__.invoke(cmd, args, options)\n}\n\n/**\n * Convert a device file path to an URL that can be loaded by the webview.\n * Note that `asset:` and `http://asset.localhost` must be added to [`app.security.csp`](https://v2.tauri.app/reference/config/#csp-1) in `tauri.conf.json`.\n * Example CSP value: `\"csp\": \"default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost\"` to use the asset protocol on image sources.\n *\n * Additionally, `\"enable\" : \"true\"` must be added to [`app.security.assetProtocol`](https://v2.tauri.app/reference/config/#assetprotocolconfig)\n * in `tauri.conf.json` and its access scope must be defined on the `scope` array on the same `assetProtocol` object.\n *\n * @param  filePath The file path.\n * @param  protocol The protocol to use. Defaults to `asset`. You only need to set this when using a custom protocol.\n * @example\n * ```typescript\n * import { appDataDir, join } from '@tauri-apps/api/path';\n * import { convertFileSrc } from '@tauri-apps/api/core';\n * const appDataDirPath = await appDataDir();\n * const filePath = await join(appDataDirPath, 'assets/video.mp4');\n * const assetUrl = convertFileSrc(filePath);\n *\n * const video = document.getElementById('my-video');\n * const source = document.createElement('source');\n * source.type = 'video/mp4';\n * source.src = assetUrl;\n * video.appendChild(source);\n * video.load();\n * ```\n *\n * @return the URL that can be used as source on the webview.\n *\n * @since 1.0.0\n */\nfunction convertFileSrc(filePath: string, protocol = 'asset'): string {\n  return window.__TAURI_INTERNALS__.convertFileSrc(filePath, protocol)\n}\n\n/**\n * A rust-backed resource stored through `tauri::Manager::resources_table` API.\n *\n * The resource lives in the main process and does not exist\n * in the Javascript world, and thus will not be cleaned up automatically\n * except on application exit. If you want to clean it up early, call {@linkcode Resource.close}\n *\n * @example\n * ```typescript\n * import { Resource, invoke } from '@tauri-apps/api/core';\n * export class DatabaseHandle extends Resource {\n *   static async open(path: string): Promise<DatabaseHandle> {\n *     const rid: number = await invoke('open_db', { path });\n *     return new DatabaseHandle(rid);\n *   }\n *\n *   async execute(sql: string): Promise<void> {\n *     await invoke('execute_sql', { rid: this.rid, sql });\n *   }\n * }\n * ```\n */\nexport class Resource {\n  readonly #rid: number\n\n  get rid(): number {\n    return this.#rid\n  }\n\n  constructor(rid: number) {\n    this.#rid = rid\n  }\n\n  /**\n   * Destroys and cleans up this resource from memory.\n   * **You should not call any method on this object anymore and should drop any reference to it.**\n   */\n  async close(): Promise<void> {\n    return invoke('plugin:resources|close', {\n      rid: this.rid\n    })\n  }\n}\n\nfunction isTauri(): boolean {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access\n  return !!((globalThis as any) || window).isTauri\n}\n\nexport type { InvokeArgs, InvokeOptions }\n\nexport {\n  transformCallback,\n  Channel,\n  PluginListener,\n  addPluginListener,\n  PermissionState,\n  checkPermissions,\n  requestPermissions,\n  invoke,\n  convertFileSrc,\n  isTauri\n}\n"
  },
  {
    "path": "packages/api/src/dpi.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport { SERIALIZE_TO_IPC_FN } from './core'\n\n/**\n * A size represented in logical pixels.\n * Logical pixels are scaled according to the window's DPI scale.\n * Most browser APIs (i.e. `MouseEvent`'s `clientX`) will return logical pixels.\n *\n * For logical-pixel-based position, see {@linkcode LogicalPosition}.\n *\n * @since 2.0.0\n */\nclass LogicalSize {\n  readonly type = 'Logical'\n  width: number\n  height: number\n\n  constructor(width: number, height: number)\n  constructor(object: { Logical: { width: number; height: number } })\n  constructor(object: { width: number; height: number })\n  constructor(\n    ...args:\n      | [number, number]\n      | [{ width: number; height: number }]\n      | [{ Logical: { width: number; height: number } }]\n  ) {\n    if (args.length === 1) {\n      if ('Logical' in args[0]) {\n        this.width = args[0].Logical.width\n        this.height = args[0].Logical.height\n      } else {\n        this.width = args[0].width\n        this.height = args[0].height\n      }\n    } else {\n      this.width = args[0]\n      this.height = args[1]\n    }\n  }\n\n  /**\n   * Converts the logical size to a physical one.\n   * @example\n   * ```typescript\n   * import { LogicalSize } from '@tauri-apps/api/dpi';\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   *\n   * const appWindow = getCurrentWindow();\n   * const factor = await appWindow.scaleFactor();\n   * const size = new LogicalSize(400, 500);\n   * const physical = size.toPhysical(factor);\n   * ```\n   *\n   * @since 2.0.0\n   */\n  toPhysical(scaleFactor: number): PhysicalSize {\n    return new PhysicalSize(this.width * scaleFactor, this.height * scaleFactor)\n  }\n\n  [SERIALIZE_TO_IPC_FN]() {\n    return {\n      width: this.width,\n      height: this.height\n    }\n  }\n\n  toJSON() {\n    // eslint-disable-next-line security/detect-object-injection\n    return this[SERIALIZE_TO_IPC_FN]()\n  }\n}\n\n/**\n * A size represented in physical pixels.\n *\n * Physical pixels represent actual screen pixels, and are DPI-independent.\n * For high-DPI windows, this means that any point in the window on the screen\n * will have a different position in logical pixels {@linkcode LogicalSize}.\n *\n * For physical-pixel-based position, see {@linkcode PhysicalPosition}.\n *\n * @since 2.0.0\n */\nclass PhysicalSize {\n  readonly type = 'Physical'\n  width: number\n  height: number\n\n  constructor(width: number, height: number)\n  constructor(object: { Physical: { width: number; height: number } })\n  constructor(object: { width: number; height: number })\n  constructor(\n    ...args:\n      | [number, number]\n      | [{ width: number; height: number }]\n      | [{ Physical: { width: number; height: number } }]\n  ) {\n    if (args.length === 1) {\n      if ('Physical' in args[0]) {\n        this.width = args[0].Physical.width\n        this.height = args[0].Physical.height\n      } else {\n        this.width = args[0].width\n        this.height = args[0].height\n      }\n    } else {\n      this.width = args[0]\n      this.height = args[1]\n    }\n  }\n\n  /**\n   * Converts the physical size to a logical one.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const appWindow = getCurrentWindow();\n   * const factor = await appWindow.scaleFactor();\n   * const size = await appWindow.innerSize(); // PhysicalSize\n   * const logical = size.toLogical(factor);\n   * ```\n   */\n  toLogical(scaleFactor: number): LogicalSize {\n    return new LogicalSize(this.width / scaleFactor, this.height / scaleFactor)\n  }\n\n  [SERIALIZE_TO_IPC_FN]() {\n    return {\n      width: this.width,\n      height: this.height\n    }\n  }\n\n  toJSON() {\n    // eslint-disable-next-line security/detect-object-injection\n    return this[SERIALIZE_TO_IPC_FN]()\n  }\n}\n\n/**\n * A size represented either in physical or in logical pixels.\n *\n * This type is basically a union type of {@linkcode LogicalSize} and {@linkcode PhysicalSize}\n * but comes in handy when using `tauri::Size` in Rust as an argument to a command, as this class\n * automatically serializes into a valid format so it can be deserialized correctly into `tauri::Size`\n *\n * So instead of\n * ```typescript\n * import { invoke } from '@tauri-apps/api/core';\n * import { LogicalSize, PhysicalSize } from '@tauri-apps/api/dpi';\n *\n * const size: LogicalSize | PhysicalSize = someFunction(); // where someFunction returns either LogicalSize or PhysicalSize\n * const validSize = size instanceof LogicalSize\n *   ? { Logical: { width: size.width, height: size.height } }\n *   : { Physical: { width: size.width, height: size.height } }\n * await invoke(\"do_something_with_size\", { size: validSize });\n * ```\n *\n * You can just use {@linkcode Size}\n * ```typescript\n * import { invoke } from '@tauri-apps/api/core';\n * import { LogicalSize, PhysicalSize, Size } from '@tauri-apps/api/dpi';\n *\n * const size: LogicalSize | PhysicalSize = someFunction(); // where someFunction returns either LogicalSize or PhysicalSize\n * const validSize = new Size(size);\n * await invoke(\"do_something_with_size\", { size: validSize });\n * ```\n *\n * @since 2.1.0\n */\nclass Size {\n  size: LogicalSize | PhysicalSize\n\n  constructor(size: LogicalSize | PhysicalSize) {\n    this.size = size\n  }\n\n  toLogical(scaleFactor: number): LogicalSize {\n    return this.size instanceof LogicalSize\n      ? this.size\n      : this.size.toLogical(scaleFactor)\n  }\n\n  toPhysical(scaleFactor: number): PhysicalSize {\n    return this.size instanceof PhysicalSize\n      ? this.size\n      : this.size.toPhysical(scaleFactor)\n  }\n\n  [SERIALIZE_TO_IPC_FN]() {\n    return {\n      [`${this.size.type}`]: {\n        width: this.size.width,\n        height: this.size.height\n      }\n    }\n  }\n\n  toJSON() {\n    // eslint-disable-next-line security/detect-object-injection\n    return this[SERIALIZE_TO_IPC_FN]()\n  }\n}\n\n/**\n * A position represented in logical pixels.\n * For an explanation of what logical pixels are, see description of {@linkcode LogicalSize}.\n *\n * @since 2.0.0\n */\nclass LogicalPosition {\n  readonly type = 'Logical'\n  x: number\n  y: number\n\n  constructor(x: number, y: number)\n  constructor(object: { Logical: { x: number; y: number } })\n  constructor(object: { x: number; y: number })\n  constructor(\n    ...args:\n      | [number, number]\n      | [{ x: number; y: number }]\n      | [{ Logical: { x: number; y: number } }]\n  ) {\n    if (args.length === 1) {\n      if ('Logical' in args[0]) {\n        this.x = args[0].Logical.x\n        this.y = args[0].Logical.y\n      } else {\n        this.x = args[0].x\n        this.y = args[0].y\n      }\n    } else {\n      this.x = args[0]\n      this.y = args[1]\n    }\n  }\n\n  /**\n   * Converts the logical position to a physical one.\n   * @example\n   * ```typescript\n   * import { LogicalPosition } from '@tauri-apps/api/dpi';\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   *\n   * const appWindow = getCurrentWindow();\n   * const factor = await appWindow.scaleFactor();\n   * const position = new LogicalPosition(400, 500);\n   * const physical = position.toPhysical(factor);\n   * ```\n   *\n   * @since 2.0.0\n   */\n  toPhysical(scaleFactor: number): PhysicalPosition {\n    return new PhysicalPosition(this.x * scaleFactor, this.y * scaleFactor)\n  }\n\n  [SERIALIZE_TO_IPC_FN]() {\n    return {\n      x: this.x,\n      y: this.y\n    }\n  }\n\n  toJSON() {\n    // eslint-disable-next-line security/detect-object-injection\n    return this[SERIALIZE_TO_IPC_FN]()\n  }\n}\n\n/**\n * A position represented in physical pixels.\n *\n * For an explanation of what physical pixels are, see description of {@linkcode PhysicalSize}.\n *\n * @since 2.0.0\n */\nclass PhysicalPosition {\n  readonly type = 'Physical'\n  x: number\n  y: number\n\n  constructor(x: number, y: number)\n  constructor(object: { Physical: { x: number; y: number } })\n  constructor(object: { x: number; y: number })\n  constructor(\n    ...args:\n      | [number, number]\n      | [{ x: number; y: number }]\n      | [{ Physical: { x: number; y: number } }]\n  ) {\n    if (args.length === 1) {\n      if ('Physical' in args[0]) {\n        this.x = args[0].Physical.x\n        this.y = args[0].Physical.y\n      } else {\n        this.x = args[0].x\n        this.y = args[0].y\n      }\n    } else {\n      this.x = args[0]\n      this.y = args[1]\n    }\n  }\n\n  /**\n   * Converts the physical position to a logical one.\n   * @example\n   * ```typescript\n   * import { PhysicalPosition } from '@tauri-apps/api/dpi';\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   *\n   * const appWindow = getCurrentWindow();\n   * const factor = await appWindow.scaleFactor();\n   * const position = new PhysicalPosition(400, 500);\n   * const physical = position.toLogical(factor);\n   * ```\n   *\n   * @since 2.0.0\n   */\n  toLogical(scaleFactor: number): LogicalPosition {\n    return new LogicalPosition(this.x / scaleFactor, this.y / scaleFactor)\n  }\n\n  [SERIALIZE_TO_IPC_FN]() {\n    return {\n      x: this.x,\n      y: this.y\n    }\n  }\n\n  toJSON() {\n    // eslint-disable-next-line security/detect-object-injection\n    return this[SERIALIZE_TO_IPC_FN]()\n  }\n}\n\n/**\n * A position represented either in physical or in logical pixels.\n *\n * This type is basically a union type of {@linkcode LogicalSize} and {@linkcode PhysicalSize}\n * but comes in handy when using `tauri::Position` in Rust as an argument to a command, as this class\n * automatically serializes into a valid format so it can be deserialized correctly into `tauri::Position`\n *\n * So instead of\n * ```typescript\n * import { invoke } from '@tauri-apps/api/core';\n * import { LogicalPosition, PhysicalPosition } from '@tauri-apps/api/dpi';\n *\n * const position: LogicalPosition | PhysicalPosition = someFunction(); // where someFunction returns either LogicalPosition or PhysicalPosition\n * const validPosition = position instanceof LogicalPosition\n *   ? { Logical: { x: position.x, y: position.y } }\n *   : { Physical: { x: position.x, y: position.y } }\n * await invoke(\"do_something_with_position\", { position: validPosition });\n * ```\n *\n * You can just use {@linkcode Position}\n * ```typescript\n * import { invoke } from '@tauri-apps/api/core';\n * import { LogicalPosition, PhysicalPosition, Position } from '@tauri-apps/api/dpi';\n *\n * const position: LogicalPosition | PhysicalPosition = someFunction(); // where someFunction returns either LogicalPosition or PhysicalPosition\n * const validPosition = new Position(position);\n * await invoke(\"do_something_with_position\", { position: validPosition });\n * ```\n *\n * @since 2.1.0\n */\nclass Position {\n  position: LogicalPosition | PhysicalPosition\n\n  constructor(position: LogicalPosition | PhysicalPosition) {\n    this.position = position\n  }\n\n  toLogical(scaleFactor: number): LogicalPosition {\n    return this.position instanceof LogicalPosition\n      ? this.position\n      : this.position.toLogical(scaleFactor)\n  }\n\n  toPhysical(scaleFactor: number): PhysicalPosition {\n    return this.position instanceof PhysicalPosition\n      ? this.position\n      : this.position.toPhysical(scaleFactor)\n  }\n\n  [SERIALIZE_TO_IPC_FN]() {\n    return {\n      [`${this.position.type}`]: {\n        x: this.position.x,\n        y: this.position.y\n      }\n    }\n  }\n\n  toJSON() {\n    // eslint-disable-next-line security/detect-object-injection\n    return this[SERIALIZE_TO_IPC_FN]()\n  }\n}\n\nexport {\n  LogicalPosition,\n  LogicalSize,\n  Size,\n  PhysicalPosition,\n  PhysicalSize,\n  Position\n}\n"
  },
  {
    "path": "packages/api/src/event.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/**\n * The event system allows you to emit events to the backend and listen to events from it.\n *\n * This package is also accessible with `window.__TAURI__.event` when [`app.withGlobalTauri`](https://v2.tauri.app/reference/config/#withglobaltauri) in `tauri.conf.json` is set to `true`.\n * @module\n */\n\nimport { invoke, transformCallback } from './core'\n\ndeclare global {\n  interface Window {\n    __TAURI_EVENT_PLUGIN_INTERNALS__: {\n      unregisterListener: (event: string, eventId: number) => void\n    }\n  }\n}\n\ntype EventTarget =\n  | { kind: 'Any' }\n  | { kind: 'AnyLabel'; label: string }\n  | { kind: 'App' }\n  | { kind: 'Window'; label: string }\n  | { kind: 'Webview'; label: string }\n  | { kind: 'WebviewWindow'; label: string }\n\ninterface Event<T> {\n  /** Event name */\n  event: EventName\n  /** Event identifier used to unlisten */\n  id: number\n  /** Event payload */\n  payload: T\n}\n\ntype EventCallback<T> = (event: Event<T>) => void\n\n// TODO(v3): mark this as Promise<void>\ntype UnlistenFn = () => void\n\ntype EventName = `${TauriEvent}` | (string & Record<never, never>)\n\ninterface Options {\n  /**\n   * The event target to listen to, defaults to `{ kind: 'Any' }`, see {@link EventTarget}.\n   *\n   * If a string is provided, {@link EventTarget.AnyLabel} is used.\n   */\n  target?: string | EventTarget\n}\n\n/**\n * @since 1.1.0\n */\nenum TauriEvent {\n  WINDOW_RESIZED = 'tauri://resize',\n  WINDOW_MOVED = 'tauri://move',\n  WINDOW_CLOSE_REQUESTED = 'tauri://close-requested',\n  WINDOW_DESTROYED = 'tauri://destroyed',\n  WINDOW_FOCUS = 'tauri://focus',\n  WINDOW_BLUR = 'tauri://blur',\n  WINDOW_SCALE_FACTOR_CHANGED = 'tauri://scale-change',\n  WINDOW_THEME_CHANGED = 'tauri://theme-changed',\n  WINDOW_CREATED = 'tauri://window-created',\n  WEBVIEW_CREATED = 'tauri://webview-created',\n  DRAG_ENTER = 'tauri://drag-enter',\n  DRAG_OVER = 'tauri://drag-over',\n  DRAG_DROP = 'tauri://drag-drop',\n  DRAG_LEAVE = 'tauri://drag-leave'\n}\n\n/**\n * Unregister the event listener associated with the given name and id.\n *\n * @ignore\n * @param event The event name\n * @param eventId Event identifier\n * @returns\n */\nasync function _unlisten(event: string, eventId: number): Promise<void> {\n  window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(event, eventId)\n  await invoke('plugin:event|unlisten', {\n    event,\n    eventId\n  })\n}\n\n/**\n * Listen to an emitted event to any {@link EventTarget|target}.\n *\n * @example\n * ```typescript\n * import { listen } from '@tauri-apps/api/event';\n * const unlisten = await listen<string>('error', (event) => {\n *   console.log(`Got error, payload: ${event.payload}`);\n * });\n *\n * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n * unlisten();\n * ```\n *\n * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n * @param handler Event handler callback.\n * @param options Event listening options.\n * @returns A promise resolving to a function to unlisten to the event.\n * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n *\n * @since 1.0.0\n */\nasync function listen<T>(\n  event: EventName,\n  handler: EventCallback<T>,\n  options?: Options\n): Promise<UnlistenFn> {\n  const target: EventTarget =\n    typeof options?.target === 'string'\n      ? { kind: 'AnyLabel', label: options.target }\n      : (options?.target ?? { kind: 'Any' })\n  return invoke<number>('plugin:event|listen', {\n    event,\n    target,\n    handler: transformCallback(handler)\n  }).then((eventId) => {\n    return async () => _unlisten(event, eventId)\n  })\n}\n\n/**\n * Listens once to an emitted event to any {@link EventTarget|target}.\n *\n * @example\n * ```typescript\n * import { once } from '@tauri-apps/api/event';\n * interface LoadedPayload {\n *   loggedIn: boolean,\n *   token: string\n * }\n * const unlisten = await once<LoadedPayload>('loaded', (event) => {\n *   console.log(`App is loaded, loggedIn: ${event.payload.loggedIn}, token: ${event.payload.token}`);\n * });\n *\n * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n * unlisten();\n * ```\n *\n * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n * @param handler Event handler callback.\n * @param options Event listening options.\n * @returns A promise resolving to a function to unlisten to the event.\n * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n *\n * @since 1.0.0\n */\nasync function once<T>(\n  event: EventName,\n  handler: EventCallback<T>,\n  options?: Options\n): Promise<UnlistenFn> {\n  return listen<T>(\n    event,\n    (eventData) => {\n      void _unlisten(event, eventData.id)\n      handler(eventData)\n    },\n    options\n  )\n}\n\n/**\n * Emits an event to all {@link EventTarget|targets}.\n *\n * @example\n * ```typescript\n * import { emit } from '@tauri-apps/api/event';\n * await emit('frontend-loaded', { loggedIn: true, token: 'authToken' });\n * ```\n *\n * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n * @param payload Event payload.\n *\n * @since 1.0.0\n */\nasync function emit<T>(event: string, payload?: T): Promise<void> {\n  await invoke('plugin:event|emit', {\n    event,\n    payload\n  })\n}\n\n/**\n * Emits an event to all {@link EventTarget|targets} matching the given target.\n *\n * @example\n * ```typescript\n * import { emitTo } from '@tauri-apps/api/event';\n * await emitTo('main', 'frontend-loaded', { loggedIn: true, token: 'authToken' });\n * ```\n *\n * @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.\n * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n * @param payload Event payload.\n *\n * @since 2.0.0\n */\nasync function emitTo<T>(\n  target: EventTarget | string,\n  event: string,\n  payload?: T\n): Promise<void> {\n  const eventTarget: EventTarget =\n    typeof target === 'string' ? { kind: 'AnyLabel', label: target } : target\n  await invoke('plugin:event|emit_to', {\n    target: eventTarget,\n    event,\n    payload\n  })\n}\n\nexport type {\n  Event,\n  EventTarget,\n  EventCallback,\n  UnlistenFn,\n  EventName,\n  Options\n}\n\nexport { listen, once, emit, emitTo, TauriEvent }\n"
  },
  {
    "path": "packages/api/src/global.d.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/** @ignore */\n\nimport type { invoke, transformCallback, convertFileSrc } from './core'\n\n/** @ignore */\ndeclare global {\n  interface Window {\n    __TAURI_INTERNALS__: {\n      invoke: typeof invoke\n      transformCallback: typeof transformCallback\n      unregisterCallback: (id: number) => void\n      runCallback: (id: number, data: unknown) => void\n      callbacks: Map<number, (data: unknown) => void>\n      convertFileSrc: typeof convertFileSrc\n      ipc: (message: {\n        cmd: string\n        callback: number\n        error: number\n        payload: unknown\n        options?: InvokeOptions\n      }) => void\n      metadata: {\n        currentWindow: WindowDef\n        currentWebview: WebviewDef\n      }\n      plugins: {\n        path: {\n          sep: string\n          delimiter: string\n        }\n      }\n    }\n    __TAURI_EVENT_PLUGIN_INTERNALS__: {\n      unregisterListener: (event: string, eventId: number) => void\n    }\n  }\n}\n\n/** @ignore */\ninterface WebviewDef {\n  windowLabel: string\n  label: string\n}\n\n/** @ignore */\ninterface WindowDef {\n  label: string\n}\n"
  },
  {
    "path": "packages/api/src/image.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport { Resource, invoke } from './core'\nimport { NativeIcon } from './menu/iconMenuItem'\n\n/// Image dimensions type.\nexport interface ImageSize {\n  /// Image width.\n  width: number\n  /// Image height.\n  height: number\n}\n\n/** A type that represents an icon that can be used in menu items. */\nexport type MenuIcon =\n  | NativeIcon\n  | string\n  | Image\n  | Uint8Array\n  | ArrayBuffer\n  | number[]\n\n/** An RGBA Image in row-major order from top to bottom. */\nexport class Image extends Resource {\n  /**\n   * Creates an Image from a resource ID. For internal use only.\n   *\n   * @ignore\n   */\n  constructor(rid: number) {\n    super(rid)\n  }\n\n  /** Creates a new Image using RGBA data, in row-major order from top to bottom, and with specified width and height. */\n  static async new(\n    rgba: number[] | Uint8Array | ArrayBuffer,\n    width: number,\n    height: number\n  ): Promise<Image> {\n    return invoke<number>('plugin:image|new', {\n      rgba: transformImage(rgba),\n      width,\n      height\n    }).then((rid) => new Image(rid))\n  }\n\n  /**\n   * Creates a new image using the provided bytes by inferring the file format.\n   * If the format is known, prefer [@link Image.fromPngBytes] or [@link Image.fromIcoBytes].\n   *\n   * Only `ico` and `png` are supported (based on activated feature flag).\n   *\n   * Note that you need the `image-ico` or `image-png` Cargo features to use this API.\n   * To enable it, change your Cargo.toml file:\n   * ```toml\n   * [dependencies]\n   * tauri = { version = \"...\", features = [\"...\", \"image-png\"] }\n   * ```\n   */\n  static async fromBytes(\n    bytes: number[] | Uint8Array | ArrayBuffer\n  ): Promise<Image> {\n    return invoke<number>('plugin:image|from_bytes', {\n      bytes: transformImage(bytes)\n    }).then((rid) => new Image(rid))\n  }\n\n  /**\n   * Creates a new image using the provided path.\n   *\n   * Only `ico` and `png` are supported (based on activated feature flag).\n   *\n   * Note that you need the `image-ico` or `image-png` Cargo features to use this API.\n   * To enable it, change your Cargo.toml file:\n   * ```toml\n   * [dependencies]\n   * tauri = { version = \"...\", features = [\"...\", \"image-png\"] }\n   * ```\n   */\n  static async fromPath(path: string): Promise<Image> {\n    return invoke<number>('plugin:image|from_path', { path }).then(\n      (rid) => new Image(rid)\n    )\n  }\n\n  /** Returns the RGBA data for this image, in row-major order from top to bottom.  */\n  async rgba(): Promise<Uint8Array> {\n    return invoke<number[]>('plugin:image|rgba', {\n      rid: this.rid\n    }).then((buffer) => new Uint8Array(buffer))\n  }\n\n  /** Returns the size of this image.  */\n  async size(): Promise<ImageSize> {\n    return invoke<ImageSize>('plugin:image|size', { rid: this.rid })\n  }\n}\n\n/**\n * Transforms image from various types into a type acceptable by Rust.\n *\n * See [tauri::image::JsImage](https://docs.rs/tauri/2/tauri/image/enum.JsImage.html) for more information.\n * Note the API signature is not stable and might change.\n */\nexport function transformImage<T>(\n  image: string | Image | Uint8Array | ArrayBuffer | number[] | null\n): T {\n  const ret =\n    image == null\n      ? null\n      : typeof image === 'string'\n        ? image\n        : image instanceof Image\n          ? image.rid\n          : image\n\n  return ret as T\n}\n"
  },
  {
    "path": "packages/api/src/index.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/**\n * The Tauri API allows you to interface with the backend layer.\n *\n * This module exposes all other modules as an object where the key is the module name, and the value is the module exports.\n * @example\n * ```typescript\n * import { event, window, path } from '@tauri-apps/api'\n * ```\n *\n * ### Vanilla JS API\n *\n * The above import syntax is for JavaScript/TypeScript with a bundler. If you're using vanilla JavaScript, you can use the global `window.__TAURI__` object instead. It requires `app.withGlobalTauri` configuration option enabled.\n *\n * @example\n * ```js\n * const { event, window: tauriWindow, path } = window.__TAURI__;\n * ```\n *\n * @module\n */\n\nimport * as app from './app'\nimport * as core from './core'\nimport * as dpi from './dpi'\nimport * as event from './event'\nimport * as image from './image'\nimport * as menu from './menu'\nimport * as mocks from './mocks'\nimport * as path from './path'\nimport * as tray from './tray'\nimport * as webview from './webview'\nimport * as webviewWindow from './webviewWindow'\nimport * as window from './window'\n\nexport {\n  app,\n  core,\n  dpi,\n  event,\n  image,\n  menu,\n  mocks,\n  path,\n  tray,\n  webview,\n  webviewWindow,\n  window\n}\n"
  },
  {
    "path": "packages/api/src/menu/base.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport { Channel, invoke, Resource } from '../core'\nimport { transformImage } from '../image'\nimport { CheckMenuItemOptions } from './checkMenuItem'\nimport { IconMenuItemOptions } from './iconMenuItem'\nimport { MenuOptions } from './menu'\nimport { MenuItemOptions } from './menuItem'\nimport { PredefinedMenuItemOptions } from './predefinedMenuItem'\nimport { SubmenuOptions } from './submenu'\n\nexport type ItemKind =\n  | 'MenuItem'\n  | 'Predefined'\n  | 'Check'\n  | 'Icon'\n  | 'Submenu'\n  | 'Menu'\n\ntype MenuItemOptionsAlias =\n  | MenuItemOptions\n  | SubmenuOptions\n  | IconMenuItemOptions\n  | PredefinedMenuItemOptions\n  | CheckMenuItemOptions\n\nfunction injectChannel(i: MenuItemOptionsAlias): MenuItemOptionsAlias & {\n  handler?: Channel<string>\n} {\n  if ('items' in i) {\n    i.items = i.items?.map((item) =>\n      'rid' in item ? item : injectChannel(item)\n    )\n  } else if ('action' in i && i.action) {\n    const handler = new Channel<string>()\n    handler.onmessage = i.action\n    delete i.action\n    return { ...i, handler }\n  }\n  return i\n}\n\nexport async function newMenu(\n  kind: ItemKind,\n  opts?:\n    | MenuOptions\n    | MenuItemOptions\n    | SubmenuOptions\n    | PredefinedMenuItemOptions\n    | CheckMenuItemOptions\n    | IconMenuItemOptions\n): Promise<[number, string]> {\n  const handler = new Channel<string>()\n\n  if (opts && typeof opts === 'object') {\n    if ('action' in opts && opts.action) {\n      handler.onmessage = opts.action\n      delete opts.action\n    }\n\n    // about predefined menu item icon\n    if (\n      'item' in opts\n      && opts.item\n      && typeof opts.item === 'object'\n      && 'About' in opts.item\n      && opts.item.About\n      && typeof opts.item.About === 'object'\n      && 'icon' in opts.item.About\n      && opts.item.About.icon\n    ) {\n      opts.item.About.icon = transformImage(opts.item.About.icon)\n    }\n\n    // icon menu item icon\n    if ('icon' in opts && opts.icon) {\n      opts.icon = transformImage(opts.icon)\n    }\n\n    // submenu items\n    if ('items' in opts && opts.items) {\n      function prepareItem(\n        i: { rid: number; kind: string } | MenuItemOptionsAlias\n      ): [number, string] | MenuItemOptionsAlias {\n        if ('rid' in i) {\n          return [i.rid, i.kind]\n        }\n\n        if ('item' in i && typeof i.item === 'object' && i.item.About?.icon) {\n          i.item.About.icon = transformImage(i.item.About.icon)\n        }\n\n        if ('icon' in i && i.icon) {\n          i.icon = transformImage(i.icon)\n        }\n\n        if ('items' in i && i.items) {\n          // @ts-expect-error the `prepareItem` return doesn't exactly match\n          // this is fine, because the difference is in `[number, string]` variant\n          i.items = i.items.map(prepareItem)\n        }\n\n        return injectChannel(i)\n      }\n\n      // @ts-expect-error the `prepareItem` return doesn't exactly match\n      // this is fine, because the difference is in `[number, string]` variant\n      opts.items = opts.items.map(prepareItem)\n    }\n  }\n\n  return invoke('plugin:menu|new', {\n    kind,\n    options: opts,\n    handler\n  })\n}\n\nexport class MenuItemBase extends Resource {\n  /** @ignore */\n  readonly #id: string\n  /** @ignore */\n  readonly #kind: ItemKind\n\n  /** The id of this item. */\n  get id(): string {\n    return this.#id\n  }\n\n  /** @ignore */\n  get kind(): string {\n    return this.#kind\n  }\n\n  /** @ignore */\n  protected constructor(rid: number, id: string, kind: ItemKind) {\n    super(rid)\n    this.#id = id\n    this.#kind = kind\n  }\n}\n"
  },
  {
    "path": "packages/api/src/menu/checkMenuItem.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport { MenuItemBase, newMenu } from './base'\nimport { invoke } from '../core'\nimport { type MenuItemOptions } from '../menu'\n\n/** Options for creating a new check menu item. */\nexport interface CheckMenuItemOptions extends MenuItemOptions {\n  /** Whether the new check menu item is enabled or not. */\n  checked?: boolean\n}\n\n/**\n * A check menu item inside a {@linkcode Menu} or {@linkcode Submenu}\n * and usually contains a text and a check mark or a similar toggle\n * that corresponds to a checked and unchecked states.\n */\nexport class CheckMenuItem extends MenuItemBase {\n  /** @ignore */\n  protected constructor(rid: number, id: string) {\n    super(rid, id, 'Check')\n  }\n\n  /** Create a new check menu item. */\n  static async new(opts: CheckMenuItemOptions): Promise<CheckMenuItem> {\n    return newMenu('Check', opts).then(\n      ([rid, id]) => new CheckMenuItem(rid, id)\n    )\n  }\n\n  /** Returns the text of this check menu item. */\n  async text(): Promise<string> {\n    return invoke('plugin:menu|text', { rid: this.rid, kind: this.kind })\n  }\n\n  /** Sets the text for this check menu item. */\n  async setText(text: string): Promise<void> {\n    return invoke('plugin:menu|set_text', {\n      rid: this.rid,\n      kind: this.kind,\n      text\n    })\n  }\n\n  /** Returns whether this check menu item is enabled or not. */\n  async isEnabled(): Promise<boolean> {\n    return invoke('plugin:menu|is_enabled', { rid: this.rid, kind: this.kind })\n  }\n\n  /** Sets whether this check menu item is enabled or not. */\n  async setEnabled(enabled: boolean): Promise<void> {\n    return invoke('plugin:menu|set_enabled', {\n      rid: this.rid,\n      kind: this.kind,\n      enabled\n    })\n  }\n\n  /** Sets the accelerator for this check menu item. */\n  async setAccelerator(accelerator: string | null): Promise<void> {\n    return invoke('plugin:menu|set_accelerator', {\n      rid: this.rid,\n      kind: this.kind,\n      accelerator\n    })\n  }\n\n  /** Returns whether this check menu item is checked or not. */\n  async isChecked(): Promise<boolean> {\n    return invoke('plugin:menu|is_checked', { rid: this.rid })\n  }\n\n  /** Sets whether this check menu item is checked or not. */\n  async setChecked(checked: boolean): Promise<void> {\n    return invoke('plugin:menu|set_checked', {\n      rid: this.rid,\n      checked\n    })\n  }\n}\n"
  },
  {
    "path": "packages/api/src/menu/iconMenuItem.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport { MenuItemBase, newMenu } from './base'\nimport { type MenuItemOptions } from '../menu'\nimport { invoke } from '../core'\nimport { transformImage, MenuIcon } from '../image'\n\n/**\n * A native Icon to be used for the menu item\n *\n * #### Platform-specific:\n *\n * - **Windows / Linux**: Unsupported.\n */\nexport enum NativeIcon {\n  /** An add item template image. */\n  Add = 'Add',\n  /** Advanced preferences toolbar icon for the preferences window. */\n  Advanced = 'Advanced',\n  /** A Bluetooth template image. */\n  Bluetooth = 'Bluetooth',\n  /** Bookmarks image suitable for a template. */\n  Bookmarks = 'Bookmarks',\n  /** A caution image. */\n  Caution = 'Caution',\n  /** A color panel toolbar icon. */\n  ColorPanel = 'ColorPanel',\n  /** A column view mode template image. */\n  ColumnView = 'ColumnView',\n  /** A computer icon. */\n  Computer = 'Computer',\n  /** An enter full-screen mode template image. */\n  EnterFullScreen = 'EnterFullScreen',\n  /** Permissions for all users. */\n  Everyone = 'Everyone',\n  /** An exit full-screen mode template image. */\n  ExitFullScreen = 'ExitFullScreen',\n  /** A cover flow view mode template image. */\n  FlowView = 'FlowView',\n  /** A folder image. */\n  Folder = 'Folder',\n  /** A burnable folder icon. */\n  FolderBurnable = 'FolderBurnable',\n  /** A smart folder icon. */\n  FolderSmart = 'FolderSmart',\n  /** A link template image. */\n  FollowLinkFreestanding = 'FollowLinkFreestanding',\n  /** A font panel toolbar icon. */\n  FontPanel = 'FontPanel',\n  /** A `go back` template image. */\n  GoLeft = 'GoLeft',\n  /** A `go forward` template image. */\n  GoRight = 'GoRight',\n  /** Home image suitable for a template. */\n  Home = 'Home',\n  /** An iChat Theater template image. */\n  IChatTheater = 'IChatTheater',\n  /** An icon view mode template image. */\n  IconView = 'IconView',\n  /** An information toolbar icon. */\n  Info = 'Info',\n  /** A template image used to denote invalid data. */\n  InvalidDataFreestanding = 'InvalidDataFreestanding',\n  /** A generic left-facing triangle template image. */\n  LeftFacingTriangle = 'LeftFacingTriangle',\n  /** A list view mode template image. */\n  ListView = 'ListView',\n  /** A locked padlock template image. */\n  LockLocked = 'LockLocked',\n  /** An unlocked padlock template image. */\n  LockUnlocked = 'LockUnlocked',\n  /** A horizontal dash, for use in menus. */\n  MenuMixedState = 'MenuMixedState',\n  /** A check mark template image, for use in menus. */\n  MenuOnState = 'MenuOnState',\n  /** A MobileMe icon. */\n  MobileMe = 'MobileMe',\n  /** A drag image for multiple items. */\n  MultipleDocuments = 'MultipleDocuments',\n  /** A network icon. */\n  Network = 'Network',\n  /** A path button template image. */\n  Path = 'Path',\n  /** General preferences toolbar icon for the preferences window. */\n  PreferencesGeneral = 'PreferencesGeneral',\n  /** A Quick Look template image. */\n  QuickLook = 'QuickLook',\n  /** A refresh template image. */\n  RefreshFreestanding = 'RefreshFreestanding',\n  /** A refresh template image. */\n  Refresh = 'Refresh',\n  /** A remove item template image. */\n  Remove = 'Remove',\n  /** A reveal contents template image. */\n  RevealFreestanding = 'RevealFreestanding',\n  /** A generic right-facing triangle template image. */\n  RightFacingTriangle = 'RightFacingTriangle',\n  /** A share view template image. */\n  Share = 'Share',\n  /** A slideshow template image. */\n  Slideshow = 'Slideshow',\n  /** A badge for a `smart` item. */\n  SmartBadge = 'SmartBadge',\n  /** Small green indicator, similar to iChat's available image. */\n  StatusAvailable = 'StatusAvailable',\n  /** Small clear indicator. */\n  StatusNone = 'StatusNone',\n  /** Small yellow indicator, similar to iChat's idle image. */\n  StatusPartiallyAvailable = 'StatusPartiallyAvailable',\n  /** Small red indicator, similar to iChat's unavailable image. */\n  StatusUnavailable = 'StatusUnavailable',\n  /** A stop progress template image. */\n  StopProgressFreestanding = 'StopProgressFreestanding',\n  /** A stop progress button template image. */\n  StopProgress = 'StopProgress',\n  /** An image of the empty trash can. */\n  TrashEmpty = 'TrashEmpty',\n  /** An image of the full trash can. */\n  TrashFull = 'TrashFull',\n  /** Permissions for a single user. */\n  User = 'User',\n  /** User account toolbar icon for the preferences window. */\n  UserAccounts = 'UserAccounts',\n  /** Permissions for a group of users. */\n  UserGroup = 'UserGroup',\n  /** Permissions for guests. */\n  UserGuest = 'UserGuest'\n}\n\n/** Options for creating a new icon menu item. */\nexport interface IconMenuItemOptions extends MenuItemOptions {\n  /**\n   * Icon to be used for the new icon menu item.\n   *\n   * Note that you may need the `image-ico` or `image-png` Cargo features to use this API.\n   * To enable it, change your Cargo.toml file:\n   * ```toml\n   * [dependencies]\n   * tauri = { version = \"...\", features = [\"...\", \"image-png\"] }\n   * ```\n   */\n  icon?: MenuIcon\n}\n\n/**\n * An icon menu item inside a {@linkcode Menu} or {@linkcode Submenu}\n * and usually contains an icon and a text.\n */\nexport class IconMenuItem extends MenuItemBase {\n  /** @ignore */\n  protected constructor(rid: number, id: string) {\n    super(rid, id, 'Icon')\n  }\n\n  /** Create a new icon menu item. */\n  static async new(opts: IconMenuItemOptions): Promise<IconMenuItem> {\n    return newMenu('Icon', opts).then(([rid, id]) => new IconMenuItem(rid, id))\n  }\n\n  /** Returns the text of this icon menu item. */\n  async text(): Promise<string> {\n    return invoke('plugin:menu|text', { rid: this.rid, kind: this.kind })\n  }\n\n  /** Sets the text for this icon menu item. */\n  async setText(text: string): Promise<void> {\n    return invoke('plugin:menu|set_text', {\n      rid: this.rid,\n      kind: this.kind,\n      text\n    })\n  }\n\n  /** Returns whether this icon menu item is enabled or not. */\n  async isEnabled(): Promise<boolean> {\n    return invoke('plugin:menu|is_enabled', { rid: this.rid, kind: this.kind })\n  }\n\n  /** Sets whether this icon menu item is enabled or not. */\n  async setEnabled(enabled: boolean): Promise<void> {\n    return invoke('plugin:menu|set_enabled', {\n      rid: this.rid,\n      kind: this.kind,\n      enabled\n    })\n  }\n\n  /** Sets the accelerator for this icon menu item. */\n  async setAccelerator(accelerator: string | null): Promise<void> {\n    return invoke('plugin:menu|set_accelerator', {\n      rid: this.rid,\n      kind: this.kind,\n      accelerator\n    })\n  }\n\n  /** Sets an icon for this icon menu item */\n  async setIcon(icon: MenuIcon | null): Promise<void> {\n    return invoke('plugin:menu|set_icon', {\n      rid: this.rid,\n      kind: this.kind,\n      icon: transformImage(icon)\n    })\n  }\n}\n"
  },
  {
    "path": "packages/api/src/menu/menu.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport {\n  MenuItemOptions,\n  SubmenuOptions,\n  IconMenuItemOptions,\n  PredefinedMenuItemOptions,\n  CheckMenuItemOptions\n} from '../menu'\nimport { MenuItem } from './menuItem'\nimport { CheckMenuItem } from './checkMenuItem'\nimport { IconMenuItem } from './iconMenuItem'\nimport { PredefinedMenuItem } from './predefinedMenuItem'\nimport { itemFromKind, Submenu } from './submenu'\nimport { type LogicalPosition, PhysicalPosition, Position } from '../dpi'\nimport { type Window } from '../window'\nimport { invoke } from '../core'\nimport { type ItemKind, MenuItemBase, newMenu } from './base'\n\n/** Options for creating a new menu. */\nexport interface MenuOptions {\n  /** Specify an id to use for the new menu. */\n  id?: string\n  /** List of items to add to the new menu. */\n  items?: Array<\n    | Submenu\n    | MenuItem\n    | PredefinedMenuItem\n    | CheckMenuItem\n    | IconMenuItem\n    | MenuItemOptions\n    | SubmenuOptions\n    | IconMenuItemOptions\n    | PredefinedMenuItemOptions\n    | CheckMenuItemOptions\n  >\n}\n\n/** A type that is either a menu bar on the window\n * on Windows and Linux or as a global menu in the menubar on macOS.\n *\n * #### Platform-specific:\n *\n * - **macOS**: if using {@linkcode Menu} for the global menubar, it can only contain {@linkcode Submenu}s.\n */\nexport class Menu extends MenuItemBase {\n  /** @ignore */\n  protected constructor(rid: number, id: string) {\n    super(rid, id, 'Menu')\n  }\n\n  /** Create a new menu. */\n  static async new(opts?: MenuOptions): Promise<Menu> {\n    return newMenu('Menu', opts).then(([rid, id]) => new Menu(rid, id))\n  }\n\n  /** Create a default menu. */\n  static async default(): Promise<Menu> {\n    return invoke<[number, string]>('plugin:menu|create_default').then(\n      ([rid, id]) => new Menu(rid, id)\n    )\n  }\n\n  /**\n   * Add a menu item to the end of this menu.\n   *\n   * #### Platform-specific:\n   *\n   * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.\n   */\n  async append<\n    T extends\n      | Submenu\n      | MenuItem\n      | PredefinedMenuItem\n      | CheckMenuItem\n      | IconMenuItem\n      | MenuItemOptions\n      | SubmenuOptions\n      | IconMenuItemOptions\n      | PredefinedMenuItemOptions\n      | CheckMenuItemOptions\n  >(items: T | T[]): Promise<void> {\n    return invoke('plugin:menu|append', {\n      rid: this.rid,\n      kind: this.kind,\n      items: (Array.isArray(items) ? items : [items]).map((i) =>\n        'rid' in i ? [i.rid, i.kind] : i\n      )\n    })\n  }\n\n  /**\n   * Add a menu item to the beginning of this menu.\n   *\n   * #### Platform-specific:\n   *\n   * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.\n   */\n  async prepend<\n    T extends\n      | Submenu\n      | MenuItem\n      | PredefinedMenuItem\n      | CheckMenuItem\n      | IconMenuItem\n      | MenuItemOptions\n      | SubmenuOptions\n      | IconMenuItemOptions\n      | PredefinedMenuItemOptions\n      | CheckMenuItemOptions\n  >(items: T | T[]): Promise<void> {\n    return invoke('plugin:menu|prepend', {\n      rid: this.rid,\n      kind: this.kind,\n      items: (Array.isArray(items) ? items : [items]).map((i) =>\n        'rid' in i ? [i.rid, i.kind] : i\n      )\n    })\n  }\n\n  /**\n   * Add a menu item to the specified position in this menu.\n   *\n   * #### Platform-specific:\n   *\n   * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.\n   */\n  async insert<\n    T extends\n      | Submenu\n      | MenuItem\n      | PredefinedMenuItem\n      | CheckMenuItem\n      | IconMenuItem\n      | MenuItemOptions\n      | SubmenuOptions\n      | IconMenuItemOptions\n      | PredefinedMenuItemOptions\n      | CheckMenuItemOptions\n  >(items: T | T[], position: number): Promise<void> {\n    return invoke('plugin:menu|insert', {\n      rid: this.rid,\n      kind: this.kind,\n      items: (Array.isArray(items) ? items : [items]).map((i) =>\n        'rid' in i ? [i.rid, i.kind] : i\n      ),\n      position\n    })\n  }\n\n  /** Remove a menu item from this menu. */\n  async remove(\n    item: Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem\n  ): Promise<void> {\n    return invoke('plugin:menu|remove', {\n      rid: this.rid,\n      kind: this.kind,\n      item: [item.rid, item.kind]\n    })\n  }\n\n  /** Remove a menu item from this menu at the specified position. */\n  async removeAt(\n    position: number\n  ): Promise<\n    | Submenu\n    | MenuItem\n    | PredefinedMenuItem\n    | CheckMenuItem\n    | IconMenuItem\n    | null\n  > {\n    return invoke<[number, string, ItemKind]>('plugin:menu|remove_at', {\n      rid: this.rid,\n      kind: this.kind,\n      position\n    }).then(itemFromKind)\n  }\n\n  /** Returns a list of menu items that has been added to this menu. */\n  async items(): Promise<\n    Array<\n      Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem\n    >\n  > {\n    return invoke<Array<[number, string, ItemKind]>>('plugin:menu|items', {\n      rid: this.rid,\n      kind: this.kind\n    }).then((i) => i.map(itemFromKind))\n  }\n\n  /** Retrieves the menu item matching the given identifier. */\n  async get(\n    id: string\n  ): Promise<\n    | Submenu\n    | MenuItem\n    | PredefinedMenuItem\n    | CheckMenuItem\n    | IconMenuItem\n    | null\n  > {\n    return invoke<[number, string, ItemKind] | null>('plugin:menu|get', {\n      rid: this.rid,\n      kind: this.kind,\n      id\n    }).then((r) => (r ? itemFromKind(r) : null))\n  }\n\n  /**\n   * Popup this menu as a context menu on the specified window.\n   *\n   * @param at If a position is provided, it is relative to the window's top-left corner.\n   * If there isn't one provided, the menu will pop up at the current location of the mouse.\n   */\n  async popup(\n    at?: PhysicalPosition | LogicalPosition | Position,\n    window?: Window\n  ): Promise<void> {\n    return invoke('plugin:menu|popup', {\n      rid: this.rid,\n      kind: this.kind,\n      window: window?.label ?? null,\n      at: at instanceof Position ? at : at ? new Position(at) : null\n    })\n  }\n\n  /**\n   * Sets the app-wide menu and returns the previous one.\n   *\n   * If a window was not created with an explicit menu or had one set explicitly,\n   * this menu will be assigned to it.\n   */\n  async setAsAppMenu(): Promise<Menu | null> {\n    return invoke<[number, string] | null>('plugin:menu|set_as_app_menu', {\n      rid: this.rid\n    }).then((r) => (r ? new Menu(r[0], r[1]) : null))\n  }\n\n  /**\n   * Sets the window menu and returns the previous one.\n   *\n   * #### Platform-specific:\n   *\n   * - **macOS:** Unsupported. The menu on macOS is app-wide and not specific to one\n   * window, if you need to set it, use {@linkcode Menu.setAsAppMenu} instead.\n   */\n  async setAsWindowMenu(window?: Window): Promise<Menu | null> {\n    return invoke<[number, string] | null>('plugin:menu|set_as_window_menu', {\n      rid: this.rid,\n      window: window?.label ?? null\n    }).then((r) => (r ? new Menu(r[0], r[1]) : null))\n  }\n}\n"
  },
  {
    "path": "packages/api/src/menu/menuItem.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport { MenuItemBase, newMenu } from './base'\nimport { invoke } from '../core'\n\n/** Options for creating a new menu item. */\nexport interface MenuItemOptions {\n  /** Specify an id to use for the new menu item. */\n  id?: string\n  /** The text of the new menu item. */\n  text: string\n  /** Whether the new menu item is enabled or not. */\n  enabled?: boolean\n  /** Specify an accelerator for the new menu item. */\n  accelerator?: string\n  /** Specify a handler to be called when this menu item is activated. */\n  action?: (id: string) => void\n}\n\n/** A menu item inside a {@linkcode Menu} or {@linkcode Submenu} and contains only text. */\nexport class MenuItem extends MenuItemBase {\n  /** @ignore */\n  protected constructor(rid: number, id: string) {\n    super(rid, id, 'MenuItem')\n  }\n\n  /** Create a new menu item. */\n  static async new(opts: MenuItemOptions): Promise<MenuItem> {\n    return newMenu('MenuItem', opts).then(([rid, id]) => new MenuItem(rid, id))\n  }\n\n  /** Returns the text of this menu item. */\n  async text(): Promise<string> {\n    return invoke('plugin:menu|text', { rid: this.rid, kind: this.kind })\n  }\n\n  /** Sets the text for this menu item. */\n  async setText(text: string): Promise<void> {\n    return invoke('plugin:menu|set_text', {\n      rid: this.rid,\n      kind: this.kind,\n      text\n    })\n  }\n\n  /** Returns whether this menu item is enabled or not. */\n  async isEnabled(): Promise<boolean> {\n    return invoke('plugin:menu|is_enabled', { rid: this.rid, kind: this.kind })\n  }\n\n  /** Sets whether this menu item is enabled or not. */\n  async setEnabled(enabled: boolean): Promise<void> {\n    return invoke('plugin:menu|set_enabled', {\n      rid: this.rid,\n      kind: this.kind,\n      enabled\n    })\n  }\n\n  /** Sets the accelerator for this menu item. */\n  async setAccelerator(accelerator: string | null): Promise<void> {\n    return invoke('plugin:menu|set_accelerator', {\n      rid: this.rid,\n      kind: this.kind,\n      accelerator\n    })\n  }\n}\n"
  },
  {
    "path": "packages/api/src/menu/predefinedMenuItem.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport { MenuItemBase, newMenu } from './base'\nimport { invoke } from '../core'\nimport { Image } from '../image'\n\n/** A metadata for the about predefined menu item. */\nexport interface AboutMetadata {\n  /** Sets the application name. */\n  name?: string\n  /** The application version. */\n  version?: string\n  /**\n   * The short version, e.g. \"1.0\".\n   *\n   * #### Platform-specific\n   *\n   * - **Windows / Linux:** Appended to the end of `version` in parentheses.\n   */\n  shortVersion?: string\n  /**\n   * The authors of the application.\n   *\n   * #### Platform-specific\n   *\n   * - **macOS:** Unsupported.\n   */\n  authors?: string[]\n  /**\n   * Application comments.\n   *\n   * #### Platform-specific\n   *\n   * - **macOS:** Unsupported.\n   */\n  comments?: string\n  /** The copyright of the application. */\n  copyright?: string\n  /**\n   * The license of the application.\n   *\n   * #### Platform-specific\n   *\n   * - **macOS:** Unsupported.\n   */\n  license?: string\n  /**\n   * The application website.\n   *\n   * #### Platform-specific\n   *\n   * - **macOS:** Unsupported.\n   */\n  website?: string\n  /**\n   * The website label.\n   *\n   * #### Platform-specific\n   *\n   * - **macOS:** Unsupported.\n   */\n  websiteLabel?: string\n  /**\n   * The credits.\n   *\n   * #### Platform-specific\n   *\n   * - **Windows / Linux:** Unsupported.\n   */\n  credits?: string\n  /**\n   * The application icon.\n   *\n   * #### Platform-specific\n   *\n   * - **Windows:** Unsupported.\n   */\n  icon?: string | Uint8Array | ArrayBuffer | number[] | Image\n}\n\n/** Options for creating a new predefined menu item. */\nexport interface PredefinedMenuItemOptions {\n  /** The text of the new predefined menu item. */\n  text?: string\n  /** The predefined item type */\n  item:\n    | 'Separator'\n    | 'Copy'\n    | 'Cut'\n    | 'Paste'\n    | 'SelectAll'\n    | 'Undo'\n    | 'Redo'\n    | 'Minimize'\n    | 'Maximize'\n    | 'Fullscreen'\n    | 'Hide'\n    | 'HideOthers'\n    | 'ShowAll'\n    | 'CloseWindow'\n    | 'Quit'\n    | 'Services'\n    | {\n        About: AboutMetadata | null\n      }\n}\n\n/** A predefined (native) menu item which has a predefined behavior by the OS or by tauri.  */\nexport class PredefinedMenuItem extends MenuItemBase {\n  /** @ignore */\n  protected constructor(rid: number, id: string) {\n    super(rid, id, 'Predefined')\n  }\n\n  /** Create a new predefined menu item. */\n  static async new(\n    opts?: PredefinedMenuItemOptions\n  ): Promise<PredefinedMenuItem> {\n    return newMenu('Predefined', opts).then(\n      ([rid, id]) => new PredefinedMenuItem(rid, id)\n    )\n  }\n\n  /** Returns the text of this predefined menu item. */\n  async text(): Promise<string> {\n    return invoke('plugin:menu|text', { rid: this.rid, kind: this.kind })\n  }\n\n  /** Sets the text for this predefined menu item. */\n  async setText(text: string): Promise<void> {\n    return invoke('plugin:menu|set_text', {\n      rid: this.rid,\n      kind: this.kind,\n      text\n    })\n  }\n}\n"
  },
  {
    "path": "packages/api/src/menu/submenu.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport {\n  IconMenuItemOptions,\n  PredefinedMenuItemOptions,\n  CheckMenuItemOptions\n} from '../menu'\nimport { MenuItem, type MenuItemOptions } from './menuItem'\nimport { CheckMenuItem } from './checkMenuItem'\nimport { IconMenuItem } from './iconMenuItem'\nimport { PredefinedMenuItem } from './predefinedMenuItem'\nimport { invoke } from '../core'\nimport { type LogicalPosition, PhysicalPosition, type Window } from '../window'\nimport { type ItemKind, MenuItemBase, newMenu } from './base'\nimport { type MenuOptions } from './menu'\nimport { Position } from '../dpi'\nimport { transformImage, MenuIcon } from '../image'\n\n/** @ignore */\nexport function itemFromKind([rid, id, kind]: [number, string, ItemKind]):\n  | Submenu\n  | MenuItem\n  | PredefinedMenuItem\n  | CheckMenuItem\n  | IconMenuItem {\n  /* eslint-disable @typescript-eslint/no-unsafe-return */\n  switch (kind) {\n    case 'Submenu':\n      // @ts-expect-error constructor is protected for external usage only, safe for us to use\n      return new Submenu(rid, id)\n    case 'Predefined':\n      // @ts-expect-error constructor is protected for external usage only, safe for us to use\n      return new PredefinedMenuItem(rid, id)\n    case 'Check':\n      // @ts-expect-error constructor is protected for external usage only, safe for us to use\n      return new CheckMenuItem(rid, id)\n    case 'Icon':\n      // @ts-expect-error constructor is protected for external usage only, safe for us to use\n      return new IconMenuItem(rid, id)\n    case 'MenuItem':\n    default:\n      // @ts-expect-error constructor is protected for external usage only, safe for us to use\n      return new MenuItem(rid, id)\n  }\n  /* eslint-enable @typescript-eslint/no-unsafe-return */\n}\n\nexport type SubmenuOptions = (Omit<MenuItemOptions, 'accelerator' | 'action'>\n  & MenuOptions) & {\n  /**\n   * Icon to be used for the submenu.\n   * Note: you may need the `image-ico` or `image-png` Cargo features to use this API.\n   */\n  icon?: MenuIcon\n}\n\n/** A type that is a submenu inside a {@linkcode Menu} or {@linkcode Submenu}. */\nexport class Submenu extends MenuItemBase {\n  /** @ignore */\n  protected constructor(rid: number, id: string) {\n    super(rid, id, 'Submenu')\n  }\n\n  /** Create a new submenu. */\n  static async new(opts: SubmenuOptions): Promise<Submenu> {\n    return newMenu('Submenu', opts).then(([rid, id]) => new Submenu(rid, id))\n  }\n\n  /** Returns the text of this submenu. */\n  async text(): Promise<string> {\n    return invoke('plugin:menu|text', { rid: this.rid, kind: this.kind })\n  }\n\n  /** Sets the text for this submenu. */\n  async setText(text: string): Promise<void> {\n    return invoke('plugin:menu|set_text', {\n      rid: this.rid,\n      kind: this.kind,\n      text\n    })\n  }\n\n  /** Returns whether this submenu is enabled or not. */\n  async isEnabled(): Promise<boolean> {\n    return invoke('plugin:menu|is_enabled', { rid: this.rid, kind: this.kind })\n  }\n\n  /** Sets whether this submenu is enabled or not. */\n  async setEnabled(enabled: boolean): Promise<void> {\n    return invoke('plugin:menu|set_enabled', {\n      rid: this.rid,\n      kind: this.kind,\n      enabled\n    })\n  }\n\n  /**\n   * Add a menu item to the end of this submenu.\n   *\n   * #### Platform-specific:\n   *\n   * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.\n   */\n  async append<\n    T extends\n      | Submenu\n      | MenuItem\n      | PredefinedMenuItem\n      | CheckMenuItem\n      | IconMenuItem\n      | MenuItemOptions\n      | SubmenuOptions\n      | IconMenuItemOptions\n      | PredefinedMenuItemOptions\n      | CheckMenuItemOptions\n  >(items: T | T[]): Promise<void> {\n    return invoke('plugin:menu|append', {\n      rid: this.rid,\n      kind: this.kind,\n      items: (Array.isArray(items) ? items : [items]).map((i) =>\n        'rid' in i ? [i.rid, i.kind] : i\n      )\n    })\n  }\n\n  /**\n   * Add a menu item to the beginning of this submenu.\n   *\n   * #### Platform-specific:\n   *\n   * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.\n   */\n  async prepend<\n    T extends\n      | Submenu\n      | MenuItem\n      | PredefinedMenuItem\n      | CheckMenuItem\n      | IconMenuItem\n      | MenuItemOptions\n      | SubmenuOptions\n      | IconMenuItemOptions\n      | PredefinedMenuItemOptions\n      | CheckMenuItemOptions\n  >(items: T | T[]): Promise<void> {\n    return invoke('plugin:menu|prepend', {\n      rid: this.rid,\n      kind: this.kind,\n      items: (Array.isArray(items) ? items : [items]).map((i) =>\n        'rid' in i ? [i.rid, i.kind] : i\n      )\n    })\n  }\n\n  /**\n   * Add a menu item to the specified position in this submenu.\n   *\n   * #### Platform-specific:\n   *\n   * - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.\n   */\n  async insert<\n    T extends\n      | Submenu\n      | MenuItem\n      | PredefinedMenuItem\n      | CheckMenuItem\n      | IconMenuItem\n      | MenuItemOptions\n      | SubmenuOptions\n      | IconMenuItemOptions\n      | PredefinedMenuItemOptions\n      | CheckMenuItemOptions\n  >(items: T | T[], position: number): Promise<void> {\n    return invoke('plugin:menu|insert', {\n      rid: this.rid,\n      kind: this.kind,\n      items: (Array.isArray(items) ? items : [items]).map((i) =>\n        'rid' in i ? [i.rid, i.kind] : i\n      ),\n      position\n    })\n  }\n\n  /** Remove a menu item from this submenu. */\n  async remove(\n    item: Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem\n  ): Promise<void> {\n    return invoke('plugin:menu|remove', {\n      rid: this.rid,\n      kind: this.kind,\n      item: [item.rid, item.kind]\n    })\n  }\n\n  /** Remove a menu item from this submenu at the specified position. */\n  async removeAt(\n    position: number\n  ): Promise<\n    | Submenu\n    | MenuItem\n    | PredefinedMenuItem\n    | CheckMenuItem\n    | IconMenuItem\n    | null\n  > {\n    return invoke<[number, string, ItemKind]>('plugin:menu|remove_at', {\n      rid: this.rid,\n      kind: this.kind,\n      position\n    }).then(itemFromKind)\n  }\n\n  /** Returns a list of menu items that has been added to this submenu. */\n  async items(): Promise<\n    Array<\n      Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem\n    >\n  > {\n    return invoke<Array<[number, string, ItemKind]>>('plugin:menu|items', {\n      rid: this.rid,\n      kind: this.kind\n    }).then((i) => i.map(itemFromKind))\n  }\n\n  /** Retrieves the menu item matching the given identifier. */\n  async get(\n    id: string\n  ): Promise<\n    | Submenu\n    | MenuItem\n    | PredefinedMenuItem\n    | CheckMenuItem\n    | IconMenuItem\n    | null\n  > {\n    return invoke<[number, string, ItemKind] | null>('plugin:menu|get', {\n      rid: this.rid,\n      kind: this.kind,\n      id\n    }).then((r) => (r ? itemFromKind(r) : null))\n  }\n\n  /**\n   * Popup this submenu as a context menu on the specified window.\n   *\n   * If the position, is provided, it is relative to the window's top-left corner.\n   */\n  async popup(\n    at?: PhysicalPosition | LogicalPosition,\n    window?: Window\n  ): Promise<void> {\n    return invoke('plugin:menu|popup', {\n      rid: this.rid,\n      kind: this.kind,\n      window: window?.label ?? null,\n      at: at instanceof Position ? at : at ? new Position(at) : null\n    })\n  }\n\n  /**\n   * Set this submenu as the Window menu for the application on macOS.\n   *\n   * This will cause macOS to automatically add window-switching items and\n   * certain other items to the menu.\n   *\n   * #### Platform-specific:\n   *\n   * - **Windows / Linux**: Unsupported.\n   */\n  async setAsWindowsMenuForNSApp(): Promise<void> {\n    return invoke('plugin:menu|set_as_windows_menu_for_nsapp', {\n      rid: this.rid\n    })\n  }\n\n  /**\n   * Set this submenu as the Help menu for the application on macOS.\n   *\n   * This will cause macOS to automatically add a search box to the menu.\n   *\n   * If no menu is set as the Help menu, macOS will automatically use any menu\n   * which has a title matching the localized word \"Help\".\n   *\n   * #### Platform-specific:\n   *\n   * - **Windows / Linux**: Unsupported.\n   */\n  async setAsHelpMenuForNSApp(): Promise<void> {\n    return invoke('plugin:menu|set_as_help_menu_for_nsapp', {\n      rid: this.rid\n    })\n  }\n\n  /** Sets an icon for this submenu */\n  async setIcon(icon: MenuIcon | null): Promise<void> {\n    return invoke('plugin:menu|set_icon', {\n      rid: this.rid,\n      kind: this.kind,\n      icon: transformImage(icon)\n    })\n  }\n}\n"
  },
  {
    "path": "packages/api/src/menu.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nexport * from './menu/submenu'\nexport * from './menu/menuItem'\nexport * from './menu/menu'\nexport * from './menu/checkMenuItem'\nexport * from './menu/iconMenuItem'\nexport * from './menu/predefinedMenuItem'\n\n/**\n * Menu types and utilities.\n *\n * This package is also accessible with `window.__TAURI__.menu` when [`app.withGlobalTauri`](https://v2.tauri.app/reference/config/#withglobaltauri) in `tauri.conf.json` is set to `true`.\n * @module\n */\n"
  },
  {
    "path": "packages/api/src/mocks.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport type { InvokeArgs, InvokeOptions } from './core'\nimport { EventName } from './event'\n\nfunction mockInternals() {\n  window.__TAURI_INTERNALS__ = window.__TAURI_INTERNALS__ ?? {}\n  window.__TAURI_EVENT_PLUGIN_INTERNALS__ =\n    window.__TAURI_EVENT_PLUGIN_INTERNALS__ ?? {}\n}\n\n/**\n * Options for `mockIPC`.\n *\n * # Options\n * `shouldMockEvents`: If true, the `listen` and `emit` functions will be mocked, allowing you to test event handling without a real backend.\n * **This will consume any events emitted with the `plugin:event` prefix.**\n *\n * @since 2.7.0\n */\nexport interface MockIPCOptions {\n  shouldMockEvents?: boolean\n}\n\n/**\n * Intercepts all IPC requests with the given mock handler.\n *\n * This function can be used when testing tauri frontend applications or when running the frontend in a Node.js context during static site generation.\n *\n * # Examples\n *\n * Testing setup using Vitest:\n * ```ts\n * import { mockIPC, clearMocks } from \"@tauri-apps/api/mocks\"\n * import { invoke } from \"@tauri-apps/api/core\"\n *\n * afterEach(() => {\n *    clearMocks()\n * })\n *\n * test(\"mocked command\", () => {\n *  mockIPC((cmd, payload) => {\n *   switch (cmd) {\n *     case \"add\":\n *       return (payload.a as number) + (payload.b as number);\n *     default:\n *       break;\n *     }\n *  });\n *\n *  expect(invoke('add', { a: 12, b: 15 })).resolves.toBe(27);\n * })\n * ```\n *\n * The callback function can also return a Promise:\n * ```js\n * import { mockIPC, clearMocks } from \"@tauri-apps/api/mocks\"\n * import { invoke } from \"@tauri-apps/api/core\"\n *\n * afterEach(() => {\n *    clearMocks()\n * })\n *\n * test(\"mocked command\", () => {\n *  mockIPC((cmd, payload) => {\n *   if(cmd === \"get_data\") {\n *    return fetch(\"https://example.com/data.json\")\n *      .then((response) => response.json())\n *   }\n *  });\n *\n *  expect(invoke('get_data')).resolves.toBe({ foo: 'bar' });\n * })\n * ```\n *\n * `listen` can also be mocked with direct calls to the `emit` function. This functionality is opt-in via the `shouldMockEvents` option:\n * ```js\n * import { mockIPC, clearMocks } from \"@tauri-apps/api/mocks\"\n * import { emit, listen } from \"@tauri-apps/api/event\"\n *\n * afterEach(() => {\n *    clearMocks()\n * })\n *\n * test(\"mocked event\", () => {\n *  mockIPC(() => {}, { shouldMockEvents: true }); // enable event mocking\n *\n *  const eventHandler = vi.fn();\n *  listen('test-event', eventHandler); // typically in component setup or similar\n *\n *  emit('test-event', { foo: 'bar' });\n *  expect(eventHandler).toHaveBeenCalledWith({\n *    event: 'test-event',\n *    payload: { foo: 'bar' }\n *  });\n * })\n * ```\n * `emitTo` is currently **not** supported by this mock implementation.\n *\n * @since 1.0.0\n */\nexport function mockIPC(\n  cb: (cmd: string, payload?: InvokeArgs) => unknown,\n  options?: MockIPCOptions\n): void {\n  mockInternals()\n\n  function isEventPluginInvoke(cmd: string): boolean {\n    return cmd.startsWith('plugin:event|')\n  }\n\n  function handleEventPlugin(cmd: string, args?: InvokeArgs): unknown {\n    switch (cmd) {\n      case 'plugin:event|listen':\n        return handleListen(args as { event: EventName; handler: number })\n      case 'plugin:event|emit':\n        return handleEmit(args as { event: EventName; payload?: unknown })\n      case 'plugin:event|unlisten':\n        return handleRemoveListener(args as { event: EventName; id: number })\n    }\n  }\n\n  const listeners = new Map<string, number[]>()\n  function handleListen(args: { event: EventName; handler: number }) {\n    if (!listeners.has(args.event)) {\n      listeners.set(args.event, [])\n    }\n    listeners.get(args.event)!.push(args.handler)\n    return args.handler\n  }\n\n  function handleEmit(args: { event: EventName; payload?: unknown }) {\n    const eventListeners = listeners.get(args.event) || []\n    for (const handler of eventListeners) {\n      runCallback(handler, args)\n    }\n    return null\n  }\n  function handleRemoveListener(args: { event: EventName; id: number }) {\n    const eventListeners = listeners.get(args.event)\n    if (eventListeners) {\n      const index = eventListeners.indexOf(args.id)\n      if (index !== -1) {\n        eventListeners.splice(index, 1)\n      }\n    }\n  }\n\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async function invoke<T>(\n    cmd: string,\n    args?: InvokeArgs,\n    _options?: InvokeOptions\n  ): Promise<T> {\n    if (options?.shouldMockEvents && isEventPluginInvoke(cmd)) {\n      return handleEventPlugin(cmd, args) as T\n    }\n\n    return cb(cmd, args) as T\n  }\n\n  const callbacks = new Map<number, (data: unknown) => void>()\n\n  function registerCallback<T = unknown>(\n    callback?: (response: T) => void,\n    once = false\n  ) {\n    const identifier = window.crypto.getRandomValues(new Uint32Array(1))[0]\n    callbacks.set(identifier, (data) => {\n      if (once) {\n        unregisterCallback(identifier)\n      }\n      return callback && callback(data as T)\n    })\n    return identifier\n  }\n\n  function unregisterCallback(id: number) {\n    callbacks.delete(id)\n  }\n\n  function runCallback(id: number, data: unknown) {\n    const callback = callbacks.get(id)\n    if (callback) {\n      callback(data)\n    } else {\n      // eslint-disable-next-line no-console\n      console.warn(\n        `[TAURI] Couldn't find callback id ${id}. This might happen when the app is reloaded while Rust is running an asynchronous operation.`\n      )\n    }\n  }\n\n  function unregisterListener(event: EventName, id: number) {\n    unregisterCallback(id)\n  }\n\n  window.__TAURI_INTERNALS__.invoke = invoke\n  window.__TAURI_INTERNALS__.transformCallback = registerCallback\n  window.__TAURI_INTERNALS__.unregisterCallback = unregisterCallback\n  window.__TAURI_INTERNALS__.runCallback = runCallback\n  window.__TAURI_INTERNALS__.callbacks = callbacks\n  window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener =\n    unregisterListener\n}\n\n/**\n * Mocks one or many window labels.\n * In non-tauri context it is required to call this function *before* using the `@tauri-apps/api/window` module.\n *\n * This function only mocks the *presence* of windows,\n * window properties (e.g. width and height) can be mocked like regular IPC calls using the `mockIPC` function.\n *\n * # Examples\n *\n * ```js\n * import { mockWindows } from \"@tauri-apps/api/mocks\";\n * import { getCurrentWindow } from \"@tauri-apps/api/window\";\n *\n * mockWindows(\"main\", \"second\", \"third\");\n *\n * const win = getCurrentWindow();\n *\n * win.label // \"main\"\n * ```\n *\n * ```js\n * import { mockWindows } from \"@tauri-apps/api/mocks\";\n *\n * mockWindows(\"main\", \"second\", \"third\");\n *\n * mockIPC((cmd, args) => {\n *  if (cmd === \"plugin:event|emit\") {\n *    console.log('emit event', args?.event, args?.payload);\n *  }\n * });\n *\n * const { emit } = await import(\"@tauri-apps/api/event\");\n * await emit('loaded'); // this will cause the mocked IPC handler to log to the console.\n * ```\n *\n * @param current Label of window this JavaScript context is running in.\n *\n * @since 1.0.0\n */\nexport function mockWindows(\n  current: string,\n  ..._additionalWindows: string[]\n): void {\n  mockInternals()\n  window.__TAURI_INTERNALS__.metadata = {\n    currentWindow: { label: current },\n    currentWebview: { windowLabel: current, label: current }\n  }\n}\n\n/**\n * Mock `convertFileSrc` function\n *\n *\n * @example\n * ```js\n * import { mockConvertFileSrc } from \"@tauri-apps/api/mocks\";\n * import { convertFileSrc } from \"@tauri-apps/api/core\";\n *\n * mockConvertFileSrc(\"windows\")\n *\n * const url = convertFileSrc(\"C:\\\\Users\\\\user\\\\file.txt\")\n * ```\n *\n * @param osName The operating system to mock, can be one of linux, macos, or windows\n *\n * @since 1.6.0\n */\nexport function mockConvertFileSrc(osName: string): void {\n  mockInternals()\n  window.__TAURI_INTERNALS__.convertFileSrc = function (\n    filePath,\n    protocol = 'asset'\n  ) {\n    const path = encodeURIComponent(filePath)\n    return osName === 'windows'\n      ? `http://${protocol}.localhost/${path}`\n      : `${protocol}://localhost/${path}`\n  }\n}\n\n/**\n * Clears mocked functions/data injected by the other functions in this module.\n * When using a test runner that doesn't provide a fresh window object for each test, calling this function will reset tauri specific properties.\n *\n * # Example\n *\n * ```js\n * import { mockWindows, clearMocks } from \"@tauri-apps/api/mocks\"\n *\n * afterEach(() => {\n *    clearMocks()\n * })\n *\n * test(\"mocked windows\", () => {\n *    mockWindows(\"main\", \"second\", \"third\");\n *\n *    expect(window.__TAURI_INTERNALS__).toHaveProperty(\"metadata\")\n * })\n *\n * test(\"no mocked windows\", () => {\n *    expect(window.__TAURI_INTERNALS__).not.toHaveProperty(\"metadata\")\n * })\n * ```\n *\n * @since 1.0.0\n */\nexport function clearMocks(): void {\n  if (typeof window.__TAURI_INTERNALS__ !== 'object') {\n    return\n  }\n\n  // @ts-expect-error \"The operand of a 'delete' operator must be optional.\" does not matter in this case\n  delete window.__TAURI_INTERNALS__.invoke\n  // @ts-expect-error \"The operand of a 'delete' operator must be optional.\" does not matter in this case\n  delete window.__TAURI_INTERNALS__.transformCallback\n  // @ts-expect-error \"The operand of a 'delete' operator must be optional.\" does not matter in this case\n  delete window.__TAURI_INTERNALS__.unregisterCallback\n  // @ts-expect-error \"The operand of a 'delete' operator must be optional.\" does not matter in this case\n  delete window.__TAURI_INTERNALS__.runCallback\n  // @ts-expect-error \"The operand of a 'delete' operator must be optional.\" does not matter in this case\n  delete window.__TAURI_INTERNALS__.callbacks\n  // @ts-expect-error \"The operand of a 'delete' operator must be optional.\" does not matter in this case\n  delete window.__TAURI_INTERNALS__.convertFileSrc\n  // @ts-expect-error \"The operand of a 'delete' operator must be optional.\" does not matter in this case\n  delete window.__TAURI_INTERNALS__.metadata\n\n  if (typeof window.__TAURI_EVENT_PLUGIN_INTERNALS__ !== 'object') {\n    return\n  }\n  // @ts-expect-error \"The operand of a 'delete' operator must be optional.\" does not matter in this case\n  delete window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener\n}\n"
  },
  {
    "path": "packages/api/src/path.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/**\n * The path module provides utilities for working with file and directory paths.\n *\n * This package is also accessible with `window.__TAURI__.path` when [`app.withGlobalTauri`](https://v2.tauri.app/reference/config/#withglobaltauri) in `tauri.conf.json` is set to `true`.\n *\n * It is recommended to allowlist only the APIs you use for optimal bundle size and security.\n * @module\n */\n\nimport { invoke } from './core'\n\n/**\n * @since 2.0.0\n */\nenum BaseDirectory {\n  /**\n   * @see {@link audioDir} for more information.\n   */\n  Audio = 1,\n  /**\n   * @see {@link cacheDir} for more information.\n   */\n  Cache = 2,\n  /**\n   * @see {@link configDir} for more information.\n   */\n  Config = 3,\n  /**\n   * @see {@link dataDir} for more information.\n   */\n  Data = 4,\n  /**\n   * @see {@link localDataDir} for more information.\n   */\n  LocalData = 5,\n  /**\n   * @see {@link documentDir} for more information.\n   */\n  Document = 6,\n  /**\n   * @see {@link downloadDir} for more information.\n   */\n  Download = 7,\n  /**\n   * @see {@link pictureDir} for more information.\n   */\n  Picture = 8,\n  /**\n   * @see {@link publicDir} for more information.\n   */\n  Public = 9,\n  /**\n   * @see {@link videoDir} for more information.\n   */\n  Video = 10,\n  /**\n   * @see {@link resourceDir} for more information.\n   */\n  Resource = 11,\n  /**\n   * @see {@link tempDir} for more information.\n   */\n  Temp = 12,\n  /**\n   * @see {@link appConfigDir} for more information.\n   */\n  AppConfig = 13,\n  /**\n   * @see {@link appDataDir} for more information.\n   */\n  AppData = 14,\n  /**\n   * @see {@link appLocalDataDir} for more information.\n   */\n  AppLocalData = 15,\n  /**\n   * @see {@link appCacheDir} for more information.\n   */\n  AppCache = 16,\n  /**\n   * @see {@link appLogDir} for more information.\n   */\n  AppLog = 17,\n  /**\n   * @see {@link desktopDir} for more information.\n   */\n  Desktop = 18,\n  /**\n   * @see {@link executableDir} for more information.\n   */\n  Executable = 19,\n  /**\n   * @see {@link fontDir} for more information.\n   */\n  Font = 20,\n  /**\n   * @see {@link homeDir} for more information.\n   */\n  Home = 21,\n  /**\n   * @see {@link runtimeDir} for more information.\n   */\n  Runtime = 22,\n  /**\n   * @see {@link templateDir} for more information.\n   */\n  Template = 23\n}\n\n/**\n * Returns the path to the suggested directory for your app's config files.\n * Resolves to `${configDir}/${bundleIdentifier}`, where `bundleIdentifier` is the [`identifier`](https://v2.tauri.app/reference/config/#identifier) value configured in `tauri.conf.json`.\n * @example\n * ```typescript\n * import { appConfigDir } from '@tauri-apps/api/path';\n * const appConfigDirPath = await appConfigDir();\n * ```\n *\n * @since 1.2.0\n */\nasync function appConfigDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.AppConfig\n  })\n}\n\n/**\n * Returns the path to the suggested directory for your app's data files.\n * Resolves to `${dataDir}/${bundleIdentifier}`, where `bundleIdentifier` is the [`identifier`](https://v2.tauri.app/reference/config/#identifier) value configured in `tauri.conf.json`.\n * @example\n * ```typescript\n * import { appDataDir } from '@tauri-apps/api/path';\n * const appDataDirPath = await appDataDir();\n * ```\n *\n * @since 1.2.0\n */\nasync function appDataDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.AppData\n  })\n}\n\n/**\n * Returns the path to the suggested directory for your app's local data files.\n * Resolves to `${localDataDir}/${bundleIdentifier}`, where `bundleIdentifier` is the [`identifier`](https://v2.tauri.app/reference/config/#identifier) value configured in `tauri.conf.json`.\n * @example\n * ```typescript\n * import { appLocalDataDir } from '@tauri-apps/api/path';\n * const appLocalDataDirPath = await appLocalDataDir();\n * ```\n *\n * @since 1.2.0\n */\nasync function appLocalDataDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.AppLocalData\n  })\n}\n\n/**\n * Returns the path to the suggested directory for your app's cache files.\n * Resolves to `${cacheDir}/${bundleIdentifier}`, where `bundleIdentifier` is the [`identifier`](https://v2.tauri.app/reference/config/#identifier) value configured in `tauri.conf.json`.\n * @example\n * ```typescript\n * import { appCacheDir } from '@tauri-apps/api/path';\n * const appCacheDirPath = await appCacheDir();\n * ```\n *\n * @since 1.2.0\n */\nasync function appCacheDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.AppCache\n  })\n}\n\n/**\n * Returns the path to the user's audio directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_MUSIC_DIR`.\n * - **macOS:** Resolves to `$HOME/Music`.\n * - **Windows:** Resolves to `{FOLDERID_Music}`.\n * @example\n * ```typescript\n * import { audioDir } from '@tauri-apps/api/path';\n * const audioDirPath = await audioDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function audioDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Audio\n  })\n}\n\n/**\n * Returns the path to the user's cache directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to `$XDG_CACHE_HOME` or `$HOME/.cache`.\n * - **macOS:** Resolves to `$HOME/Library/Caches`.\n * - **Windows:** Resolves to `{FOLDERID_LocalAppData}`.\n * @example\n * ```typescript\n * import { cacheDir } from '@tauri-apps/api/path';\n * const cacheDirPath = await cacheDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function cacheDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Cache\n  })\n}\n\n/**\n * Returns the path to the user's config directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to `$XDG_CONFIG_HOME` or `$HOME/.config`.\n * - **macOS:** Resolves to `$HOME/Library/Application Support`.\n * - **Windows:** Resolves to `{FOLDERID_RoamingAppData}`.\n * @example\n * ```typescript\n * import { configDir } from '@tauri-apps/api/path';\n * const configDirPath = await configDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function configDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Config\n  })\n}\n\n/**\n * Returns the path to the user's data directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to `$XDG_DATA_HOME` or `$HOME/.local/share`.\n * - **macOS:** Resolves to `$HOME/Library/Application Support`.\n * - **Windows:** Resolves to `{FOLDERID_RoamingAppData}`.\n * @example\n * ```typescript\n * import { dataDir } from '@tauri-apps/api/path';\n * const dataDirPath = await dataDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function dataDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Data\n  })\n}\n\n/**\n * Returns the path to the user's desktop directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DESKTOP_DIR`.\n * - **macOS:** Resolves to `$HOME/Desktop`.\n * - **Windows:** Resolves to `{FOLDERID_Desktop}`.\n * @example\n * ```typescript\n * import { desktopDir } from '@tauri-apps/api/path';\n * const desktopPath = await desktopDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function desktopDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Desktop\n  })\n}\n\n/**\n * Returns the path to the user's document directory.\n * @example\n * ```typescript\n * import { documentDir } from '@tauri-apps/api/path';\n * const documentDirPath = await documentDir();\n * ```\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DOCUMENTS_DIR`.\n * - **macOS:** Resolves to `$HOME/Documents`.\n * - **Windows:** Resolves to `{FOLDERID_Documents}`.\n *\n * @since 1.0.0\n */\nasync function documentDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Document\n  })\n}\n\n/**\n * Returns the path to the user's download directory.\n *\n * #### Platform-specific\n *\n * - **Linux**: Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DOWNLOAD_DIR`.\n * - **macOS**: Resolves to `$HOME/Downloads`.\n * - **Windows**: Resolves to `{FOLDERID_Downloads}`.\n * @example\n * ```typescript\n * import { downloadDir } from '@tauri-apps/api/path';\n * const downloadDirPath = await downloadDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function downloadDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Download\n  })\n}\n\n/**\n * Returns the path to the user's executable directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to `$XDG_BIN_HOME/../bin` or `$XDG_DATA_HOME/../bin` or `$HOME/.local/bin`.\n * - **macOS:** Not supported.\n * - **Windows:** Not supported.\n * @example\n * ```typescript\n * import { executableDir } from '@tauri-apps/api/path';\n * const executableDirPath = await executableDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function executableDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Executable\n  })\n}\n\n/**\n * Returns the path to the user's font directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to `$XDG_DATA_HOME/fonts` or `$HOME/.local/share/fonts`.\n * - **macOS:** Resolves to `$HOME/Library/Fonts`.\n * - **Windows:** Not supported.\n * @example\n * ```typescript\n * import { fontDir } from '@tauri-apps/api/path';\n * const fontDirPath = await fontDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function fontDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Font\n  })\n}\n\n/**\n * Returns the path to the user's home directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to `$HOME`.\n * - **macOS:** Resolves to `$HOME`.\n * - **Windows:** Resolves to `{FOLDERID_Profile}`.\n * @example\n * ```typescript\n * import { homeDir } from '@tauri-apps/api/path';\n * const homeDirPath = await homeDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function homeDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Home\n  })\n}\n\n/**\n * Returns the path to the user's local data directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to `$XDG_DATA_HOME` or `$HOME/.local/share`.\n * - **macOS:** Resolves to `$HOME/Library/Application Support`.\n * - **Windows:** Resolves to `{FOLDERID_LocalAppData}`.\n * @example\n * ```typescript\n * import { localDataDir } from '@tauri-apps/api/path';\n * const localDataDirPath = await localDataDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function localDataDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.LocalData\n  })\n}\n\n/**\n * Returns the path to the user's picture directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_PICTURES_DIR`.\n * - **macOS:** Resolves to `$HOME/Pictures`.\n * - **Windows:** Resolves to `{FOLDERID_Pictures}`.\n * @example\n * ```typescript\n * import { pictureDir } from '@tauri-apps/api/path';\n * const pictureDirPath = await pictureDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function pictureDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Picture\n  })\n}\n\n/**\n * Returns the path to the user's public directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_PUBLICSHARE_DIR`.\n * - **macOS:** Resolves to `$HOME/Public`.\n * - **Windows:** Resolves to `{FOLDERID_Public}`.\n * @example\n * ```typescript\n * import { publicDir } from '@tauri-apps/api/path';\n * const publicDirPath = await publicDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function publicDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Public\n  })\n}\n\n/**\n * Returns the path to the application's resource directory.\n * To resolve a resource path, see {@linkcode resolveResource}.\n *\n * ## Platform-specific\n *\n * Although we provide the exact path where this function resolves to,\n * this is not a contract and things might change in the future\n *\n * - **Windows:** Resolves to the directory that contains the main executable.\n * - **Linux:** When running in an AppImage, the `APPDIR` variable will be set to\n *   the mounted location of the app, and the resource dir will be `${APPDIR}/usr/lib/${exe_name}`.\n *   If not running in an AppImage, the path is `/usr/lib/${exe_name}`.\n *   When running the app from `src-tauri/target/(debug|release)/`, the path is `${exe_dir}/../lib/${exe_name}`.\n * - **macOS:** Resolves to `${exe_dir}/../Resources` (inside .app).\n * - **iOS:** Resolves to `${exe_dir}/assets`.\n * - **Android:** Currently the resources are stored in the APK as assets so it's not a normal file system path,\n *   we return a special URI prefix `asset://localhost/` here that can be used with the [file system plugin](https://tauri.app/plugin/file-system/),\n *\n * @example\n * ```typescript\n * import { resourceDir } from '@tauri-apps/api/path';\n * const resourceDirPath = await resourceDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function resourceDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Resource\n  })\n}\n\n/**\n * Resolve the path to a resource file.\n * @example\n * ```typescript\n * import { resolveResource } from '@tauri-apps/api/path';\n * const resourcePath = await resolveResource('script.sh');\n * ```\n *\n * @param resourcePath The path to the resource.\n * Must follow the same syntax as defined in `tauri.conf.json > bundle > resources`, i.e. keeping subfolders and parent dir components (`../`).\n * @returns The full path to the resource.\n *\n * @since 1.0.0\n */\nasync function resolveResource(resourcePath: string): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Resource,\n    path: resourcePath\n  })\n}\n\n/**\n * Returns the path to the user's runtime directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to `$XDG_RUNTIME_DIR`.\n * - **macOS:** Not supported.\n * - **Windows:** Not supported.\n * @example\n * ```typescript\n * import { runtimeDir } from '@tauri-apps/api/path';\n * const runtimeDirPath = await runtimeDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function runtimeDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Runtime\n  })\n}\n\n/**\n * Returns the path to the user's template directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_TEMPLATES_DIR`.\n * - **macOS:** Not supported.\n * - **Windows:** Resolves to `{FOLDERID_Templates}`.\n * @example\n * ```typescript\n * import { templateDir } from '@tauri-apps/api/path';\n * const templateDirPath = await templateDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function templateDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Template\n  })\n}\n\n/**\n * Returns the path to the user's video directory.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_VIDEOS_DIR`.\n * - **macOS:** Resolves to `$HOME/Movies`.\n * - **Windows:** Resolves to `{FOLDERID_Videos}`.\n * @example\n * ```typescript\n * import { videoDir } from '@tauri-apps/api/path';\n * const videoDirPath = await videoDir();\n * ```\n *\n * @since 1.0.0\n */\nasync function videoDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Video\n  })\n}\n\n/**\n * Returns the path to the suggested directory for your app's log files.\n *\n * #### Platform-specific\n *\n * - **Linux:** Resolves to `${configDir}/${bundleIdentifier}/logs`.\n * - **macOS:** Resolves to `${homeDir}/Library/Logs/{bundleIdentifier}`\n * - **Windows:** Resolves to `${configDir}/${bundleIdentifier}/logs`.\n * @example\n * ```typescript\n * import { appLogDir } from '@tauri-apps/api/path';\n * const appLogDirPath = await appLogDir();\n * ```\n *\n * @since 1.2.0\n */\nasync function appLogDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.AppLog\n  })\n}\n\n/**\n * Returns a temporary directory.\n * @example\n * ```typescript\n * import { tempDir } from '@tauri-apps/api/path';\n * const temp = await tempDir();\n * ```\n *\n * @since 2.0.0\n */\nasync function tempDir(): Promise<string> {\n  return invoke('plugin:path|resolve_directory', {\n    directory: BaseDirectory.Temp\n  })\n}\n\n/**\n * Returns the platform-specific path segment separator:\n * - `\\` on Windows\n * - `/` on POSIX\n *\n * @since 2.0.0\n */\nfunction sep(): string {\n  return window.__TAURI_INTERNALS__.plugins.path.sep\n}\n\n/**\n * Returns the platform-specific path segment delimiter:\n * - `;` on Windows\n * - `:` on POSIX\n *\n * @since 2.0.0\n */\nfunction delimiter(): string {\n  return window.__TAURI_INTERNALS__.plugins.path.delimiter\n}\n/**\n * Resolves a sequence of `paths` or `path` segments into an absolute path.\n * @example\n * ```typescript\n * import { resolve, appDataDir } from '@tauri-apps/api/path';\n * const appDataDirPath = await appDataDir();\n * const path = await resolve(appDataDirPath, '..', 'users', 'tauri', 'avatar.png');\n * ```\n *\n * @since 1.0.0\n */\nasync function resolve(...paths: string[]): Promise<string> {\n  return invoke('plugin:path|resolve', { paths })\n}\n\n/**\n * Normalizes the given `path`, resolving `'..'` and `'.'` segments and resolve symbolic links.\n * @example\n * ```typescript\n * import { normalize, appDataDir } from '@tauri-apps/api/path';\n * const appDataDirPath = await appDataDir();\n * const path = await normalize(`${appDataDirPath}/../users/tauri/avatar.png`);\n * ```\n *\n * @since 1.0.0\n */\nasync function normalize(path: string): Promise<string> {\n  return invoke('plugin:path|normalize', { path })\n}\n\n/**\n *  Joins all given `path` segments together using the platform-specific separator as a delimiter, then normalizes the resulting path.\n * @example\n * ```typescript\n * import { join, appDataDir } from '@tauri-apps/api/path';\n * const appDataDirPath = await appDataDir();\n * const path = await join(appDataDirPath, 'users', 'tauri', 'avatar.png');\n * ```\n *\n * @since 1.0.0\n */\nasync function join(...paths: string[]): Promise<string> {\n  return invoke('plugin:path|join', { paths })\n}\n\n/**\n * Returns the parent directory of a given `path`. Trailing directory separators are ignored.\n * @example\n * ```typescript\n * import { dirname } from '@tauri-apps/api/path';\n * const dir = await dirname('/path/to/somedir/');\n * assert(dir === '/path/to');\n * ```\n *\n * @since 1.0.0\n */\nasync function dirname(path: string): Promise<string> {\n  return invoke('plugin:path|dirname', { path })\n}\n\n/**\n * Returns the extension of the `path`.\n * @example\n * ```typescript\n * import { extname } from '@tauri-apps/api/path';\n * const ext = await extname('/path/to/file.html');\n * assert(ext === 'html');\n * ```\n *\n * @since 1.0.0\n */\nasync function extname(path: string): Promise<string> {\n  return invoke('plugin:path|extname', { path })\n}\n\n/**\n * Returns the last portion of a `path`. Trailing directory separators are ignored.\n * @example\n * ```typescript\n * import { basename } from '@tauri-apps/api/path';\n * const base = await basename('path/to/app.conf');\n * assert(base === 'app.conf');\n * ```\n * @param ext An optional file extension to be removed from the returned path.\n *\n * @since 1.0.0\n */\nasync function basename(path: string, ext?: string): Promise<string> {\n  return invoke('plugin:path|basename', { path, ext })\n}\n\n/**\n * Returns whether the path is absolute or not.\n * @example\n * ```typescript\n * import { isAbsolute } from '@tauri-apps/api/path';\n * assert(await isAbsolute('/home/tauri'));\n * ```\n *\n * @since 1.0.0\n */\nasync function isAbsolute(path: string): Promise<boolean> {\n  return invoke('plugin:path|is_absolute', { path })\n}\n\nexport {\n  BaseDirectory,\n  appConfigDir,\n  appDataDir,\n  appLocalDataDir,\n  appCacheDir,\n  appLogDir,\n  audioDir,\n  cacheDir,\n  configDir,\n  dataDir,\n  desktopDir,\n  documentDir,\n  downloadDir,\n  executableDir,\n  fontDir,\n  homeDir,\n  localDataDir,\n  pictureDir,\n  publicDir,\n  resourceDir,\n  resolveResource,\n  runtimeDir,\n  templateDir,\n  videoDir,\n  sep,\n  delimiter,\n  resolve,\n  normalize,\n  join,\n  dirname,\n  extname,\n  basename,\n  isAbsolute,\n  tempDir\n}\n"
  },
  {
    "path": "packages/api/src/tray.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport type { Menu, Submenu } from './menu'\nimport { Channel, invoke, Resource } from './core'\nimport { Image, transformImage } from './image'\nimport { PhysicalPosition, PhysicalSize } from './dpi'\n\nexport type MouseButtonState = 'Up' | 'Down'\nexport type MouseButton = 'Left' | 'Right' | 'Middle'\nexport type TrayIconEventType =\n  | 'Click'\n  | 'DoubleClick'\n  | 'Enter'\n  | 'Move'\n  | 'Leave'\n\nexport type TrayIconEventBase<T extends TrayIconEventType> = {\n  /** The tray icon event type */\n  type: T\n  /** Id of the tray icon which triggered this event. */\n  id: string\n  /** Physical position of the click the triggered this event. */\n  position: PhysicalPosition\n  /** Position and size of the tray icon. */\n  rect: {\n    position: PhysicalPosition\n    size: PhysicalSize\n  }\n}\n\nexport type TrayIconClickEvent = {\n  /** Mouse button that triggered this event. */\n  button: MouseButton\n  /** Mouse button state when this event was triggered. */\n  buttonState: MouseButtonState\n}\n\n/**\n * Describes a tray icon event.\n *\n * #### Platform-specific:\n *\n * - **Linux**: Unsupported. The event is not emitted even though the icon is shown,\n * the icon will still show a context menu on right click.\n */\nexport type TrayIconEvent =\n  | (TrayIconEventBase<'Click'> & TrayIconClickEvent)\n  | (TrayIconEventBase<'DoubleClick'> & Omit<TrayIconClickEvent, 'buttonState'>)\n  | TrayIconEventBase<'Enter'>\n  | TrayIconEventBase<'Move'>\n  | TrayIconEventBase<'Leave'>\n\ntype RustTrayIconEvent = Omit<TrayIconEvent, 'rect'> & {\n  rect: {\n    position: {\n      Physical: { x: number; y: number }\n    }\n    size: {\n      Physical: { width: number; height: number }\n    }\n  }\n}\n\n/**\n * Tray icon types and utilities.\n *\n * This package is also accessible with `window.__TAURI__.tray` when [`app.withGlobalTauri`](https://v2.tauri.app/reference/config/#withglobaltauri) in `tauri.conf.json` is set to `true`.\n * @module\n */\n\n/** {@link TrayIcon.new|`TrayIcon`} creation options */\nexport interface TrayIconOptions {\n  /** The tray icon id. If undefined, a random one will be assigned */\n  id?: string\n  /** The tray icon menu */\n  menu?: Menu | Submenu\n  /**\n   * The tray icon which could be icon bytes or path to the icon file.\n   *\n   * Note that you may need the `image-ico` or `image-png` Cargo features to use this API.\n   * To enable it, change your Cargo.toml file:\n   * ```toml\n   * [dependencies]\n   * tauri = { version = \"...\", features = [\"...\", \"image-png\"] }\n   * ```\n   */\n  icon?: string | Uint8Array | ArrayBuffer | number[] | Image\n  /** The tray icon tooltip */\n  tooltip?: string\n  /**\n   * The tray title\n   *\n   * #### Platform-specific\n   *\n   * - **Linux:** The title will not be shown unless there is an icon\n   * as well.  The title is useful for numerical and other frequently\n   * updated information.  In general, it shouldn't be shown unless a\n   * user requests it as it can take up a significant amount of space\n   * on the user's panel.  This may not be shown in all visualizations.\n   * - **Windows:** Unsupported.\n   */\n  title?: string\n  /**\n   * The tray icon temp dir path. **Linux only**.\n   *\n   * On Linux, we need to write the icon to the disk and usually it will\n   * be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.\n   */\n  tempDirPath?: string\n  /**\n   * Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.\n   */\n  iconAsTemplate?: boolean\n  /**\n   * Whether to show the tray menu on left click or not, default is `true`.\n   *\n   * #### Platform-specific:\n   *\n   * - **Linux**: Unsupported.\n   *\n   * @deprecated use {@linkcode TrayIconOptions.showMenuOnLeftClick} instead.\n   */\n  menuOnLeftClick?: boolean\n  /**\n   * Whether to show the tray menu on left click or not, default is `true`.\n   *\n   * #### Platform-specific:\n   *\n   * - **Linux**: Unsupported.\n   *\n   * @since 2.2.0\n   */\n  showMenuOnLeftClick?: boolean\n  /** A handler for an event on the tray icon. */\n  action?: (event: TrayIconEvent) => void\n}\n\n/**\n * Tray icon class and associated methods. This type constructor is private,\n * instead, you should use the static method {@linkcode TrayIcon.new}.\n *\n * #### Warning\n *\n * Unlike Rust, javascript does not have any way to run cleanup code\n * when an object is being removed by garbage collection, but this tray icon\n * will be cleaned up when the tauri app exists, however if you want to cleanup\n * this object early, you need to call {@linkcode TrayIcon.close}.\n *\n * @example\n * ```ts\n * import { TrayIcon } from '@tauri-apps/api/tray';\n * const tray = await TrayIcon.new({ tooltip: 'awesome tray tooltip' });\n * tray.set_tooltip('new tooltip');\n * ```\n */\nexport class TrayIcon extends Resource {\n  /** The id associated with this tray icon.   */\n  public id: string\n\n  private constructor(rid: number, id: string) {\n    super(rid)\n    this.id = id\n  }\n\n  /** Gets a tray icon using the provided id. */\n  static async getById(id: string): Promise<TrayIcon | null> {\n    return invoke<number>('plugin:tray|get_by_id', { id }).then((rid) =>\n      rid ? new TrayIcon(rid, id) : null\n    )\n  }\n\n  /**\n   * Removes a tray icon using the provided id from tauri's internal state.\n   *\n   * Note that this may cause the tray icon to disappear\n   * if it wasn't cloned somewhere else or referenced by JS.\n   */\n  static async removeById(id: string): Promise<void> {\n    return invoke('plugin:tray|remove_by_id', { id })\n  }\n\n  /**\n   * Creates a new {@linkcode TrayIcon}\n   *\n   * #### Platform-specific:\n   *\n   * - **Linux:** Sometimes the icon won't be visible unless a menu is set.\n   * Setting an empty {@linkcode Menu} is enough.\n   */\n  static async new(options?: TrayIconOptions): Promise<TrayIcon> {\n    if (options?.menu) {\n      // @ts-expect-error we only need the rid and kind\n      options.menu = [options.menu.rid, options.menu.kind]\n    }\n    if (options?.icon) {\n      options.icon = transformImage(options.icon)\n    }\n\n    const handler = new Channel<RustTrayIconEvent>()\n    if (options?.action) {\n      const action = options.action\n      handler.onmessage = (e) => action(mapEvent(e))\n      delete options.action\n    }\n\n    return invoke<[number, string]>('plugin:tray|new', {\n      options: options ?? {},\n      handler\n    }).then(([rid, id]) => new TrayIcon(rid, id))\n  }\n\n  /**\n   *  Sets a new tray icon. If `null` is provided, it will remove the icon.\n   *\n   * Note that you may need the `image-ico` or `image-png` Cargo features to use this API.\n   * To enable it, change your Cargo.toml file:\n   * ```toml\n   * [dependencies]\n   * tauri = { version = \"...\", features = [\"...\", \"image-png\"] }\n   * ```\n   */\n  async setIcon(\n    icon: string | Image | Uint8Array | ArrayBuffer | number[] | null\n  ): Promise<void> {\n    let trayIcon = null\n    if (icon) {\n      trayIcon = transformImage(icon)\n    }\n    return invoke('plugin:tray|set_icon', { rid: this.rid, icon: trayIcon })\n  }\n\n  /**\n   * Sets a new tray menu.\n   *\n   * #### Platform-specific:\n   *\n   * - **Linux**: once a menu is set it cannot be removed so `null` has no effect\n   */\n  async setMenu(menu: Menu | Submenu | null): Promise<void> {\n    if (menu) {\n      // @ts-expect-error we only need the rid and kind\n      menu = [menu.rid, menu.kind]\n    }\n    return invoke('plugin:tray|set_menu', { rid: this.rid, menu })\n  }\n\n  /**\n   * Sets the tooltip for this tray icon.\n   *\n   * #### Platform-specific:\n   *\n   * - **Linux:** Unsupported\n   */\n  async setTooltip(tooltip: string | null): Promise<void> {\n    return invoke('plugin:tray|set_tooltip', { rid: this.rid, tooltip })\n  }\n\n  /**\n   * Sets the tooltip for this tray icon.\n   *\n   * #### Platform-specific:\n   *\n   * - **Linux:** The title will not be shown unless there is an icon\n   * as well.  The title is useful for numerical and other frequently\n   * updated information.  In general, it shouldn't be shown unless a\n   * user requests it as it can take up a significant amount of space\n   * on the user's panel.  This may not be shown in all visualizations.\n   * - **Windows:** Unsupported\n   */\n  async setTitle(title: string | null): Promise<void> {\n    return invoke('plugin:tray|set_title', { rid: this.rid, title })\n  }\n\n  /** Show or hide this tray icon. */\n  async setVisible(visible: boolean): Promise<void> {\n    return invoke('plugin:tray|set_visible', { rid: this.rid, visible })\n  }\n\n  /**\n   * Sets the tray icon temp dir path. **Linux only**.\n   *\n   * On Linux, we need to write the icon to the disk and usually it will\n   * be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.\n   */\n  async setTempDirPath(path: string | null): Promise<void> {\n    return invoke('plugin:tray|set_temp_dir_path', { rid: this.rid, path })\n  }\n\n  /** Sets the current icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only** */\n  async setIconAsTemplate(asTemplate: boolean): Promise<void> {\n    return invoke('plugin:tray|set_icon_as_template', {\n      rid: this.rid,\n      asTemplate\n    })\n  }\n\n  /**\n   *  Disable or enable showing the tray menu on left click.\n   *\n   * #### Platform-specific:\n   *\n   * - **Linux**: Unsupported.\n   *\n   * @deprecated use {@linkcode TrayIcon.setShowMenuOnLeftClick} instead.\n   */\n  async setMenuOnLeftClick(onLeft: boolean): Promise<void> {\n    return invoke('plugin:tray|set_show_menu_on_left_click', {\n      rid: this.rid,\n      onLeft\n    })\n  }\n\n  /**\n   *  Disable or enable showing the tray menu on left click.\n   *\n   * #### Platform-specific:\n   *\n   * - **Linux**: Unsupported.\n   *\n   * @since 2.2.0\n   */\n  async setShowMenuOnLeftClick(onLeft: boolean): Promise<void> {\n    return invoke('plugin:tray|set_show_menu_on_left_click', {\n      rid: this.rid,\n      onLeft\n    })\n  }\n}\n\nfunction mapEvent(e: RustTrayIconEvent): TrayIconEvent {\n  const out = e as unknown as TrayIconEvent\n\n  out.position = new PhysicalPosition(e.position)\n  out.rect.position = new PhysicalPosition(e.rect.position)\n  out.rect.size = new PhysicalSize(e.rect.size)\n\n  return out\n}\n"
  },
  {
    "path": "packages/api/src/webview.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/**\n * Provides APIs to create webviews, communicate with other webviews and manipulate the current webview.\n *\n * #### Webview events\n *\n * Events can be listened to using {@link Webview.listen}:\n * ```typescript\n * import { getCurrentWebview } from \"@tauri-apps/api/webview\";\n * getCurrentWebview().listen(\"my-webview-event\", ({ event, payload }) => { });\n * ```\n *\n * @module\n */\n\nimport { PhysicalPosition, PhysicalSize } from './dpi'\nimport type { LogicalPosition, LogicalSize } from './dpi'\nimport { Position, Size } from './dpi'\nimport type { EventName, EventCallback, UnlistenFn } from './event'\nimport {\n  TauriEvent,\n  // imported for documentation purposes\n  type EventTarget,\n  emit,\n  emitTo,\n  listen,\n  once\n} from './event'\nimport { invoke } from './core'\nimport {\n  BackgroundThrottlingPolicy,\n  ScrollBarStyle,\n  Color,\n  Window,\n  getCurrentWindow\n} from './window'\nimport { WebviewWindow } from './webviewWindow'\n\n/** The drag and drop event types. */\ntype DragDropEvent =\n  | { type: 'enter'; paths: string[]; position: PhysicalPosition }\n  | { type: 'over'; position: PhysicalPosition }\n  | { type: 'drop'; paths: string[]; position: PhysicalPosition }\n  | { type: 'leave' }\n\n/**\n * Get an instance of `Webview` for the current webview.\n *\n * @since 2.0.0\n */\nfunction getCurrentWebview(): Webview {\n  return new Webview(\n    getCurrentWindow(),\n    window.__TAURI_INTERNALS__.metadata.currentWebview.label,\n    {\n      // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor\n      skip: true\n    }\n  )\n}\n\n/**\n * Gets a list of instances of `Webview` for all available webviews.\n *\n * @since 2.0.0\n */\nasync function getAllWebviews(): Promise<Webview[]> {\n  return invoke<Array<{ windowLabel: string; label: string }>>(\n    'plugin:webview|get_all_webviews'\n  ).then((webviews) =>\n    webviews.map(\n      (w) =>\n        new Webview(\n          new Window(w.windowLabel, {\n            // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor\n            skip: true\n          }),\n          w.label,\n          {\n            // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor\n            skip: true\n          }\n        )\n    )\n  )\n}\n\n/** @ignore */\n// events that are emitted right here instead of by the created webview\nconst localTauriEvents = ['tauri://created', 'tauri://error']\n/** @ignore */\nexport type WebviewLabel = string\n\n/**\n * Create new webview or get a handle to an existing one.\n *\n * Webviews are identified by a *label*  a unique identifier that can be used to reference it later.\n * It may only contain alphanumeric characters `a-zA-Z` plus the following special characters `-`, `/`, `:` and `_`.\n *\n * @example\n * ```typescript\n * import { Window } from \"@tauri-apps/api/window\"\n * import { Webview } from \"@tauri-apps/api/webview\"\n *\n * const appWindow = new Window('uniqueLabel');\n *\n * appWindow.once('tauri://created', async function () {\n *   // `new Webview` Should be called after the window is successfully created,\n *   // or webview may not be attached to the window since window is not created yet.\n *\n *   // loading embedded asset:\n *   const webview = new Webview(appWindow, 'theUniqueLabel', {\n *     url: 'path/to/page.html',\n *\n *     // create a webview with specific logical position and size\n *     x: 0,\n *     y: 0,\n *     width: 800,\n *     height: 600,\n *   });\n *   // alternatively, load a remote URL:\n *   const webview = new Webview(appWindow, 'theUniqueLabel', {\n *     url: 'https://github.com/tauri-apps/tauri',\n *\n *     // create a webview with specific logical position and size\n *     x: 0,\n *     y: 0,\n *     width: 800,\n *     height: 600,\n *   });\n *\n *   webview.once('tauri://created', function () {\n *     // webview successfully created\n *   });\n *   webview.once('tauri://error', function (e) {\n *     // an error happened creating the webview\n *   });\n *\n *\n *   // emit an event to the backend\n *   await webview.emit(\"some-event\", \"data\");\n *   // listen to an event from the backend\n *   const unlisten = await webview.listen(\"event-name\", e => { });\n *   unlisten();\n * });\n * ```\n *\n * @since 2.0.0\n */\nclass Webview {\n  /** The webview label. It is a unique identifier for the webview, can be used to reference it later. */\n  label: WebviewLabel\n  /** The window hosting this webview. */\n  window: Window\n  /** Local event listeners. */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  listeners: Record<string, Array<EventCallback<any>>>\n\n  /**\n   * Creates a new Webview.\n   * @example\n   * ```typescript\n   * import { Window } from '@tauri-apps/api/window'\n   * import { Webview } from '@tauri-apps/api/webview'\n   * const appWindow = new Window('my-label')\n   *\n   * appWindow.once('tauri://created', async function() {\n   *   const webview = new Webview(appWindow, 'my-label', {\n   *     url: 'https://github.com/tauri-apps/tauri',\n   *\n   *     // create a webview with specific logical position and size\n   *     x: 0,\n   *     y: 0,\n   *     width: 800,\n   *     height: 600,\n   *   });\n   *\n   *   webview.once('tauri://created', function () {\n   *     // webview successfully created\n   *   });\n   *   webview.once('tauri://error', function (e) {\n   *     // an error happened creating the webview\n   *   });\n   * });\n   * ```\n   *\n   * @param window the window to add this webview to.\n   * @param label The unique webview label. Must be alphanumeric: `a-zA-Z-/:_`.\n   * @returns The {@link Webview} instance to communicate with the webview.\n   */\n  constructor(window: Window, label: WebviewLabel, options: WebviewOptions) {\n    this.window = window\n    this.label = label\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n    this.listeners = Object.create(null)\n\n    // @ts-expect-error `skip` is not a public API so it is not defined in WebviewOptions\n    if (!options?.skip) {\n      invoke('plugin:webview|create_webview', {\n        windowLabel: window.label,\n        options: {\n          ...options,\n          label\n        }\n      })\n        .then(async () => this.emit('tauri://created'))\n        .catch(async (e: string) => this.emit('tauri://error', e))\n    }\n  }\n\n  /**\n   * Gets the Webview for the webview associated with the given label.\n   * @example\n   * ```typescript\n   * import { Webview } from '@tauri-apps/api/webview';\n   * const mainWebview = Webview.getByLabel('main');\n   * ```\n   *\n   * @param label The webview label.\n   * @returns The Webview instance to communicate with the webview or null if the webview doesn't exist.\n   */\n  static async getByLabel(label: string): Promise<Webview | null> {\n    return (await getAllWebviews()).find((w) => w.label === label) ?? null\n  }\n\n  /**\n   * Get an instance of `Webview` for the current webview.\n   */\n  static getCurrent(): Webview {\n    return getCurrentWebview()\n  }\n\n  /**\n   * Gets a list of instances of `Webview` for all available webviews.\n   */\n  static async getAll(): Promise<Webview[]> {\n    return getAllWebviews()\n  }\n\n  /**\n   * Listen to an emitted event on this webview.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * const unlisten = await getCurrentWebview().listen<string>('state-changed', (event) => {\n   *   console.log(`Got error: ${payload}`);\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n   * @param handler Event handler.\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async listen<T>(\n    event: EventName,\n    handler: EventCallback<T>\n  ): Promise<UnlistenFn> {\n    if (this._handleTauriEvent(event, handler)) {\n      return () => {\n        // eslint-disable-next-line security/detect-object-injection\n        const listeners = this.listeners[event]\n        listeners.splice(listeners.indexOf(handler), 1)\n      }\n    }\n    return listen(event, handler, {\n      target: { kind: 'Webview', label: this.label }\n    })\n  }\n\n  /**\n   * Listen to an emitted event on this webview only once.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * const unlisten = await getCurrent().once<null>('initialized', (event) => {\n   *   console.log(`Webview initialized!`);\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n   * @param handler Event handler.\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async once<T>(\n    event: EventName,\n    handler: EventCallback<T>\n  ): Promise<UnlistenFn> {\n    if (this._handleTauriEvent(event, handler)) {\n      return () => {\n        // eslint-disable-next-line security/detect-object-injection\n        const listeners = this.listeners[event]\n        listeners.splice(listeners.indexOf(handler), 1)\n      }\n    }\n    return once(event, handler, {\n      target: { kind: 'Webview', label: this.label }\n    })\n  }\n\n  /**\n   * Emits an event to all {@link EventTarget|targets}.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * await getCurrentWebview().emit('webview-loaded', { loggedIn: true, token: 'authToken' });\n   * ```\n   *\n   * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n   * @param payload Event payload.\n   */\n  async emit<T>(event: string, payload?: T): Promise<void> {\n    if (localTauriEvents.includes(event)) {\n      // eslint-disable-next-line\n      for (const handler of this.listeners[event] || []) {\n        handler({\n          event,\n          id: -1,\n          payload\n        })\n      }\n      return\n    }\n    return emit<T>(event, payload)\n  }\n\n  /**\n   * Emits an event to all {@link EventTarget|targets} matching the given target.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * await getCurrentWebview().emitTo('main', 'webview-loaded', { loggedIn: true, token: 'authToken' });\n   * ```\n   *\n   * @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.\n   * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n   * @param payload Event payload.\n   */\n  async emitTo<T>(\n    target: string | EventTarget,\n    event: string,\n    payload?: T\n  ): Promise<void> {\n    if (localTauriEvents.includes(event)) {\n      // eslint-disable-next-line\n      for (const handler of this.listeners[event] || []) {\n        handler({\n          event,\n          id: -1,\n          payload\n        })\n      }\n      return\n    }\n    return emitTo<T>(target, event, payload)\n  }\n\n  /** @ignore */\n  _handleTauriEvent<T>(event: string, handler: EventCallback<T>): boolean {\n    if (localTauriEvents.includes(event)) {\n      if (!(event in this.listeners)) {\n        // eslint-disable-next-line security/detect-object-injection\n        this.listeners[event] = [handler]\n      } else {\n        // eslint-disable-next-line security/detect-object-injection\n        this.listeners[event].push(handler)\n      }\n      return true\n    }\n    return false\n  }\n\n  // Getters\n  /**\n   * The position of the top-left hand corner of the webview's client area relative to the top-left hand corner of the desktop.\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * const position = await getCurrentWebview().position();\n   * ```\n   *\n   * @returns The webview's position.\n   */\n  async position(): Promise<PhysicalPosition> {\n    return invoke<{ x: number; y: number }>('plugin:webview|webview_position', {\n      label: this.label\n    }).then((p) => new PhysicalPosition(p))\n  }\n\n  /**\n   * The physical size of the webview's client area.\n   * The client area is the content of the webview, excluding the title bar and borders.\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * const size = await getCurrentWebview().size();\n   * ```\n   *\n   * @returns The webview's size.\n   */\n  async size(): Promise<PhysicalSize> {\n    return invoke<{ width: number; height: number }>(\n      'plugin:webview|webview_size',\n      {\n        label: this.label\n      }\n    ).then((s) => new PhysicalSize(s))\n  }\n\n  // Setters\n\n  /**\n   * Closes the webview.\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * await getCurrentWebview().close();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async close(): Promise<void> {\n    return invoke('plugin:webview|webview_close', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Resizes the webview.\n   * @example\n   * ```typescript\n   * import { getCurrent, LogicalSize } from '@tauri-apps/api/webview';\n   * await getCurrentWebview().setSize(new LogicalSize(600, 500));\n   * ```\n   *\n   * @param size The logical or physical size.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setSize(size: LogicalSize | PhysicalSize | Size): Promise<void> {\n    return invoke('plugin:webview|set_webview_size', {\n      label: this.label,\n      value: size instanceof Size ? size : new Size(size)\n    })\n  }\n\n  /**\n   * Sets the webview position.\n   * @example\n   * ```typescript\n   * import { getCurrent, LogicalPosition } from '@tauri-apps/api/webview';\n   * await getCurrentWebview().setPosition(new LogicalPosition(600, 500));\n   * ```\n   *\n   * @param position The new position, in logical or physical pixels.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setPosition(\n    position: LogicalPosition | PhysicalPosition | Position\n  ): Promise<void> {\n    return invoke('plugin:webview|set_webview_position', {\n      label: this.label,\n      value: position instanceof Position ? position : new Position(position)\n    })\n  }\n\n  /**\n   * Bring the webview to front and focus.\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * await getCurrentWebview().setFocus();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setFocus(): Promise<void> {\n    return invoke('plugin:webview|set_webview_focus', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Sets whether the webview should automatically grow and shrink its size and position when the parent window resizes.\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * await getCurrentWebview().setAutoResize(true);\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setAutoResize(autoResize: boolean): Promise<void> {\n    return invoke('plugin:webview|set_webview_auto_resize', {\n      label: this.label,\n      value: autoResize\n    })\n  }\n\n  /**\n   * Hide the webview.\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * await getCurrentWebview().hide();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async hide(): Promise<void> {\n    return invoke('plugin:webview|webview_hide', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Show the webview.\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * await getCurrentWebview().show();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async show(): Promise<void> {\n    return invoke('plugin:webview|webview_show', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Set webview zoom level.\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * await getCurrentWebview().setZoom(1.5);\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setZoom(scaleFactor: number): Promise<void> {\n    return invoke('plugin:webview|set_webview_zoom', {\n      label: this.label,\n      value: scaleFactor\n    })\n  }\n\n  /**\n   * Moves this webview to the given label.\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * await getCurrentWebview().reparent('other-window');\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async reparent(window: Window | WebviewWindow | string): Promise<void> {\n    return invoke('plugin:webview|reparent', {\n      label: this.label,\n      window: typeof window === 'string' ? window : window.label\n    })\n  }\n\n  /**\n   * Clears all browsing data for this webview.\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from '@tauri-apps/api/webview';\n   * await getCurrentWebview().clearAllBrowsingData();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async clearAllBrowsingData(): Promise<void> {\n    return invoke('plugin:webview|clear_all_browsing_data')\n  }\n\n  /**\n   * Specify the webview background color.\n   *\n   * #### Platfrom-specific:\n   *\n   * - **macOS / iOS**: Not implemented.\n   * - **Windows**:\n   *   - On Windows 7, transparency is not supported and the alpha value will be ignored.\n   *   - On Windows higher than 7: translucent colors are not supported so any alpha value other than `0` will be replaced by `255`\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   *\n   * @since 2.1.0\n   */\n  async setBackgroundColor(color: Color | null): Promise<void> {\n    return invoke('plugin:webview|set_webview_background_color', { color })\n  }\n\n  // Listeners\n\n  /**\n   * Listen to a file drop event.\n   * The listener is triggered when the user hovers the selected files on the webview,\n   * drops the files or cancels the operation.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWebview } from \"@tauri-apps/api/webview\";\n   * const unlisten = await getCurrentWebview().onDragDropEvent((event) => {\n   *  if (event.payload.type === 'over') {\n   *    console.log('User hovering', event.payload.position);\n   *  } else if (event.payload.type === 'drop') {\n   *    console.log('User dropped', event.payload.paths);\n   *  } else {\n   *    console.log('File drop cancelled');\n   *  }\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * When the debugger panel is open, the drop position of this event may be inaccurate due to a known limitation.\n   * To retrieve the correct drop position, please detach the debugger.\n   *\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async onDragDropEvent(\n    handler: EventCallback<DragDropEvent>\n  ): Promise<UnlistenFn> {\n    type DragPayload = { paths: string[]; position: PhysicalPosition }\n\n    const unlistenDragEnter = await this.listen<DragPayload>(\n      TauriEvent.DRAG_ENTER,\n      (event) => {\n        handler({\n          ...event,\n          payload: {\n            type: 'enter',\n            paths: event.payload.paths,\n            position: new PhysicalPosition(event.payload.position)\n          }\n        })\n      }\n    )\n\n    const unlistenDragOver = await this.listen<DragPayload>(\n      TauriEvent.DRAG_OVER,\n      (event) => {\n        handler({\n          ...event,\n          payload: {\n            type: 'over',\n            position: new PhysicalPosition(event.payload.position)\n          }\n        })\n      }\n    )\n\n    const unlistenDragDrop = await this.listen<DragPayload>(\n      TauriEvent.DRAG_DROP,\n      (event) => {\n        handler({\n          ...event,\n          payload: {\n            type: 'drop',\n            paths: event.payload.paths,\n            position: new PhysicalPosition(event.payload.position)\n          }\n        })\n      }\n    )\n\n    const unlistenDragLeave = await this.listen<null>(\n      TauriEvent.DRAG_LEAVE,\n      (event) => {\n        handler({ ...event, payload: { type: 'leave' } })\n      }\n    )\n\n    return () => {\n      unlistenDragEnter()\n      unlistenDragDrop()\n      unlistenDragOver()\n      unlistenDragLeave()\n    }\n  }\n}\n\n/**\n * Configuration for the webview to create.\n *\n * @since 2.0.0\n */\ninterface WebviewOptions {\n  /**\n   * Remote URL or local file path to open.\n   *\n   * - URL such as `https://github.com/tauri-apps` is opened directly on a Tauri webview.\n   * - data: URL such as `data:text/html,<html>...` is only supported with the `webview-data-url` Cargo feature for the `tauri` dependency.\n   * - local file path or route such as `/path/to/page.html` or `/users` is appended to the application URL (the devServer URL on development, or `tauri://localhost/` and `https://tauri.localhost/` on production).\n   */\n  url?: string\n  /** The initial vertical position in logical pixels. */\n  x: number\n  /** The initial horizontal position in logical pixels. */\n  y: number\n  /** The initial width in logical pixels. */\n  width: number\n  /** The initial height in logical pixels. */\n  height: number\n  /**\n   * Whether the webview is transparent or not.\n   * Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri.conf.json > app > macOSPrivateApi`.\n   * WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.\n   */\n  transparent?: boolean\n  /**\n   * Whether the webview should have focus or not\n   *\n   * @since 2.1.0\n   */\n  focus?: boolean\n  /**\n   * Whether the drag and drop is enabled or not on the webview. By default it is enabled.\n   *\n   * Disabling it is required to use HTML5 drag and drop on the frontend on Windows.\n   */\n  dragDropEnabled?: boolean\n  /**\n   * Whether clicking an inactive webview also clicks through to the webview on macOS.\n   */\n  acceptFirstMouse?: boolean\n  /**\n   * The user agent for the webview.\n   */\n  userAgent?: string\n  /**\n   * Whether or not the webview should be launched in incognito mode.\n   *\n   * #### Platform-specific\n   *\n   * - **Android:** Unsupported.\n   */\n  incognito?: boolean\n  /**\n   * The proxy URL for the WebView for all network requests.\n   *\n   * Must be either a `http://` or a `socks5://` URL.\n   *\n   * #### Platform-specific\n   *\n   * - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.\n   * */\n  proxyUrl?: string\n  /**\n   * Whether page zooming by hotkeys is enabled\n   *\n   * #### Platform-specific:\n   *\n   * - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.\n   * - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,\n   * 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission\n   *\n   * - **Android / iOS**: Unsupported.\n   */\n  zoomHotkeysEnabled?: boolean\n\n  /**\n   * Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.\n   *\n   * #### Note\n   *\n   * Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.\n   *\n   * #### Warning\n   *\n   * Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access them.\n   *\n   * @since 2.1.0\n   */\n  useHttpsScheme?: boolean\n  /**\n   * Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default.\n   *\n   * This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.\n   *\n   * #### Platform-specific\n   *\n   * - macOS: This will call private functions on **macOS**.\n   * - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.\n   * - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.\n   *\n   * @since 2.1.0\n   */\n  devtools?: boolean\n  /**\n   * Set the window and webview background color.\n   *\n   * #### Platform-specific:\n   *\n   * - **macOS / iOS**: Not implemented.\n   * - **Windows**:\n   *   - On Windows 7, alpha channel is ignored.\n   *   - On Windows 8 and newer, if alpha channel is not `0`, it will be ignored.\n   *\n   * @since 2.1.0\n   */\n  backgroundColor?: Color\n\n  /** Change the default background throttling behaviour.\n   *\n   * By default, browsers use a suspend policy that will throttle timers and even unload\n   * the whole tab (view) to free resources after roughly 5 minutes when a view became\n   * minimized or hidden. This will pause all tasks until the documents visibility state\n   * changes back from hidden to visible by bringing the view back to the foreground.\n   *\n   * ## Platform-specific\n   *\n   * - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.\n   * - **iOS**: Supported since version 17.0+.\n   * - **macOS**: Supported since version 14.0+.\n   *\n   * see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578\n   *\n   * @since 2.3.0\n   */\n  backgroundThrottling?: BackgroundThrottlingPolicy\n  /**\n   * Whether we should disable JavaScript code execution on the webview or not.\n   */\n  javascriptDisabled?: boolean\n  /**\n   * on macOS and iOS there is a link preview on long pressing links, this is enabled by default.\n   * see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview\n   */\n  allowLinkPreview?: boolean\n  /**\n   * Allows disabling the input accessory view on iOS.\n   *\n   * The accessory view is the view that appears above the keyboard when a text input element is focused.\n   * It usually displays a view with \"Done\", \"Next\" buttons.\n   */\n  disableInputAccessoryView?: boolean\n  /**\n   * Set a custom path for the webview's data directory (localStorage, cache, etc.) **relative to [`appDataDir()`]/${label}**.\n   * For security reasons, paths outside of that location can only be configured on the Rust side.\n   *\n   * #### Platform-specific:\n   *\n   * - **Windows**: WebViews with different values for settings like `additionalBrowserArgs`, `browserExtensionsEnabled` or `scrollBarStyle` must have different data directories.\n   * - **macOS / iOS**: Unsupported, use `dataStoreIdentifier` instead.\n   * - **Android**: Unsupported.\n   *\n   * @since 2.9.0\n   */\n  dataDirectory?: string\n  /**\n   * Initialize the WebView with a custom data store identifier. This can be seen as a replacement for `dataDirectory` which is unavailable in WKWebView.\n   * See https://developer.apple.com/documentation/webkit/wkwebsitedatastore/init(foridentifier:)?language=objc\n   *\n   * The array must contain 16 u8 numbers.\n   *\n   * #### Platform-specific:\n   *\n   * - **macOS / iOS**: Available on macOS >= 14 and iOS >= 17\n   * - **Windows / Linux / Android**: Unsupported.\n   *\n   * @since 2.9.0\n   */\n  dataStoreIdentifier?: number[]\n  /**\n   * Specifies the native scrollbar style to use with the webview.\n   * CSS styles that modify the scrollbar are applied on top of the native appearance configured here.\n   *\n   * Defaults to `default`, which is the browser default.\n   *\n   * ## Platform-specific\n   *\n   * - **Windows**:\n   *   - `fluentOverlay` requires WebView2 Runtime version 125.0.2535.41 or higher, and does nothing\n   *     on older versions.\n   *   - This option must be given the same value for all webviews.\n   * - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.\n   */\n  scrollBarStyle?: ScrollBarStyle\n}\n\nexport { Webview, getCurrentWebview, getAllWebviews }\n\nexport type { DragDropEvent, WebviewOptions, Color }\n"
  },
  {
    "path": "packages/api/src/webviewWindow.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport {\n  getCurrentWebview,\n  Webview,\n  WebviewLabel,\n  WebviewOptions\n} from './webview'\nimport type { WindowOptions } from './window'\nimport { Window } from './window'\nimport { listen, once } from './event'\nimport type { EventName, EventCallback, UnlistenFn } from './event'\nimport { invoke } from './core'\nimport type { Color, DragDropEvent } from './webview'\n\n/**\n * Get an instance of `Webview` for the current webview window.\n *\n * @since 2.0.0\n */\nfunction getCurrentWebviewWindow(): WebviewWindow {\n  const webview = getCurrentWebview()\n  // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor\n  return new WebviewWindow(webview.label, { skip: true })\n}\n\n/**\n * Gets a list of instances of `Webview` for all available webview windows.\n *\n * @since 2.0.0\n */\nasync function getAllWebviewWindows(): Promise<WebviewWindow[]> {\n  return invoke<string[]>('plugin:window|get_all_windows').then((windows) =>\n    windows.map(\n      (w) =>\n        new WebviewWindow(w, {\n          // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor\n          skip: true\n        })\n    )\n  )\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging\ninterface WebviewWindow extends Webview, Window {}\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging\nclass WebviewWindow {\n  label: string\n  /** Local event listeners. */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  listeners: Record<string, Array<EventCallback<any>>>\n\n  /**\n   * Creates a new {@link Window} hosting a {@link Webview}.\n   * @example\n   * ```typescript\n   * import { WebviewWindow } from '@tauri-apps/api/webviewWindow'\n   * const webview = new WebviewWindow('my-label', {\n   *   url: 'https://github.com/tauri-apps/tauri'\n   * });\n   * webview.once('tauri://created', function () {\n   *  // webview successfully created\n   * });\n   * webview.once('tauri://error', function (e) {\n   *  // an error happened creating the webview\n   * });\n   * ```\n   *\n   * @param label The unique webview label. Must be alphanumeric: `a-zA-Z-/:_`.\n   * @returns The {@link WebviewWindow} instance to communicate with the window and webview.\n   */\n  constructor(\n    label: WebviewLabel,\n    options: Omit<WebviewOptions, 'x' | 'y' | 'width' | 'height'>\n      & WindowOptions = {}\n  ) {\n    this.label = label\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n    this.listeners = Object.create(null)\n\n    // @ts-expect-error `skip` is not a public API so it is not defined in WebviewOptions\n    if (!options?.skip) {\n      invoke('plugin:webview|create_webview_window', {\n        options: {\n          ...options,\n          parent:\n            typeof options.parent === 'string'\n              ? options.parent\n              : options.parent?.label,\n          label\n        }\n      })\n        .then(async () => this.emit('tauri://created'))\n        .catch(async (e: string) => this.emit('tauri://error', e))\n    }\n  }\n\n  /**\n   * Gets the Webview for the webview associated with the given label.\n   * @example\n   * ```typescript\n   * import { WebviewWindow } from '@tauri-apps/api/webviewWindow';\n   * const mainWebview = WebviewWindow.getByLabel('main');\n   * ```\n   *\n   * @param label The webview label.\n   * @returns The Webview instance to communicate with the webview or null if the webview doesn't exist.\n   */\n  static async getByLabel(label: string): Promise<WebviewWindow | null> {\n    const webview =\n      (await getAllWebviewWindows()).find((w) => w.label === label) ?? null\n    if (webview) {\n      // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor\n      return new WebviewWindow(webview.label, { skip: true })\n    }\n    return null\n  }\n\n  /**\n   * Get an instance of `Webview` for the current webview.\n   */\n  static getCurrent(): WebviewWindow {\n    return getCurrentWebviewWindow()\n  }\n\n  /**\n   * Gets a list of instances of `Webview` for all available webviews.\n   */\n  static async getAll(): Promise<WebviewWindow[]> {\n    return getAllWebviewWindows()\n  }\n\n  /**\n   * Listen to an emitted event on this webview window.\n   *\n   * @example\n   * ```typescript\n   * import { WebviewWindow } from '@tauri-apps/api/webviewWindow';\n   * const unlisten = await WebviewWindow.getCurrent().listen<string>('state-changed', (event) => {\n   *   console.log(`Got error: ${payload}`);\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n   * @param handler Event handler.\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async listen<T>(\n    event: EventName,\n    handler: EventCallback<T>\n  ): Promise<UnlistenFn> {\n    if (this._handleTauriEvent(event, handler)) {\n      return () => {\n        // eslint-disable-next-line security/detect-object-injection\n        const listeners = this.listeners[event]\n        listeners.splice(listeners.indexOf(handler), 1)\n      }\n    }\n    return listen(event, handler, {\n      target: { kind: 'WebviewWindow', label: this.label }\n    })\n  }\n\n  /**\n   * Listen to an emitted event on this webview window only once.\n   *\n   * @example\n   * ```typescript\n   * import { WebviewWindow } from '@tauri-apps/api/webviewWindow';\n   * const unlisten = await WebviewWindow.getCurrent().once<null>('initialized', (event) => {\n   *   console.log(`Webview initialized!`);\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n   * @param handler Event handler.\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async once<T>(\n    event: EventName,\n    handler: EventCallback<T>\n  ): Promise<UnlistenFn> {\n    if (this._handleTauriEvent(event, handler)) {\n      return () => {\n        // eslint-disable-next-line security/detect-object-injection\n        const listeners = this.listeners[event]\n        listeners.splice(listeners.indexOf(handler), 1)\n      }\n    }\n    return once(event, handler, {\n      target: { kind: 'WebviewWindow', label: this.label }\n    })\n  }\n\n  /**\n   * Set the window and webview background color.\n   *\n   * #### Platform-specific:\n   *\n   * - **Android / iOS:** Unsupported for the window layer.\n   * - **macOS / iOS**: Not implemented for the webview layer.\n   * - **Windows**:\n   *   - alpha channel is ignored for the window layer.\n   *   - On Windows 7, alpha channel is ignored for the webview layer.\n   *   - On Windows 8 and newer, if alpha channel is not `0`, it will be ignored.\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   *\n   * @since 2.1.0\n   */\n  async setBackgroundColor(color: Color): Promise<void> {\n    return invoke('plugin:window|set_background_color', { color }).then(() => {\n      return invoke('plugin:webview|set_webview_background_color', { color })\n    })\n  }\n}\n\n// Order matters, we use window APIs by default\napplyMixins(WebviewWindow, [Window, Webview])\n\n/** Extends a base class by other specified classes, without overriding existing properties */\nfunction applyMixins(\n  baseClass: { prototype: unknown },\n  extendedClasses: unknown\n): void {\n  ;(Array.isArray(extendedClasses)\n    ? extendedClasses\n    : [extendedClasses]\n  ).forEach((extendedClass: { prototype: unknown }) => {\n    Object.getOwnPropertyNames(extendedClass.prototype).forEach((name) => {\n      if (\n        typeof baseClass.prototype === 'object'\n        && baseClass.prototype\n        && name in baseClass.prototype\n      )\n        return\n      Object.defineProperty(\n        baseClass.prototype,\n        name,\n        // eslint-disable-next-line\n        Object.getOwnPropertyDescriptor(extendedClass.prototype, name)\n          ?? Object.create(null)\n      )\n    })\n  })\n}\n\nexport { WebviewWindow, getCurrentWebviewWindow, getAllWebviewWindows }\nexport type { DragDropEvent, Color }\n"
  },
  {
    "path": "packages/api/src/window.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/**\n * Provides APIs to create windows, communicate with other windows and manipulate the current window.\n *\n * #### Window events\n *\n * Events can be listened to using {@link Window.listen}:\n * ```typescript\n * import { getCurrentWindow } from \"@tauri-apps/api/window\";\n * getCurrentWindow().listen(\"my-window-event\", ({ event, payload }) => { });\n * ```\n *\n * @module\n */\n\nimport {\n  LogicalPosition,\n  LogicalSize,\n  PhysicalPosition,\n  PhysicalSize,\n  Position,\n  Size\n} from './dpi'\nimport type { Event, EventName, EventCallback, UnlistenFn } from './event'\nimport {\n  TauriEvent,\n  // imported for documentation purposes\n  type EventTarget,\n  emit,\n  emitTo,\n  listen,\n  once\n} from './event'\nimport { invoke } from './core'\nimport { WebviewWindow } from './webviewWindow'\nimport type { DragDropEvent } from './webview'\nimport { Image, transformImage } from './image'\n\n/**\n * Allows you to retrieve information about a given monitor.\n *\n * @since 1.0.0\n */\nexport interface Monitor {\n  /** Human-readable name of the monitor */\n  name: string | null\n  /** The monitor's resolution. */\n  size: PhysicalSize\n  /** the Top-left corner position of the monitor relative to the larger full screen area. */\n  position: PhysicalPosition\n  /** The monitor's work area. */\n  workArea: {\n    position: PhysicalPosition\n    size: PhysicalSize\n  }\n  /** The scale factor that can be used to map physical pixels to logical pixels. */\n  scaleFactor: number\n}\n\ntype Theme = 'light' | 'dark'\ntype TitleBarStyle = 'visible' | 'transparent' | 'overlay'\n\ntype ResizeDirection =\n  | 'East'\n  | 'North'\n  | 'NorthEast'\n  | 'NorthWest'\n  | 'South'\n  | 'SouthEast'\n  | 'SouthWest'\n  | 'West'\n\n/**\n * The payload for the `scaleChange` event.\n *\n * @since 1.0.2\n */\ninterface ScaleFactorChanged {\n  /** The new window scale factor. */\n  scaleFactor: number\n  /** The new window size */\n  size: PhysicalSize\n}\n\n/**\n * Attention type to request on a window.\n *\n * @since 1.0.0\n */\nenum UserAttentionType {\n  /**\n   * #### Platform-specific\n   * - **macOS:** Bounces the dock icon until the application is in focus.\n   * - **Windows:** Flashes both the window and the taskbar button until the application is in focus.\n   */\n  Critical = 1,\n  /**\n   * #### Platform-specific\n   * - **macOS:** Bounces the dock icon once.\n   * - **Windows:** Flashes the taskbar button until the application is in focus.\n   */\n  Informational\n}\n\nclass CloseRequestedEvent {\n  /** Event name */\n  event: EventName\n  /** Event identifier used to unlisten */\n  id: number\n  private _preventDefault = false\n\n  constructor(event: Event<unknown>) {\n    this.event = event.event\n    this.id = event.id\n  }\n\n  preventDefault(): void {\n    this._preventDefault = true\n  }\n\n  isPreventDefault(): boolean {\n    return this._preventDefault\n  }\n}\n\nexport type CursorIcon =\n  | 'default'\n  | 'crosshair'\n  | 'hand'\n  | 'arrow'\n  | 'move'\n  | 'text'\n  | 'wait'\n  | 'help'\n  | 'progress'\n  // something cannot be done\n  | 'notAllowed'\n  | 'contextMenu'\n  | 'cell'\n  | 'verticalText'\n  | 'alias'\n  | 'copy'\n  | 'noDrop'\n  // something can be grabbed\n  | 'grab'\n  /// something is grabbed\n  | 'grabbing'\n  | 'allScroll'\n  | 'zoomIn'\n  | 'zoomOut'\n  // edge is to be moved\n  | 'eResize'\n  | 'nResize'\n  | 'neResize'\n  | 'nwResize'\n  | 'sResize'\n  | 'seResize'\n  | 'swResize'\n  | 'wResize'\n  | 'ewResize'\n  | 'nsResize'\n  | 'neswResize'\n  | 'nwseResize'\n  | 'colResize'\n  | 'rowResize'\n\nexport enum ProgressBarStatus {\n  /**\n   * Hide progress bar.\n   */\n  None = 'none',\n  /**\n   * Normal state.\n   */\n  Normal = 'normal',\n  /**\n   * Indeterminate state. **Treated as Normal on Linux and macOS**\n   */\n  Indeterminate = 'indeterminate',\n  /**\n   * Paused state. **Treated as Normal on Linux**\n   */\n  Paused = 'paused',\n  /**\n   * Error state. **Treated as Normal on linux**\n   */\n  Error = 'error'\n}\n\nexport interface WindowSizeConstraints {\n  minWidth?: number\n  minHeight?: number\n  maxWidth?: number\n  maxHeight?: number\n}\n\nexport interface ProgressBarState {\n  /**\n   * The progress bar status.\n   */\n  status?: ProgressBarStatus\n  /**\n   * The progress bar progress. This can be a value ranging from `0` to `100`\n   */\n  progress?: number\n}\n\n/**\n * Get an instance of `Window` for the current window.\n *\n * @since 1.0.0\n */\nfunction getCurrentWindow(): Window {\n  return new Window(window.__TAURI_INTERNALS__.metadata.currentWindow.label, {\n    // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor\n    skip: true\n  })\n}\n\n/**\n * Gets a list of instances of `Window` for all available windows.\n *\n * @since 1.0.0\n */\nasync function getAllWindows(): Promise<Window[]> {\n  return invoke<string[]>('plugin:window|get_all_windows').then((windows) =>\n    windows.map(\n      (w) =>\n        new Window(w, {\n          // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor\n          skip: true\n        })\n    )\n  )\n}\n\n/** @ignore */\n// events that are emitted right here instead of by the created window\nconst localTauriEvents = ['tauri://created', 'tauri://error']\n/** @ignore */\nexport type WindowLabel = string\n\n/**\n * Create new window or get a handle to an existing one.\n *\n * Windows are identified by a *label*  a unique identifier that can be used to reference it later.\n * It may only contain alphanumeric characters `a-zA-Z` plus the following special characters `-`, `/`, `:` and `_`.\n *\n * @example\n * ```typescript\n * import { Window } from \"@tauri-apps/api/window\"\n *\n * const appWindow = new Window('theUniqueLabel');\n *\n * appWindow.once('tauri://created', function () {\n *  // window successfully created\n * });\n * appWindow.once('tauri://error', function (e) {\n *  // an error happened creating the window\n * });\n *\n * // emit an event to the backend\n * await appWindow.emit(\"some-event\", \"data\");\n * // listen to an event from the backend\n * const unlisten = await appWindow.listen(\"event-name\", e => {});\n * unlisten();\n * ```\n *\n * @since 2.0.0\n */\nclass Window {\n  /** The window label. It is a unique identifier for the window, can be used to reference it later. */\n  label: WindowLabel\n  /** Local event listeners. */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  listeners: Record<string, Array<EventCallback<any>>>\n\n  /**\n   * Creates a new Window.\n   * @example\n   * ```typescript\n   * import { Window } from '@tauri-apps/api/window';\n   * const appWindow = new Window('my-label');\n   * appWindow.once('tauri://created', function () {\n   *  // window successfully created\n   * });\n   * appWindow.once('tauri://error', function (e) {\n   *  // an error happened creating the window\n   * });\n   * ```\n   *\n   * @param label The unique window label. Must be alphanumeric: `a-zA-Z-/:_`.\n   * @returns The {@link Window} instance to communicate with the window.\n   */\n  constructor(label: WindowLabel, options: WindowOptions = {}) {\n    this.label = label\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n    this.listeners = Object.create(null)\n\n    // @ts-expect-error `skip` is not a public API so it is not defined in WindowOptions\n    if (!options?.skip) {\n      invoke('plugin:window|create', {\n        options: {\n          ...options,\n          parent:\n            typeof options.parent === 'string'\n              ? options.parent\n              : options.parent?.label,\n          label\n        }\n      })\n        .then(async () => this.emit('tauri://created'))\n        .catch(async (e: string) => this.emit('tauri://error', e))\n    }\n  }\n\n  /**\n   * Gets the Window associated with the given label.\n   * @example\n   * ```typescript\n   * import { Window } from '@tauri-apps/api/window';\n   * const mainWindow = Window.getByLabel('main');\n   * ```\n   *\n   * @param label The window label.\n   * @returns The Window instance to communicate with the window or null if the window doesn't exist.\n   */\n  static async getByLabel(label: string): Promise<Window | null> {\n    return (await getAllWindows()).find((w) => w.label === label) ?? null\n  }\n\n  /**\n   * Get an instance of `Window` for the current window.\n   */\n  static getCurrent(): Window {\n    return getCurrentWindow()\n  }\n\n  /**\n   * Gets a list of instances of `Window` for all available windows.\n   */\n  static async getAll(): Promise<Window[]> {\n    return getAllWindows()\n  }\n\n  /**\n   *  Gets the focused window.\n   * @example\n   * ```typescript\n   * import { Window } from '@tauri-apps/api/window';\n   * const focusedWindow = Window.getFocusedWindow();\n   * ```\n   *\n   * @returns The Window instance or `undefined` if there is not any focused window.\n   */\n  static async getFocusedWindow(): Promise<Window | null> {\n    for (const w of await getAllWindows()) {\n      if (await w.isFocused()) {\n        return w\n      }\n    }\n    return null\n  }\n\n  /**\n   * Listen to an emitted event on this window.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const unlisten = await getCurrentWindow().listen<string>('state-changed', (event) => {\n   *   console.log(`Got error: ${payload}`);\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n   * @param handler Event handler.\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async listen<T>(\n    event: EventName,\n    handler: EventCallback<T>\n  ): Promise<UnlistenFn> {\n    if (this._handleTauriEvent(event, handler)) {\n      return () => {\n        // eslint-disable-next-line security/detect-object-injection\n        const listeners = this.listeners[event]\n        listeners.splice(listeners.indexOf(handler), 1)\n      }\n    }\n    return listen(event, handler, {\n      target: { kind: 'Window', label: this.label }\n    })\n  }\n\n  /**\n   * Listen to an emitted event on this window only once.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const unlisten = await getCurrentWindow().once<null>('initialized', (event) => {\n   *   console.log(`Window initialized!`);\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n   * @param handler Event handler.\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async once<T>(\n    event: EventName,\n    handler: EventCallback<T>\n  ): Promise<UnlistenFn> {\n    if (this._handleTauriEvent(event, handler)) {\n      return () => {\n        // eslint-disable-next-line security/detect-object-injection\n        const listeners = this.listeners[event]\n        listeners.splice(listeners.indexOf(handler), 1)\n      }\n    }\n    return once(event, handler, {\n      target: { kind: 'Window', label: this.label }\n    })\n  }\n\n  /**\n   * Emits an event to all {@link EventTarget|targets}.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().emit('window-loaded', { loggedIn: true, token: 'authToken' });\n   * ```\n   *\n   * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n   * @param payload Event payload.\n   */\n  async emit<T>(event: string, payload?: T): Promise<void> {\n    if (localTauriEvents.includes(event)) {\n      // eslint-disable-next-line\n      for (const handler of this.listeners[event] || []) {\n        handler({\n          event,\n          id: -1,\n          payload\n        })\n      }\n      return\n    }\n    return emit<T>(event, payload)\n  }\n\n  /**\n   * Emits an event to all {@link EventTarget|targets} matching the given target.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().emit('main', 'window-loaded', { loggedIn: true, token: 'authToken' });\n   * ```\n   * @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.\n   * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n   * @param payload Event payload.\n   */\n  async emitTo<T>(\n    target: string | EventTarget,\n    event: string,\n    payload?: T\n  ): Promise<void> {\n    if (localTauriEvents.includes(event)) {\n      // eslint-disable-next-line security/detect-object-injection\n      for (const handler of this.listeners[event] || []) {\n        handler({\n          event,\n          id: -1,\n          payload\n        })\n      }\n      return\n    }\n    return emitTo<T>(target, event, payload)\n  }\n\n  /** @ignore */\n  _handleTauriEvent<T>(event: string, handler: EventCallback<T>): boolean {\n    if (localTauriEvents.includes(event)) {\n      if (!(event in this.listeners)) {\n        // eslint-disable-next-line\n        this.listeners[event] = [handler]\n      } else {\n        // eslint-disable-next-line\n        this.listeners[event].push(handler)\n      }\n      return true\n    }\n    return false\n  }\n\n  // Getters\n  /**\n   * The scale factor that can be used to map physical pixels to logical pixels.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const factor = await getCurrentWindow().scaleFactor();\n   * ```\n   *\n   * @returns The window's monitor scale factor.\n   */\n  async scaleFactor(): Promise<number> {\n    return invoke('plugin:window|scale_factor', {\n      label: this.label\n    })\n  }\n\n  /**\n   * The position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const position = await getCurrentWindow().innerPosition();\n   * ```\n   *\n   * @returns The window's inner position.\n   */\n  async innerPosition(): Promise<PhysicalPosition> {\n    return invoke<{ x: number; y: number }>('plugin:window|inner_position', {\n      label: this.label\n    }).then((p) => new PhysicalPosition(p))\n  }\n\n  /**\n   * The position of the top-left hand corner of the window relative to the top-left hand corner of the desktop.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const position = await getCurrentWindow().outerPosition();\n   * ```\n   *\n   * @returns The window's outer position.\n   */\n  async outerPosition(): Promise<PhysicalPosition> {\n    return invoke<{ x: number; y: number }>('plugin:window|outer_position', {\n      label: this.label\n    }).then((p) => new PhysicalPosition(p))\n  }\n\n  /**\n   * The physical size of the window's client area.\n   * The client area is the content of the window, excluding the title bar and borders.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const size = await getCurrentWindow().innerSize();\n   * ```\n   *\n   * @returns The window's inner size.\n   */\n  async innerSize(): Promise<PhysicalSize> {\n    return invoke<{ width: number; height: number }>(\n      'plugin:window|inner_size',\n      {\n        label: this.label\n      }\n    ).then((s) => new PhysicalSize(s))\n  }\n\n  /**\n   * The physical size of the entire window.\n   * These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const size = await getCurrentWindow().outerSize();\n   * ```\n   *\n   * @returns The window's outer size.\n   */\n  async outerSize(): Promise<PhysicalSize> {\n    return invoke<{ width: number; height: number }>(\n      'plugin:window|outer_size',\n      {\n        label: this.label\n      }\n    ).then((s) => new PhysicalSize(s))\n  }\n\n  /**\n   * Gets the window's current fullscreen state.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const fullscreen = await getCurrentWindow().isFullscreen();\n   * ```\n   *\n   * @returns Whether the window is in fullscreen mode or not.\n   */\n  async isFullscreen(): Promise<boolean> {\n    return invoke('plugin:window|is_fullscreen', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Gets the window's current minimized state.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const minimized = await getCurrentWindow().isMinimized();\n   * ```\n   */\n  async isMinimized(): Promise<boolean> {\n    return invoke('plugin:window|is_minimized', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Gets the window's current maximized state.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const maximized = await getCurrentWindow().isMaximized();\n   * ```\n   *\n   * @returns Whether the window is maximized or not.\n   */\n  async isMaximized(): Promise<boolean> {\n    return invoke('plugin:window|is_maximized', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Gets the window's current focus state.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const focused = await getCurrentWindow().isFocused();\n   * ```\n   *\n   * @returns Whether the window is focused or not.\n   */\n  async isFocused(): Promise<boolean> {\n    return invoke('plugin:window|is_focused', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Gets the window's current decorated state.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const decorated = await getCurrentWindow().isDecorated();\n   * ```\n   *\n   * @returns Whether the window is decorated or not.\n   */\n  async isDecorated(): Promise<boolean> {\n    return invoke('plugin:window|is_decorated', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Gets the window's current resizable state.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const resizable = await getCurrentWindow().isResizable();\n   * ```\n   *\n   * @returns Whether the window is resizable or not.\n   */\n  async isResizable(): Promise<boolean> {\n    return invoke('plugin:window|is_resizable', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Gets the window's native maximize button state.\n   *\n   * #### Platform-specific\n   *\n   * - **Linux / iOS / Android:** Unsupported.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const maximizable = await getCurrentWindow().isMaximizable();\n   * ```\n   *\n   * @returns Whether the window's native maximize button is enabled or not.\n   */\n  async isMaximizable(): Promise<boolean> {\n    return invoke('plugin:window|is_maximizable', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Gets the window's native minimize button state.\n   *\n   * #### Platform-specific\n   *\n   * - **Linux / iOS / Android:** Unsupported.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const minimizable = await getCurrentWindow().isMinimizable();\n   * ```\n   *\n   * @returns Whether the window's native minimize button is enabled or not.\n   */\n  async isMinimizable(): Promise<boolean> {\n    return invoke('plugin:window|is_minimizable', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Gets the window's native close button state.\n   *\n   * #### Platform-specific\n   *\n   * - **iOS / Android:** Unsupported.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const closable = await getCurrentWindow().isClosable();\n   * ```\n   *\n   * @returns Whether the window's native close button is enabled or not.\n   */\n  async isClosable(): Promise<boolean> {\n    return invoke('plugin:window|is_closable', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Gets the window's current visible state.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const visible = await getCurrentWindow().isVisible();\n   * ```\n   *\n   * @returns Whether the window is visible or not.\n   */\n  async isVisible(): Promise<boolean> {\n    return invoke('plugin:window|is_visible', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Gets the window's current title.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const title = await getCurrentWindow().title();\n   * ```\n   */\n  async title(): Promise<string> {\n    return invoke('plugin:window|title', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Gets the window's current theme.\n   *\n   * #### Platform-specific\n   *\n   * - **macOS:** Theme was introduced on macOS 10.14. Returns `light` on macOS 10.13 and below.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const theme = await getCurrentWindow().theme();\n   * ```\n   *\n   * @returns The window theme.\n   */\n  async theme(): Promise<Theme | null> {\n    return invoke('plugin:window|theme', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Whether the window is configured to be always on top of other windows or not.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * const alwaysOnTop = await getCurrentWindow().isAlwaysOnTop();\n   * ```\n   *\n   * @returns Whether the window is visible or not.\n   */\n  async isAlwaysOnTop(): Promise<boolean> {\n    return invoke('plugin:window|is_always_on_top', {\n      label: this.label\n    })\n  }\n\n  // Setters\n\n  /**\n   * Centers the window.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().center();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async center(): Promise<void> {\n    return invoke('plugin:window|center', {\n      label: this.label\n    })\n  }\n\n  /**\n   *  Requests user attention to the window, this has no effect if the application\n   * is already focused. How requesting for user attention manifests is platform dependent,\n   * see `UserAttentionType` for details.\n   *\n   * Providing `null` will unset the request for user attention. Unsetting the request for\n   * user attention might not be done automatically by the WM when the window receives input.\n   *\n   * #### Platform-specific\n   *\n   * - **macOS:** `null` has no effect.\n   * - **Linux:** Urgency levels have the same effect.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().requestUserAttention();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async requestUserAttention(\n    requestType: UserAttentionType | null\n  ): Promise<void> {\n    let requestType_ = null\n    if (requestType) {\n      if (requestType === UserAttentionType.Critical) {\n        requestType_ = { type: 'Critical' }\n      } else {\n        requestType_ = { type: 'Informational' }\n      }\n    }\n\n    return invoke('plugin:window|request_user_attention', {\n      label: this.label,\n      value: requestType_\n    })\n  }\n\n  /**\n   * Updates the window resizable flag.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setResizable(false);\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setResizable(resizable: boolean): Promise<void> {\n    return invoke('plugin:window|set_resizable', {\n      label: this.label,\n      value: resizable\n    })\n  }\n\n  /**\n   * Enable or disable the window.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setEnabled(false);\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   *\n   * @since 2.0.0\n   */\n  async setEnabled(enabled: boolean): Promise<void> {\n    return invoke('plugin:window|set_enabled', {\n      label: this.label,\n      value: enabled\n    })\n  }\n\n  /**\n   * Whether the window is enabled or disabled.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setEnabled(false);\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   *\n   * @since 2.0.0\n   */\n  async isEnabled(): Promise<boolean> {\n    return invoke('plugin:window|is_enabled', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Sets whether the window's native maximize button is enabled or not.\n   * If resizable is set to false, this setting is ignored.\n   *\n   * #### Platform-specific\n   *\n   * - **macOS:** Disables the \"zoom\" button in the window titlebar, which is also used to enter fullscreen mode.\n   * - **Linux / iOS / Android:** Unsupported.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setMaximizable(false);\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setMaximizable(maximizable: boolean): Promise<void> {\n    return invoke('plugin:window|set_maximizable', {\n      label: this.label,\n      value: maximizable\n    })\n  }\n\n  /**\n   * Sets whether the window's native minimize button is enabled or not.\n   *\n   * #### Platform-specific\n   *\n   * - **Linux / iOS / Android:** Unsupported.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setMinimizable(false);\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setMinimizable(minimizable: boolean): Promise<void> {\n    return invoke('plugin:window|set_minimizable', {\n      label: this.label,\n      value: minimizable\n    })\n  }\n\n  /**\n   * Sets whether the window's native close button is enabled or not.\n   *\n   * #### Platform-specific\n   *\n   * - **Linux:** GTK+ will do its best to convince the window manager not to show a close button. Depending on the system, this function may not have any effect when called on a window that is already visible\n   * - **iOS / Android:** Unsupported.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setClosable(false);\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setClosable(closable: boolean): Promise<void> {\n    return invoke('plugin:window|set_closable', {\n      label: this.label,\n      value: closable\n    })\n  }\n\n  /**\n   * Sets the window title.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setTitle('Tauri');\n   * ```\n   *\n   * @param title The new title\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setTitle(title: string): Promise<void> {\n    return invoke('plugin:window|set_title', {\n      label: this.label,\n      value: title\n    })\n  }\n\n  /**\n   * Maximizes the window.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().maximize();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async maximize(): Promise<void> {\n    return invoke('plugin:window|maximize', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Unmaximizes the window.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().unmaximize();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async unmaximize(): Promise<void> {\n    return invoke('plugin:window|unmaximize', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Toggles the window maximized state.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().toggleMaximize();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async toggleMaximize(): Promise<void> {\n    return invoke('plugin:window|toggle_maximize', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Minimizes the window.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().minimize();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async minimize(): Promise<void> {\n    return invoke('plugin:window|minimize', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Unminimizes the window.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().unminimize();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async unminimize(): Promise<void> {\n    return invoke('plugin:window|unminimize', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Sets the window visibility to true.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().show();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async show(): Promise<void> {\n    return invoke('plugin:window|show', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Sets the window visibility to false.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().hide();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async hide(): Promise<void> {\n    return invoke('plugin:window|hide', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Closes the window.\n   *\n   * Note this emits a closeRequested event so you can intercept it. To force window close, use {@link Window.destroy}.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().close();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async close(): Promise<void> {\n    return invoke('plugin:window|close', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Destroys the window. Behaves like {@link Window.close} but forces the window close instead of emitting a closeRequested event.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().destroy();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async destroy(): Promise<void> {\n    return invoke('plugin:window|destroy', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Whether the window should have borders and bars.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setDecorations(false);\n   * ```\n   *\n   * @param decorations Whether the window should have borders and bars.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setDecorations(decorations: boolean): Promise<void> {\n    return invoke('plugin:window|set_decorations', {\n      label: this.label,\n      value: decorations\n    })\n  }\n\n  /**\n   * Whether or not the window should have shadow.\n   *\n   * #### Platform-specific\n   *\n   * - **Windows:**\n   *   - `false` has no effect on decorated window, shadows are always ON.\n   *   - `true` will make undecorated window have a 1px white border,\n   * and on Windows 11, it will have a rounded corners.\n   * - **Linux:** Unsupported.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setShadow(false);\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setShadow(enable: boolean): Promise<void> {\n    return invoke('plugin:window|set_shadow', {\n      label: this.label,\n      value: enable\n    })\n  }\n\n  /**\n   * Set window effects.\n   */\n  async setEffects(effects: Effects): Promise<void> {\n    return invoke('plugin:window|set_effects', {\n      label: this.label,\n      value: effects\n    })\n  }\n\n  /**\n   * Clear any applied effects if possible.\n   */\n  async clearEffects(): Promise<void> {\n    return invoke('plugin:window|set_effects', {\n      label: this.label,\n      value: null\n    })\n  }\n\n  /**\n   * Whether the window should always be on top of other windows.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setAlwaysOnTop(true);\n   * ```\n   *\n   * @param alwaysOnTop Whether the window should always be on top of other windows or not.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setAlwaysOnTop(alwaysOnTop: boolean): Promise<void> {\n    return invoke('plugin:window|set_always_on_top', {\n      label: this.label,\n      value: alwaysOnTop\n    })\n  }\n\n  /**\n   * Whether the window should always be below other windows.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setAlwaysOnBottom(true);\n   * ```\n   *\n   * @param alwaysOnBottom Whether the window should always be below other windows or not.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setAlwaysOnBottom(alwaysOnBottom: boolean): Promise<void> {\n    return invoke('plugin:window|set_always_on_bottom', {\n      label: this.label,\n      value: alwaysOnBottom\n    })\n  }\n\n  /**\n   * Prevents the window contents from being captured by other apps.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setContentProtected(true);\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setContentProtected(protected_: boolean): Promise<void> {\n    return invoke('plugin:window|set_content_protected', {\n      label: this.label,\n      value: protected_\n    })\n  }\n\n  /**\n   * Resizes the window with a new inner size.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow, LogicalSize } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setSize(new LogicalSize(600, 500));\n   * ```\n   *\n   * @param size The logical or physical inner size.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setSize(size: LogicalSize | PhysicalSize | Size): Promise<void> {\n    return invoke('plugin:window|set_size', {\n      label: this.label,\n      value: size instanceof Size ? size : new Size(size)\n    })\n  }\n\n  /**\n   * Sets the window minimum inner size. If the `size` argument is not provided, the constraint is unset.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow, PhysicalSize } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setMinSize(new PhysicalSize(600, 500));\n   * ```\n   *\n   * @param size The logical or physical inner size, or `null` to unset the constraint.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setMinSize(\n    size: LogicalSize | PhysicalSize | Size | null | undefined\n  ): Promise<void> {\n    return invoke('plugin:window|set_min_size', {\n      label: this.label,\n      value: size instanceof Size ? size : size ? new Size(size) : null\n    })\n  }\n\n  /**\n   * Sets the window maximum inner size. If the `size` argument is undefined, the constraint is unset.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow, LogicalSize } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setMaxSize(new LogicalSize(600, 500));\n   * ```\n   *\n   * @param size The logical or physical inner size, or `null` to unset the constraint.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setMaxSize(\n    size: LogicalSize | PhysicalSize | Size | null | undefined\n  ): Promise<void> {\n    return invoke('plugin:window|set_max_size', {\n      label: this.label,\n      value: size instanceof Size ? size : size ? new Size(size) : null\n    })\n  }\n\n  /**\n   * Sets the window inner size constraints.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setSizeConstraints({ minWidth: 300 });\n   * ```\n   *\n   * @param constraints The logical or physical inner size, or `null` to unset the constraint.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setSizeConstraints(\n    constraints: WindowSizeConstraints | null | undefined\n  ): Promise<void> {\n    function logical(pixel?: number): { Logical: number } | null {\n      return pixel ? { Logical: pixel } : null\n    }\n\n    return invoke('plugin:window|set_size_constraints', {\n      label: this.label,\n      value: {\n        minWidth: logical(constraints?.minWidth),\n        minHeight: logical(constraints?.minHeight),\n        maxWidth: logical(constraints?.maxWidth),\n        maxHeight: logical(constraints?.maxHeight)\n      }\n    })\n  }\n\n  /**\n   * Sets the window outer position.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow, LogicalPosition } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setPosition(new LogicalPosition(600, 500));\n   * ```\n   *\n   * @param position The new position, in logical or physical pixels.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setPosition(\n    position: LogicalPosition | PhysicalPosition | Position\n  ): Promise<void> {\n    return invoke('plugin:window|set_position', {\n      label: this.label,\n      value: position instanceof Position ? position : new Position(position)\n    })\n  }\n\n  /**\n   * Sets the window fullscreen state.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setFullscreen(true);\n   * ```\n   *\n   * @param fullscreen Whether the window should go to fullscreen or not.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setFullscreen(fullscreen: boolean): Promise<void> {\n    return invoke('plugin:window|set_fullscreen', {\n      label: this.label,\n      value: fullscreen\n    })\n  }\n\n  /**\n   * On macOS, Toggles a fullscreen mode that doesn’t require a new macOS space. Returns a boolean indicating whether the transition was successful (this won’t work if the window was already in the native fullscreen).\n   * This is how fullscreen used to work on macOS in versions before Lion. And allows the user to have a fullscreen window without using another space or taking control over the entire monitor.\n   *\n   * On other platforms, this is the same as {@link Window.setFullscreen}.\n   *\n   * @param fullscreen Whether the window should go to simple fullscreen or not.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setSimpleFullscreen(fullscreen: boolean): Promise<void> {\n    return invoke('plugin:window|set_simple_fullscreen', {\n      label: this.label,\n      value: fullscreen\n    })\n  }\n\n  /**\n   * Bring the window to front and focus.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setFocus();\n   * ```\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setFocus(): Promise<void> {\n    return invoke('plugin:window|set_focus', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Sets whether the window can be focused.\n   *\n   * #### Platform-specific\n   *\n   * - **macOS**: If the window is already focused, it is not possible to unfocus it after calling `set_focusable(false)`.\n   *   In this case, you might consider calling {@link Window.setFocus} but it will move the window to the back i.e. at the bottom in terms of z-order.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setFocusable(true);\n   * ```\n   *\n   * @param focusable Whether the window can be focused.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setFocusable(focusable: boolean): Promise<void> {\n    return invoke('plugin:window|set_focusable', {\n      label: this.label,\n      value: focusable\n    })\n  }\n\n  /**\n   * Sets the window icon.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setIcon('/tauri/awesome.png');\n   * ```\n   *\n   * Note that you may need the `image-ico` or `image-png` Cargo features to use this API.\n   * To enable it, change your Cargo.toml file:\n   * ```toml\n   * [dependencies]\n   * tauri = { version = \"...\", features = [\"...\", \"image-png\"] }\n   * ```\n   *\n   * @param icon Icon bytes or path to the icon file.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setIcon(\n    icon: string | Image | Uint8Array | ArrayBuffer | number[]\n  ): Promise<void> {\n    return invoke('plugin:window|set_icon', {\n      label: this.label,\n      value: transformImage(icon)\n    })\n  }\n\n  /**\n   * Whether the window icon should be hidden from the taskbar or not.\n   *\n   * #### Platform-specific\n   *\n   * - **macOS:** Unsupported.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setSkipTaskbar(true);\n   * ```\n   *\n   * @param skip true to hide window icon, false to show it.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setSkipTaskbar(skip: boolean): Promise<void> {\n    return invoke('plugin:window|set_skip_taskbar', {\n      label: this.label,\n      value: skip\n    })\n  }\n\n  /**\n   * Grabs the cursor, preventing it from leaving the window.\n   *\n   * There's no guarantee that the cursor will be hidden. You should\n   * hide it by yourself if you want so.\n   *\n   * #### Platform-specific\n   *\n   * - **Linux:** Unsupported.\n   * - **macOS:** This locks the cursor in a fixed location, which looks visually awkward.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setCursorGrab(true);\n   * ```\n   *\n   * @param grab `true` to grab the cursor icon, `false` to release it.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setCursorGrab(grab: boolean): Promise<void> {\n    return invoke('plugin:window|set_cursor_grab', {\n      label: this.label,\n      value: grab\n    })\n  }\n\n  /**\n   * Modifies the cursor's visibility.\n   *\n   * #### Platform-specific\n   *\n   * - **Windows:** The cursor is only hidden within the confines of the window.\n   * - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is\n   *   outside of the window.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setCursorVisible(false);\n   * ```\n   *\n   * @param visible If `false`, this will hide the cursor. If `true`, this will show the cursor.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setCursorVisible(visible: boolean): Promise<void> {\n    return invoke('plugin:window|set_cursor_visible', {\n      label: this.label,\n      value: visible\n    })\n  }\n\n  /**\n   * Modifies the cursor icon of the window.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setCursorIcon('help');\n   * ```\n   *\n   * @param icon The new cursor icon.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setCursorIcon(icon: CursorIcon): Promise<void> {\n    return invoke('plugin:window|set_cursor_icon', {\n      label: this.label,\n      value: icon\n    })\n  }\n\n  /**\n   * Sets the window background color.\n   *\n   * #### Platform-specific:\n   *\n   * - **Windows:** alpha channel is ignored.\n   * - **iOS / Android:** Unsupported.\n   *\n   * @returns A promise indicating the success or failure of the operation.\n   *\n   * @since 2.1.0\n   */\n  async setBackgroundColor(color: Color): Promise<void> {\n    return invoke('plugin:window|set_background_color', { color })\n  }\n\n  /**\n   * Changes the position of the cursor in window coordinates.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow, LogicalPosition } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setCursorPosition(new LogicalPosition(600, 300));\n   * ```\n   *\n   * @param position The new cursor position.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setCursorPosition(\n    position: LogicalPosition | PhysicalPosition | Position\n  ): Promise<void> {\n    return invoke('plugin:window|set_cursor_position', {\n      label: this.label,\n      value: position instanceof Position ? position : new Position(position)\n    })\n  }\n\n  /**\n   * Changes the cursor events behavior.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setIgnoreCursorEvents(true);\n   * ```\n   *\n   * @param ignore `true` to ignore the cursor events; `false` to process them as usual.\n   * @returns A promise indicating the success or failure of the operation.\n   */\n  async setIgnoreCursorEvents(ignore: boolean): Promise<void> {\n    return invoke('plugin:window|set_ignore_cursor_events', {\n      label: this.label,\n      value: ignore\n    })\n  }\n\n  /**\n   * Starts dragging the window.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().startDragging();\n   * ```\n   *\n   * @return A promise indicating the success or failure of the operation.\n   */\n  async startDragging(): Promise<void> {\n    return invoke('plugin:window|start_dragging', {\n      label: this.label\n    })\n  }\n\n  /**\n   * Starts resize-dragging the window.\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().startResizeDragging();\n   * ```\n   *\n   * @return A promise indicating the success or failure of the operation.\n   */\n  async startResizeDragging(direction: ResizeDirection): Promise<void> {\n    return invoke('plugin:window|start_resize_dragging', {\n      label: this.label,\n      value: direction\n    })\n  }\n\n  /**\n   * Sets the badge count. It is app wide and not specific to this window.\n   *\n   * #### Platform-specific\n   *\n   * - **Windows**: Unsupported. Use @{linkcode Window.setOverlayIcon} instead.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setBadgeCount(5);\n   * ```\n   *\n   * @param count The badge count. Use `undefined` to remove the badge.\n   * @return A promise indicating the success or failure of the operation.\n   */\n  async setBadgeCount(count?: number): Promise<void> {\n    return invoke('plugin:window|set_badge_count', {\n      label: this.label,\n      value: count\n    })\n  }\n\n  /**\n   * Sets the badge cont **macOS only**.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setBadgeLabel(\"Hello\");\n   * ```\n   *\n   * @param label The badge label. Use `undefined` to remove the badge.\n   * @return A promise indicating the success or failure of the operation.\n   */\n  async setBadgeLabel(label?: string): Promise<void> {\n    return invoke('plugin:window|set_badge_label', {\n      label: this.label,\n      value: label\n    })\n  }\n\n  /**\n   * Sets the overlay icon. **Windows only**\n   * The overlay icon can be set for every window.\n   *\n   *\n   * Note that you may need the `image-ico` or `image-png` Cargo features to use this API.\n   * To enable it, change your Cargo.toml file:\n   *\n   * ```toml\n   * [dependencies]\n   * tauri = { version = \"...\", features = [\"...\", \"image-png\"] }\n   * ```\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setOverlayIcon(\"/tauri/awesome.png\");\n   * ```\n   *\n   * @param icon Icon bytes or path to the icon file. Use `undefined` to remove the overlay icon.\n   * @return A promise indicating the success or failure of the operation.\n   */\n  async setOverlayIcon(\n    icon?: string | Image | Uint8Array | ArrayBuffer | number[]\n  ): Promise<void> {\n    return invoke('plugin:window|set_overlay_icon', {\n      label: this.label,\n      value: icon ? transformImage(icon) : undefined\n    })\n  }\n\n  /**\n   * Sets the taskbar progress state.\n   *\n   * #### Platform-specific\n   *\n   * - **Linux / macOS**: Progress bar is app-wide and not specific to this window.\n   * - **Linux**: Only supported desktop environments with `libunity` (e.g. GNOME).\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow, ProgressBarStatus } from '@tauri-apps/api/window';\n   * await getCurrentWindow().setProgressBar({\n   *   status: ProgressBarStatus.Normal,\n   *   progress: 50,\n   * });\n   * ```\n   *\n   * @return A promise indicating the success or failure of the operation.\n   */\n  async setProgressBar(state: ProgressBarState): Promise<void> {\n    return invoke('plugin:window|set_progress_bar', {\n      label: this.label,\n      value: state\n    })\n  }\n\n  /**\n   * Sets whether the window should be visible on all workspaces or virtual desktops.\n   *\n   * #### Platform-specific\n   *\n   * - **Windows / iOS / Android:** Unsupported.\n   *\n   * @since 2.0.0\n   */\n  async setVisibleOnAllWorkspaces(visible: boolean): Promise<void> {\n    return invoke('plugin:window|set_visible_on_all_workspaces', {\n      label: this.label,\n      value: visible\n    })\n  }\n\n  /**\n   * Sets the title bar style. **macOS only**.\n   *\n   * @since 2.0.0\n   */\n  async setTitleBarStyle(style: TitleBarStyle): Promise<void> {\n    return invoke('plugin:window|set_title_bar_style', {\n      label: this.label,\n      value: style\n    })\n  }\n\n  /**\n   * Set window theme, pass in `null` or `undefined` to follow system theme\n   *\n   * #### Platform-specific\n   *\n   * - **Linux / macOS**: Theme is app-wide and not specific to this window.\n   * - **iOS / Android:** Unsupported.\n   *\n   * @since 2.0.0\n   */\n  async setTheme(theme?: Theme | null): Promise<void> {\n    return invoke('plugin:window|set_theme', {\n      label: this.label,\n      value: theme\n    })\n  }\n\n  // Listeners\n\n  /**\n   * Listen to window resize.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from \"@tauri-apps/api/window\";\n   * const unlisten = await getCurrentWindow().onResized(({ payload: size }) => {\n   *  console.log('Window resized', size);\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async onResized(handler: EventCallback<PhysicalSize>): Promise<UnlistenFn> {\n    return this.listen<PhysicalSize>(TauriEvent.WINDOW_RESIZED, (e) => {\n      e.payload = new PhysicalSize(e.payload)\n      handler(e)\n    })\n  }\n\n  /**\n   * Listen to window move.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from \"@tauri-apps/api/window\";\n   * const unlisten = await getCurrentWindow().onMoved(({ payload: position }) => {\n   *  console.log('Window moved', position);\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async onMoved(handler: EventCallback<PhysicalPosition>): Promise<UnlistenFn> {\n    return this.listen<PhysicalPosition>(TauriEvent.WINDOW_MOVED, (e) => {\n      e.payload = new PhysicalPosition(e.payload)\n      handler(e)\n    })\n  }\n\n  /**\n   * Listen to window close requested. Emitted when the user requests to closes the window.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from \"@tauri-apps/api/window\";\n   * import { confirm } from '@tauri-apps/api/dialog';\n   * const unlisten = await getCurrentWindow().onCloseRequested(async (event) => {\n   *   const confirmed = await confirm('Are you sure?');\n   *   if (!confirmed) {\n   *     // user did not confirm closing the window; let's prevent it\n   *     event.preventDefault();\n   *   }\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async onCloseRequested(\n    handler: (event: CloseRequestedEvent) => void | Promise<void>\n  ): Promise<UnlistenFn> {\n    // eslint-disable-next-line @typescript-eslint/no-misused-promises\n    return this.listen(TauriEvent.WINDOW_CLOSE_REQUESTED, async (event) => {\n      const evt = new CloseRequestedEvent(event)\n      await handler(evt)\n      if (!evt.isPreventDefault()) {\n        await this.destroy()\n      }\n    })\n  }\n\n  /**\n   * Listen to a file drop event.\n   * The listener is triggered when the user hovers the selected files on the webview,\n   * drops the files or cancels the operation.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from \"@tauri-apps/api/webview\";\n   * const unlisten = await getCurrentWindow().onDragDropEvent((event) => {\n   *  if (event.payload.type === 'over') {\n   *    console.log('User hovering', event.payload.position);\n   *  } else if (event.payload.type === 'drop') {\n   *    console.log('User dropped', event.payload.paths);\n   *  } else {\n   *    console.log('File drop cancelled');\n   *  }\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async onDragDropEvent(\n    handler: EventCallback<DragDropEvent>\n  ): Promise<UnlistenFn> {\n    type DragPayload = { paths: string[]; position: PhysicalPosition }\n\n    const unlistenDrag = await this.listen<DragPayload>(\n      TauriEvent.DRAG_ENTER,\n      (event) => {\n        handler({\n          ...event,\n          payload: {\n            type: 'enter',\n            paths: event.payload.paths,\n            position: new PhysicalPosition(event.payload.position)\n          }\n        })\n      }\n    )\n\n    const unlistenDragOver = await this.listen<DragPayload>(\n      TauriEvent.DRAG_OVER,\n      (event) => {\n        handler({\n          ...event,\n          payload: {\n            type: 'over',\n            position: new PhysicalPosition(event.payload.position)\n          }\n        })\n      }\n    )\n\n    const unlistenDrop = await this.listen<DragPayload>(\n      TauriEvent.DRAG_DROP,\n      (event) => {\n        handler({\n          ...event,\n          payload: {\n            type: 'drop',\n            paths: event.payload.paths,\n            position: new PhysicalPosition(event.payload.position)\n          }\n        })\n      }\n    )\n\n    const unlistenCancel = await this.listen<null>(\n      TauriEvent.DRAG_LEAVE,\n      (event) => {\n        handler({ ...event, payload: { type: 'leave' } })\n      }\n    )\n\n    return () => {\n      unlistenDrag()\n      unlistenDrop()\n      unlistenDragOver()\n      unlistenCancel()\n    }\n  }\n\n  /**\n   * Listen to window focus change.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from \"@tauri-apps/api/window\";\n   * const unlisten = await getCurrentWindow().onFocusChanged(({ payload: focused }) => {\n   *  console.log('Focus changed, window is focused? ' + focused);\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async onFocusChanged(handler: EventCallback<boolean>): Promise<UnlistenFn> {\n    const unlistenFocus = await this.listen<PhysicalPosition>(\n      TauriEvent.WINDOW_FOCUS,\n      (event) => {\n        handler({ ...event, payload: true })\n      }\n    )\n    const unlistenBlur = await this.listen<PhysicalPosition>(\n      TauriEvent.WINDOW_BLUR,\n      (event) => {\n        handler({ ...event, payload: false })\n      }\n    )\n    return () => {\n      unlistenFocus()\n      unlistenBlur()\n    }\n  }\n\n  /**\n   * Listen to window scale change. Emitted when the window's scale factor has changed.\n   * The following user actions can cause DPI changes:\n   * - Changing the display's resolution.\n   * - Changing the display's scale factor (e.g. in Control Panel on Windows).\n   * - Moving the window to a display with a different scale factor.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from \"@tauri-apps/api/window\";\n   * const unlisten = await getCurrentWindow().onScaleChanged(({ payload }) => {\n   *  console.log('Scale changed', payload.scaleFactor, payload.size);\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async onScaleChanged(\n    handler: EventCallback<ScaleFactorChanged>\n  ): Promise<UnlistenFn> {\n    return this.listen<ScaleFactorChanged>(\n      TauriEvent.WINDOW_SCALE_FACTOR_CHANGED,\n      handler\n    )\n  }\n\n  /**\n   * Listen to the system theme change.\n   *\n   * @example\n   * ```typescript\n   * import { getCurrentWindow } from \"@tauri-apps/api/window\";\n   * const unlisten = await getCurrentWindow().onThemeChanged(({ payload: theme }) => {\n   *  console.log('New theme: ' + theme);\n   * });\n   *\n   * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n   * unlisten();\n   * ```\n   *\n   * @returns A promise resolving to a function to unlisten to the event.\n   * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n   */\n  async onThemeChanged(handler: EventCallback<Theme>): Promise<UnlistenFn> {\n    return this.listen<Theme>(TauriEvent.WINDOW_THEME_CHANGED, handler)\n  }\n}\n\n/**\n * An RGBA color. Each value has minimum of 0 and maximum of 255.\n *\n * It can be either a string `#ffffff`, an array of 3 or 4 elements or an object.\n *\n * @since 2.0.0\n */\ntype Color =\n  | [number, number, number]\n  | [number, number, number, number]\n  | { red: number; green: number; blue: number; alpha: number }\n  | string\n\n/**\n * Background throttling policy\n *\n * @since 2.0.0\n */\nenum BackgroundThrottlingPolicy {\n  Disabled = 'disabled',\n  Throttle = 'throttle',\n  Suspend = 'suspend'\n}\n\n/**\n * The scrollbar style to use in the webview.\n *\n * ## Platform-specific\n *\n * **Windows**: This option must be given the same value for all webviews.\n *\n * @since 2.8.0\n */\nenum ScrollBarStyle {\n  /**\n   * The default scrollbar style for the webview.\n   */\n  Default = 'default',\n  /**\n   * Fluent UI style overlay scrollbars. **Windows Only**\n   *\n   * Requires WebView2 Runtime version 125.0.2535.41 or higher, does nothing on older versions,\n   * see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/?tabs=dotnetcsharp#10253541\n   */\n  FluentOverlay = 'fluentOverlay'\n}\n\n/**\n * Platform-specific window effects\n *\n * @since 2.0.0\n */\nenum Effect {\n  /**\n   * A default material appropriate for the view's effectiveAppearance.  **macOS 10.14-**\n   *\n   * @deprecated since macOS 10.14. You should instead choose an appropriate semantic material.\n   */\n  AppearanceBased = 'appearanceBased',\n  /**\n   *  **macOS 10.14-**\n   *\n   * @deprecated since macOS 10.14. Use a semantic material instead.\n   */\n  Light = 'light',\n  /**\n   *  **macOS 10.14-**\n   *\n   * @deprecated since macOS 10.14. Use a semantic material instead.\n   */\n  Dark = 'dark',\n  /**\n   *  **macOS 10.14-**\n   *\n   * @deprecated since macOS 10.14. Use a semantic material instead.\n   */\n  MediumLight = 'mediumLight',\n  /**\n   *  **macOS 10.14-**\n   *\n   * @deprecated since macOS 10.14. Use a semantic material instead.\n   */\n  UltraDark = 'ultraDark',\n  /**\n   *  **macOS 10.10+**\n   */\n  Titlebar = 'titlebar',\n  /**\n   *  **macOS 10.10+**\n   */\n  Selection = 'selection',\n  /**\n   *  **macOS 10.11+**\n   */\n  Menu = 'menu',\n  /**\n   *  **macOS 10.11+**\n   */\n  Popover = 'popover',\n  /**\n   *  **macOS 10.11+**\n   */\n  Sidebar = 'sidebar',\n  /**\n   *  **macOS 10.14+**\n   */\n  HeaderView = 'headerView',\n  /**\n   *  **macOS 10.14+**\n   */\n  Sheet = 'sheet',\n  /**\n   *  **macOS 10.14+**\n   */\n  WindowBackground = 'windowBackground',\n  /**\n   *  **macOS 10.14+**\n   */\n  HudWindow = 'hudWindow',\n  /**\n   *  **macOS 10.14+**\n   */\n  FullScreenUI = 'fullScreenUI',\n  /**\n   *  **macOS 10.14+**\n   */\n  Tooltip = 'tooltip',\n  /**\n   *  **macOS 10.14+**\n   */\n  ContentBackground = 'contentBackground',\n  /**\n   *  **macOS 10.14+**\n   */\n  UnderWindowBackground = 'underWindowBackground',\n  /**\n   *  **macOS 10.14+**\n   */\n  UnderPageBackground = 'underPageBackground',\n  /**\n   *  **Windows 11 Only**\n   */\n  Mica = 'mica',\n  /**\n   * **Windows 7/10/11(22H1) Only**\n   *\n   * #### Notes\n   *\n   * This effect has bad performance when resizing/dragging the window on Windows 11 build 22621.\n   */\n  Blur = 'blur',\n  /**\n   * **Windows 10/11**\n   *\n   * #### Notes\n   *\n   * This effect has bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000.\n   */\n  Acrylic = 'acrylic',\n  /**\n   * Tabbed effect that matches the system dark preference **Windows 11 Only**\n   */\n  Tabbed = 'tabbed',\n  /**\n   * Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**\n   */\n  TabbedDark = 'tabbedDark',\n  /**\n   * Tabbed effect with light mode **Windows 11 Only**\n   */\n  TabbedLight = 'tabbedLight'\n}\n\n/**\n * Window effect state **macOS only**\n *\n * @see https://developer.apple.com/documentation/appkit/nsvisualeffectview/state\n *\n * @since 2.0.0\n */\nenum EffectState {\n  /**\n   *  Make window effect state follow the window's active state **macOS only**\n   */\n  FollowsWindowActiveState = 'followsWindowActiveState',\n  /**\n   *  Make window effect state always active **macOS only**\n   */\n  Active = 'active',\n  /**\n   *  Make window effect state always inactive **macOS only**\n   */\n  Inactive = 'inactive'\n}\n\n/** The window effects configuration object\n *\n * @since 2.0.0\n */\ninterface Effects {\n  /**\n   *  List of Window effects to apply to the Window.\n   * Conflicting effects will apply the first one and ignore the rest.\n   */\n  effects: Effect[]\n  /**\n   * Window effect state **macOS Only**\n   */\n  state?: EffectState\n  /**\n   * Window effect corner radius **macOS Only**\n   */\n  radius?: number\n  /**\n   *  Window effect color. Affects {@link Effect.Blur} and {@link Effect.Acrylic} only\n   * on Windows 10 v1903+. Doesn't have any effect on Windows 7 or Windows 11.\n   */\n  color?: Color\n}\n\n/**\n * Minimum margin to work area\n */\ninterface PreventOverflowMargin {\n  width: number\n  height: number\n}\n\n/**\n * Configuration for the window to create.\n *\n * @since 1.0.0\n */\ninterface WindowOptions {\n  /** Show window in the center of the screen.. */\n  center?: boolean\n  /** The initial vertical position in logical pixels. Only applies if `y` is also set. */\n  x?: number\n  /** The initial horizontal position in logical pixels. Only applies if `x` is also set. */\n  y?: number\n  /** The initial width in logical pixels. */\n  width?: number\n  /** The initial height in logical pixels. */\n  height?: number\n  /** The minimum width in logical pixels. Only applies if `minHeight` is also set. */\n  minWidth?: number\n  /** The minimum height in logical pixels. Only applies if `minWidth` is also set. */\n  minHeight?: number\n  /** The maximum width in logical pixels. Only applies if `maxHeight` is also set. */\n  maxWidth?: number\n  /** The maximum height in logical pixels. Only applies if `maxWidth` is also set. */\n  maxHeight?: number\n  /**\n   * Prevent the window from overflowing the working area (e.g. monitor size - taskbar size)\n   * on creation, which means the window size will be limited to `monitor size - taskbar size`\n   *\n   * Can either be set to `true` or to a {@link PreventOverflowMargin} object to set an additional margin\n   * that should be considered to determine the working area\n   * (in this case the window size will be limited to `monitor size - taskbar size - margin`)\n   *\n   * **NOTE**: The overflow check is only performed on window creation, resizes can still overflow\n   *\n   * #### Platform-specific\n   *\n   * - **iOS / Android:** Unsupported.\n   */\n  preventOverflow?: boolean | PreventOverflowMargin\n  /** Whether the window is resizable or not. */\n  resizable?: boolean\n  /** Window title. */\n  title?: string\n  /** Whether the window is in fullscreen mode or not. */\n  fullscreen?: boolean\n  /** Whether the window will be initially focused or not. */\n  focus?: boolean\n  /** Whether the window can be focused or not. */\n  focusable?: boolean\n  /**\n   * Whether the window is transparent or not.\n   * Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri.conf.json > app > macOSPrivateApi`.\n   * WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.\n   */\n  transparent?: boolean\n  /** Whether the window should be maximized upon creation or not. */\n  maximized?: boolean\n  /** Whether the window should be immediately visible upon creation or not. */\n  visible?: boolean\n  /** Whether the window should have borders and bars or not. */\n  decorations?: boolean\n  /** Whether the window should always be on top of other windows or not. */\n  alwaysOnTop?: boolean\n  /** Whether the window should always be below other windows. */\n  alwaysOnBottom?: boolean\n  /** Prevents the window contents from being captured by other apps. */\n  contentProtected?: boolean\n  /** Whether or not the window icon should be added to the taskbar. */\n  skipTaskbar?: boolean\n  /**\n   *  Whether or not the window has shadow.\n   *\n   * #### Platform-specific\n   *\n   * - **Windows:**\n   *   - `false` has no effect on decorated window, shadows are always ON.\n   *   - `true` will make undecorated window have a 1px white border,\n   * and on Windows 11, it will have a rounded corners.\n   * - **Linux:** Unsupported.\n   *\n   * @since 2.0.0\n   */\n  shadow?: boolean\n  /**\n   * The initial window theme. Defaults to the system theme.\n   *\n   * Only implemented on Windows and macOS 10.14+.\n   */\n  theme?: Theme\n  /**\n   * The style of the macOS title bar.\n   */\n  titleBarStyle?: TitleBarStyle\n  /**\n   * The position of the window controls on macOS.\n   *\n   * Requires `titleBarStyle: 'overlay'` and `decorations: true`.\n   *\n   * @since 2.4.0\n   */\n  trafficLightPosition?: LogicalPosition\n  /**\n   * If `true`, sets the window title to be hidden on macOS.\n   */\n  hiddenTitle?: boolean\n  /**\n   * Defines the window [tabbing identifier](https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier) on macOS.\n   *\n   * Windows with the same tabbing identifier will be grouped together.\n   * If the tabbing identifier is not set, automatic tabbing will be disabled.\n   */\n  tabbingIdentifier?: string\n  /**\n   * Whether the window's native maximize button is enabled or not. Defaults to `true`.\n   */\n  maximizable?: boolean\n  /**\n   * Whether the window's native minimize button is enabled or not. Defaults to `true`.\n   */\n  minimizable?: boolean\n  /**\n   * Whether the window's native close button is enabled or not. Defaults to `true`.\n   */\n  closable?: boolean\n  /**\n   * Sets a parent to the window to be created. Can be either a {@linkcode Window} or a label of the window.\n   *\n   * #### Platform-specific\n   *\n   * - **Windows**: This sets the passed parent as an owner window to the window to be created.\n   *   From [MSDN owned windows docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows):\n   *     - An owned window is always above its owner in the z-order.\n   *     - The system automatically destroys an owned window when its owner is destroyed.\n   *     - An owned window is hidden when its owner is minimized.\n   * - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>\n   * - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>\n   */\n  parent?: Window | WebviewWindow | string\n  /** Whether the window should be visible on all workspaces or virtual desktops.\n   *\n   * #### Platform-specific\n   *\n   * - **Windows / iOS / Android:** Unsupported.\n   *\n   * @since 2.0.0\n   */\n  visibleOnAllWorkspaces?: boolean\n  /**\n   * Window effects.\n   *\n   * Requires the window to be transparent.\n   *\n   * #### Platform-specific:\n   *\n   * - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>\n   * - **Linux**: Unsupported\n   */\n  windowEffects?: Effects\n  /**\n   * Set the window background color.\n   *\n   * #### Platform-specific:\n   *\n   * - **Android / iOS:** Unsupported.\n   * - **Windows**: alpha channel is ignored.\n   *\n   * @since 2.1.0\n   */\n  backgroundColor?: Color\n\n  /** Change the default background throttling behaviour.\n   *\n   * ## Platform-specific\n   *\n   * - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.\n   * - **iOS**: Supported since version 17.0+.\n   * - **macOS**: Supported since version 14.0+.\n   *\n   * see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578\n   *\n   * @since 2.3.0\n   */\n  backgroundThrottling?: BackgroundThrottlingPolicy\n  /**\n   * Whether we should disable JavaScript code execution on the webview or not.\n   */\n  javascriptDisabled?: boolean\n  /**\n   * on macOS and iOS there is a link preview on long pressing links, this is enabled by default.\n   * see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview\n   */\n  allowLinkPreview?: boolean\n  /**\n   * Allows disabling the input accessory view on iOS.\n   *\n   * The accessory view is the view that appears above the keyboard when a text input element is focused.\n   * It usually displays a view with \"Done\", \"Next\" buttons.\n   */\n  disableInputAccessoryView?: boolean\n  /**\n   * Specifies the native scrollbar style to use with the webview.\n   * CSS styles that modify the scrollbar are applied on top of the native appearance configured here.\n   *\n   * Defaults to `default`, which is the browser default.\n   *\n   * ## Platform-specific\n   *\n   * - **Windows**:\n   *   - `fluentOverlay` requires WebView2 Runtime version 125.0.2535.41 or higher, and does nothing\n   *     on older versions.\n   *   - This option must be given the same value for all webviews.\n   * - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.\n   */\n  scrollBarStyle?: ScrollBarStyle\n}\n\nfunction mapMonitor(m: Monitor | null): Monitor | null {\n  return m === null\n    ? null\n    : {\n        name: m.name,\n        scaleFactor: m.scaleFactor,\n        position: new PhysicalPosition(m.position),\n        size: new PhysicalSize(m.size),\n        workArea: {\n          position: new PhysicalPosition(m.workArea.position),\n          size: new PhysicalSize(m.workArea.size)\n        }\n      }\n}\n\n/**\n * Returns the monitor on which the window currently resides.\n * Returns `null` if current monitor can't be detected.\n * @example\n * ```typescript\n * import { currentMonitor } from '@tauri-apps/api/window';\n * const monitor = await currentMonitor();\n * ```\n *\n * @since 1.0.0\n */\nasync function currentMonitor(): Promise<Monitor | null> {\n  return invoke<Monitor | null>('plugin:window|current_monitor').then(\n    mapMonitor\n  )\n}\n\n/**\n * Returns the primary monitor of the system.\n * Returns `null` if it can't identify any monitor as a primary one.\n * @example\n * ```typescript\n * import { primaryMonitor } from '@tauri-apps/api/window';\n * const monitor = await primaryMonitor();\n * ```\n *\n * @since 1.0.0\n */\nasync function primaryMonitor(): Promise<Monitor | null> {\n  return invoke<Monitor | null>('plugin:window|primary_monitor').then(\n    mapMonitor\n  )\n}\n\n/**\n * Returns the monitor that contains the given point. Returns `null` if can't find any.\n * @example\n * ```typescript\n * import { monitorFromPoint } from '@tauri-apps/api/window';\n * const monitor = await monitorFromPoint(100.0, 200.0);\n * ```\n *\n * @since 1.0.0\n */\nasync function monitorFromPoint(x: number, y: number): Promise<Monitor | null> {\n  return invoke<Monitor | null>('plugin:window|monitor_from_point', {\n    x,\n    y\n  }).then(mapMonitor)\n}\n\n/**\n * Returns the list of all the monitors available on the system.\n * @example\n * ```typescript\n * import { availableMonitors } from '@tauri-apps/api/window';\n * const monitors = await availableMonitors();\n * ```\n *\n * @since 1.0.0\n */\nasync function availableMonitors(): Promise<Monitor[]> {\n  return invoke<Monitor[]>('plugin:window|available_monitors').then(\n    (ms) => ms.map(mapMonitor) as Monitor[]\n  )\n}\n\n/**\n * Get the cursor position relative to the top-left hand corner of the desktop.\n *\n * Note that the top-left hand corner of the desktop is not necessarily the same as the screen.\n * If the user uses a desktop with multiple monitors,\n * the top-left hand corner of the desktop is the top-left hand corner of the main monitor on Windows and macOS\n * or the top-left of the leftmost monitor on X11.\n *\n * The coordinates can be negative if the top-left hand corner of the window is outside of the visible screen region.\n */\nasync function cursorPosition(): Promise<PhysicalPosition> {\n  return invoke<PhysicalPosition>('plugin:window|cursor_position').then(\n    (v) => new PhysicalPosition(v)\n  )\n}\n\nexport {\n  Window,\n  CloseRequestedEvent,\n  getCurrentWindow,\n  getAllWindows,\n  LogicalSize,\n  PhysicalSize,\n  LogicalPosition,\n  PhysicalPosition,\n  UserAttentionType,\n  Effect,\n  EffectState,\n  currentMonitor,\n  monitorFromPoint,\n  primaryMonitor,\n  availableMonitors,\n  cursorPosition\n}\n\nexport type {\n  Effects,\n  Theme,\n  TitleBarStyle,\n  ScaleFactorChanged,\n  WindowOptions,\n  Color,\n  BackgroundThrottlingPolicy,\n  DragDropEvent,\n  ScrollBarStyle\n}\n"
  },
  {
    "path": "packages/api/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2019\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noImplicitAny\": true,\n    \"noEmit\": true\n  }\n}\n"
  },
  {
    "path": "packages/cli/.cargo/config.toml",
    "content": "# napi-rs can't handle custom target-dir configs yet so we make sure it's set to the default\n[build]\ntarget-dir = \"target\"\n\n[target.aarch64-unknown-linux-gnu]\nlinker = \"aarch64-linux-gnu-gcc\"\n\n[target.aarch64-unknown-linux-musl]\nlinker = \"aarch64-linux-musl-gcc\"\nrustflags = [\"-C\", \"target-feature=-crt-static\"]\n\n[target.armv7-unknown-linux-gnueabihf]\nlinker = \"arm-linux-gnueabihf-gcc\"\n\n[target.riscv64gc-unknown-linux-gnu]\nlinker = \"riscv64-linux-gnu-gcc\"\n\n[target.x86_64-pc-windows-msvc]\nrustflags = [\"-C\", \"target-feature=+crt-static\"]\n[target.i686-pc-windows-msvc]\nrustflags = [\"-C\", \"target-feature=+crt-static\"]\n[target.aarch64-pc-windows-msvc]\nrustflags = [\"-C\", \"target-feature=+crt-static\"]\n"
  },
  {
    "path": "packages/cli/.gitignore",
    "content": "# debug builds of NAPI\n*.node\n"
  },
  {
    "path": "packages/cli/.npmignore",
    "content": "target\nCargo.lock\n.cargo\n.github\nnpm\ntest\n.eslintrc\n.prettierignore\nrustfmt.toml\n*.node\n__tests__\nsrc\nCargo.toml\nbuild.rs"
  },
  {
    "path": "packages/cli/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[2.10.1]\n\n### Bug Fixes\n\n- [`35c35f27a`](https://www.github.com/tauri-apps/tauri/commit/35c35f27aedc430b602ec74059b271128c15ad36) ([#14931](https://www.github.com/tauri-apps/tauri/pull/14931) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Support comma-separated list of Cargo features on all commands.\n- [`0d1cb83ba`](https://www.github.com/tauri-apps/tauri/commit/0d1cb83bab2aa482c7d73116893fd7ff6aa56283) ([#14932](https://www.github.com/tauri-apps/tauri/pull/14932) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix missing Cargo args when running mobile dev and build commands.\n- [`33754ae5e`](https://www.github.com/tauri-apps/tauri/commit/33754ae5e3740d022483b6164511c5c001a3c24b) ([#15022](https://www.github.com/tauri-apps/tauri/pull/15022) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix updater signing private keys generated using `tauri signer generate` with empty password can't be used (The keys generated during tauri were broken between v2.9.3 and v2.10.0, you'll need to regenerate them)\n\n### What's Changed\n\n- [`7be58a1c6`](https://www.github.com/tauri-apps/tauri/commit/7be58a1c643a7ed6d0f484a7e1134022618eb2b2) ([#14894](https://www.github.com/tauri-apps/tauri/pull/14894) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Log patching bundle type information again\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.10.1`\n\n## \\[2.10.0]\n\n### Enhancements\n\n- [`f82594410`](https://www.github.com/tauri-apps/tauri/commit/f82594410cd57d6f794f58d4afea0ed335aa796f) ([#13253](https://www.github.com/tauri-apps/tauri/pull/13253) by [@Armaldio](https://www.github.com/tauri-apps/tauri/../../Armaldio)) Allow electron to run the CLI directly\n- [`a2abe2e6b`](https://www.github.com/tauri-apps/tauri/commit/a2abe2e6bcb9e1eed8484240dfdb76a5bc28ae58) ([#14607](https://www.github.com/tauri-apps/tauri/pull/14607) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Simplified internal representation of `features: Option<Vec<String>>` with `Vec<String>`, no user facing changes\n- [`84b04c4a8`](https://www.github.com/tauri-apps/tauri/commit/84b04c4a8d3310b7a7091d10e36244bf94996e51) ([#14759](https://www.github.com/tauri-apps/tauri/pull/14759) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added new environment variables for `tauri signer sign` command, to align with existing environment variables used in `tauri build`, `tauri bundle` and `tauri signer generate`\n\n  - `TAURI_SIGNING_PRIVATE_KEY`\n  - `TAURI_SIGNING_PRIVATE_KEY_PATH`\n  - `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`\n\n  The old environment variables are deprecated and will be removed in a future release.\n\n  - `TAURI_PRIVATE_KEY`\n  - `TAURI_PRIVATE_KEY_PATH`\n  - `TAURI_PRIVATE_KEY_PASSWORD`\n\n### Bug Fixes\n\n- [`62aa13a12`](https://www.github.com/tauri-apps/tauri/commit/62aa13a124ef46bb5ce9887a2a574dd35ef86d4f) ([#14629](https://www.github.com/tauri-apps/tauri/pull/14629) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `android build`'s `--aab` and `--apk` flags requiring a value to be provided.\n- [`eccff9758`](https://www.github.com/tauri-apps/tauri/commit/eccff97588232055bd0cafd83e6ee03d11a501fb) ([#14779](https://www.github.com/tauri-apps/tauri/pull/14779) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix empty associated-domains entitlements when domains are not configured for deep links.\n- [`ea31b07f1`](https://www.github.com/tauri-apps/tauri/commit/ea31b07f19e0aa467ed0f921f60575cfe09809c8) ([#14789](https://www.github.com/tauri-apps/tauri/pull/14789) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fixed the command description for `tauri inspect`\n- [`7fca58230`](https://www.github.com/tauri-apps/tauri/commit/7fca58230f97c3e6834134419514a0c7dbbe784b) ([#14830](https://www.github.com/tauri-apps/tauri/pull/14830) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Updated `nsis_tauri_utils` to 0.5.3:\n\n  - Use an alternative method `CreateProcessWithTokenW` to run programs as user, this fixed a problem that the program launched with the previous method can't query its own handle\n- [`53611c4d7`](https://www.github.com/tauri-apps/tauri/commit/53611c4d7bdaf89b9a5d7c46a9c4bf4e34216148) ([#14747](https://www.github.com/tauri-apps/tauri/pull/14747) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Only watch dependent workspace members when running `tauri dev` instead of watching on all members\n- [`1b0e335d3`](https://www.github.com/tauri-apps/tauri/commit/1b0e335d3f3445948d6590f7e074275d97cd9859) ([#14713](https://www.github.com/tauri-apps/tauri/pull/14713) by [@wasuaje](https://www.github.com/tauri-apps/tauri/../../wasuaje)) `tauri signer sign` doesn't work for files without an extension\n\n### What's Changed\n\n- [`e3fdcb500`](https://www.github.com/tauri-apps/tauri/commit/e3fdcb5002b362b46cde2a1971e4e7f2a1161208) ([#14836](https://www.github.com/tauri-apps/tauri/pull/14836) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Continued refactors of tauri-cli, fix too weak atomics.\n- [`0575dd287`](https://www.github.com/tauri-apps/tauri/commit/0575dd287e021b61d2aedf64d62ae84a2c925fb4) ([#14521](https://www.github.com/tauri-apps/tauri/pull/14521) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Change the way bundle type information is added to binary files. Instead of looking up the value of a variable we simply look for the default value.\n- [`7f7d9aac2`](https://www.github.com/tauri-apps/tauri/commit/7f7d9aac214e22d9492490543f7a9bcae0a6659e) ([#14668](https://www.github.com/tauri-apps/tauri/pull/14668) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Refactored internal use of static on config and directory resolvings, no user facing changes, please report any regressions if you encounter any\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.10.0`\n\n## \\[2.9.6]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.9.6`\n\n## \\[2.9.5]\n\n### Bug Fixes\n\n- [`f855caf8a`](https://www.github.com/tauri-apps/tauri/commit/f855caf8a3830aa5dd6d0b039312866a5d9c3606) ([#14481](https://www.github.com/tauri-apps/tauri/pull/14481) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fixed the mismatched tauri package versions check didn't work for pnpm\n\n### Performance Improvements\n\n- [`ce98d87ce`](https://www.github.com/tauri-apps/tauri/commit/ce98d87ce0aaa907285852eb80691197424e03c3) ([#14474](https://www.github.com/tauri-apps/tauri/pull/14474) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) refactor: remove needless collect. No user facing changes.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.9.5`\n\n## \\[2.9.4]\n\n### Bug Fixes\n\n- [`b586ecf1f`](https://www.github.com/tauri-apps/tauri/commit/b586ecf1f4b3b087f9aa6c4668c2c18b1b7925f4) ([#14416](https://www.github.com/tauri-apps/tauri/pull/14416) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Premultiply Alpha before Resizing which gets rid of the gray fringe around the icons for svg images.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.9.4`\n\n## \\[2.9.3]\n\n### Bug Fixes\n\n- [`fd8c30b4f`](https://www.github.com/tauri-apps/tauri/commit/fd8c30b4f1bca8dd7165c5c0ebe7fbfd17662153) ([#14353](https://www.github.com/tauri-apps/tauri/pull/14353) by [@ChaseKnowlden](https://www.github.com/tauri-apps/tauri/../../ChaseKnowlden)) Premultiply Alpha before Resizing which gets rid of the gray fringe around the icons.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.9.3`\n\n## \\[2.9.2]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.9.2`\n\n## \\[2.9.1]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.9.1`\n\n## \\[2.9.0]\n\n### New Features\n\n- [`3b4fac201`](https://www.github.com/tauri-apps/tauri/commit/3b4fac2017832d426dd07c5e24e26684eda57f7b) ([#14194](https://www.github.com/tauri-apps/tauri/pull/14194)) Add `tauri.conf.json > bundle > android > autoIncrementVersionCode` config option to automatically increment the Android version code.\n- [`673867aa0`](https://www.github.com/tauri-apps/tauri/commit/673867aa0e1ccd766ee879ffe96aba58c758613c) ([#14094](https://www.github.com/tauri-apps/tauri/pull/14094)) Try to detect ANDROID_HOME and NDK_HOME environment variables from default system locations and install them if needed using the Android Studio command line tools.\n- [`3d6868d09`](https://www.github.com/tauri-apps/tauri/commit/3d6868d09c323d68a152f3c3f8c7256311bd020a) ([#14128](https://www.github.com/tauri-apps/tauri/pull/14128)) Added support to defining the content type of the declared file association on macOS (maps to LSItemContentTypes property).\n- [`3d6868d09`](https://www.github.com/tauri-apps/tauri/commit/3d6868d09c323d68a152f3c3f8c7256311bd020a) ([#14128](https://www.github.com/tauri-apps/tauri/pull/14128)) Added support to defining the metadata for custom types declared in `tauri.conf.json > bundle > fileAssociations > exportedType` via the `UTExportedTypeDeclarations` Info.plist property.\n- [`ed7c9a410`](https://www.github.com/tauri-apps/tauri/commit/ed7c9a4100e08c002212265549d12130d021ad1e) ([#14108](https://www.github.com/tauri-apps/tauri/pull/14108)) Added `bundle > macOS > infoPlist` and `bundle > iOS > infoPlist` configurations to allow defining custom Info.plist extensions.\n- [`75082cc5b`](https://www.github.com/tauri-apps/tauri/commit/75082cc5b340e30e2c4b4cd4bd6a1fe5382164aa) ([#14120](https://www.github.com/tauri-apps/tauri/pull/14120)) Added `ios run` and `android run` commands to run the app in production mode.\n- [`cc8c0b531`](https://www.github.com/tauri-apps/tauri/commit/cc8c0b53171173dbd1d01781a50de1a3ea159031) ([#14031](https://www.github.com/tauri-apps/tauri/pull/14031)) Added support to universal app links on macOS with the `plugins > deep-link > desktop > domains` configuration.\n\n### Enhancements\n\n- [`b5aa01870`](https://www.github.com/tauri-apps/tauri/commit/b5aa018702bf45dc98297698f9b7d238705865a6) ([#14268](https://www.github.com/tauri-apps/tauri/pull/14268)) Update cargo-mobile2 to 0.21, enhancing error messages and opening Xcode when multiple apps are installed.\n- [`55453e845`](https://www.github.com/tauri-apps/tauri/commit/55453e8453d927b8197f1ba9f26fd944482938f7) ([#14262](https://www.github.com/tauri-apps/tauri/pull/14262)) Check mismatched versions in `tauri info`\n- [`1a6627ee7`](https://www.github.com/tauri-apps/tauri/commit/1a6627ee7d085a4e66784e2705254714d68c7244) ([#14122](https://www.github.com/tauri-apps/tauri/pull/14122)) Set a default log level filter when running `tauri add log`.\n- [`b06b3bd09`](https://www.github.com/tauri-apps/tauri/commit/b06b3bd091b0fed26cdcfb23cacb0462a7a9cc2d) ([#14126](https://www.github.com/tauri-apps/tauri/pull/14126)) Improve error messages with more context.\n- [`f6622a3e3`](https://www.github.com/tauri-apps/tauri/commit/f6622a3e342f5dd5fb3cf6e0f79fb309a10e9b3d) ([#14129](https://www.github.com/tauri-apps/tauri/pull/14129)) Prompt to install the iOS platform if it isn't installed yet.\n- [`6bbb530fd`](https://www.github.com/tauri-apps/tauri/commit/6bbb530fd5edfc07b180a4f3782b8566872ca3b1) ([#14105](https://www.github.com/tauri-apps/tauri/pull/14105)) Warn if productName is empty when initializing mobile project.\n\n### Bug Fixes\n\n- [`19fb6f7cb`](https://www.github.com/tauri-apps/tauri/commit/19fb6f7cb0d702cb2f25f6f2d1e11014d9dada5d) ([#14146](https://www.github.com/tauri-apps/tauri/pull/14146)) Strip Windows-only extensions from the binary path so an Android project initialized on Windows can be used on UNIX systems.\n- [`19fb6f7cb`](https://www.github.com/tauri-apps/tauri/commit/19fb6f7cb0d702cb2f25f6f2d1e11014d9dada5d) ([#14146](https://www.github.com/tauri-apps/tauri/pull/14146)) Enhance Android build script usage on Windows by attempting to run cmd, bat and exe formats.\n- [`28a2f9bc5`](https://www.github.com/tauri-apps/tauri/commit/28a2f9bc55f658eb71ef1a970ff9f791346f7682) ([#14101](https://www.github.com/tauri-apps/tauri/pull/14101)) Fix iOS CLI usage after modifying the package name.\n- [`d2938486e`](https://www.github.com/tauri-apps/tauri/commit/d2938486e9d974debd90c15d7160b8a17bf4d763) ([#14261](https://www.github.com/tauri-apps/tauri/pull/14261)) Replaced the non-standard nerd font character with `  ⱼₛ ` in `tarui info`\n- [`25e920e16`](https://www.github.com/tauri-apps/tauri/commit/25e920e169db900ca4f07c2bb9eb290e9f9f2c7d) ([#14298](https://www.github.com/tauri-apps/tauri/pull/14298)) Wait for dev server to exit before exiting the CLI when the app is closed on `tauri dev --no-watch`.\n- [`b0012424c`](https://www.github.com/tauri-apps/tauri/commit/b0012424c5f432debfa42ba145e2672966d5f6d5) ([#14115](https://www.github.com/tauri-apps/tauri/pull/14115)) Resolve local IP address when `tauri.conf.json > build > devUrl` host is `0.0.0.0`.\n- [`abf7e8850`](https://www.github.com/tauri-apps/tauri/commit/abf7e8850ba41e7173e9e9a3fdd6dfb8f357d72d) ([#14118](https://www.github.com/tauri-apps/tauri/pull/14118)) Fixes mobile project initialization when using `pnpx` or `pnpm dlx`.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.9.0`\n\n## \\[2.8.4]\n\n### Enhancements\n\n- [`f70b28529`](https://www.github.com/tauri-apps/tauri/commit/f70b28529d226a2dec2f41709d8934f8f5adab25) ([#14093](https://www.github.com/tauri-apps/tauri/pull/14093) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ensure Rust targets for mobile are installed when running the dev and build commands (previously only checked on init).\n- [`a9b342125`](https://www.github.com/tauri-apps/tauri/commit/a9b342125d5ac1bc9a4b2e8b5f73e8ca3cbcb8b2) ([#14114](https://www.github.com/tauri-apps/tauri/pull/14114) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS dev and build targeting the simulator on Intel machines.\n- [`61b9b681e`](https://www.github.com/tauri-apps/tauri/commit/61b9b681e88067a53b79d2318ae005dc25addcd6) ([#14111](https://www.github.com/tauri-apps/tauri/pull/14111) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Retain `RUST_*` environment variables when running the mobile commands.\n- [`c23bec62d`](https://www.github.com/tauri-apps/tauri/commit/c23bec62d6d5724798869681aa1534423aae28e2) ([#14083](https://www.github.com/tauri-apps/tauri/pull/14083) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Tauri now ignores `macOS.minimumSystemVersion` in `tauri dev` to prevent forced rebuilds of macOS specific dependencies when using something like `rust-analyzer` at the same time as `tauri dev`.\n\n### Bug Fixes\n\n- [`c37a29833`](https://www.github.com/tauri-apps/tauri/commit/c37a298331d6d744b15d32d55a2db83c884a3d6a) ([#14112](https://www.github.com/tauri-apps/tauri/pull/14112) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix usage with Deno failing with `ReferenceError: require is not defined`.\n- [`bcf000c0a`](https://www.github.com/tauri-apps/tauri/commit/bcf000c0a8607eedf488fb949b982f519abda43d) ([#14110](https://www.github.com/tauri-apps/tauri/pull/14110) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes running `ios` commands with `deno` crashing due to incorrect current working directory resolution.\n- [`7db7142f9`](https://www.github.com/tauri-apps/tauri/commit/7db7142f9ff7dc2f5719602e199b77129ceb19d3) ([#14119](https://www.github.com/tauri-apps/tauri/pull/14119) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes empty device name when using an Android emulator causing the emulator to never be detected as running.\n- [`956b4fd6f`](https://www.github.com/tauri-apps/tauri/commit/956b4fd6ffbb4312123b107ca96c87a001359b9d) ([#14106](https://www.github.com/tauri-apps/tauri/pull/14106) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Use the correct export method on Xcode < 15.4.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.8.4`\n\n## \\[2.8.3]\n\n### Bug Fixes\n\n- [`0ac89d3b6`](https://www.github.com/tauri-apps/tauri/commit/0ac89d3b6c8c4a4826a4c42726e4f4a8941b3fde) ([#14078](https://www.github.com/tauri-apps/tauri/pull/14078) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Updated `cargo-mobile2` to allow running on iOS simulators that have a higher version than the XCode SDK. This fixes compatiblity issues with Apple's recent \"iOS 18.5 + iOS 18.6 Simulator\" platform support component.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.8.3`\n\n## \\[2.8.2]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.8.1`\n\n## \\[2.8.1]\n\n### Bug Fixes\n\n- [`f0172a454`](https://www.github.com/tauri-apps/tauri/commit/f0172a454aea101cb699233be3d73dbf86e64a31) ([#14038](https://www.github.com/tauri-apps/tauri/pull/14038) by [@KushalMeghani1644](https://www.github.com/tauri-apps/tauri/../../KushalMeghani1644)) Fixes `removeDataStore` return type.\n\n## \\[2.8.0]\n\n### New Features\n\n- [`91508c0b8`](https://www.github.com/tauri-apps/tauri/commit/91508c0b8d16ec61c7706e93b711c5a85aaffb4a) ([#13881](https://www.github.com/tauri-apps/tauri/pull/13881) by [@pepperoni505](https://www.github.com/tauri-apps/tauri/../../pepperoni505)) Introduces a new configuration option that allows you to specify custom folders to watch for changes when running `tauri dev`.\n- [`bc4afe7dd`](https://www.github.com/tauri-apps/tauri/commit/bc4afe7dd4780f02c2d4b1f07d97185fbc5d2bba) ([#13993](https://www.github.com/tauri-apps/tauri/pull/13993) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Check installed plugin NPM/crate versions for incompatible releases.\n- [`0c402bfb6`](https://www.github.com/tauri-apps/tauri/commit/0c402bfb6bd0bec24d928fcabe2ffef1f5cff19a) ([#13997](https://www.github.com/tauri-apps/tauri/pull/13997) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Increase default iOS deployment target iOS to 14.0.\n- [`d6d5f3707`](https://www.github.com/tauri-apps/tauri/commit/d6d5f3707768a094ff7e961ae75ba0398d772655) ([#13358](https://www.github.com/tauri-apps/tauri/pull/13358) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `--root-certificate-path` option to `android dev` and `ios dev` to be able to connect to HTTPS dev servers.\n\n### Enhancements\n\n- [`8b465a12b`](https://www.github.com/tauri-apps/tauri/commit/8b465a12ba73e94d7a3995defd9cc362d15eeebe) ([#13913](https://www.github.com/tauri-apps/tauri/pull/13913) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler now pulls the latest AppImage linuxdeploy plugin instead of using the built-in one. This should remove the libfuse requirement.\n- [`390cb9c36`](https://www.github.com/tauri-apps/tauri/commit/390cb9c36a4e2416891b64514e7ad5fc0a85ccf2) ([#13953](https://www.github.com/tauri-apps/tauri/pull/13953) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Reduced the log level of the binary patcher crate `goblin` to only show its debug logs in `-vv` and above.\n- [`4475e93e1`](https://www.github.com/tauri-apps/tauri/commit/4475e93e136e9e2bd5f3c7817fa2040924f630f6) ([#13824](https://www.github.com/tauri-apps/tauri/pull/13824) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The bundler and cli will now read TLS Certificates installed on the system when downloading tools and checking versions.\n\n### Bug Fixes\n\n- [`f0dcf9637`](https://www.github.com/tauri-apps/tauri/commit/f0dcf9637cc0d42eda05fed7dd6c5ff98bbf19ae) ([#13980](https://www.github.com/tauri-apps/tauri/pull/13980) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix the generated plugin init code of `tauri add` for `tauri-plugin-autostart` and `tauri-plugin-single-instance`\n- [`4d270a96a`](https://www.github.com/tauri-apps/tauri/commit/4d270a96a891ae83f7df751abcbe12b7072212d5) ([#13943](https://www.github.com/tauri-apps/tauri/pull/13943) by [@acx0](https://www.github.com/tauri-apps/tauri/../../acx0)) Fix codesigning verification failures caused by binary-patching during bundling\n- [`b21d86a8a`](https://www.github.com/tauri-apps/tauri/commit/b21d86a8a3ef29f16628b7d4de17ce1214e9bf49) ([#13981](https://www.github.com/tauri-apps/tauri/pull/13981) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix `tauri permission add` could add duplicated permissions to the capability files\n- [`9c938be45`](https://www.github.com/tauri-apps/tauri/commit/9c938be4520fce9204361f3b59439844bc5c91e8) ([#13912](https://www.github.com/tauri-apps/tauri/pull/13912) by [@takecchi](https://www.github.com/tauri-apps/tauri/../../takecchi)) Properly migrate svelte to v5 in the plugin example template\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.8.0`\n\n## \\[2.7.1]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.7.1`\n\n## \\[2.7.0]\n\n### New Features\n\n- [`33d079392`](https://www.github.com/tauri-apps/tauri/commit/33d079392ac4a5a153b7d8a6d82fefd6f54a2bdf) ([#13811](https://www.github.com/tauri-apps/tauri/pull/13811) by [@mhbagheri-99](https://www.github.com/tauri-apps/tauri/../../mhbagheri-99)) Allow runner configuration to be an object with cmd, cwd, and args properties. The runner can now be configured as `{ \"cmd\": \"my_runner\", \"cwd\": \"/path\", \"args\": [\"--quiet\"] }` while maintaining backwards compatibility with the existing string format.\n\n### Enhancements\n\n- [`232265c70`](https://www.github.com/tauri-apps/tauri/commit/232265c70e1c213bbb3f84b5541ddc07d330fce1) ([#13209](https://www.github.com/tauri-apps/tauri/pull/13209) by [@kandrelczyk](https://www.github.com/tauri-apps/tauri/../../kandrelczyk)) Binaries are patched before bundling to add the type of a bundle they will placed in. This information will be used during update process to select the correct target.\n\n### Bug Fixes\n\n- [`916aeaa48`](https://www.github.com/tauri-apps/tauri/commit/916aeaa48646a483a78e51cfe1633800ee62c37c) ([#13781](https://www.github.com/tauri-apps/tauri/pull/13781) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes Android dev and build commands reading `tauri.ios.conf.json` instead of `tauri.android.conf.json` to merge platform-specific configuration.\n- [`0f248b111`](https://www.github.com/tauri-apps/tauri/commit/0f248b111ffb8af934eaf64bd8f4591e628da786) ([#13799](https://www.github.com/tauri-apps/tauri/pull/13799) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Install iOS dependencies when needed.\n- [`7a6fd5b75`](https://www.github.com/tauri-apps/tauri/commit/7a6fd5b75d61071e2771f6277c0376ec206d302a) ([#13863](https://www.github.com/tauri-apps/tauri/pull/13863) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The AppImage bundler now pulls the AppRun binaries from our GitHub mirror, fixing 404 errors.\n- [`bda830410`](https://www.github.com/tauri-apps/tauri/commit/bda8304107da7ca60caaba5674faa793491898c6) ([#13833](https://www.github.com/tauri-apps/tauri/pull/13833) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fail with an error when trying to migrate from v2 alpha\n- [`bda830410`](https://www.github.com/tauri-apps/tauri/commit/bda8304107da7ca60caaba5674faa793491898c6) ([#13833](https://www.github.com/tauri-apps/tauri/pull/13833) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Use v2 stable instead of v2-rc when migrating from v2-beta\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.7.0`\n\n## \\[2.6.2]\n\n### Bug Fixes\n\n- [`cbd962972`](https://www.github.com/tauri-apps/tauri/commit/cbd9629729ed6eb208ba2234d014c11c4e9f1c8c) ([#13730](https://www.github.com/tauri-apps/tauri/pull/13730) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Load `--config` arguments when running the Xcode and Android Studio build scripts.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.6.2`\n\n## \\[2.6.1]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.6.1`\n\n## \\[2.6.0]\n\n### New Features\n\n- [`414619c36`](https://www.github.com/tauri-apps/tauri/commit/414619c36e94e21939534dd72c0438b93da75546) ([#13536](https://www.github.com/tauri-apps/tauri/pull/13536) by [@Tunglies](https://www.github.com/tauri-apps/tauri/../../Tunglies)) Added support for the `bundleName` property in the macOS bundler configuration. This allows specifying the `CFBundleName` value for generated macOS bundles.\n- [`3242e1c94`](https://www.github.com/tauri-apps/tauri/commit/3242e1c946c441b58665ba5d612f3a3f1eafe0b6) ([#13659](https://www.github.com/tauri-apps/tauri/pull/13659) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow passing Cargo arguments to mobile dev and build commands.\n- [`d1ce9af62`](https://www.github.com/tauri-apps/tauri/commit/d1ce9af62881e3f7d86a495c9c40df5b7f9d1c04) ([#13660](https://www.github.com/tauri-apps/tauri/pull/13660) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow passing `--config` arguments to the `ios init` and `android init` commands to tweak the configuration used to initialize the mobile projects.\n- [`7322f0579`](https://www.github.com/tauri-apps/tauri/commit/7322f057923aaec88960ad5556776774b745762f) ([#13502](https://www.github.com/tauri-apps/tauri/pull/13502) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Allow using `CheckIfAppIsRunning` macro inside NSIS hooks, for example `!insertmacro CheckIfAppIsRunning \"another-executable.exe\" \"Another Executable\"`.\n- [`4a880ca69`](https://www.github.com/tauri-apps/tauri/commit/4a880ca697bab6d63a2a51ea94e1988cc8c4ea4a) ([#13658](https://www.github.com/tauri-apps/tauri/pull/13658) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Synchronize Tauri config productName changes with the iOS Xcode project.\n- [`8ee14a864`](https://www.github.com/tauri-apps/tauri/commit/8ee14a86480510c15823586cf28084e615cb7a9c) ([#13618](https://www.github.com/tauri-apps/tauri/pull/13618) by [@Sky-walkerX](https://www.github.com/tauri-apps/tauri/../../Sky-walkerX)) Warn the user that the app id shouldn't end in `.app` because it conflicts with the application bundle extension on macOS\n\n### Bug Fixes\n\n- [`35aa7e121`](https://www.github.com/tauri-apps/tauri/commit/35aa7e1218f34d0805e280e3ec32529d0cb0d733) ([#13294](https://www.github.com/tauri-apps/tauri/pull/13294) by [@kingsword09](https://www.github.com/tauri-apps/tauri/../../kingsword09)) fix: allow the target directory to be inside frontendDir as long as it is not the Rust target directory inside frontendDir.\n- [`ec6065fa4`](https://www.github.com/tauri-apps/tauri/commit/ec6065fa4a6427266ecfb0c0f62f008574bb7880) ([#13625](https://www.github.com/tauri-apps/tauri/pull/13625) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes Android and iOS dev/build commands not working when the app identifier is being modified by the `--config` option.\n- [`5a5291d66`](https://www.github.com/tauri-apps/tauri/commit/5a5291d66cb8a955c9d4f8e975782646ac0cc6e7) ([#13483](https://www.github.com/tauri-apps/tauri/pull/13483) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix simulator build detection on Xcode.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.6.0`\n- [`9c16eefa3`](https://www.github.com/tauri-apps/tauri/commit/9c16eefa319b4697bac1d1019bbb5f93eca63173) ([#13629](https://www.github.com/tauri-apps/tauri/pull/13629) by [@sftse](https://www.github.com/tauri-apps/tauri/../../sftse)) Update html5ever to 0.29 and kuchikiki to version 0.8.8-speedreader.\n\n## \\[2.5.0]\n\n### New Features\n\n- [`0aa48fb9e`](https://www.github.com/tauri-apps/tauri/commit/0aa48fb9e4b9d7b5bf3522000a76ebc1836394ed) ([#13030](https://www.github.com/tauri-apps/tauri/pull/13030)) Added `bundleVersion` to iOS and macOS configuration to support specifying a `CFBundleVersion`.\n\n### Enhancements\n\n- [`ad3fd3890`](https://www.github.com/tauri-apps/tauri/commit/ad3fd3890f1fa26a9f9be04ff1bc156d6dd2a8bc) ([#13152](https://www.github.com/tauri-apps/tauri/pull/13152)) Detect package manager from environment variable `npm_config_user_agent` first\n- [`82406c61e`](https://www.github.com/tauri-apps/tauri/commit/82406c61e0fbb775ef00791ccab45349325bdd45) ([#13231](https://www.github.com/tauri-apps/tauri/pull/13231)) Improve iOS simulator usage, checking if Xcode iOS SDK is installed and allowing usage of Simulator for older iOS releases (previously only supported when running on Xcode via `ios dev --open`).\n\n### Bug Fixes\n\n- [`2dccfab53`](https://www.github.com/tauri-apps/tauri/commit/2dccfab5321fef55d45f3a4c674b6151b1c4424a) ([#13236](https://www.github.com/tauri-apps/tauri/pull/13236)) Fix `fileAssociations` missing `LSHandlerRank` on macOS.\n- [`080252903`](https://www.github.com/tauri-apps/tauri/commit/0802529031c4fd309edff374a8694e93ddec161d) ([#13210](https://www.github.com/tauri-apps/tauri/pull/13210)) Fixes iOS dev not working on Xcode 16.3 simulators. To apply the fix, either regenerate the Xcode project with `rm -r src-tauri/gen/apple && tauri ios init` or remove the `arm64-sim` architecture from the Xcode project.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.5.0`\n\n## \\[2.4.1]\n\n### Enhancements\n\n- [`f805061d1`](https://www.github.com/tauri-apps/tauri/commit/f805061d1152bc4790dbdb9475a506afcdd1de75) ([#13079](https://www.github.com/tauri-apps/tauri/pull/13079) by [@Pietagorh](https://www.github.com/tauri-apps/tauri/../../Pietagorh)) Add support for passing TOML and JSON5 config files to `--config` arg\n\n### Bug Fixes\n\n- [`794af778e`](https://www.github.com/tauri-apps/tauri/commit/794af778e4915ffb6a4fe9bae8fba04bc880503d) ([#13117](https://www.github.com/tauri-apps/tauri/pull/13117) by [@Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Fix setting merge config value to null with `--config` arg no longer works\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.4.1`\n\n## \\[2.4.0]\n\n### New Features\n\n- [`d91bfa5cb`](https://www.github.com/tauri-apps/tauri/commit/d91bfa5cb921a078758edd45ef3eaff71358d1eb) ([#12970](https://www.github.com/tauri-apps/tauri/pull/12970) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow merging multiple configuration values on `tauri dev`, `tauri build`, `tauri bundle`, `tauri android dev`, `tauri android build`, `tauri ios dev` and `tauri ios build`.\n- [`30f5a1553`](https://www.github.com/tauri-apps/tauri/commit/30f5a1553d3c0ce460c9006764200a9210915a44) ([#12366](https://www.github.com/tauri-apps/tauri/pull/12366) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Added `trafficLightPosition` window configuration to set the traffic light buttons position on macOS.\n\n### Enhancements\n\n- [`f981a5ee8`](https://www.github.com/tauri-apps/tauri/commit/f981a5ee8b292b9ea09329f60cecc7f688dda734) ([#12602](https://www.github.com/tauri-apps/tauri/pull/12602) by [@kxxt](https://www.github.com/tauri-apps/tauri/../../kxxt)) Add basic support for linux riscv64 platform.\n\n### Bug Fixes\n\n- [`0c4700e99`](https://www.github.com/tauri-apps/tauri/commit/0c4700e9907f242eabe579eb6149a1d75174185c) ([#12985](https://www.github.com/tauri-apps/tauri/pull/12985) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The cli will now accept `--bundles updater` again. It's still no-op as it has been for all v2 versions. If you want to build updater artifacts, enable `createUpdaterArtifacts` in `tauri.conf.json`.\n- [`b83921226`](https://www.github.com/tauri-apps/tauri/commit/b83921226cb3084992bb5357e7e39a09ea97843e) ([#12977](https://www.github.com/tauri-apps/tauri/pull/12977) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `tauri ios` commands using the wrong working directory with `bun@>1.2`.\n- [`f268b3dbd`](https://www.github.com/tauri-apps/tauri/commit/f268b3dbdf313484c85b4a1f69cd7cec63049f35) ([#12871](https://www.github.com/tauri-apps/tauri/pull/12871) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ignore parent .gitignore files on the Tauri project path detection.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.4.0`\n\n## \\[2.3.1]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.3.1`\n\n## \\[2.3.0]\n\n### Enhancements\n\n- [`a2d36b8c3`](https://www.github.com/tauri-apps/tauri/commit/a2d36b8c34a8dcfc6736797ca5cd4665faf75e7e) ([#12181](https://www.github.com/tauri-apps/tauri/pull/12181) by [@bastiankistner](https://www.github.com/tauri-apps/tauri/../../bastiankistner)) Add an option to change the default background throttling policy (currently for WebKit only).\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.3.0`\n\n## \\[2.2.7]\n\n### Bug Fixes\n\n- [`8e9134c4a`](https://www.github.com/tauri-apps/tauri/commit/8e9134c4a2047329be0dbb868b7ae061a9d3f190) ([#12511](https://www.github.com/tauri-apps/tauri/pull/12511) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused `tauri dev` to fail because of an incorrect `--bins` flag.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.2.7`\n\n## \\[2.2.6]\n\n### Enhancements\n\n- [`1a86974aa`](https://www.github.com/tauri-apps/tauri/commit/1a86974aa3d09957c6b1142a17bbfed9998798fd) ([#12406](https://www.github.com/tauri-apps/tauri/pull/12406) by [@bradleat](https://www.github.com/tauri-apps/tauri/../../bradleat)) `ios build --open` will now let xcode start the rust build process.\n- [`0b79af711`](https://www.github.com/tauri-apps/tauri/commit/0b79af711430934362602fb950c3e4cb5b59cf9c) ([#12438](https://www.github.com/tauri-apps/tauri/pull/12438) by [@3lpsy](https://www.github.com/tauri-apps/tauri/../../3lpsy)) Log the command used to start the rust app in development.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.2.6`\n\n## \\[2.2.5]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.2.5`\n\n## \\[2.2.4]\n\n### Bug Fixes\n\n- [`cad550445`](https://www.github.com/tauri-apps/tauri/commit/cad5504455ffa53e297cebff473c113b1afa5d29) ([#12354](https://www.github.com/tauri-apps/tauri/pull/12354) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed and issue that caused `tauri add` to try to install incorrect npm packages.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.2.4`\n\n## \\[2.2.3]\n\n### Enhancements\n\n- [`a0f2c84d5`](https://www.github.com/tauri-apps/tauri/commit/a0f2c84d51f5086c5055867d6f61ea90c463a26c) ([#12204](https://www.github.com/tauri-apps/tauri/pull/12204) by [@pjf-dev](https://www.github.com/tauri-apps/tauri/../../pjf-dev)) Enhance `tauri icon` command by including 64x64 png size in default icon sizes.\n\n### Bug Fixes\n\n- [`98f62e65a`](https://www.github.com/tauri-apps/tauri/commit/98f62e65a27a375272c6b4d9f34c23e142b9d3a6) ([#12246](https://www.github.com/tauri-apps/tauri/pull/12246) by [@marcomq](https://www.github.com/tauri-apps/tauri/../../marcomq)) Properly add NPM packages for community plugins when using the `tauri add` command.\n- [`b9a99a5c6`](https://www.github.com/tauri-apps/tauri/commit/b9a99a5c69d8a2a1a3ff30e500b46872258dca15) ([#12297](https://www.github.com/tauri-apps/tauri/pull/12297) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the built-in dev server to constantly refresh on Linux. This only affected users who do not have `devUrl` point to a URL.\n- [`ef21ed9ac`](https://www.github.com/tauri-apps/tauri/commit/ef21ed9ac1c045c38b0c04e3d71a441694abc257) ([#12290](https://www.github.com/tauri-apps/tauri/pull/12290) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS build failing when the development team contains spaces.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.2.3`\n\n## \\[2.2.2]\n\n### Bug Fixes\n\n- [`26fc9558f`](https://www.github.com/tauri-apps/tauri/commit/26fc9558fe7b2fe649f61926da88f36110dd5707) ([#12178](https://www.github.com/tauri-apps/tauri/pull/12178) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused the `tauri dev` file watcher to exit after detecting file changes.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.2.2`\n\n## \\[2.2.1]\n\n### Bug Fixes\n\n- [`881729448`](https://www.github.com/tauri-apps/tauri/commit/881729448c9abd0d0c7941a8a31c94119ce827af) ([#12164](https://www.github.com/tauri-apps/tauri/pull/12164) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue that caused `tauri dev` to crash before showing the app on Linux.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.2.1`\n\n## \\[2.2.0]\n\n### New Features\n\n- [`cccb308c7`](https://www.github.com/tauri-apps/tauri/commit/cccb308c7b559b0838138d6cea280665f060c925) ([#11562](https://www.github.com/tauri-apps/tauri/pull/11562) by [@jLynx](https://www.github.com/tauri-apps/tauri/../../jLynx)) Generate signature for `.deb` packages when `createUpdaterArtifacts` option is enabled.\n- [`74212d40d`](https://www.github.com/tauri-apps/tauri/commit/74212d40d80dba4501b3d4ae30104fa3d447bdf9) ([#11653](https://www.github.com/tauri-apps/tauri/pull/11653) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Include Linux destkop environment and session type in `tauri info` command.\n\n### Enhancements\n\n- [`93a3a043d`](https://www.github.com/tauri-apps/tauri/commit/93a3a043d39cc96515d51d98beeb14261d3a246b) ([#11727](https://www.github.com/tauri-apps/tauri/pull/11727) by [@Kiyozz](https://www.github.com/tauri-apps/tauri/../../Kiyozz)) Add support for `Portuguese` language for NSIS windows installer.\n\n### Bug Fixes\n\n- [`c8700656b`](https://www.github.com/tauri-apps/tauri/commit/c8700656be3001a0cc6e087f23aebd482430a85b) ([#11985](https://www.github.com/tauri-apps/tauri/pull/11985) by [@ShaunSHamilton](https://www.github.com/tauri-apps/tauri/../../ShaunSHamilton)) Fix `tauri remove` from removing object type (`{}`) permissions.\n- [`0ae06c5ca`](https://www.github.com/tauri-apps/tauri/commit/0ae06c5ca89cecd24154affdc69668f5e1e67d85) ([#11914](https://www.github.com/tauri-apps/tauri/pull/11914) by [@wtto00](https://www.github.com/tauri-apps/tauri/../../wtto00)) Fix the exclude path in file `Cargo.toml` of plugin template generated by cli. Path changed in [#9346](https://github.com/tauri-apps/tauri/pull/9346)\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.2.0`\n\n## \\[2.1.0]\n\n### New Features\n\n- [`6bf917941`](https://www.github.com/tauri-apps/tauri/commit/6bf917941ff0fcc49e86b3ba427340b75f3ce49c) ([#11322](https://www.github.com/tauri-apps/tauri/pull/11322) by [@ShaunSHamilton](https://www.github.com/tauri-apps/tauri/../../ShaunSHamilton)) Add `tauri remove` to remove plugins from projects.\n- [`058c0db72`](https://www.github.com/tauri-apps/tauri/commit/058c0db72f43fbe1574d0db654560e693755cd7e) ([#11584](https://www.github.com/tauri-apps/tauri/pull/11584) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > rpm > compression` config option to control RPM bundle compression type and level.\n\n### Enhancements\n\n- [`1f311832a`](https://www.github.com/tauri-apps/tauri/commit/1f311832ab5b2d62a533dfcf9b1d78bddf249ae8) ([#11405](https://www.github.com/tauri-apps/tauri/pull/11405) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add more context for errors when decoding secret and public keys for signing updater artifacts.\n- [`e0d1307d3`](https://www.github.com/tauri-apps/tauri/commit/e0d1307d3f78987d0059921a5ab01ea4b26e0ef1) ([#11414](https://www.github.com/tauri-apps/tauri/pull/11414) by [@Czxck001](https://www.github.com/tauri-apps/tauri/../../Czxck001)) Migrate the `$schema` Tauri configuration to the v2 format.\n- [`c43d5df15`](https://www.github.com/tauri-apps/tauri/commit/c43d5df15828ecffa606482ea2b60350c488c981) ([#11512](https://www.github.com/tauri-apps/tauri/pull/11512) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Associate a newly created capability file with the `main` window on the `tauri add` and `tauri permission add` commands.\n\n### Bug Fixes\n\n- [`7af01ff2c`](https://www.github.com/tauri-apps/tauri/commit/7af01ff2ce623d727cd13a4c8a549c1c80031882) ([#11523](https://www.github.com/tauri-apps/tauri/pull/11523) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `tauri migrate` failing to install NPM depenencies when running from Deno.\n- [`100a4455a`](https://www.github.com/tauri-apps/tauri/commit/100a4455aa48df508510bbc08273215bdf70c012) ([#11529](https://www.github.com/tauri-apps/tauri/pull/11529) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix detecting yarn berry (v2 and higher) in various tauri cli commands.\n- [`60e86d5f6`](https://www.github.com/tauri-apps/tauri/commit/60e86d5f6e0f0c769d34ef368cd8801a918d796d) ([#11624](https://www.github.com/tauri-apps/tauri/pull/11624) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Use the public network IP address on `android dev` by default on Windows.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.1.0`\n\n## \\[2.0.4]\n\n### Enhancements\n\n- [`e4c9268b1`](https://www.github.com/tauri-apps/tauri/commit/e4c9268b19c614dc9ebb0895448fd16de7efee80) ([#11258](https://www.github.com/tauri-apps/tauri/pull/11258) by [@regexident](https://www.github.com/tauri-apps/tauri/../../regexident)) Support custom project directory structure where the Tauri app folder is not a subfolder of the frontend project.\n  The frontend and Tauri app project paths can be set with the `TAURI_FRONTEND_PATH` and the `TAURI_APP_PATH` environment variables respectively.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.4`\n\n## \\[2.0.3]\n\n### New Features\n\n- [`eda5713ea`](https://www.github.com/tauri-apps/tauri/commit/eda5713eab78d28182071ea25ceca5f1994f37ea) ([#11242](https://www.github.com/tauri-apps/tauri/pull/11242) by [@alex-sandri](https://www.github.com/tauri-apps/tauri/../../alex-sandri)) Add `Italian` to supported NSIS installer languages\n- [`b3563e3d6`](https://www.github.com/tauri-apps/tauri/commit/b3563e3d6ae8dc90ee68f25f575cd5538ab1915b) ([#11304](https://www.github.com/tauri-apps/tauri/pull/11304) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add Deno support in tauri-cli operations.\n\n### Bug Fixes\n\n- [`d609bef9f`](https://www.github.com/tauri-apps/tauri/commit/d609bef9fd7cd6eeb2bd701558100bd9cfb6e6f6) ([#11314](https://www.github.com/tauri-apps/tauri/pull/11314) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix android invalid proguard file when using an `identifier` that contains a component that is a reserved kotlin keyword, like `in`, `class`, etc\n- [`069c05e44`](https://www.github.com/tauri-apps/tauri/commit/069c05e44fd6f30083fdc00dd6c0001278898592) ([#11315](https://www.github.com/tauri-apps/tauri/pull/11315) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix CLI crashing and failing to find a `.ico` file when `bundle > icon` option is using globs and doesn't have a string that ends with `.ico`.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.3`\n\n## \\[2.0.2]\n\n### What's Changed\n\n- [`4475fbb50`](https://www.github.com/tauri-apps/tauri/commit/4475fbb502c5ffb3cea4de6bef1c7869be39bed6) ([#11208](https://www.github.com/tauri-apps/tauri/pull/11208) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Update cargo-mobile2 to 0.17.3, fixing lib name validation.\n- [`a49a19ffa`](https://www.github.com/tauri-apps/tauri/commit/a49a19ffa304f031fb1a04d31a567cc7f42a380a) ([#11218](https://www.github.com/tauri-apps/tauri/pull/11218)) Fix bundling `appimage`, `deb` and `rpm` bundles failing to open when using `mainBinaryName` with spaces.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.2`\n\n## \\[2.0.1]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.1`\n\n## \\[2.0.0]\n\n### What's Changed\n\n- [`637285790`](https://www.github.com/tauri-apps/tauri/commit/6372857905ae9c0aedb7f482ddf6cf9f9836c9f2) Promote to v2 stable!\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0`\n\n## \\[2.0.0-rc.18]\n\n### Enhancements\n\n- [`a08e6ffa6`](https://www.github.com/tauri-apps/tauri/commit/a08e6ffa6fe499553be3c4c620726d6031cd6dd3) ([#11185](https://www.github.com/tauri-apps/tauri/pull/11185) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Enhance port forwarding on `android dev` to be more resilient and tolerate delays when booting up devices.\n- [`6cfe7edf6`](https://www.github.com/tauri-apps/tauri/commit/6cfe7edf63636fdf66c429efdeb7bc9a0f404e9f) ([#11186](https://www.github.com/tauri-apps/tauri/pull/11186) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Retain logger verbosity on the `android-studio-script` and `xcode-script` commands.\n- [`60a5aea53`](https://www.github.com/tauri-apps/tauri/commit/60a5aea53db02ae6af325812ab97555f2c013d70) ([#11181](https://www.github.com/tauri-apps/tauri/pull/11181) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Set the `TRUNK_SERVE_ADDRESS` environment variable when running on iOS physical devices to support Trunk.\n\n### Bug Fixes\n\n- [`f5d61822b`](https://www.github.com/tauri-apps/tauri/commit/f5d61822bf5988827776dd58bed75c19364e86bd) ([#11184](https://www.github.com/tauri-apps/tauri/pull/11184) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS application not including the provided capabilities (entitlements).\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.18`\n\n## \\[2.0.0-rc.17]\n\n### New Features\n\n- [`a944b9b05`](https://www.github.com/tauri-apps/tauri/commit/a944b9b05bc5ae6125ff451e86c5b207c511f3d7) ([#11118](https://www.github.com/tauri-apps/tauri/pull/11118) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `--github-workflows` flag for `tauri plugin new/init`.\n- [`f57a729cd`](https://www.github.com/tauri-apps/tauri/commit/f57a729cd8f7e10d8daf0b9d5b85f9c7ad530496) ([#11039](https://www.github.com/tauri-apps/tauri/pull/11039) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `tauri inspect wix-upgrade-code` to print default Upgrade Code for your MSI installer derived from `productName`.\n\n### Bug Fixes\n\n- [`62b52f60a`](https://www.github.com/tauri-apps/tauri/commit/62b52f60a22ef84c4a2a2d9e662038b49f58e16c) ([#11064](https://www.github.com/tauri-apps/tauri/pull/11064) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `tauri add` failing to add NPM depenency with `npm` package manager.\n- [`56e087471`](https://www.github.com/tauri-apps/tauri/commit/56e087471a347f6bee7422221a956925c60b17e3) ([#11100](https://www.github.com/tauri-apps/tauri/pull/11100) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix iOS xcode-script usage with `bun`.\n- [`b88e22a5f`](https://www.github.com/tauri-apps/tauri/commit/b88e22a5fe4e2e4376d6cad64d1e74d104ca8927) ([#11063](https://www.github.com/tauri-apps/tauri/pull/11063) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) The cli now only sets the iOS deployment target environment variable when building for iOS.\n- [`8d22c0c81`](https://www.github.com/tauri-apps/tauri/commit/8d22c0c814e7227d5e56ce9a08929045ccea1a1b) ([#11101](https://www.github.com/tauri-apps/tauri/pull/11101) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Only modify the iOS Xcode project \"sign style\" if we need to enforce manual signing.\n- [`df24cb944`](https://www.github.com/tauri-apps/tauri/commit/df24cb944249ee398f6c8ba8c19757b398eec701) ([#11168](https://www.github.com/tauri-apps/tauri/pull/11168) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes Xcode pbxproj file parsing not expecting `_` in build configuration IDs.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.17`\n\n### Breaking Changes\n\n- [`a944b9b05`](https://www.github.com/tauri-apps/tauri/commit/a944b9b05bc5ae6125ff451e86c5b207c511f3d7) ([#11118](https://www.github.com/tauri-apps/tauri/pull/11118) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) `tauri plugin init/new` will no longer generate a `.github` directory with workflows by default, instead use the new `--github-workflows` flag.\n\n## \\[2.0.0-rc.16]\n\n### New Features\n\n- [`9bb8fc618`](https://www.github.com/tauri-apps/tauri/commit/9bb8fc6189a93bcb811588b36e710d0f7818a1f9) ([#11030](https://www.github.com/tauri-apps/tauri/pull/11030) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `--no-example` flag for `tauri plugin new` and `tauri plugin init` to disable creation of an example project.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.16`\n\n## \\[2.0.0-rc.15]\n\n### Enhancements\n\n- [`5a0e922d4`](https://www.github.com/tauri-apps/tauri/commit/5a0e922d40dc3b7d9a8e3a65ccaf76d09f026cb8) ([#11007](https://www.github.com/tauri-apps/tauri/pull/11007) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Automatically discover the `src-tauri/src/main.rs` binary when it is not explicitly defined in the Cargo manifest bin array.\n\n### Bug Fixes\n\n- [`94e9d476e`](https://www.github.com/tauri-apps/tauri/commit/94e9d476ef506b1b8c09f55b81620c7839f98086) ([#11011](https://www.github.com/tauri-apps/tauri/pull/11011) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `main_binary_name` in custom wix and nsis templates including `.exe`\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.15`\n\n## \\[2.0.0-rc.14]\n\n### Enhancements\n\n- [`6c5340f8b`](https://www.github.com/tauri-apps/tauri/commit/6c5340f8b2549dfe89f19656304e65cd670afc92) ([#11004](https://www.github.com/tauri-apps/tauri/pull/11004) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added the `log` plugin to the app template, which is required to visualize logs on Android and iOS.\n- [`3ad2427dc`](https://www.github.com/tauri-apps/tauri/commit/3ad2427dc08f12c61edc726b587acce32eca1080) ([#10961](https://www.github.com/tauri-apps/tauri/pull/10961) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Only render app logs on iOS unless `-vv` is provided to the `ios dev` command.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.13`\n\n## \\[2.0.0-rc.13]\n\n### Bug Fixes\n\n- [`a5848af65`](https://www.github.com/tauri-apps/tauri/commit/a5848af65b10d89686314cf737b7fd9d91f99dd8) ([#10944](https://www.github.com/tauri-apps/tauri/pull/10944) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Synchronize app version (`tauri.conf.json > version` or `Cargo.toml > package > version`) with the `CFBundleVersion` and `CFBundleShortVersionString` Info.plist values.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.12`\n\n## \\[2.0.0-rc.12]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.11`\n\n## \\[2.0.0-rc.11]\n\n### Enhancements\n\n- [`9c9644d15`](https://www.github.com/tauri-apps/tauri/commit/9c9644d155818d9efcad65b60aa985a59e767922) ([#10845](https://www.github.com/tauri-apps/tauri/pull/10845) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Enhance iOS library validation, checking libs built with link time optimization.\n\n### Bug Fixes\n\n- [`b42683592`](https://www.github.com/tauri-apps/tauri/commit/b42683592d446f25c2005b59e9e3ec551175906d) ([#10847](https://www.github.com/tauri-apps/tauri/pull/10847) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes `ios build --target [aarch64-sim | x86_64]` failing to generate the app bundle for the iOS simulator.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.10`\n\n## \\[2.0.0-rc.10]\n\n### Bug Fixes\n\n- [`6faa03276`](https://www.github.com/tauri-apps/tauri/commit/6faa032766b23cd161503905d4c79365ff6c50d1) ([#10854](https://www.github.com/tauri-apps/tauri/pull/10854) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes iOS code signing failing on CI due to a missing development certificate.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.9`\n\n## \\[2.0.0-rc.9]\n\n### Bug Fixes\n\n- [`5af1f5dec`](https://www.github.com/tauri-apps/tauri/commit/5af1f5dec1bb98f335169df8c5e30c19a24cae07) ([#10851](https://www.github.com/tauri-apps/tauri/pull/10851) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes `ios build` failing to build iOS app in CI when using an API key for automatic signing.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.9`\n\n## \\[2.0.0-rc.8]\n\n### New Features\n\n- [`91e9e784a`](https://www.github.com/tauri-apps/tauri/commit/91e9e784aa59634e3fe6359f8b78d071d76a9e42) ([#10729](https://www.github.com/tauri-apps/tauri/pull/10729) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add plugins information in `tauri info` output\n- [`09e9dc1aa`](https://www.github.com/tauri-apps/tauri/commit/09e9dc1aab1b66aa6a3a009d5873db586abe76a0) ([#10752](https://www.github.com/tauri-apps/tauri/pull/10752) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow Xcode to manage iOS code sign and provisioning profiles by default.\n  On CI, the `APPLE_API_KEY`, `APPLE_API_ISSUER` and `APPLE_API_KEY_PATH` environment variables must be provided for authentication.\n\n### Enhancements\n\n- [`3a4972b39`](https://www.github.com/tauri-apps/tauri/commit/3a4972b394c65c32eefebfb2181ba56b0cfc08f7) ([#10793](https://www.github.com/tauri-apps/tauri/pull/10793) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Include architecture in the `tauri info` output.\n- [`fd68b7fde`](https://www.github.com/tauri-apps/tauri/commit/fd68b7fdea3890d9f0a373a252a3682bd9d04138) ([#10785](https://www.github.com/tauri-apps/tauri/pull/10785) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Remove the `.cargo/config` file creation that used to fix mobile build caches.\n- [`f67a9eb6d`](https://www.github.com/tauri-apps/tauri/commit/f67a9eb6de4567c2374b8cdbabadcf0ca44d28fb) ([#10802](https://www.github.com/tauri-apps/tauri/pull/10802) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Synchronize identifier, development team and lib name with the iOS Xcode project.\n\n### Bug Fixes\n\n- [`83ed090bf`](https://www.github.com/tauri-apps/tauri/commit/83ed090bfa58a1784495f474d93b16a568be513f) ([#10790](https://www.github.com/tauri-apps/tauri/pull/10790) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Do not quit `ios dev` and `android dev` process when we fail to attach the logger.\n- [`2d31aef75`](https://www.github.com/tauri-apps/tauri/commit/2d31aef759f496f3afe46b7697176e61a8570511) ([#10751](https://www.github.com/tauri-apps/tauri/pull/10751) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Ensure gradlew is executable and does not use CRLF so it can be used on UNIX systems.\n- [`02b2f964a`](https://www.github.com/tauri-apps/tauri/commit/02b2f964a70c61ff08b5052bd9fcde472d706d9c) ([#10795](https://www.github.com/tauri-apps/tauri/pull/10795) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix the `add` command NPM version specifier for known plugins from `2.0.0-rc` (unknown version requirement) to `^2.0.0-rc`.\n- [`84070bae9`](https://www.github.com/tauri-apps/tauri/commit/84070bae92d234bc3630e795cfaf79f869f3a751) ([#10792](https://www.github.com/tauri-apps/tauri/pull/10792) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix `tauri plugin ios init` not generating the iOS folder.\n- [`edb2ca31f`](https://www.github.com/tauri-apps/tauri/commit/edb2ca31f70a39004b6a09ae53425f22e243318e) ([#10794](https://www.github.com/tauri-apps/tauri/pull/10794) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Migrate v1 plugins NPM packages.\n- [`9718dc9e8`](https://www.github.com/tauri-apps/tauri/commit/9718dc9e8c9bc91d9a5d9e0e06a7afab62492152) ([#10791](https://www.github.com/tauri-apps/tauri/pull/10791) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Reintroduce the `targetSdk` value in the Android application template.\n\n### What's Changed\n\n- [`fb6bf3142`](https://www.github.com/tauri-apps/tauri/commit/fb6bf314252c88dd49af74bdbb8499df370836ae) ([#10763](https://www.github.com/tauri-apps/tauri/pull/10763) by [@rdlabo](https://www.github.com/tauri-apps/tauri/../../rdlabo)) Update plugin template Android code to match documentation on Android package ID usage.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.8`\n\n### Breaking Changes\n\n- [`073bb4f45`](https://www.github.com/tauri-apps/tauri/commit/073bb4f459a923541b94970dfa7e087bccaa2cfd) ([#10772](https://www.github.com/tauri-apps/tauri/pull/10772) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Removed the deprecated `webview_fixed_runtime_path` config option, use the `webview_install_mode` instead.\n\n## \\[2.0.0-rc.7]\n\n### Enhancements\n\n- [`da8c9a7d3`](https://www.github.com/tauri-apps/tauri/commit/da8c9a7d3069398c26826aeb082caa44b7c92809) ([#10669](https://www.github.com/tauri-apps/tauri/pull/10669) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Modify both ExportOptions.plist and project.pbxproj to reflect changes for the `IOS_CERTIFICATE`, `IOS_CERTIFICATE_PASSWORD` and `IOS_MOBILE_PROVISION` environment variables.\n\n### Bug Fixes\n\n- [`793ee0531`](https://www.github.com/tauri-apps/tauri/commit/793ee0531730597e6008c9c0dedabbab7a2bef53) ([#10700](https://www.github.com/tauri-apps/tauri/pull/10700) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Allow hyphens and underscores on app identifiers.\n- [`da8c9a7d3`](https://www.github.com/tauri-apps/tauri/commit/da8c9a7d3069398c26826aeb082caa44b7c92809) ([#10669](https://www.github.com/tauri-apps/tauri/pull/10669) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Synchronize Xcode project changes with the ExportOptions.plist file so `ios build` calls can work with code signing changes made in Xcode.\n\n### What's Changed\n\n- [`f4d5241b3`](https://www.github.com/tauri-apps/tauri/commit/f4d5241b377d0f7a1b58100ee19f7843384634ac) ([#10731](https://www.github.com/tauri-apps/tauri/pull/10731) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Update documentation icon path.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.7`\n\n### Breaking Changes\n\n- [`da8c9a7d3`](https://www.github.com/tauri-apps/tauri/commit/da8c9a7d3069398c26826aeb082caa44b7c92809) ([#10669](https://www.github.com/tauri-apps/tauri/pull/10669) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) The `IOS_CERTIFICATE`, `IOS_CERTIFICATE_PASSWORD` and `IOS_MOBILE_PROVISION` environment variables are now read by the `ios build` command instead of `ios init`.\n\n## \\[2.0.0-rc.6]\n\n### New Features\n\n- [`da381e07f`](https://www.github.com/tauri-apps/tauri/commit/da381e07f3770988fe6d0859a02331b87cc6723f) ([#10696](https://www.github.com/tauri-apps/tauri/pull/10696) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Inject configured resources on mobile apps.\n\n### Bug Fixes\n\n- [`1a60822a4`](https://www.github.com/tauri-apps/tauri/commit/1a60822a4220b6dbb1ad7295a2e37d6c3004edad) ([#10699](https://www.github.com/tauri-apps/tauri/pull/10699) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Changed the `add` command to use a version requirement that matches the CLI's stable and prerelease numbers.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.6`\n\n## \\[2.0.0-rc.5]\n\n### New Features\n\n- [`8d148a9e2`](https://www.github.com/tauri-apps/tauri/commit/8d148a9e2566edebfea2d75f32df7c9396d765a4) ([#10634](https://www.github.com/tauri-apps/tauri/pull/10634) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) Custom sign command with object notation for whitespaces in the command path and arguments.\n\n### Bug Fixes\n\n- [`8ae52a615`](https://www.github.com/tauri-apps/tauri/commit/8ae52a615a11d934930001da63ce6ac8442c7efc) ([#10676](https://www.github.com/tauri-apps/tauri/pull/10676) by [@rdlabo](https://www.github.com/tauri-apps/tauri/../../rdlabo)) Change plugin template call to `register_ios_plugin` params to snake case\n- [`7796a8fc6`](https://www.github.com/tauri-apps/tauri/commit/7796a8fc649cd7397a67048c71f8d1fbf822122a) ([#10687](https://www.github.com/tauri-apps/tauri/pull/10687) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fix Swift plugin compilation on older versions.\n- [`9b99ebab1`](https://www.github.com/tauri-apps/tauri/commit/9b99ebab17d6a043d82a7aeecfb76c56a995c287) ([#10431](https://www.github.com/tauri-apps/tauri/pull/10431) by [@mrguiman](https://www.github.com/tauri-apps/tauri/../../mrguiman)) Do not include the target arch when building and archiving the iOS application,\n  which makes Xcode project modifications more flexible.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.5`\n\n## \\[2.0.0-rc.4]\n\n### New Features\n\n- [`78e22bedc`](https://www.github.com/tauri-apps/tauri/commit/78e22bedcab5096f1a4e667321fc8b2817b79214) ([#10602](https://www.github.com/tauri-apps/tauri/pull/10602) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add necessary options to `AndroidManifest.xml` in android template to support AndroidTV.\n- [`3bec7b159`](https://www.github.com/tauri-apps/tauri/commit/3bec7b1595e28630a22b9fb16540beafd5eb7969) ([#10544](https://www.github.com/tauri-apps/tauri/pull/10544) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) v1 migrate script now migrates Svelte and Vue.js code.\n\n### Enhancements\n\n- [`bba1a4419`](https://www.github.com/tauri-apps/tauri/commit/bba1a441917defcdf9e88221e9b0e1cdd744e77a) ([#10457](https://www.github.com/tauri-apps/tauri/pull/10457) by [@mmvanheusden](https://www.github.com/tauri-apps/tauri/../../mmvanheusden)) Added `--no-fmt` option to the `add` command to skip formatting the code after applying changes.\n- [`71d00646a`](https://www.github.com/tauri-apps/tauri/commit/71d00646a9b7c52311ba087820e52fd19861b3d8) ([#10504](https://www.github.com/tauri-apps/tauri/pull/10504) by [@fu050409](https://www.github.com/tauri-apps/tauri/../../fu050409)) Improve the `init` command behavior by detecting the project NPM package manager.\n- [`8deb1966a`](https://www.github.com/tauri-apps/tauri/commit/8deb1966ace93d1350f271d525a878ba4b0879ce) ([#10652](https://www.github.com/tauri-apps/tauri/pull/10652) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Infer macOS codesign identity from the `APPLE_CERTIFICATE` environment variable when provided, meaning the identity no longer needs to be provided when signing on CI using that option. If the imported certificate name does not match a provided signingIdentity configuration, an error is returned.\n- [`f35bcda28`](https://www.github.com/tauri-apps/tauri/commit/f35bcda2895a1350df31853da76a051783b9fd3f) ([#10598](https://www.github.com/tauri-apps/tauri/pull/10598) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) `permission add` and `add` commands now check if the plugin is known and if it is either desktop or mobile only\n  we add the permission to a target-specific capability.\n\n### Bug Fixes\n\n- [`f712f31d1`](https://www.github.com/tauri-apps/tauri/commit/f712f31d1d21e85fab99194530702c70e45c63fc) ([#10639](https://www.github.com/tauri-apps/tauri/pull/10639) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Include notarization error output in the error message if it fails.\n- [`9f75d0622`](https://www.github.com/tauri-apps/tauri/commit/9f75d06228fcb7036cf7a4e215abc7bc8d1a0a56) ([#10604](https://www.github.com/tauri-apps/tauri/pull/10604) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes `android dev` port forward failing under some conditions, add better logging and error handling.\n- [`2d47352a0`](https://www.github.com/tauri-apps/tauri/commit/2d47352a07a7d742e62291a5e6810aed79fc8b50) ([#10418](https://www.github.com/tauri-apps/tauri/pull/10418) by [@samkearney](https://www.github.com/tauri-apps/tauri/../../samkearney)) CLI commands will now consistently search for the `app_dir` (the directory containing `package.json`) from the current working directory of the command invocation.\n- [`f4cd68f04`](https://www.github.com/tauri-apps/tauri/commit/f4cd68f040635f019ff989667289cfe9061c7dfb) ([#10600](https://www.github.com/tauri-apps/tauri/pull/10600) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes `android dev` not working when using the builtin dev server.\n- [`41c7a6646`](https://www.github.com/tauri-apps/tauri/commit/41c7a6646ba9afbb2322986fb39054a43a88e604) ([#10572](https://www.github.com/tauri-apps/tauri/pull/10572) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Exit with code 1 if a panic occurs when running the CLI with `bun`.\n- [`9089d9763`](https://www.github.com/tauri-apps/tauri/commit/9089d97637e49bebbe7dba8adc6351e04b53a44d) ([#10605](https://www.github.com/tauri-apps/tauri/pull/10605) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes `[android|ios] build --config <config>` failing to resolve.\n- [`712f1049f`](https://www.github.com/tauri-apps/tauri/commit/712f1049fae74bfda5f360adcee7210cea92fe63) ([#10569](https://www.github.com/tauri-apps/tauri/pull/10569) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes running `ios dev` and `ios build` using `bun`.\n- [`3998570fd`](https://www.github.com/tauri-apps/tauri/commit/3998570fd3d03c1bb282bd060a4aafb4ab5437f9) ([#10540](https://www.github.com/tauri-apps/tauri/pull/10540) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes v1 migration of Cargo.toml dependencies and features.\n- [`3beba92b5`](https://www.github.com/tauri-apps/tauri/commit/3beba92b5bdc62ed00c9f6a9b8f8c05cfa78f8dc) ([#10542](https://www.github.com/tauri-apps/tauri/pull/10542) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes v1 frontend code migration when using plugin default imports.\n- [`10fb027b7`](https://www.github.com/tauri-apps/tauri/commit/10fb027b7590cf2c020b5c220328b9051c05adca) ([#10656](https://www.github.com/tauri-apps/tauri/pull/10656) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Migrate v1 plugins to their v2 releases.\n- [`10fb027b7`](https://www.github.com/tauri-apps/tauri/commit/10fb027b7590cf2c020b5c220328b9051c05adca) ([#10656](https://www.github.com/tauri-apps/tauri/pull/10656) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Prevent duplicate permissions on v1 migration.\n- [`b160f9359`](https://www.github.com/tauri-apps/tauri/commit/b160f9359d6f661d280185d2a2a4bdf280b8e72c) ([#10638](https://www.github.com/tauri-apps/tauri/pull/10638) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Only validate the output iOS library on debug builds.\n- [`4bfe4880f`](https://www.github.com/tauri-apps/tauri/commit/4bfe4880fbef42d1a115f840e712d4a2f59c8ab3) ([#10550](https://www.github.com/tauri-apps/tauri/pull/10550) by [@anatawa12](https://www.github.com/tauri-apps/tauri/../../anatawa12)) fails to build universal fat binary if main bin is renamed to another name in `Cargo.toml`\n- [`f3837d5b9`](https://www.github.com/tauri-apps/tauri/commit/f3837d5b98f0caebc3337f9a9e8127e7b96c3fc5) ([#10539](https://www.github.com/tauri-apps/tauri/pull/10539) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Improve migration tooling by supporting TOML configs, handle nulls and properly check for updater migration.\n\n### What's Changed\n\n- [`794cf8234`](https://www.github.com/tauri-apps/tauri/commit/794cf8234f8b620c74cbd23cc4b81be9b2edc386) ([#10571](https://www.github.com/tauri-apps/tauri/pull/10571) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Change iOS template default export method from deprecated `development` to `debugging`.\n- [`bfc49cc7a`](https://www.github.com/tauri-apps/tauri/commit/bfc49cc7a1d43e3378e93865b9b37ce4bddfa6e6) ([#10558](https://www.github.com/tauri-apps/tauri/pull/10558) by [@ahqsoftwares](https://www.github.com/tauri-apps/tauri/../../ahqsoftwares)) Remove targetSdk from gradle files\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.4`\n\n## \\[2.0.0-rc.3]\n\n### Enhancements\n\n- [`5f56cb0a8`](https://www.github.com/tauri-apps/tauri/commit/5f56cb0a8b9c6f695bc6439a8db997c98b3a3997) ([#10507](https://www.github.com/tauri-apps/tauri/pull/10507) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Update gradle to 8.9 and the gradle android plugin to 8.5.1 in the android templates (requires latest Android Studio). This should add support for Java 21 but Java 17 keeps being the recommended version.\n\n### Bug Fixes\n\n- [`f5dfc0280`](https://www.github.com/tauri-apps/tauri/commit/f5dfc02800dbd3bdee671b032454c49ac7102fb4) ([#10533](https://www.github.com/tauri-apps/tauri/pull/10533) by [@FabianLars](https://www.github.com/tauri-apps/tauri/../../FabianLars)) Fixed an issue causing `tauri ios init` to fail if `iOS.minimumSystemVersion` was not configured explicitly.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.3`\n\n## \\[2.0.0-rc.2]\n\n### New Features\n\n- [`8dc81b6cc`](https://www.github.com/tauri-apps/tauri/commit/8dc81b6cc2b8235b11f74a971d6aa3a5df5e9f68) ([#10496](https://www.github.com/tauri-apps/tauri/pull/10496) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `bundle > ios > template` configuration option for custom Xcode project YML Handlebars template using XcodeGen.\n- [`02c00abc6`](https://www.github.com/tauri-apps/tauri/commit/02c00abc63cf86e9bf9179cbb143d5145a9397b6) ([#10495](https://www.github.com/tauri-apps/tauri/pull/10495) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `bundle > ios > minimumSystemVersion` configuration option.\n\n### Enhancements\n\n- [`8e1e15304`](https://www.github.com/tauri-apps/tauri/commit/8e1e15304e9dc98d7f875fc8dceb7d4ce19adc47) ([#10483](https://www.github.com/tauri-apps/tauri/pull/10483) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Check if the Rust library contains the symbols required at runtime for Android and iOS apps.\n- [`ca6868956`](https://www.github.com/tauri-apps/tauri/commit/ca68689564cbc8dfa9a5220d3daf81a44ef81fcc) ([#10479](https://www.github.com/tauri-apps/tauri/pull/10479) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Check if identifier or lib name changed when running mobile commands.\n\n### Bug Fixes\n\n- [`2e8ab7bac`](https://www.github.com/tauri-apps/tauri/commit/2e8ab7bac12046d734fb07a1b4fe5e03004b305e) ([#10481](https://www.github.com/tauri-apps/tauri/pull/10481) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Migration from v1 to v2 now adds the updater plugin when it is active.\n\n### What's Changed\n\n- [`a3cd9779a`](https://www.github.com/tauri-apps/tauri/commit/a3cd9779a47428e306a628d658740669faf69ccd) ([#10480](https://www.github.com/tauri-apps/tauri/pull/10480) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Removed the `[android|ios] open` command. It is recommended to use `[android|ios] dev --open` or `[android|ios] build --open` instead.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.2`\n\n## \\[2.0.0-rc.1]\n\n### Bug Fixes\n\n- [`fb1933f17`](https://www.github.com/tauri-apps/tauri/commit/fb1933f17442674e53374578e57a8cad241ac3c6) ([#10467](https://www.github.com/tauri-apps/tauri/pull/10467) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes running `android dev --open`.\n- [`206914fe8`](https://www.github.com/tauri-apps/tauri/commit/206914fe8d97eb61a2ff2a80e94e65e7a42bcea5) ([#10466](https://www.github.com/tauri-apps/tauri/pull/10466) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Fixes running `adb reverse` in Node.js context.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.1`\n\n## \\[2.0.0-rc.0]\n\n### New Features\n\n- [`d5511c311`](https://www.github.com/tauri-apps/tauri/commit/d5511c3117b1a117cb0b7359c5fa09aa4795122b) ([#10395](https://www.github.com/tauri-apps/tauri/pull/10395)) Added migration from `2.0.0-beta` to `2.0.0-rc`.\n- [`a5bfbaa62`](https://www.github.com/tauri-apps/tauri/commit/a5bfbaa62b8cd0aacbb33f730d4e30b43c461fe1)([#9962](https://www.github.com/tauri-apps/tauri/pull/9962)) Added `bundle > iOS > frameworks` configuration to define a list of frameworks that are linked to the Xcode project when it is generated.\n\n### Enhancements\n\n- [`a0841d509`](https://www.github.com/tauri-apps/tauri/commit/a0841d509abc43b62bb7c755e8727f3f461862d1) ([#10421](https://www.github.com/tauri-apps/tauri/pull/10421)) Changes the default behavior of the `dev` command to only expose to localhost (`127.0.0.1`) instead of the default system interface.\n\n### Security fixes\n\n- [`289ae5555`](https://www.github.com/tauri-apps/tauri/commit/289ae5555da3802741018015bfe4927729a2eb33) ([#10386](https://www.github.com/tauri-apps/tauri/pull/10386)) Re-enable TLS checks that were previously disabled to support an insecure HTTPS custom protocol on Android which is no longer used.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-rc.0`\n\n### Breaking Changes\n\n- [`758d28c8a`](https://www.github.com/tauri-apps/tauri/commit/758d28c8a2d5c9567158e339326b765f72da983e) ([#10390](https://www.github.com/tauri-apps/tauri/pull/10390)) Core plugin permissions are now prefixed with `core:`, the `core:default` permission set can now be used and the `core` plugin name is reserved.\n  The `tauri migrate` tool will automate the migration process, which involves prefixing all `app`, `event`, `image`, `menu`, `path`, `resources`, `tray`, `webview` and `window` permissions with `core:`.\n- [`7ba67b4ac`](https://www.github.com/tauri-apps/tauri/commit/7ba67b4aca8d3f3b1aa5ad08819605029d36e6b4)([#10437](https://www.github.com/tauri-apps/tauri/pull/10437)) `ios dev` and `android dev` now uses localhost for the development server unless running on an iOS device,\n  which still requires connecting to the public network address. To conditionally check this on your frontend\n  framework's configuration you can check for the existence of the `TAURI_DEV_HOST`\n  environment variable instead of checking if the target is iOS or Android (previous recommendation).\n\n## \\[2.0.0-beta.23]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.23`\n\n## \\[2.0.0-beta.22]\n\n### New Features\n\n- [`7c7fa0964`](https://www.github.com/tauri-apps/tauri/commit/7c7fa0964db3403037fdb9a34de2b877ddb8df1c) ([#9963](https://www.github.com/tauri-apps/tauri/pull/9963) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `--method` argument for `ios build` to select the export options' method.\n- [`7c7fa0964`](https://www.github.com/tauri-apps/tauri/commit/7c7fa0964db3403037fdb9a34de2b877ddb8df1c) ([#9963](https://www.github.com/tauri-apps/tauri/pull/9963) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Setup iOS signing by reading `IOS_CERTIFICATE`, `IOS_CERTIFICATE_PASSWORD` and `IOS_MOBILE_PROVISION` environment variables.\n\n### Enhancements\n\n- [`c01e87ad4`](https://www.github.com/tauri-apps/tauri/commit/c01e87ad46e2a5b3fb8d018739e724ef932008d7) ([#10198](https://www.github.com/tauri-apps/tauri/pull/10198) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Enhance `tauri migrate` to also migrate variables like `appWindow`:\n\n  ```ts\n  import { appWindow } from '@tauri-apps/api/window'\n  ```\n\n  will become:\n\n  ```ts\n  import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'\n  const appWindow = getCurrentWebviewWindow()\n  ```\n\n### Bug Fixes\n\n- [`94136578b`](https://www.github.com/tauri-apps/tauri/commit/94136578bc89e4b973c471050ae9c2d83ffcb7c6) ([#10186](https://www.github.com/tauri-apps/tauri/pull/10186) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `migrate` command, migrating incorrect permissions for `clipboard`.\n- [`c01e87ad4`](https://www.github.com/tauri-apps/tauri/commit/c01e87ad46e2a5b3fb8d018739e724ef932008d7) ([#10198](https://www.github.com/tauri-apps/tauri/pull/10198) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `tauri migrate` incorrectly migrating `@tauri-apps/api/tauri` module to just `core` and `@tauri-apps/api/window` to just `webviewWindow`.\n- [`15e125996`](https://www.github.com/tauri-apps/tauri/commit/15e12599667b749c3d7cd2259e6cf7c7b5c6e2be) ([#10234](https://www.github.com/tauri-apps/tauri/pull/10234) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix cli failing to detect the correct cargo target directory when using cargo `--target-dir` flag with `tauri build` or `tauri dev`\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.22`\n\n## \\[2.0.0-beta.21]\n\n### New Features\n\n- [`656a64974`](https://www.github.com/tauri-apps/tauri/commit/656a64974468bc207bf39537e02ae179bdee9b83) ([#9318](https://www.github.com/tauri-apps/tauri/pull/9318)) Added a configuration option to disable hardened runtime on macOS codesign.\n\n### Enhancements\n\n- [`f44a2ec47`](https://www.github.com/tauri-apps/tauri/commit/f44a2ec47c13243d472fa08a9df8b20d8490d79f) ([#10030](https://www.github.com/tauri-apps/tauri/pull/10030)) Enhance the plugin template to include `permissions/default.toml` and default capabilities file for the example application.\n\n### Bug Fixes\n\n- [`019a74e97`](https://www.github.com/tauri-apps/tauri/commit/019a74e970958d29cf69a6f24669d603399dcbb3) ([#9931](https://www.github.com/tauri-apps/tauri/pull/9931)) Fix wrong migration of `clipboard` and `globalShortcut` modules\n- [`27838365a`](https://www.github.com/tauri-apps/tauri/commit/27838365a6841b0d3fa645ba2528221d23d4aeb2) ([#10135](https://www.github.com/tauri-apps/tauri/pull/10135)) Fix parsing of cargo profile when using `--profile=<profile>` syntax.\n- [`79542f4d4`](https://www.github.com/tauri-apps/tauri/commit/79542f4d4542bd97451da7605de16e8464d6a06c) ([#10039](https://www.github.com/tauri-apps/tauri/pull/10039)) Fixed an issue that prevented `tauri icon` from rendering `<text>` nodes in SVG files.\n- [`40c0f44e1`](https://www.github.com/tauri-apps/tauri/commit/40c0f44e1c74c18ed0d6c645724d650637725456) ([#9971](https://www.github.com/tauri-apps/tauri/pull/9971)) Changed the deployment target of plugin iOS Xcode project to 13.0 so it works on older iOS releases.\n- [`f56cdc9e3`](https://www.github.com/tauri-apps/tauri/commit/f56cdc9e391c4d55e4d7e935203d0f891864f22d) ([#10016](https://www.github.com/tauri-apps/tauri/pull/10016)) Add missing dependency `libayatana-appindicator3.so.1` for rpm package.\n- [`1601da5b5`](https://www.github.com/tauri-apps/tauri/commit/1601da5b525de05cb813002d611f22ea4217a4fb) ([#10114](https://www.github.com/tauri-apps/tauri/pull/10114)) Removed alpha channel from default icons in iOS template to comply with Apple's human interface guideline\n  (https://developer.apple.com/design/human-interface-guidelines/app-icons), because\n  transparent icons with alpha channel are not allowed, and will be rejected\n  upon upload to Apple appstore.\n\n### What's Changed\n\n- [`3cca5c2be`](https://www.github.com/tauri-apps/tauri/commit/3cca5c2be88bbd52139e7dda371e88510d28bc8e) ([#9924](https://www.github.com/tauri-apps/tauri/pull/9924)) Migrate to new Android buildFeatures.buildConfig format.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.21`\n- [`f955f7b49`](https://www.github.com/tauri-apps/tauri/commit/f955f7b4903bcea376c0a8b430736f66c8cebf56) ([#9929](https://www.github.com/tauri-apps/tauri/pull/9929)) Switch from `dirs_next` to `dirs` as `dirs_next` is now unmaintained while `dirs` is\n\n### Breaking Changes\n\n- [`911242f09`](https://www.github.com/tauri-apps/tauri/commit/911242f0928e0a2add3595fa9de27850fb875fa6) ([#9883](https://www.github.com/tauri-apps/tauri/pull/9883)) Move updater target from `bundle > targets` to a separate field `bundle > createUpdaterArtifacts`\n\n## \\[2.0.0-beta.20]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.20`\n\n## \\[2.0.0-beta.19]\n\n### New Features\n\n- [`8a1ae2dea`](https://www.github.com/tauri-apps/tauri/commit/8a1ae2deaf3086e531ada25b1627f900e2e421fb)([#9843](https://www.github.com/tauri-apps/tauri/pull/9843)) Added an option to use a Xcode project for the iOS plugin instead of a plain SwiftPM project.\n- [`9e4b2253f`](https://www.github.com/tauri-apps/tauri/commit/9e4b2253f6ddaccd0f5c88734287bd5c84d4936a)([#9734](https://www.github.com/tauri-apps/tauri/pull/9734)) Add `tauri bundle` subcommand which runs the bundle phase only, best paired with `tauri build --no-bundle`\n\n### Enhancements\n\n- [`8b032c3cf`](https://www.github.com/tauri-apps/tauri/commit/8b032c3cf638e64e50df9d9cf8bc789c7e285987)([#9896](https://www.github.com/tauri-apps/tauri/pull/9896)) Add a blank LaunchScreen.storyboard to the iOS project init template to pass the App Store validation.\n- [`9970d88be`](https://www.github.com/tauri-apps/tauri/commit/9970d88becee1560a4b2a7ffc1fe65991a42a8c9)([#9892](https://www.github.com/tauri-apps/tauri/pull/9892)) Update to latest gradle.\n\n### What's Changed\n\n- [`80aa50498`](https://www.github.com/tauri-apps/tauri/commit/80aa504987dd9cfa59aa5848c4d7960e1d58d0e6)([#9870](https://www.github.com/tauri-apps/tauri/pull/9870)) Updated Android target SDK to 34.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.19`\n\n### Breaking Changes\n\n- [`265c23886`](https://www.github.com/tauri-apps/tauri/commit/265c23886ee5efbcc6d7188ff5c84cb32fa82aea)([#9375](https://www.github.com/tauri-apps/tauri/pull/9375)) Avoid renaming main binary to product name and perserve the name generated by cargo.\n- [`1df5cdeb0`](https://www.github.com/tauri-apps/tauri/commit/1df5cdeb06f5464e0eec4055e21b7b7bc8739eed)([#9858](https://www.github.com/tauri-apps/tauri/pull/9858)) Use `tauri.conf.json > identifier` to set the `PackageName` in Android and `BundleId` in iOS.\n\n## \\[2.0.0-beta.18]\n\n### Bug Fixes\n\n- [`beda18bce`](https://www.github.com/tauri-apps/tauri/commit/beda18bce95fd6e10543b2d8f1eca5fb7ca0655b)([#9855](https://www.github.com/tauri-apps/tauri/pull/9855)) Fixed an issue that caused `tauri add` to fail for multiple rust-only and platform-specific plugins.\n- [`4a33bc6a6`](https://www.github.com/tauri-apps/tauri/commit/4a33bc6a62d2ed9371191c8a7f78ff3f33930455)([#9553](https://www.github.com/tauri-apps/tauri/pull/9553)) Fixes `pnpm` detection when initializing and running a mobile project.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.18`\n\n## \\[2.0.0-beta.17]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.17`\n\n## \\[2.0.0-beta.16]\n\n### Bug Fixes\n\n- [`97ec422f2`](https://www.github.com/tauri-apps/tauri/commit/97ec422f22d069b9570931834241c7e47bc68cc3)([#9638](https://www.github.com/tauri-apps/tauri/pull/9638)) Exit `tauri icon` with non-zero code when it fails.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.16`\n\n## \\[2.0.0-beta.15]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.15`\n\n## \\[2.0.0-beta.14]\n\n### Enhancements\n\n- [`8a63ceb4f`](https://www.github.com/tauri-apps/tauri/commit/8a63ceb4f31c422311b0f7dff173a9c8c0e1a604)([#9473](https://www.github.com/tauri-apps/tauri/pull/9473)) Ignore `.DS_Store` by default for `tauri dev` hot reloads.\n\n### Bug Fixes\n\n- [`e64b8f1dc`](https://www.github.com/tauri-apps/tauri/commit/e64b8f1dcedad3222f46755bf6f30392a7ec2f90)([#9479](https://www.github.com/tauri-apps/tauri/pull/9479)) Upgrade `heck` to v0.5 to better support Chinese and Japanese product name, because Chinese do not have word separation.\n- [`aaa332c6e`](https://www.github.com/tauri-apps/tauri/commit/aaa332c6e78c956debd11efda021a0406621a01d)([#9540](https://www.github.com/tauri-apps/tauri/pull/9540)) Fix `tauri migrate` trying to migrate to a non-existing plugin.\n- [`e64b8f1dc`](https://www.github.com/tauri-apps/tauri/commit/e64b8f1dcedad3222f46755bf6f30392a7ec2f90)([#9479](https://www.github.com/tauri-apps/tauri/pull/9479)) Fixed an issue causing the `build.runner` and `build.features` configs to not take effect.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.14`\n\n## \\[2.0.0-beta.13]\n\n### Bug Fixes\n\n- [`73c1c2d33`](https://www.github.com/tauri-apps/tauri/commit/73c1c2d33872651c32c761c838714b684980c668)([#9457](https://www.github.com/tauri-apps/tauri/pull/9457)) Gracefully handle Non-UTF8 files when using `tauri migrate`\n- [`9331435a5`](https://www.github.com/tauri-apps/tauri/commit/9331435a50cc3769720bd2671da8510699d28671)([#9412](https://www.github.com/tauri-apps/tauri/pull/9412)) Fix `tauri info` crashing when Node.js is not installed.\n\n### What's Changed\n\n- [`8f4b1050c`](https://www.github.com/tauri-apps/tauri/commit/8f4b1050c4de0e9194680408ff3a6902b67045f8)([#9459](https://www.github.com/tauri-apps/tauri/pull/9459)) Show full expected path of `frontendDist` when if can't be found.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.13`\n\n## \\[2.0.0-beta.12]\n\n### New Features\n\n- [`93e0e1392`](https://www.github.com/tauri-apps/tauri/commit/93e0e1392ec341fcadf696c03e78f0ca1e73c941) Support specifying a version for `tauri add` subcommand, for example: `tauri add window-state@2.0.0-beta.2`\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.12`\n\n## \\[2.0.0-beta.11]\n\n### Enhancements\n\n- [`ac76a22f3`](https://www.github.com/tauri-apps/tauri/commit/ac76a22f383028d9bacdedebeb41d3fca5ec9dac)([#9183](https://www.github.com/tauri-apps/tauri/pull/9183)) Allow empty responses for `devUrl`, `beforeDevCommand` and `beforeBuildCommands` questions in `tauri init`.\n- [`b525ddadf`](https://www.github.com/tauri-apps/tauri/commit/b525ddadf7e7588c3e195cf0f821c9862c545d06)([#9237](https://www.github.com/tauri-apps/tauri/pull/9237)) `openssl` is no longer a required dependency on macOS.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.11`\n\n## \\[2.0.0-beta.10]\n\n### New Features\n\n- [`7213b9e47`](https://www.github.com/tauri-apps/tauri/commit/7213b9e47242bef814aa7257e0bf84631bf5fe7e)([#9124](https://www.github.com/tauri-apps/tauri/pull/9124)) Add default permission for a plugin to capabilities when using `tauri add <plugin>`.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.10`\n\n## \\[2.0.0-beta.9]\n\n### Bug Fixes\n\n- [`c3ea3a2b7`](https://www.github.com/tauri-apps/tauri/commit/c3ea3a2b7d2fe3085f05b63dd1feb962beb4b7b3)([#9126](https://www.github.com/tauri-apps/tauri/pull/9126)) Fix bundling when `plugins > updater > windows > installerArgs` are set in `tauri.conf.json`\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.9`\n\n## \\[2.0.0-beta.8]\n\n### Enhancements\n\n- [`3e472d0af`](https://www.github.com/tauri-apps/tauri/commit/3e472d0afcd67545dd6d9f18d304580a3b2759a8)([#9115](https://www.github.com/tauri-apps/tauri/pull/9115)) Changed the permission and capability platforms to be optional.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.8`\n\n## \\[2.0.0-beta.7]\n\n### Enhancements\n\n- [`c68218b36`](https://www.github.com/tauri-apps/tauri/commit/c68218b362c417b62e56c7a2b5b32c13fe035a83)([#8990](https://www.github.com/tauri-apps/tauri/pull/8990)) Add `--no-bundle` flag for `tauri build` command to skip bundling. Previously `none` was used to skip bundling, it will now be treated as invalid format and a warning will be emitted instead.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.7`\n\n## \\[2.0.0-beta.6]\n\n### Bug Fixes\n\n- [`f5f3ed5f`](https://www.github.com/tauri-apps/tauri/commit/f5f3ed5f6faa0b51e83244acc15e9006299a03ba)([#9009](https://www.github.com/tauri-apps/tauri/pull/9009)) Fixes Android and iOS project initialization when the Tauri CLI is on a different disk partition.\n- [`d7d03c71`](https://www.github.com/tauri-apps/tauri/commit/d7d03c7197212f3a5bebe08c929417d60927eb89)([#9017](https://www.github.com/tauri-apps/tauri/pull/9017)) Fixes dev watcher on mobile dev.\n- [`b658ded6`](https://www.github.com/tauri-apps/tauri/commit/b658ded614cfc169228cb22ad5bfc64478dfe161)([#9015](https://www.github.com/tauri-apps/tauri/pull/9015)) Fixes truncation of existing BuildTask.kt when running `tauri android init`.\n\n### What's Changed\n\n- [`3657ad82`](https://www.github.com/tauri-apps/tauri/commit/3657ad82f88ce528551d032d521c52eed3f396b4)([#9008](https://www.github.com/tauri-apps/tauri/pull/9008)) Updates to new ACL manifest path.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.6`\n\n## \\[2.0.0-beta.5]\n\n### New Features\n\n- [`06d63d67`](https://www.github.com/tauri-apps/tauri/commit/06d63d67a061459dd533ddcae755922427a6dfc5)([#8827](https://www.github.com/tauri-apps/tauri/pull/8827)) Add new subcommands for managing permissions and cababilities:\n\n  - `tauri permission new`\n  - `tauri permission add`\n  - `tauri permission rm`\n  - `tauri permission ls`\n  - `tauri capability new`\n\n### Breaking Changes\n\n- [`b9e6a018`](https://www.github.com/tauri-apps/tauri/commit/b9e6a01879d9233040f3d3fab11c59e70563da7e)([#8937](https://www.github.com/tauri-apps/tauri/pull/8937)) The `custom-protocol` Cargo feature is no longer required on your application and is now ignored. To check if running on production, use `#[cfg(not(dev))]` instead of `#[cfg(feature = \"custom-protocol\")]`.\n\n### Enhancements\n\n- [`9be314f0`](https://www.github.com/tauri-apps/tauri/commit/9be314f07a4ca5d14433d41919492f3e91b5536a)([#8951](https://www.github.com/tauri-apps/tauri/pull/8951)) Add plugins to `Cargo.toml` when using `tauri migrate`\n\n### Bug Fixes\n\n- [`cbd9755e`](https://www.github.com/tauri-apps/tauri/commit/cbd9755e0926a7e47e59deb50f4bb93d621791a5)([#8977](https://www.github.com/tauri-apps/tauri/pull/8977)) Fixes process logs not showing on `ios dev`.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.5`\n\n## \\[2.0.0-beta.4]\n\n### Bug Fixes\n\n- [`e538ba58`](https://www.github.com/tauri-apps/tauri/commit/e538ba586c5b8b50955586c8ef2704adb5d7cc43)([#8949](https://www.github.com/tauri-apps/tauri/pull/8949)) Fixes android and iOS process spawning not working on Node.js.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.4`\n\n### Breaking Changes\n\n- [`a76fb118`](https://www.github.com/tauri-apps/tauri/commit/a76fb118ce2de22e1bdb4216bf0ac01dfc3e5799)([#8950](https://www.github.com/tauri-apps/tauri/pull/8950)) Changed the capability format to allow configuring both `remote: { urls: Vec<String> }` and `local: bool (default: true)` instead of choosing one on the `context` field.\n\n## \\[2.0.0-beta.3]\n\n### Enhancements\n\n- [`a029b9f7`](https://www.github.com/tauri-apps/tauri/commit/a029b9f77e432533a403c292940fa3efba68692c)([#8910](https://www.github.com/tauri-apps/tauri/pull/8910)) Setting up code signing is no longer required on iOS when using the simulator.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.3`\n\n## \\[2.0.0-beta.2]\n\n### Enhancements\n\n- [`83a68deb`](https://www.github.com/tauri-apps/tauri/commit/83a68deb5676d39cd4728d2e140f6b46d5f787ed)([#8797](https://www.github.com/tauri-apps/tauri/pull/8797)) Update app template following capabilities configuration change.\n\n### Bug Fixes\n\n- [`aa06a053`](https://www.github.com/tauri-apps/tauri/commit/aa06a0534cf224038866e0ddd6910ea873b2574d)([#8810](https://www.github.com/tauri-apps/tauri/pull/8810)) Fix `tauri plugin android init` printing invalid code that has a missing closing `\"`.\n- [`3cee26a5`](https://www.github.com/tauri-apps/tauri/commit/3cee26a58ab44639a12c7816f4096655daa327a4)([#8865](https://www.github.com/tauri-apps/tauri/pull/8865)) On Windows, fixed `tauri info` fails to detect the build tool when the system language is CJK.\n- [`052e8b43`](https://www.github.com/tauri-apps/tauri/commit/052e8b4311d9f0f963a2866163be27bfd8f70c60)([#8838](https://www.github.com/tauri-apps/tauri/pull/8838)) Downgrade minisign dependency fixing updater signing key bug and prevent it from happening in the future.\n- [`fb0d9971`](https://www.github.com/tauri-apps/tauri/commit/fb0d997117367e3387896bcd0fba004579475c40)([#8783](https://www.github.com/tauri-apps/tauri/pull/8783)) Fixes a regression on the `--config` argument not accepting file paths.\n- [`baca704d`](https://www.github.com/tauri-apps/tauri/commit/baca704d4b5fae239fc320d10140f35bd705bfbb)([#8768](https://www.github.com/tauri-apps/tauri/pull/8768)) Do not migrate updater configuration if the active flag is set to false.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.2`\n\n## \\[2.0.0-beta.1]\n\n### Enhancements\n\n- [`4e101f80`](https://www.github.com/tauri-apps/tauri/commit/4e101f801657e7d01ce8c22f9c6468067d0caab2)([#8756](https://www.github.com/tauri-apps/tauri/pull/8756)) Moved the capability JSON schema to the `src-tauri/gen` folder so it's easier to track changes on the `capabilities` folder.\n- [`4e101f80`](https://www.github.com/tauri-apps/tauri/commit/4e101f801657e7d01ce8c22f9c6468067d0caab2)([#8756](https://www.github.com/tauri-apps/tauri/pull/8756)) Update app and plugin templates following generated files change from tauri-build and tauri-plugin.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.1`\n\n## \\[2.0.0-beta.0]\n\n### New Features\n\n- [`7fcc0bcd`](https://www.github.com/tauri-apps/tauri/commit/7fcc0bcd3482bbc8771e330942ef6cd78cc8ec35)([#8490](https://www.github.com/tauri-apps/tauri/pull/8490)) Add plugin initialization rust code when using `tauri add`\n- [`1878766f`](https://www.github.com/tauri-apps/tauri/commit/1878766f7f81a03b0f0b87ec33ee113d7aa7a902)([#8667](https://www.github.com/tauri-apps/tauri/pull/8667)) Migrate the allowlist config to the new capability file format.\n\n### Enhancements\n\n- [`d6c7568c`](https://www.github.com/tauri-apps/tauri/commit/d6c7568c27445653edf570f3969163bc358ba2ba)([#8720](https://www.github.com/tauri-apps/tauri/pull/8720)) Add `files` option to the AppImage Configuration.\n- [`b3209bb2`](https://www.github.com/tauri-apps/tauri/commit/b3209bb28bb379d5046d577c7e42319d6e76ced0)([#8688](https://www.github.com/tauri-apps/tauri/pull/8688)) Ignore global `.gitignore` when searching for tauri directory.\n- [`e691208e`](https://www.github.com/tauri-apps/tauri/commit/e691208e7b39bb8e3ffc9bf66cff731a5025ef16)([#7837](https://www.github.com/tauri-apps/tauri/pull/7837)) Prevent unneeded double Cargo.toml rewrite on `dev` and `build`.\n- [`f492efd7`](https://www.github.com/tauri-apps/tauri/commit/f492efd7144fdd8d25cac0c4d2389a95ab75fb02)([#8666](https://www.github.com/tauri-apps/tauri/pull/8666)) Update app and plugin template following the new access control permission model.\n\n### Bug Fixes\n\n- [`9cb9aa79`](https://www.github.com/tauri-apps/tauri/commit/9cb9aa7978f231f7da238b33d6ab33fdd2d2c842)([#8672](https://www.github.com/tauri-apps/tauri/pull/8672)) Allow license field in Cargo.toml to be `{ workspace = true }`\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-beta.0`\n\n### Breaking Changes\n\n- [`8de308d1`](https://www.github.com/tauri-apps/tauri/commit/8de308d1bf6a855d7a26af58bd0e744938ba47d8)([#8723](https://www.github.com/tauri-apps/tauri/pull/8723)) Restructured Tauri config per [RFC#5](https://github.com/tauri-apps/rfcs/blob/f3e82a6b0c5390401e855850d47dc7b7d9afd684/texts/0005-tauri-config-restructure.md):\n\n  - Moved `package.productName`, `package.version` and `tauri.bundle.identifier` fields to the top-level.\n  - Removed `package` object.\n  - Renamed `tauri` object to `app`.\n  - Moved `tauri.bundle` object to the top-level.\n  - Renamed `build.distDir` field to `frontendDist`.\n  - Renamed `build.devPath` field to `devUrl` and will no longer accepts paths, it will only accept URLs.\n  - Moved `tauri.pattern` to `app.security.pattern`.\n  - Removed `tauri.bundle.updater` object, and its fields have been moved to the updater plugin under `plugins.updater` object.\n  - Moved `build.withGlobalTauri` to `app.withGlobalTauri`.\n  - Moved `tauri.bundle.dmg` object to `bundle.macOS.dmg`.\n  - Moved `tauri.bundle.deb` object to `bundle.linux.deb`.\n  - Moved `tauri.bundle.appimage` object to `bundle.linux.appimage`.\n  - Removed all license fields from each bundle configuration object and instead added `bundle.license` and `bundle.licenseFile`.\n  - Renamed `AppUrl` to `FrontendDist` and refactored its variants to be more explicit.\n\n## \\[2.0.0-alpha.21]\n\n### New Features\n\n- [`27bad32d`](https://www.github.com/tauri-apps/tauri/commit/27bad32d4d4acca8155b20225d529d540fb9aaf4)([#7798](https://www.github.com/tauri-apps/tauri/pull/7798)) Add `files` object on the `tauri > bundle > macOS` configuration option.\n- [`0ec28c39`](https://www.github.com/tauri-apps/tauri/commit/0ec28c39f478de7199a66dd75e8642e1aa1344e6)([#8529](https://www.github.com/tauri-apps/tauri/pull/8529)) Include tauri-build on the migration script.\n\n### Enhancements\n\n- [`091100ac`](https://www.github.com/tauri-apps/tauri/commit/091100acbb507b51de39fb1446f685926f888fd2)([#5202](https://www.github.com/tauri-apps/tauri/pull/5202)) Add RPM packaging\n\n### Bug Fixes\n\n- [`4f73057e`](https://www.github.com/tauri-apps/tauri/commit/4f73057e6fd4c137bc112367fb91f5fc0c8a39f6)([#8486](https://www.github.com/tauri-apps/tauri/pull/8486)) Prevent `Invalid target triple` warnings and correctly set `TAURI_ENV_` vars when target triple contains 4 components.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-alpha.21`\n\n### Breaking Changes\n\n- [`4f73057e`](https://www.github.com/tauri-apps/tauri/commit/4f73057e6fd4c137bc112367fb91f5fc0c8a39f6)([#8486](https://www.github.com/tauri-apps/tauri/pull/8486)) Removed `TAURI_ENV_PLATFORM_TYPE` which will not be set for CLI hook commands anymore, use `TAURI_ENV_PLATFORM` instead. Also Changed value of `TAURI_ENV_PLATFORM` and `TAURI_ENV_ARCH` values to match the target triple more accurately:\n\n  - `darwin` and `androideabi` are no longer replaced with `macos` and `android` in `TAURI_ENV_PLATFORM`.\n  - `i686` and `i586` are no longer replaced with `x86` in `TAURI_ENV_ARCH`.\n\n## \\[2.0.0-alpha.20]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-alpha.20`\n\n## \\[2.0.0-alpha.19]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-alpha.19`\n\n## \\[2.0.0-alpha.18]\n\n### New Features\n\n- [`50f7ccbb`](https://www.github.com/tauri-apps/tauri/commit/50f7ccbbf3467f33cc7dd1cca53125fec6eda1c6)([#6444](https://www.github.com/tauri-apps/tauri/pull/6444)) Add suport to SVG input image for the `tauri icon` command.\n- [`25e5f91d`](https://www.github.com/tauri-apps/tauri/commit/25e5f91dae7fe2bbc1ba4317d5d829402bfd1d50)([#8200](https://www.github.com/tauri-apps/tauri/pull/8200)) Merge `src-tauri/Info.plist` and `src-tauri/Info.ios.plist` with the iOS project plist file.\n\n### Enhancements\n\n- [`01a7a983`](https://www.github.com/tauri-apps/tauri/commit/01a7a983aba2946b455a608b8a6a4b08cb25fc11)([#8128](https://www.github.com/tauri-apps/tauri/pull/8128)) Transform paths to relative to the mobile project for the IDE script runner script.\n\n### Bug Fixes\n\n- [`88dac86f`](https://www.github.com/tauri-apps/tauri/commit/88dac86f3b301d1919df6473a9e20f46b560f29b)([#8149](https://www.github.com/tauri-apps/tauri/pull/8149)) Ensure `tauri add` prints `rust_code` with plugin name in snake case.\n- [`977d0e52`](https://www.github.com/tauri-apps/tauri/commit/977d0e52f14b1ad01c86371765ef25b36572459e)([#8202](https://www.github.com/tauri-apps/tauri/pull/8202)) Fixes `android build --open` and `ios build --open` IDE failing to read CLI options.\n- [`bfbbefdb`](https://www.github.com/tauri-apps/tauri/commit/bfbbefdb9e13ed1f42f6db7fa9ceaa84db1267e9)([#8161](https://www.github.com/tauri-apps/tauri/pull/8161)) Fix invalid plugin template.\n- [`92b50a3a`](https://www.github.com/tauri-apps/tauri/commit/92b50a3a398c9d55b6992a8f5c2571e4d72bdaaf)([#8209](https://www.github.com/tauri-apps/tauri/pull/8209)) Added support to Xcode's archive. This requires regenerating the Xcode project.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-alpha.18`\n\n## \\[2.0.0-alpha.17]\n\n### Enhancements\n\n- [`c6c59cf2`](https://www.github.com/tauri-apps/tauri/commit/c6c59cf2373258b626b00a26f4de4331765dd487) Pull changes from Tauri 1.5 release.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-alpha.17`\n\n### Breaking Changes\n\n- [`198abe3c`](https://www.github.com/tauri-apps/tauri/commit/198abe3c2cae06dacab860b3a93f715dcf529a95)([#8076](https://www.github.com/tauri-apps/tauri/pull/8076)) Updated the mobile plugin templates following the tauri v2.0.0-alpha.17 changes.\n\n## \\[2.0.0-alpha.16]\n\n### New Features\n\n- [`8b166e9b`](https://www.github.com/tauri-apps/tauri/commit/8b166e9bf82e69ddb3200a3a825614980bd8d433)([#7949](https://www.github.com/tauri-apps/tauri/pull/7949)) Add `--no-dev-server-wait` option to skip waiting for the dev server to start when using `tauri dev`.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-alpha.16`\n\n### Breaking Changes\n\n- [`8b166e9b`](https://www.github.com/tauri-apps/tauri/commit/8b166e9bf82e69ddb3200a3a825614980bd8d433)([#7949](https://www.github.com/tauri-apps/tauri/pull/7949)) Changed a number of environment variables used by tauri CLI for consistency and clarity:\n\n  - `TAURI_PRIVATE_KEY` -> `TAURI_SIGNING_PRIVATE_KEY`\n  - `TAURI_KEY_PASSWORD` -> `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`\n  - `TAURI_SKIP_DEVSERVER_CHECK` -> `TAURI_CLI_NO_DEV_SERVER_WAIT`\n  - `TAURI_DEV_SERVER_PORT` -> `TAURI_CLI_PORT`\n  - `TAURI_PATH_DEPTH` -> `TAURI_CLI_CONFIG_DEPTH`\n  - `TAURI_FIPS_COMPLIANT` -> `TAURI_BUNDLER_WIX_FIPS_COMPLIANT`\n  - `TAURI_DEV_WATCHER_IGNORE_FILE` -> `TAURI_CLI_WATCHER_IGNORE_FILENAME`\n  - `TAURI_TRAY` -> `TAURI_LINUX_AYATANA_APPINDICATOR`\n  - `TAURI_APPLE_DEVELOPMENT_TEAM` -> `APPLE_DEVELOPMENT_TEAM`\n- [`4caa1cca`](https://www.github.com/tauri-apps/tauri/commit/4caa1cca990806f2c2ef32d5dabaf56e82f349e6)([#7990](https://www.github.com/tauri-apps/tauri/pull/7990)) The `tauri plugin` subcommand is receving a couple of consitency and quality of life improvements:\n\n  - Renamed `tauri plugin android/ios add` command to `tauri plugin android/ios init` to match the `tauri plugin init` command.\n  - Removed the `-n/--name` argument from the `tauri plugin init`, `tauri plugin android/ios init`, and is now parsed from the first positional argument.\n  - Added `tauri plugin new` to create a plugin in a new directory.\n  - Changed `tauri plugin init` to initalize a plugin in an existing directory (defaults to current directory) instead of creating a new one.\n  - Changed `tauri plugin init` to NOT generate mobile projects by default, you can opt-in to generate them using `--android` and `--ios` flags or `--mobile` flag or initalize them later using `tauri plugin android/ios init`.\n- [`8b166e9b`](https://www.github.com/tauri-apps/tauri/commit/8b166e9bf82e69ddb3200a3a825614980bd8d433)([#7949](https://www.github.com/tauri-apps/tauri/pull/7949)) Removed checking for a new version of the CLI.\n- [`ebcc21e4`](https://www.github.com/tauri-apps/tauri/commit/ebcc21e4b95f4e8c27639fb1bca545b432f52d5e)([#8057](https://www.github.com/tauri-apps/tauri/pull/8057)) Renamed the beforeDevCommand, beforeBuildCommand and beforeBundleCommand hooks environment variables from `TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG` to `TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG` to differentiate the prefix with other CLI environment variables.\n\n## \\[2.0.0-alpha.15]\n\n### New Features\n\n- [`b2f17723`](https://www.github.com/tauri-apps/tauri/commit/b2f17723a415f04c2620132a6305eb138d7cb47f)([#7971](https://www.github.com/tauri-apps/tauri/pull/7971)) Use `devicectl` to connect to iOS 17+ devices on macOS 14+.\n\n### Bug Fixes\n\n- [`100d9ede`](https://www.github.com/tauri-apps/tauri/commit/100d9ede35995d9db21d2087dd5606adfafb89a5)([#7802](https://www.github.com/tauri-apps/tauri/pull/7802)) Properly read platform-specific configuration files for mobile targets.\n- [`228e5a4c`](https://www.github.com/tauri-apps/tauri/commit/228e5a4c76ad5f97409c912d07699b49ba4bb162)([#7902](https://www.github.com/tauri-apps/tauri/pull/7902)) Fixes `icon` command not writing files to the correct Android project folders.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-alpha.15`\n\n## \\[2.0.0-alpha.14]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-alpha.14`\n\n### Breaking Changes\n\n- [`d5074af5`](https://www.github.com/tauri-apps/tauri/commit/d5074af562b2b5cb6c5711442097c4058af32db6)([#7801](https://www.github.com/tauri-apps/tauri/pull/7801)) The custom protocol on Android now uses the `http` scheme instead of `https`.\n\n## \\[2.0.0-alpha.13]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-alpha.13`\n\n### Breaking Changes\n\n- [`4cb51a2d`](https://www.github.com/tauri-apps/tauri/commit/4cb51a2d56cfcae0749062c79ede5236bd8c02c2)([#7779](https://www.github.com/tauri-apps/tauri/pull/7779)) The custom protocol on Windows now uses the `http` scheme instead of `https`.\n- [`974e38b4`](https://www.github.com/tauri-apps/tauri/commit/974e38b4ddc198530aa977ec77d513b76013b9f3)([#7744](https://www.github.com/tauri-apps/tauri/pull/7744)) Renamed the `plugin add` command to `add`.\n\n## \\[2.0.0-alpha.12]\n\n### Bug Fixes\n\n- [`b75a1210`](https://www.github.com/tauri-apps/tauri/commit/b75a1210bed589187678861d7314ae6279bf7c87)([#7762](https://www.github.com/tauri-apps/tauri/pull/7762)) Fixes a regression on alpha.11 where iOS logs aren't being displayed when using `ios dev` with a real device.\n- [`8faa5a4a`](https://www.github.com/tauri-apps/tauri/commit/8faa5a4a1238a44ca7b54d2084aaed553ac2a1ba)([#7765](https://www.github.com/tauri-apps/tauri/pull/7765)) Ensure asset directory exists on the iOS project.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-alpha.12`\n\n## \\[2.0.0-alpha.11]\n\n### New Features\n\n- [`522de0e7`](https://www.github.com/tauri-apps/tauri/commit/522de0e78891d0bdf6387a5118985fc41a11baeb)([#7447](https://www.github.com/tauri-apps/tauri/pull/7447)) Expose an environment variable `TAURI_${PLUGIN_NAME}_PLUGIN_CONFIG` for each defined plugin configuration object.\n- [`c7dacca4`](https://www.github.com/tauri-apps/tauri/commit/c7dacca4661c6ddf937c1a3dd3ace896d5baf40c)([#7446](https://www.github.com/tauri-apps/tauri/pull/7446)) Expose the `TAURI_IOS_PROJECT_PATH` and `TAURI_IOS_APP_NAME` environment variables when using `ios` commands.\n- [`aa94f719`](https://www.github.com/tauri-apps/tauri/commit/aa94f7197e4345a7cab1617272b10895859674f9)([#7445](https://www.github.com/tauri-apps/tauri/pull/7445)) Generate empty entitlements file for the iOS project.\n- [`d010bc07`](https://www.github.com/tauri-apps/tauri/commit/d010bc07b81116bef769b64cdc19b23dff762d48)([#7554](https://www.github.com/tauri-apps/tauri/pull/7554)) Set the iOS project PRODUCT_NAME value to the string under `tauri.conf.json > package > productName` if it is set.\n- [`8af24974`](https://www.github.com/tauri-apps/tauri/commit/8af2497496f11ee481472dfdc3125757346c1a3e)([#7561](https://www.github.com/tauri-apps/tauri/pull/7561)) The `migrate` command now automatically reads all JavaScript files and updates `@tauri-apps/api` import paths and install the missing plugins.\n\n### Enhancements\n\n- [`fbeb5b91`](https://www.github.com/tauri-apps/tauri/commit/fbeb5b9185baeda19e865228179e3e44c165f1d9)([#7170](https://www.github.com/tauri-apps/tauri/pull/7170)) Update migrate command to update the configuration CSP to include `ipc:` on the `connect-src` directive, needed by the new IPC using custom protocols.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@2.0.0-alpha.11`\n\n## \\[2.0.0-alpha.10]\n\n### New Features\n\n- [`7e5905ae`](https://www.github.com/tauri-apps/tauri/commit/7e5905ae1d56b920de0e821be28036cbbe302518)([#7023](https://www.github.com/tauri-apps/tauri/pull/7023)) Added `tauri plugin add` command to add a plugin to the Tauri project.\n- [`b0f94775`](https://www.github.com/tauri-apps/tauri/commit/b0f947752a315b7b89c5979de50157f997f1dd6e)([#7008](https://www.github.com/tauri-apps/tauri/pull/7008)) Added `migrate` command.\n\n### Enhancements\n\n- [`aa6c9164`](https://www.github.com/tauri-apps/tauri/commit/aa6c9164e63b5316d690f25b1c118f1b12310570)([#7007](https://www.github.com/tauri-apps/tauri/pull/7007)) Don't build library files when building desktop targets.\n- [`a28fdf7e`](https://www.github.com/tauri-apps/tauri/commit/a28fdf7ec71bf6db2498569004de83318b6d25ac)([#7044](https://www.github.com/tauri-apps/tauri/pull/7044)) Skip Rust target installation if they are already installed.\n- [`735db1ce`](https://www.github.com/tauri-apps/tauri/commit/735db1ce839a16ba998c9e6786c441e3bf6c90b3)([#7044](https://www.github.com/tauri-apps/tauri/pull/7044)) Add `--skip-targets-install` flag for `tauri android init` and `tauri ios init` to skip installing needed rust targets vie rustup.\n\n### Bug Fixes\n\n- [`1ed2600d`](https://www.github.com/tauri-apps/tauri/commit/1ed2600da67715908af857255305eaeb293d8791)([#6771](https://www.github.com/tauri-apps/tauri/pull/6771)) Set current directory to tauri directory before reading config file.\n- [`4847b87b`](https://www.github.com/tauri-apps/tauri/commit/4847b87b1067dd8c6e73986059f51e6eee1f1121)([#7209](https://www.github.com/tauri-apps/tauri/pull/7209)) Fix `tauri (android|ios) (dev|build)` failing when using `npx tauri`\n- [`655c714e`](https://www.github.com/tauri-apps/tauri/commit/655c714e4100f69d4265c5ea3f08f5bc11709446)([#7240](https://www.github.com/tauri-apps/tauri/pull/7240)) Fixes panic when exiting the `ios dev` command with Ctrl + C.\n- [`6252380f`](https://www.github.com/tauri-apps/tauri/commit/6252380f4447c66913b0f06611e5949005b1eec2)([#7241](https://www.github.com/tauri-apps/tauri/pull/7241)) Exit `beforeDevCommand` process if the android or iOS `dev` command fails.\n\n## \\[2.0.0-alpha.9]\n\n- [`19cd0e49`](https://www.github.com/tauri-apps/tauri/commit/19cd0e49603ad3500cd2180bfa16e1649e3a771a)([#6811](https://www.github.com/tauri-apps/tauri/pull/6811)) Add `key.properties` file to android's `.gitignore`.\n- [`124d5c5a`](https://www.github.com/tauri-apps/tauri/commit/124d5c5adf67f0b68d2e41c7ddb07d9cb63f1996)([#6788](https://www.github.com/tauri-apps/tauri/pull/6788)) On mobile, fix regression introduced in `tauri-cli` version `2.0.0-alpha.3` where library not found error was thrown.\n- [`31444ac1`](https://www.github.com/tauri-apps/tauri/commit/31444ac196add770f2ad18012d7c18bce7538f22)([#6725](https://www.github.com/tauri-apps/tauri/pull/6725)) Update mobile template to `wry@0.28`\n- [`41f49aea`](https://www.github.com/tauri-apps/tauri/commit/41f49aeae646f2cb70b42002bb1371c79e592243)([#6659](https://www.github.com/tauri-apps/tauri/pull/6659)) Update tauri-mobile to fix running ADB scripts.\n- [`6d1fa49f`](https://www.github.com/tauri-apps/tauri/commit/6d1fa49fce3a03965ce7c656390e682ce5b776e3)([#6881](https://www.github.com/tauri-apps/tauri/pull/6881)) Clear Android plugin JSON file before building Rust library to ensure removed plugins are propagated to the Android project.\n- [`5a9307d1`](https://www.github.com/tauri-apps/tauri/commit/5a9307d11c1643221bc2a280feb00024f8fa6030)([#6890](https://www.github.com/tauri-apps/tauri/pull/6890)) Update android template to gradle 8.0\n- [`73c803a5`](https://www.github.com/tauri-apps/tauri/commit/73c803a561181137f20366f5d52511392a619f2b)([#6837](https://www.github.com/tauri-apps/tauri/pull/6837)) Inject Tauri configuration in the Android assets.\n- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Moved the updater configuration to the `BundleConfig`.\n- [`d48aaa15`](https://www.github.com/tauri-apps/tauri/commit/d48aaa150a1ceeb65ec0ba18f1e3795f70c838e3)([#6894](https://www.github.com/tauri-apps/tauri/pull/6894)) Add Cargo manifest files for the plugin example templates.\n- [`e1e85dc2`](https://www.github.com/tauri-apps/tauri/commit/e1e85dc2a5f656fc37867e278cae8042037740ac)([#6925](https://www.github.com/tauri-apps/tauri/pull/6925)) Removed the allowlist configuration.\n\n## \\[2.0.0-alpha.8]\n\n- Do not gitignore the Android project's `buildSrc` folder by default since we removed absolute paths from it.\n  - [ee2d3b97](https://www.github.com/tauri-apps/tauri/commit/ee2d3b971df6d3630b8d935394fb4febcfa3a909) fix(cli): remove buildSrc from Android project gitignored paths ([#6702](https://www.github.com/tauri-apps/tauri/pull/6702)) on 2023-04-13\n- Fixes iOS build script using the wrong path for the app library file.\n  - [abc5f91f](https://www.github.com/tauri-apps/tauri/commit/abc5f91fa3569efc9dfdee46d1c501eda8755944) fix(cli): iOS Xcode script using incorrect library path ([#6699](https://www.github.com/tauri-apps/tauri/pull/6699)) on 2023-04-13\n\n## \\[2.0.0-alpha.7]\n\n- Add `--release` flag for `tauri android dev` however you will need to sign your Android app, see https://next--tauri.netlify.app/next/guides/distribution/sign-android\n  - [63f088e5](https://www.github.com/tauri-apps/tauri/commit/63f088e5fc9701fd7fb329dad7ffb27a2d8fd5aa) feat(cli): add `--release` for `android dev` ([#6638](https://www.github.com/tauri-apps/tauri/pull/6638)) on 2023-04-05\n- Build only specified rust targets for `tauri android build` instead of all.\n  - [d03e47d1](https://www.github.com/tauri-apps/tauri/commit/d03e47d141c3917520975be9081775dbc4e9d4fd) fix: only build specified rust targets for aab/apk build ([#6625](https://www.github.com/tauri-apps/tauri/pull/6625)) on 2023-04-05\n- Use local ip address for built-in dev server on mobile.\n  - [7fec0f08](https://www.github.com/tauri-apps/tauri/commit/7fec0f083c932dc63ccb8716080d97e2ab985b25) fix(cli): use local ip addr for built-in server on mobile, closes [#6454](https://www.github.com/tauri-apps/tauri/pull/6454) ([#6631](https://www.github.com/tauri-apps/tauri/pull/6631)) on 2023-04-04\n- Readd the Cargo.toml file to the plugin template.\n  - [5288a386](https://www.github.com/tauri-apps/tauri/commit/5288a386f1bf8ac11f991350463c3f5c20983f43) fix(cli): readd Cargo.toml to the plugin template ([#6637](https://www.github.com/tauri-apps/tauri/pull/6637)) on 2023-04-04\n\n## \\[2.0.0-alpha.6]\n\n- Use Ubuntu 20.04 to compile the CLI, increasing the minimum libc version required.\n- Automatically enable the `rustls-tls` tauri feature on mobile and `native-tls` on desktop if `rustls-tls` is not enabled.\n  - [cfdee00f](https://www.github.com/tauri-apps/tauri/commit/cfdee00f2b1455a9719bc44823fdaeabbe4c1cb2) refactor(core): fix tls features, use rustls on mobile ([#6591](https://www.github.com/tauri-apps/tauri/pull/6591)) on 2023-03-30\n\n## \\[2.0.0-alpha.5]\n\n- Fixes the iOS project script to build the Rust library.\n  - [6e3e4c22](https://www.github.com/tauri-apps/tauri/commit/6e3e4c22be51500bec7856d90dcb2e40ef7fe1b4) fix(cli): use correct variable on script to build Rust iOS code ([#6581](https://www.github.com/tauri-apps/tauri/pull/6581)) on 2023-03-29\n- Fix `tauri android build/dev` crashing when used with standalone `pnpm` executable on Windows.\n  - [39df2c98](https://www.github.com/tauri-apps/tauri/commit/39df2c982e5e2ee8617b40f829a2f2e4abfce412) fix(cli/android): fallback to `${program}.cmd` ([#6576](https://www.github.com/tauri-apps/tauri/pull/6576)) on 2023-03-29\n  - [6b469c40](https://www.github.com/tauri-apps/tauri/commit/6b469c40c6244ba773d7478adbb41db6fdc72822) chore(changes): adjust change file for Android script execution fix on 2023-03-29\n\n## \\[2.0.0-alpha.4]\n\n- Fix android project build crashing when using `pnpm` caused by extra `--`.\n  - [c787f749](https://www.github.com/tauri-apps/tauri/commit/c787f749de01b79d891615aad8c37b23037fff4c) fix(cli): only add `--` to generated android template for npm ([#6508](https://www.github.com/tauri-apps/tauri/pull/6508)) on 2023-03-21\n- Fixes the Android build gradle plugin implementation on Windows.\n  - [00241fa9](https://www.github.com/tauri-apps/tauri/commit/00241fa92d104870068a701519340633cc35b716) fix(cli): append .cmd on the gradle plugin binary on Windows, fix [#6502](https://www.github.com/tauri-apps/tauri/pull/6502) ([#6503](https://www.github.com/tauri-apps/tauri/pull/6503)) on 2023-03-21\n- Update `napi-rs` dependencies to latest to fix CLI hanging up forever.\n  - [d5ac76b5](https://www.github.com/tauri-apps/tauri/commit/d5ac76b53c7f35253db84ddfbf4ecf975ff6307d) chore(deps): update napi-rs, closes [#6502](https://www.github.com/tauri-apps/tauri/pull/6502) ([#6513](https://www.github.com/tauri-apps/tauri/pull/6513)) on 2023-03-21\n\n## \\[2.0.0-alpha.3]\n\n- Added `plugin android add` and `plugin ios add` commands to add mobile plugin functionality to existing projects.\n  - [14d03d42](https://www.github.com/tauri-apps/tauri/commit/14d03d426e86d966950a790926c04560c76203b3) refactor(cli): enhance plugin commands for mobile ([#6289](https://www.github.com/tauri-apps/tauri/pull/6289)) on 2023-02-16\n- Add commands to add native Android and iOS functionality to plugins.\n  - [05dad087](https://www.github.com/tauri-apps/tauri/commit/05dad0876842e2a7334431247d49365cee835d3e) feat: initial work for iOS plugins ([#6205](https://www.github.com/tauri-apps/tauri/pull/6205)) on 2023-02-11\n- Use temp file instead of environment variable to pass CLI IPC websocket address to the IDE.\n  - [894a8d06](https://www.github.com/tauri-apps/tauri/commit/894a8d060c12a482a0fc5b3714f3848189b809de) refactor(cli): use temp file to communicate IPC websocket address ([#6219](https://www.github.com/tauri-apps/tauri/pull/6219)) on 2023-02-08\n- Change the Android template to enable minification on release and pull ProGuard rules from proguard-tauri.pro.\n  - [bef4ef51](https://www.github.com/tauri-apps/tauri/commit/bef4ef51bc2c633b88db121c2087a38dddb7d6bf) feat(android): enable minify on release, add proguard rules ([#6257](https://www.github.com/tauri-apps/tauri/pull/6257)) on 2023-02-13\n- Print an error if the Android project was generated with an older bundle identifier or package name.\n  - [79eb0542](https://www.github.com/tauri-apps/tauri/commit/79eb054292b04bb089a0e4df401a5986b33b691e) feat(cli): handle Android package identifier change ([#6314](https://www.github.com/tauri-apps/tauri/pull/6314)) on 2023-02-19\n- Fixes the generated mobile build script when using an NPM runner.\n  - [62f15265](https://www.github.com/tauri-apps/tauri/commit/62f152659204ce1218178596f463f0bcfbd4e6dc) fix(cli): generate build script using NPM runner if it was used ([#6233](https://www.github.com/tauri-apps/tauri/pull/6233)) on 2023-02-10\n- Resolve Android package name from single word bundle identifiers.\n  - [60a8b07d](https://www.github.com/tauri-apps/tauri/commit/60a8b07dc7c56c9c45331cb57d9afb410e7eadf3) fix: handle single word bundle identifier when resolving Android domain ([#6313](https://www.github.com/tauri-apps/tauri/pull/6313)) on 2023-02-19\n- Update Android project template with fix to crash on orientation change.\n  - [947eb391](https://www.github.com/tauri-apps/tauri/commit/947eb391ca41cebdb11abd9ffaec642baffbf44a) fix(android): crash on orientation change due to activity recreation ([#6261](https://www.github.com/tauri-apps/tauri/pull/6261)) on 2023-02-13\n- Added `--ios-color` option to the `tauri icon` command.\n  - [67755425](https://www.github.com/tauri-apps/tauri/commit/677554257e40e05b1af0dd61c982d6be8a8a033c) feat(cli): add `--ios-color` option to set iOS icon background color ([#6247](https://www.github.com/tauri-apps/tauri/pull/6247)) on 2023-02-12\n- Fixes HMR on mobile when devPath is configured to load a filesystem path.\n  - [4a82da29](https://www.github.com/tauri-apps/tauri/commit/4a82da2919e0564ec993b2005dc65b5b49407b36) fix(cli): use local ip address for reload ([#6285](https://www.github.com/tauri-apps/tauri/pull/6285)) on 2023-02-16\n- Ignore the `gen` folder on the dev watcher.\n  - [cab4ff95](https://www.github.com/tauri-apps/tauri/commit/cab4ff95b98aeac88401c1fed2d8b8940e4180cb) fix(cli): ignore the `gen` folder on the dev watcher ([#6232](https://www.github.com/tauri-apps/tauri/pull/6232)) on 2023-02-09\n- Correctly pass arguments from `npm run` to `tauri`.\n  - [1b343bd1](https://www.github.com/tauri-apps/tauri/commit/1b343bd11686f47f24a87298d8192097c66250f6) fix(cli): use `npm run tauri -- foo` for correctly passing args to tauri ([#6448](https://www.github.com/tauri-apps/tauri/pull/6448)) on 2023-03-16\n- Changed the `--api` flag on `plugin init` to `--no-api`.\n  - [14d03d42](https://www.github.com/tauri-apps/tauri/commit/14d03d426e86d966950a790926c04560c76203b3) refactor(cli): enhance plugin commands for mobile ([#6289](https://www.github.com/tauri-apps/tauri/pull/6289)) on 2023-02-16\n\n## \\[2.0.0-alpha.2]\n\n- Fixes `TAURI_*` environment variables for hook scripts on mobile commands.\n  - [1af9be90](https://www.github.com/tauri-apps/tauri/commit/1af9be904a309138b9f79dc741391000b1652c75) feat(cli): properly fill target for TAURI\\_ env vars on mobile ([#6116](https://www.github.com/tauri-apps/tauri/pull/6116)) on 2023-01-23\n- Force colored logs on mobile commands.\n  - [2c4a0bbd](https://www.github.com/tauri-apps/tauri/commit/2c4a0bbd1fbe15d7500264e6490772397e1917ed) feat(cli): force colored logs on mobile commands ([#5934](https://www.github.com/tauri-apps/tauri/pull/5934)) on 2022-12-28\n- Keep the process alive even when the iOS application is closed.\n  - [dee9460f](https://www.github.com/tauri-apps/tauri/commit/dee9460f9c9bc92e9c638e7691e616849ac2085b) feat: keep CLI alive when iOS app exits, show logs, closes [#5855](https://www.github.com/tauri-apps/tauri/pull/5855) ([#5902](https://www.github.com/tauri-apps/tauri/pull/5902)) on 2022-12-27\n- Show all application logs on iOS.\n  - [dee9460f](https://www.github.com/tauri-apps/tauri/commit/dee9460f9c9bc92e9c638e7691e616849ac2085b) feat: keep CLI alive when iOS app exits, show logs, closes [#5855](https://www.github.com/tauri-apps/tauri/pull/5855) ([#5902](https://www.github.com/tauri-apps/tauri/pull/5902)) on 2022-12-27\n- Print log output for all tags on Android development.\n  - [8cc11149](https://www.github.com/tauri-apps/tauri/commit/8cc111494d74161e489152e52191e1442dd99759) fix(cli): print Android logs for all tags on 2023-01-17\n- Add support to custom and kebab case library names for mobile apps.\n  - [50f6dd87](https://www.github.com/tauri-apps/tauri/commit/50f6dd87b1ac2c99f8794b055f1acba4ef7d34d3) feat: improvements to support hyphens in crate name ([#5989](https://www.github.com/tauri-apps/tauri/pull/5989)) on 2023-01-06\n- Fix target directory detection when compiling for Android.\n  - [e873bae0](https://www.github.com/tauri-apps/tauri/commit/e873bae09f0f27517f720a753f51c1dcb903f883) fix(cli): Cargo target dir detection on Android, closes [#5865](https://www.github.com/tauri-apps/tauri/pull/5865) ([#5932](https://www.github.com/tauri-apps/tauri/pull/5932)) on 2022-12-28\n\n## \\[2.0.0-alpha.1]\n\n- Fixes running on device using Xcode 14.\n  - [1e4a6758](https://www.github.com/tauri-apps/tauri/commit/1e4a675843c486bddc11292d09fb766e98758514) fix(cli): run on iOS device on Xcode 14 ([#5807](https://www.github.com/tauri-apps/tauri/pull/5807)) on 2022-12-12\n- Improve local IP address detection with user selection.\n  - [76204b89](https://www.github.com/tauri-apps/tauri/commit/76204b893846a04552f8f8b87ad2c9b55e1b417f) feat(cli): improve local IP detection ([#5817](https://www.github.com/tauri-apps/tauri/pull/5817)) on 2022-12-12\n\n## \\[2.0.0-alpha.0]\n\n- Added `android build` command.\n  - [4c9ea450](https://www.github.com/tauri-apps/tauri/commit/4c9ea450c3b47c6b8c825ba32e9837909945ccd7) feat(cli): add `android build` command ([#4999](https://www.github.com/tauri-apps/tauri/pull/4999)) on 2022-08-22\n- Added `ios build` command.\n  - [403859d4](https://www.github.com/tauri-apps/tauri/commit/403859d47e1a9bf978b353fa58e4b971e66337a3) feat(cli): add `ios build` command ([#5002](https://www.github.com/tauri-apps/tauri/pull/5002)) on 2022-08-22\n- Added `android dev` and `ios dev` commands.\n  - [6f061504](https://www.github.com/tauri-apps/tauri/commit/6f0615044d09ec58393a7ebca5e45bb175e20db3) feat(cli): add `android dev` and `ios dev` commands ([#4982](https://www.github.com/tauri-apps/tauri/pull/4982)) on 2022-08-20\n- Added `android init` and `ios init` commands.\n  - [d44f67f7](https://www.github.com/tauri-apps/tauri/commit/d44f67f7afd30a81d53a973ec603b2a253150bde) feat: add `android init` and `ios init` commands ([#4942](https://www.github.com/tauri-apps/tauri/pull/4942)) on 2022-08-15\n- Added `android open` and `ios open` commands.\n  - [a9c8e565](https://www.github.com/tauri-apps/tauri/commit/a9c8e565c6495961940877df7090f307be16b554) feat: add `android open` and `ios open` commands ([#4946](https://www.github.com/tauri-apps/tauri/pull/4946)) on 2022-08-15\n- First mobile alpha release!\n  - [fa3a1098](https://www.github.com/tauri-apps/tauri/commit/fa3a10988a03aed1b66fb17d893b1a9adb90f7cd) feat(ci): prepare 2.0.0-alpha.0 ([#5786](https://www.github.com/tauri-apps/tauri/pull/5786)) on 2022-12-08\n\n## \\[1.5.11]\n\n### Bug Fixes\n\n- [`b15948b11`](https://www.github.com/tauri-apps/tauri/commit/b15948b11c0e362eea7ef57a4606f15f7dbd886b)([#8903](https://www.github.com/tauri-apps/tauri/pull/8903)) Fix `.taurignore` failing to ignore in some cases.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@1.5.11`\n\n## \\[1.5.10]\n\n### Bug Fixes\n\n- [`b0f27814`](https://www.github.com/tauri-apps/tauri/commit/b0f27814b90ded2f1ed44b7852080eedbff0d9e4)([#8776](https://www.github.com/tauri-apps/tauri/pull/8776)) Fix `fail to rename app` when using `--profile dev`.\n- [`0bff8c32`](https://www.github.com/tauri-apps/tauri/commit/0bff8c325d004fdead2023f58e0f5fd73a9c22ba)([#8697](https://www.github.com/tauri-apps/tauri/pull/8697)) Fix the built-in dev server failing to serve files when URL had queries `?` and other url components.\n- [`67d7877f`](https://www.github.com/tauri-apps/tauri/commit/67d7877f27f265c133a70d48a46c83ffff31d571)([#8520](https://www.github.com/tauri-apps/tauri/pull/8520)) The cli now also watches cargo workspace members if the tauri folder is the workspace root.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@1.5.10`\n\n## \\[1.5.9]\n\n### Bug Fixes\n\n- [`0a2175ea`](https://www.github.com/tauri-apps/tauri/commit/0a2175eabb736b2a4cd01ab682e08be0b5ebb2b9)([#8439](https://www.github.com/tauri-apps/tauri/pull/8439)) Expand glob patterns in workspace member paths so the CLI would watch all matching pathhs.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@1.5.9`\n\n## \\[1.5.8]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@1.5.8`\n\n## \\[1.5.7]\n\n### Bug Fixes\n\n- [`1d5aa38a`](https://www.github.com/tauri-apps/tauri/commit/1d5aa38ae418ea31f593590b6d32cf04d3bfd8c1)([#8162](https://www.github.com/tauri-apps/tauri/pull/8162)) Fixes errors on command output, occuring when the output stream contains an invalid UTF-8 character, or ends with a multi-bytes UTF-8 character.\n- [`f26d9f08`](https://www.github.com/tauri-apps/tauri/commit/f26d9f0884f63f61b9f4d4fac15e6b251163793e)([#8263](https://www.github.com/tauri-apps/tauri/pull/8263)) Fixes an issue in the NSIS installer which caused the uninstallation to leave empty folders on the system if the `resources` feature was used.\n- [`92bc7d0e`](https://www.github.com/tauri-apps/tauri/commit/92bc7d0e16157434330a1bcf1eefda6f0f1e5f85)([#8233](https://www.github.com/tauri-apps/tauri/pull/8233)) Fixes an issue in the NSIS installer which caused the installation to take much longer than expected when many `resources` were added to the bundle.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@1.5.7`\n\n## \\[1.5.6]\n\n### Bug Fixes\n\n- [`5264e41d`](https://www.github.com/tauri-apps/tauri/commit/5264e41db3763e4c2eb0c3c21bd423fb7bece3e2)([#8082](https://www.github.com/tauri-apps/tauri/pull/8082)) Downgraded `rust-minisign` to `0.7.3` to fix signing updater bundles with empty passwords.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@1.5.6`\n\n## \\[1.5.5]\n\n### Enhancements\n\n- [`9bead42d`](https://www.github.com/tauri-apps/tauri/commit/9bead42dbca0fb6dd7ea0b6bfb2f2308a5c5f992)([#8059](https://www.github.com/tauri-apps/tauri/pull/8059)) Allow rotating the updater private key.\n\n### Bug Fixes\n\n- [`be8e5aa3`](https://www.github.com/tauri-apps/tauri/commit/be8e5aa3071d9bc5d0bd24647e8168f312d11c8d)([#8042](https://www.github.com/tauri-apps/tauri/pull/8042)) Fixes duplicated newlines on command outputs.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@1.5.5`\n\n## \\[1.5.4]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@1.5.4`\n\n## \\[1.5.3]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@1.5.3`\n\n## \\[1.5.2]\n\n### Dependencies\n\n- Upgraded to `tauri-cli@1.5.2`\n\n## \\[1.5.1]\n\n### Bug Fixes\n\n- [`d6eb46cf`](https://www.github.com/tauri-apps/tauri/commit/d6eb46cf1116d147121f6b6db9d390b5e2fb238d)([#7934](https://www.github.com/tauri-apps/tauri/pull/7934)) On macOS, fix the `apple-id` option name when using `notarytools submit`.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@1.5.1`\n\n## \\[1.5.0]\n\n### New Features\n\n- [`e1526626`](https://www.github.com/tauri-apps/tauri/commit/e152662687ece7a62d383923a50751cc0dd34331)([#7723](https://www.github.com/tauri-apps/tauri/pull/7723)) Support Bun package manager in CLI\n\n### Enhancements\n\n- [`13279917`](https://www.github.com/tauri-apps/tauri/commit/13279917d4cae071d0ce3a686184d48af079f58a)([#7713](https://www.github.com/tauri-apps/tauri/pull/7713)) Add version of Rust Tauri CLI installed with Cargo to `tauri info` command.\n\n### Bug Fixes\n\n- [`dad4f54e`](https://www.github.com/tauri-apps/tauri/commit/dad4f54eec9773d2ea6233a7d9fd218741173823)([#7277](https://www.github.com/tauri-apps/tauri/pull/7277)) Removed the automatic version check of the CLI that ran after `tauri` commands which caused various issues.\n\n### Dependencies\n\n- Upgraded to `tauri-cli@1.5.0`\n\n## \\[1.4.0]\n\n### New Features\n\n- [`0ddbb3a1`](https://www.github.com/tauri-apps/tauri/commit/0ddbb3a1dc1961ba5c6c1a60081513c1380c8af1)([#7015](https://www.github.com/tauri-apps/tauri/pull/7015)) Provide prebuilt CLIs for Windows ARM64 targets.\n- [`35cd751a`](https://www.github.com/tauri-apps/tauri/commit/35cd751adc6fef1f792696fa0cfb471b0bf99374)([#5176](https://www.github.com/tauri-apps/tauri/pull/5176)) Added the `desktop_template` option on `tauri.conf.json > tauri > bundle > deb`.\n- [`6c5ade08`](https://www.github.com/tauri-apps/tauri/commit/6c5ade08d97844bb685789d30e589400bbe3e04c)([#4537](https://www.github.com/tauri-apps/tauri/pull/4537)) Added `tauri completions` to generate shell completions scripts.\n- [`e092f799`](https://www.github.com/tauri-apps/tauri/commit/e092f799469ff32c7d1595d0f07d06fd2dab5c29)([#6887](https://www.github.com/tauri-apps/tauri/pull/6887)) Add `nsis > template` option to specify custom NSIS installer template.\n\n### Enhancements\n\n- [`d75c1b82`](https://www.github.com/tauri-apps/tauri/commit/d75c1b829bd96d9e3a672bcc79120597d5ada4a0)([#7181](https://www.github.com/tauri-apps/tauri/pull/7181)) Print a useful error when `updater` bundle target is specified without an updater-enabled target.\n- [`52474e47`](https://www.github.com/tauri-apps/tauri/commit/52474e479d695865299d8c8d868fb98b99731020)([#7141](https://www.github.com/tauri-apps/tauri/pull/7141)) Enhance injection of Cargo features.\n- [`2659ca1a`](https://www.github.com/tauri-apps/tauri/commit/2659ca1ab4799a5bda65c229c149e98bd01eb1ee)([#6900](https://www.github.com/tauri-apps/tauri/pull/6900)) Add `rustls` as default Cargo feature.\n\n### Bug Fixes\n\n- [`3cb7a3e6`](https://www.github.com/tauri-apps/tauri/commit/3cb7a3e642bb10ee90dc1d24daa48b8c8c15c9ce)([#6997](https://www.github.com/tauri-apps/tauri/pull/6997)) Fix built-in devserver adding hot-reload code to non-html files.\n- [`fb7ef8da`](https://www.github.com/tauri-apps/tauri/commit/fb7ef8dacd9ade96976c84d22507782cdaf38acf)([#6667](https://www.github.com/tauri-apps/tauri/pull/6667)) Fix nodejs binary regex when `0` is in the version name, for example `node-20`\n- [`1253bbf7`](https://www.github.com/tauri-apps/tauri/commit/1253bbf7ae11a87887e0b3bd98cc26dbb98c8130)([#7013](https://www.github.com/tauri-apps/tauri/pull/7013)) Fixes Cargo.toml feature rewriting.\n\n## \\[1.3.1]\n\n- Correctly escape XML for resource files in WiX bundler.\n  - Bumped due to a bump in tauri-bundler.\n    - Bumped due to a bump in cli.rs.\n  - [6a6b1388](https://www.github.com/tauri-apps/tauri/commit/6a6b1388ea5787aea4c0e0b0701a4772087bbc0f) fix(bundler): correctly escape resource xml, fixes [#6853](https://www.github.com/tauri-apps/tauri/pull/6853) ([#6855](https://www.github.com/tauri-apps/tauri/pull/6855)) on 2023-05-04\n\n- Added the following languages to the NSIS bundler:\n\n- `Spanish`\n\n- `SpanishInternational`\n\n- Bumped due to a bump in tauri-bundler.\n  - Bumped due to a bump in cli.rs.\n\n- [422b4817](https://www.github.com/tauri-apps/tauri/commit/422b48179856504e980a156500afa8e22c44d357) Add Spanish and SpanishInternational languages ([#6871](https://www.github.com/tauri-apps/tauri/pull/6871)) on 2023-05-06\n\n- Correctly escape arguments in NSIS script to fix bundling apps that use non-default WebView2 install modes.\n  - Bumped due to a bump in tauri-bundler.\n    - Bumped due to a bump in cli.rs.\n  - [2915bd06](https://www.github.com/tauri-apps/tauri/commit/2915bd068ed40dc01a363b69212c6b6f2d3ec01e) fix(bundler): Fix webview install modes in NSIS bundler ([#6854](https://www.github.com/tauri-apps/tauri/pull/6854)) on 2023-05-04\n\n## \\[1.3.0]\n\n- Add `--ci` flag and respect the `CI` environment variable on the `signer generate` command. In this case the default password will be an empty string and the CLI will not prompt for a value.\n  - [8fb1df8a](https://www.github.com/tauri-apps/tauri/commit/8fb1df8aa65a52cdb4a7e1bb9dda9b912a7a2895) feat(cli): add `--ci` flag to `signer generate`, closes [#6089](https://www.github.com/tauri-apps/tauri/pull/6089) ([#6097](https://www.github.com/tauri-apps/tauri/pull/6097)) on 2023-01-19\n- Fix Outdated Github Actions in the Plugin Templates `with-api` and `backend`\n  - [a926b49a](https://www.github.com/tauri-apps/tauri/commit/a926b49a01925ca757d391994bfac3beea29599b) Fix Github Actions of Tauri Plugin with-api template ([#6603](https://www.github.com/tauri-apps/tauri/pull/6603)) on 2023-04-03\n- Do not crash on Cargo.toml watcher.\n  - [e8014a7f](https://www.github.com/tauri-apps/tauri/commit/e8014a7f612a1094461ddad63aacc498a2682ff5) fix(cli): do not crash on watcher ([#6303](https://www.github.com/tauri-apps/tauri/pull/6303)) on 2023-02-17\n- Fix crash when nodejs binary has the version in its name, for example `node-18`\n  - [1c8229fb](https://www.github.com/tauri-apps/tauri/commit/1c8229fbe273554c0c97cccee45d5967f5df1b9f) fix(cli.js): detect `node-<version>` binary, closes [#6427](https://www.github.com/tauri-apps/tauri/pull/6427) ([#6432](https://www.github.com/tauri-apps/tauri/pull/6432)) on 2023-03-16\n- Add `--png` option for the `icon` command to generate custom icon sizes.\n  - [9d214412](https://www.github.com/tauri-apps/tauri/commit/9d2144128fc5fad67d8404bce95f82297ebb0e4a) feat(cli): add option to make custom icon sizes, closes [#5121](https://www.github.com/tauri-apps/tauri/pull/5121) ([#5246](https://www.github.com/tauri-apps/tauri/pull/5246)) on 2022-12-27\n- Skip the password prompt on the build command when `TAURI_KEY_PASSWORD` environment variable is empty and the `--ci` argument is provided or the `CI` environment variable is set.\n  - [d4f89af1](https://www.github.com/tauri-apps/tauri/commit/d4f89af18d69fd95a4d8a1ede8442547c6a6d0ee) feat: skip password prompt on the build command if CI is set fixes [#6089](https://www.github.com/tauri-apps/tauri/pull/6089) on 2023-01-18\n- Fix `default-run` not deserialized.\n  - [57c6bf07](https://www.github.com/tauri-apps/tauri/commit/57c6bf07bb380847abdf27c3fff9891d99c1c98c) fix(cli): fix default-run not deserialized ([#6584](https://www.github.com/tauri-apps/tauri/pull/6584)) on 2023-03-30\n- Fixes HTML serialization removing template tags on the dev server.\n  - [314f0e21](https://www.github.com/tauri-apps/tauri/commit/314f0e212fd2b9e452bfe3424cdce2b0bf37b5d7) fix(cli): web_dev_server html template serialization (fix [#6165](https://www.github.com/tauri-apps/tauri/pull/6165)) ([#6166](https://www.github.com/tauri-apps/tauri/pull/6166)) on 2023-01-29\n- Use escaping on Handlebars templates.\n  - [6d6b6e65](https://www.github.com/tauri-apps/tauri/commit/6d6b6e653ea70fc02794f723092cdc860995c259) feat: configure escaping on handlebars templates ([#6678](https://www.github.com/tauri-apps/tauri/pull/6678)) on 2023-05-02\n- Add initial support for building `nsis` bundles on non-Windows platforms.\n  - [60e6f6c3](https://www.github.com/tauri-apps/tauri/commit/60e6f6c3f1605f3064b5bb177992530ff788ccf0) feat(bundler): Add support for creating NSIS bundles on unix hosts ([#5788](https://www.github.com/tauri-apps/tauri/pull/5788)) on 2023-01-19\n- Add `nsis` bundle target\n  - [c94e1326](https://www.github.com/tauri-apps/tauri/commit/c94e1326a7c0767a13128a8b1d327a00156ece12) feat(bundler): add `nsis`, closes [#4450](https://www.github.com/tauri-apps/tauri/pull/4450), closes [#2319](https://www.github.com/tauri-apps/tauri/pull/2319) ([#4674](https://www.github.com/tauri-apps/tauri/pull/4674)) on 2023-01-03\n- Remove default features from Cargo.toml template.\n  - [b08ae637](https://www.github.com/tauri-apps/tauri/commit/b08ae637a0f58b38cbce9b8a1fa0b6c5dc0cfd05) fix(cli): remove default features from template ([#6074](https://www.github.com/tauri-apps/tauri/pull/6074)) on 2023-01-17\n- Use Ubuntu 20.04 to compile the CLI, increasing the minimum libc version required.\n\n## \\[1.2.3]\n\n- Pin `ignore` to `=0.4.18`.\n  - [adcb082b](https://www.github.com/tauri-apps/tauri/commit/adcb082b1651ecb2a6208b093e12f4185aa3fc98) chore(deps): pin `ignore` to =0.4.18 on 2023-01-17\n\n## \\[1.2.2]\n\n- Detect SvelteKit and Vite for the init and info commands.\n  - [9d872ab8](https://www.github.com/tauri-apps/tauri/commit/9d872ab8728b1b121909af434adcd5936e5afb7d) feat(cli): detect SvelteKit and Vite ([#5742](https://www.github.com/tauri-apps/tauri/pull/5742)) on 2022-12-02\n- Detect SolidJS and SolidStart for the init and info commands.\n  - [9e7ce0a8](https://www.github.com/tauri-apps/tauri/commit/9e7ce0a8eef4bf3536645976e3e09162fbf772ab) feat(cli): detect SolidJS and SolidStart ([#5758](https://www.github.com/tauri-apps/tauri/pull/5758)) on 2022-12-08\n- Use older icon types to work around a macOS bug resulting in corrupted 16x16px and 32x32px icons in bundled apps.\n  - [2d545eff](https://www.github.com/tauri-apps/tauri/commit/2d545eff58734ec70f23f11a429d35435cdf090e) fix(cli): corrupted icons in bundled macOS icons ([#5698](https://www.github.com/tauri-apps/tauri/pull/5698)) on 2022-11-28\n\n## \\[1.2.1]\n\n- Fixes injection of Cargo features defined in the configuration file.\n  - [1ecaeb29](https://www.github.com/tauri-apps/tauri/commit/1ecaeb29aa798f591f6488dc6c3a7a8d22f6073e) fix(cli): inject config feature flags when features arg is not provided on 2022-11-18\n\n## \\[1.2.0]\n\n- Detect JSON5 and TOML configuration files in the dev watcher.\n  - [e7ccbd85](https://www.github.com/tauri-apps/tauri/commit/e7ccbd8573f6b9124e80c0b67fa2365729c3c196) feat(cli): detect JSON5 and TOML configuration files in the dev watcher ([#5439](https://www.github.com/tauri-apps/tauri/pull/5439)) on 2022-10-19\n- Log dev watcher file change detection.\n  - [9076d5d2](https://www.github.com/tauri-apps/tauri/commit/9076d5d2e76d432aef475ba403e9ab5bd3b9d2b0) feat(cli): add prompt information when file changing detected, closes [#5417](https://www.github.com/tauri-apps/tauri/pull/5417) ([#5428](https://www.github.com/tauri-apps/tauri/pull/5428)) on 2022-10-19\n- Fix crash when nodejs binary has the version in its name, for example `node18` or when running through deno.\n  - [7a231cd1](https://www.github.com/tauri-apps/tauri/commit/7a231cd1c99101f63354b13bb36223568d2f0a11) fix(cli): detect deno ([#5475](https://www.github.com/tauri-apps/tauri/pull/5475)) on 2022-10-28\n- Changed the project template to not enable all APIs by default.\n  - [582c25a0](https://www.github.com/tauri-apps/tauri/commit/582c25a0f0fa2725d786ec4edd0defe7811ad6e8) refactor(cli): disable api-all on templates ([#5538](https://www.github.com/tauri-apps/tauri/pull/5538)) on 2022-11-03\n\n## \\[1.1.1]\n\n- Fix wrong cli metadata that caused new projects (created through `tauri init`) fail to build\n  - [db26aaf2](https://www.github.com/tauri-apps/tauri/commit/db26aaf2b44ce5335c9223c571ef2b2175e0cd6d) fix: fix wrong cli metadata ([#5214](https://www.github.com/tauri-apps/tauri/pull/5214)) on 2022-09-16\n\n## \\[1.1.0]\n\n- Allow adding `build > beforeBundleCommand` in tauri.conf.json to run a shell command before the bundling phase.\n  - [57ab9847](https://www.github.com/tauri-apps/tauri/commit/57ab9847eb2d8c9a5da584b873b7c072e9ee26bf) feat(cli): add `beforeBundleCommand`, closes [#4879](https://www.github.com/tauri-apps/tauri/pull/4879) ([#4893](https://www.github.com/tauri-apps/tauri/pull/4893)) on 2022-08-09\n- Change `before_dev_command` and `before_build_command` config value to allow configuring the current working directory.\n  - [d6f7d3cf](https://www.github.com/tauri-apps/tauri/commit/d6f7d3cfe8a7ec8d68c8341016c4e0a3103da587) Add cwd option to `before` commands, add wait option to dev [#4740](https://www.github.com/tauri-apps/tauri/pull/4740) [#3551](https://www.github.com/tauri-apps/tauri/pull/3551) ([#4834](https://www.github.com/tauri-apps/tauri/pull/4834)) on 2022-08-02\n- Allow configuring the `before_dev_command` to force the CLI to wait for the command to finish before proceeding.\n  - [d6f7d3cf](https://www.github.com/tauri-apps/tauri/commit/d6f7d3cfe8a7ec8d68c8341016c4e0a3103da587) Add cwd option to `before` commands, add wait option to dev [#4740](https://www.github.com/tauri-apps/tauri/pull/4740) [#3551](https://www.github.com/tauri-apps/tauri/pull/3551) ([#4834](https://www.github.com/tauri-apps/tauri/pull/4834)) on 2022-08-02\n- Check if the default build target is set in the Cargo configuration.\n  - [436f3d8d](https://www.github.com/tauri-apps/tauri/commit/436f3d8d66727f5b64165522f0b55f4ab54bd1ae) feat(cli): load Cargo configuration to check default build target ([#4990](https://www.github.com/tauri-apps/tauri/pull/4990)) on 2022-08-21\n- Use `cargo metadata` to detect the workspace root and target directory.\n  - [fea70eff](https://www.github.com/tauri-apps/tauri/commit/fea70effad219c0794d919f8834fa1a1ffd204c7) refactor(cli): Use `cargo metadata` to detect the workspace root and target directory, closes [#4632](https://www.github.com/tauri-apps/tauri/pull/4632), [#4928](https://www.github.com/tauri-apps/tauri/pull/4928). ([#4932](https://www.github.com/tauri-apps/tauri/pull/4932)) on 2022-08-21\n- Prompt for `beforeDevCommand` and `beforeBuildCommand` in `tauri init`.\n  - [6d4945c9](https://www.github.com/tauri-apps/tauri/commit/6d4945c9f06cd1f7018e1c48686ba682aae817df) feat(cli): prompt for before\\*Command, closes [#4691](https://www.github.com/tauri-apps/tauri/pull/4691) ([#4721](https://www.github.com/tauri-apps/tauri/pull/4721)) on 2022-07-25\n- Added support to configuration files in TOML format (Tauri.toml file).\n  - [ae83d008](https://www.github.com/tauri-apps/tauri/commit/ae83d008f9e1b89bfc8dddaca42aa5c1fbc36f6d) feat: add support to TOML config file `Tauri.toml`, closes [#4806](https://www.github.com/tauri-apps/tauri/pull/4806) ([#4813](https://www.github.com/tauri-apps/tauri/pull/4813)) on 2022-08-02\n- Automatically use any `.taurignore` file as ignore rules for dev watcher and app path finder.\n  - [596fa08d](https://www.github.com/tauri-apps/tauri/commit/596fa08d48e371c7bd29e1ef799119ac8fca0d0b) feat(cli): automatically use `.taurignore`, ref [#4617](https://www.github.com/tauri-apps/tauri/pull/4617) ([#4623](https://www.github.com/tauri-apps/tauri/pull/4623)) on 2022-07-28\n- Enable WiX FIPS compliance when the `TAURI_FIPS_COMPLIANT` environment variable is set to `true`.\n  - [d88b9de7](https://www.github.com/tauri-apps/tauri/commit/d88b9de7aaeaaa2e42e4795dbc2b8642b5ae7a50) feat(core): add `fips_compliant` wix config option, closes [#4541](https://www.github.com/tauri-apps/tauri/pull/4541) ([#4843](https://www.github.com/tauri-apps/tauri/pull/4843)) on 2022-08-04\n- Fixes dev watcher incorrectly exiting the CLI when sequential file updates are detected.\n  - [47fab680](https://www.github.com/tauri-apps/tauri/commit/47fab6809a1e23b3b9a84695e2d91ff0826ba79a) fix(cli): dev watcher incorrectly killing process on multiple file write ([#4684](https://www.github.com/tauri-apps/tauri/pull/4684)) on 2022-07-25\n- Add `libc` field to Node packages.\n  - [f7d2dfc7](https://www.github.com/tauri-apps/tauri/commit/f7d2dfc7a6d086dd1a218d6c1492b3fef8a64f03) chore: add libc field to node packages ([#4856](https://www.github.com/tauri-apps/tauri/pull/4856)) on 2022-08-04\n- Set the `MACOSX_DEPLOYMENT_TARGET` environment variable with the configuration `minimum_system_version` value.\n  - [fa23310f](https://www.github.com/tauri-apps/tauri/commit/fa23310f23cb9e6a02ec2524f1ef394a5b42990e) fix(cli): set MACOSX_DEPLOYMENT_TARGET env var, closes [#4704](https://www.github.com/tauri-apps/tauri/pull/4704) ([#4842](https://www.github.com/tauri-apps/tauri/pull/4842)) on 2022-08-02\n- Added `--no-watch` argument to the `dev` command to disable the file watcher.\n  - [0983d7ce](https://www.github.com/tauri-apps/tauri/commit/0983d7ce7f24ab43f9ae7b5e1177ff244d8885a8) feat(cli): add `--no-watch` argument to the dev command, closes [#4617](https://www.github.com/tauri-apps/tauri/pull/4617) ([#4793](https://www.github.com/tauri-apps/tauri/pull/4793)) on 2022-07-29\n- Validate updater signature matches configured public key.\n  - [b2a8930b](https://www.github.com/tauri-apps/tauri/commit/b2a8930b3c4b72c50ce72e73575f42c9cbe91bad) feat(cli): validate updater private key when signing ([#4754](https://www.github.com/tauri-apps/tauri/pull/4754)) on 2022-07-25\n\n## \\[1.0.5]\n\n- Correctly fill the architecture when building Debian packages targeting ARM64 (aarch64).\n  - Bumped due to a bump in cli.rs.\n  - [635f23b8](https://www.github.com/tauri-apps/tauri/commit/635f23b88adbb8726d628f67840709cd870836dc) fix(bundler): correctly set debian architecture for aarch64 ([#4700](https://www.github.com/tauri-apps/tauri/pull/4700)) on 2022-07-17\n\n## \\[1.0.4]\n\n- Do not capture and force colors of `cargo build` output.\n  - [c635a0da](https://www.github.com/tauri-apps/tauri/commit/c635a0dad437860d54109adffaf245b7c21bc684) refactor(cli): do not capture and force colors of cargo build output ([#4627](https://www.github.com/tauri-apps/tauri/pull/4627)) on 2022-07-12\n- Reduce the amount of allocations when converting cases.\n  - [bc370e32](https://www.github.com/tauri-apps/tauri/commit/bc370e326810446e15b1f50fb962b980114ba16b) feat: reduce the amount of `heck`-related allocations ([#4634](https://www.github.com/tauri-apps/tauri/pull/4634)) on 2022-07-11\n\n## \\[1.0.3]\n\n- Changed the app template to not set the default app menu as it is now set automatically on macOS which is the platform that needs a menu to function properly.\n  - [91055883](https://www.github.com/tauri-apps/tauri/commit/9105588373cc8401bd9ad79bdef26f509b2d76b7) feat: add implicit default menu for macOS only, closes [#4551](https://www.github.com/tauri-apps/tauri/pull/4551) ([#4570](https://www.github.com/tauri-apps/tauri/pull/4570)) on 2022-07-04\n- Improved bundle identifier validation showing the exact source of the configuration value.\n  - [8e3e7fc6](https://www.github.com/tauri-apps/tauri/commit/8e3e7fc64641afc7a6833bc93205e6f525562545) feat(cli): improve bundle identifier validation, closes [#4589](https://www.github.com/tauri-apps/tauri/pull/4589) ([#4596](https://www.github.com/tauri-apps/tauri/pull/4596)) on 2022-07-05\n- Improve configuration deserialization error messages.\n  - [9170c920](https://www.github.com/tauri-apps/tauri/commit/9170c9207044fa561535f624916dfdbaa41ff79d) feat(core): improve config deserialization error messages ([#4607](https://www.github.com/tauri-apps/tauri/pull/4607)) on 2022-07-06\n- Revert the `run` command to run in a separate thread.\n  - [f65eb4f8](https://www.github.com/tauri-apps/tauri/commit/f65eb4f84d8e511cd30d01d20a8223a297f7e584) fix(cli.js): revert `run` command to be nonblocking on 2022-07-04\n- Skip the static link of the `vcruntime140.dll` if the `STATIC_VCRUNTIME` environment variable is set to `false`.\n  - [2e61abaa](https://www.github.com/tauri-apps/tauri/commit/2e61abaa9ae5d7a41ca1fa6505b5d6c368625ce5) feat(cli): allow dynamic link vcruntime, closes [#4565](https://www.github.com/tauri-apps/tauri/pull/4565) ([#4601](https://www.github.com/tauri-apps/tauri/pull/4601)) on 2022-07-06\n- The `TAURI_CONFIG` environment variable now represents the configuration to be merged instead of the entire JSON.\n  - [fa028ebf](https://www.github.com/tauri-apps/tauri/commit/fa028ebf3c8ca7b43a70d283a01dbea86217594f) refactor: do not pass entire config from CLI to core, send patch instead ([#4598](https://www.github.com/tauri-apps/tauri/pull/4598)) on 2022-07-06\n- Watch for Cargo workspace members in the `dev` file watcher.\n  - [dbb8c87b](https://www.github.com/tauri-apps/tauri/commit/dbb8c87b96dec9942b1bf877b29bafb8246514d4) feat(cli): watch Cargo workspaces in the dev command, closes [#4222](https://www.github.com/tauri-apps/tauri/pull/4222) ([#4572](https://www.github.com/tauri-apps/tauri/pull/4572)) on 2022-07-03\n\n## \\[1.0.2]\n\n- Fixes a crash on the `signer sign` command.\n  - [8e808fec](https://www.github.com/tauri-apps/tauri/commit/8e808fece95f2e506acf2c446d37b9913fd67d50) fix(cli.rs): conflicts_with arg doesn't exist closes  ([#4538](https://www.github.com/tauri-apps/tauri/pull/4538)) on 2022-06-30\n\n## \\[1.0.1]\n\n- No longer adds the `pkg-config` dependency to `.deb` packages when the `systemTray` is used.\n  This only works with recent versions of `libappindicator-sys` (including https://github.com/tauri-apps/libappindicator-rs/pull/38),\n  so a `cargo update` may be necessary if you create `.deb` bundles and use the tray feature.\n  - [0e6edeb1](https://www.github.com/tauri-apps/tauri/commit/0e6edeb14f379af1e02a7cebb4e3a5c9e87ebf7f) fix(cli): Don't add `pkg-config` to `deb` ([#4508](https://www.github.com/tauri-apps/tauri/pull/4508)) on 2022-06-29\n- AppImage bundling will now prefer bundling correctly named appindicator library (including `.1` version suffix). With a symlink for compatibility with the old naming.\n  - [bf45ca1d](https://www.github.com/tauri-apps/tauri/commit/bf45ca1df6691c05bdf72c5716cc01e89a7791d4) fix(cli,bundler): prefer AppImage libraries with ABI version ([#4505](https://www.github.com/tauri-apps/tauri/pull/4505)) on 2022-06-29\n- Improve error message when `cargo` is not installed.\n  - [e0e5f772](https://www.github.com/tauri-apps/tauri/commit/e0e5f772430f6349ec99ba891e601331e376e3c7) feat(cli): improve `cargo not found` error message, closes [#4428](https://www.github.com/tauri-apps/tauri/pull/4428) ([#4430](https://www.github.com/tauri-apps/tauri/pull/4430)) on 2022-06-21\n- The app template now only sets the default menu on macOS.\n  - [5105b428](https://www.github.com/tauri-apps/tauri/commit/5105b428c4726b2179cd4b3244350d1a1ee73734) feat(cli): change app template to only set default menu on macOS ([#4518](https://www.github.com/tauri-apps/tauri/pull/4518)) on 2022-06-29\n- Warn if updater is enabled but not in the bundle target list.\n  - [31c15cd2](https://www.github.com/tauri-apps/tauri/commit/31c15cd2bd94dbe39fb94982a15cbe02ac5d8925) docs(config): enhance documentation for bundle targets, closes [#3251](https://www.github.com/tauri-apps/tauri/pull/3251) ([#4418](https://www.github.com/tauri-apps/tauri/pull/4418)) on 2022-06-21\n- Check if target exists and is installed on dev and build commands.\n  - [13b8a240](https://www.github.com/tauri-apps/tauri/commit/13b8a2403d1353a8c3a643fbc6b6e862af68376e) feat(cli): validate target argument ([#4458](https://www.github.com/tauri-apps/tauri/pull/4458)) on 2022-06-24\n- Fixes the covector configuration on the plugin templates.\n  - [b8a64d01](https://www.github.com/tauri-apps/tauri/commit/b8a64d01bab11f955b7bbdf323d0afa1a3db4b64) fix(cli): add prepublish scripts to the plugin templates on 2022-06-19\n- Set the binary name to the product name in development.\n  - [b025b9f5](https://www.github.com/tauri-apps/tauri/commit/b025b9f581ac1a6ae0a26789c2be1e9928fb0282) refactor(cli): set binary name on dev ([#4447](https://www.github.com/tauri-apps/tauri/pull/4447)) on 2022-06-23\n- Allow registering a `.gitignore` file to skip watching some project files and directories via the `TAURI_DEV_WATCHER_IGNORE_FILE` environment variable.\n  - [83186dd8](https://www.github.com/tauri-apps/tauri/commit/83186dd89768407984db35fb67c3cc51f50ea8f5) Read extra ignore file for dev watcher, closes [#4406](https://www.github.com/tauri-apps/tauri/pull/4406) ([#4409](https://www.github.com/tauri-apps/tauri/pull/4409)) on 2022-06-20\n- Fix shebang for `kill-children.sh`.\n  - [35dd51db](https://www.github.com/tauri-apps/tauri/commit/35dd51db6826ec1eed7b90082b9eb6b2a699b627) fix(cli): add shebang for kill-children.sh, closes [#4262](https://www.github.com/tauri-apps/tauri/pull/4262) ([#4416](https://www.github.com/tauri-apps/tauri/pull/4416)) on 2022-06-22\n- Update plugin templates to use newer `tauri-apps/create-pull-request` GitHub action.\n  - [07f90795](https://www.github.com/tauri-apps/tauri/commit/07f9079532a42f3517d96faeaf46cad6176b31ac) chore(cli): update plugin template tauri-apps/create-pull-request on 2022-06-19\n- Use UNIX path separator on the init `$schema` field.\n  - [01053045](https://www.github.com/tauri-apps/tauri/commit/010530459ef62c48eed68ca965f2688accabcf69) chore(cli): use unix path separator on $schema ([#4384](https://www.github.com/tauri-apps/tauri/pull/4384)) on 2022-06-19\n- The `info` command now can check the Cargo lockfile on workspaces.\n  - [12f65219](https://www.github.com/tauri-apps/tauri/commit/12f65219ea75a51ebd38659ddce1563e015a036c) fix(cli): read lockfile from workspace on the info command, closes [#4232](https://www.github.com/tauri-apps/tauri/pull/4232) ([#4423](https://www.github.com/tauri-apps/tauri/pull/4423)) on 2022-06-21\n- Preserve the `Cargo.toml` formatting when the features array is not changed.\n  - [6650e5d6](https://www.github.com/tauri-apps/tauri/commit/6650e5d6720c215530ca1fdccd19bd2948dd6ca3) fix(cli): preserve Cargo manifest formatting when possible ([#4431](https://www.github.com/tauri-apps/tauri/pull/4431)) on 2022-06-21\n- Change the updater signature metadata to include the file name instead of its full path.\n  - [094b3eb3](https://www.github.com/tauri-apps/tauri/commit/094b3eb352bcf5de28414015e7c44290d619ea8c) fix(cli): file name instead of path on updater sig comment, closes [#4467](https://www.github.com/tauri-apps/tauri/pull/4467) ([#4484](https://www.github.com/tauri-apps/tauri/pull/4484)) on 2022-06-27\n- Validate bundle identifier as it must only contain alphanumeric characters, hyphens and periods.\n  - [0674a801](https://www.github.com/tauri-apps/tauri/commit/0674a80129d7c31bc93257849afc0a5069129fed) fix: assert config.bundle.identifier to be only alphanumeric, hyphens or dots. closes [#4359](https://www.github.com/tauri-apps/tauri/pull/4359) ([#4363](https://www.github.com/tauri-apps/tauri/pull/4363)) on 2022-06-17\n\n## \\[1.0.0]\n\n- Upgrade to `stable`!\n  - [f4bb30cc](https://www.github.com/tauri-apps/tauri/commit/f4bb30cc73d6ba9b9ef19ef004dc5e8e6bb901d3) feat(covector): prepare for v1 ([#4351](https://www.github.com/tauri-apps/tauri/pull/4351)) on 2022-06-15\n\n## \\[1.0.0-rc.16]\n\n- Use the default window menu in the app template.\n  - [4c4acc30](https://www.github.com/tauri-apps/tauri/commit/4c4acc3094218dd9cee0f1ad61810c979e0b41fa) feat: implement `Default` for `Menu`, closes [#2398](https://www.github.com/tauri-apps/tauri/pull/2398) ([#4291](https://www.github.com/tauri-apps/tauri/pull/4291)) on 2022-06-15\n\n## \\[1.0.0-rc.15]\n\n- Removed the tray icon from the Debian and AppImage bundles since they are embedded in the binary now.\n  - [4ce8e228](https://www.github.com/tauri-apps/tauri/commit/4ce8e228134cd3f22973b74ef26ca0d165fbbbd9) refactor(core): use `Icon` for tray icons ([#4342](https://www.github.com/tauri-apps/tauri/pull/4342)) on 2022-06-14\n\n## \\[1.0.0-rc.14]\n\n- Set the `TRAY_LIBRARY_PATH` environment variable to make the bundle copy the appindicator library to the AppImage.\n  - [34552444](https://www.github.com/tauri-apps/tauri/commit/3455244436578003a5fbb447b039e5c8971152ec) feat(cli): bundle appindicator library in the AppImage, closes [#3859](https://www.github.com/tauri-apps/tauri/pull/3859) ([#4267](https://www.github.com/tauri-apps/tauri/pull/4267)) on 2022-06-07\n- Set the `APPIMAGE_BUNDLE_GSTREAMER` environment variable to make the bundler copy additional gstreamer files to the AppImage.\n  - [d335fae9](https://www.github.com/tauri-apps/tauri/commit/d335fae92cdcbb0ee18aad4e54558914afa3e778) feat(bundler): bundle additional gstreamer files, closes [#4092](https://www.github.com/tauri-apps/tauri/pull/4092) ([#4271](https://www.github.com/tauri-apps/tauri/pull/4271)) on 2022-06-10\n- Configure the AppImage bundler to copy the `/usr/bin/xdg-open` binary if it exists and the shell `open` API is enabled.\n  - [2322ac11](https://www.github.com/tauri-apps/tauri/commit/2322ac11cf6290c6bf65413048a049c8072f863b) fix(bundler): bundle `/usr/bin/xdg-open` in appimage if open API enabled ([#4265](https://www.github.com/tauri-apps/tauri/pull/4265)) on 2022-06-04\n- Fixes multiple occurrences handling of the `bundles` and `features` arguments.\n  - [f685df39](https://www.github.com/tauri-apps/tauri/commit/f685df399a5a05480b6e4f5d92da71f3b87895ef) fix(cli): parsing of arguments with multiple values, closes [#4231](https://www.github.com/tauri-apps/tauri/pull/4231) ([#4233](https://www.github.com/tauri-apps/tauri/pull/4233)) on 2022-05-29\n- Log command output in real time instead of waiting for it to finish.\n  - [76d1eaae](https://www.github.com/tauri-apps/tauri/commit/76d1eaaebda5c8f6b0d41bf6587945e98cd441f3) feat(cli): debug command output in real time ([#4318](https://www.github.com/tauri-apps/tauri/pull/4318)) on 2022-06-12\n- Configure the `STATIC_VCRUNTIME` environment variable so `tauri-build` statically links it on the build command.\n  - [d703d27a](https://www.github.com/tauri-apps/tauri/commit/d703d27a707edc028f13b35603205da1133fcc2b) fix(build): statically link VC runtime only on `tauri build` ([#4292](https://www.github.com/tauri-apps/tauri/pull/4292)) on 2022-06-07\n- Use the `TAURI_TRAY` environment variable to determine which package should be added to the Debian `depends` section. Possible values are `ayatana` and `gtk`.\n  - [6216eb49](https://www.github.com/tauri-apps/tauri/commit/6216eb49e72863bfb6d4c9edb8827b21406ac393) refactor(core): drop `ayatana-tray` and `gtk-tray` Cargo features ([#4247](https://www.github.com/tauri-apps/tauri/pull/4247)) on 2022-06-02\n\n## \\[1.0.0-rc.13]\n\n- Check if `$CWD/src-tauri/tauri.conf.json` exists before walking through the file tree to find the tauri dir in case the whole project is gitignored.\n  - [bd8f3e29](https://www.github.com/tauri-apps/tauri/commit/bd8f3e298a0cb71809f2e93cc3ebc8e6e5b6a626) fix(cli): manual config lookup to handle gitignored folders, fixes [#3527](https://www.github.com/tauri-apps/tauri/pull/3527) ([#4224](https://www.github.com/tauri-apps/tauri/pull/4224)) on 2022-05-26\n- Statically link the Visual C++ runtime instead of using a merge module on the installer.\n  - [bb061509](https://www.github.com/tauri-apps/tauri/commit/bb061509fb674bef86ecbc1de3aa8f3e367a9907) refactor(core): statically link vcruntime, closes [#4122](https://www.github.com/tauri-apps/tauri/pull/4122) ([#4227](https://www.github.com/tauri-apps/tauri/pull/4227)) on 2022-05-27\n\n## \\[1.0.0-rc.12]\n\n- Properly fetch the NPM dependency information when using Yarn 2+.\n  - [cdfa6255](https://www.github.com/tauri-apps/tauri/commit/cdfa62551115586725bd3e4c04f12c5256f20790) fix(cli): properly read info when using yarn 2+, closes [#4106](https://www.github.com/tauri-apps/tauri/pull/4106) ([#4193](https://www.github.com/tauri-apps/tauri/pull/4193)) on 2022-05-23\n\n## \\[1.0.0-rc.11]\n\n- Allow configuring the display options for the MSI execution allowing quieter updates.\n  - [9f2c3413](https://www.github.com/tauri-apps/tauri/commit/9f2c34131952ea83c3f8e383bc3cec7e1450429f) feat(core): configure msiexec display options, closes [#3951](https://www.github.com/tauri-apps/tauri/pull/3951) ([#4061](https://www.github.com/tauri-apps/tauri/pull/4061)) on 2022-05-15\n\n## \\[1.0.0-rc.10]\n\n- Resolve binary file extension from target triple instead of compile-time checks to allow cross compilation.\n  - [4562e671](https://www.github.com/tauri-apps/tauri/commit/4562e671e4795e9386429348bf738f7078706945) fix(build): append .exe binary based on target triple instead of running OS, closes [#3870](https://www.github.com/tauri-apps/tauri/pull/3870) ([#4032](https://www.github.com/tauri-apps/tauri/pull/4032)) on 2022-05-03\n- Fixes text overflow on `tauri dev` on Windows.\n  - [094534d1](https://www.github.com/tauri-apps/tauri/commit/094534d138a9286e4746b61adff2da616e3b6a61) fix(cli): dev command stderr text overflow on Windows, closes [#3995](https://www.github.com/tauri-apps/tauri/pull/3995) ([#4000](https://www.github.com/tauri-apps/tauri/pull/4000)) on 2022-04-29\n- Improve CLI's logging output, making use of the standard rust `log` system.\n  - [35f21471](https://www.github.com/tauri-apps/tauri/commit/35f2147161e6697cbd2824681eeaf870b5a991c2) feat(cli): Improve CLI logging ([#4060](https://www.github.com/tauri-apps/tauri/pull/4060)) on 2022-05-07\n- Don't override the default keychain on macOS while code signing.\n  - [a4fcaf1d](https://www.github.com/tauri-apps/tauri/commit/a4fcaf1d04aafc3b4d42186f0fb386797d959a9d) fix: don't override default keychain, closes [#4008](https://www.github.com/tauri-apps/tauri/pull/4008) ([#4053](https://www.github.com/tauri-apps/tauri/pull/4053)) on 2022-05-05\n- - Remove startup delay in `tauri dev` caused by checking for a newer cli version. The check is now done upon process exit.\n- Add `TAURI_SKIP_UPDATE_CHECK` env variable to skip checking for a newer CLI version.\n- [bbabc8cd](https://www.github.com/tauri-apps/tauri/commit/bbabc8cd1ea2c1f6806610fd2d533c99305d320c) fix(cli.rs): remove startup delay in `tauri dev` ([#3999](https://www.github.com/tauri-apps/tauri/pull/3999)) on 2022-04-29\n- Fix `tauri info` panic when a package isn't installed.\n  - [4f0f3187](https://www.github.com/tauri-apps/tauri/commit/4f0f3187c9e69262ef28350331b368c831ab930a) fix(cli.rs): fix `tauri info` panic when a package isn't installed, closes [#3985](https://www.github.com/tauri-apps/tauri/pull/3985) ([#3996](https://www.github.com/tauri-apps/tauri/pull/3996)) on 2022-04-29\n- Added `$schema` support to `tauri.conf.json`.\n  - [715cbde3](https://www.github.com/tauri-apps/tauri/commit/715cbde3842a916c4ebeab2cab348e1774b5c192) feat(config): add `$schema` to `tauri.conf.json`, closes [#3464](https://www.github.com/tauri-apps/tauri/pull/3464) ([#4031](https://www.github.com/tauri-apps/tauri/pull/4031)) on 2022-05-03\n- **Breaking change:** The `dev` command now reads the custom config file from CWD instead of the Tauri folder.\n  - [a1929c6d](https://www.github.com/tauri-apps/tauri/commit/a1929c6dacccd00af4cdbcc4d29cfb98d8428f55) fix(cli): always read custom config file from CWD, closes [#4067](https://www.github.com/tauri-apps/tauri/pull/4067) ([#4074](https://www.github.com/tauri-apps/tauri/pull/4074)) on 2022-05-07\n- Fixes a Powershell crash when sending SIGINT to the dev command.\n  - [32048486](https://www.github.com/tauri-apps/tauri/commit/320484866b83ecabb01eb58d158e0fedd9dd08be) fix(cli): powershell crashing on SIGINT, closes [#3997](https://www.github.com/tauri-apps/tauri/pull/3997) ([#4007](https://www.github.com/tauri-apps/tauri/pull/4007)) on 2022-04-29\n- Prevent building when the bundle identifier is the default `com.tauri.dev`.\n  - [95726ebb](https://www.github.com/tauri-apps/tauri/commit/95726ebb6180d371be44bff9f16ca1eee049006a) feat(cli): prevent default bundle identifier from building, closes [#4041](https://www.github.com/tauri-apps/tauri/pull/4041) ([#4042](https://www.github.com/tauri-apps/tauri/pull/4042)) on 2022-05-04\n\n## \\[1.0.0-rc.9]\n\n- Exit CLI when Cargo returns a non-compilation error in `tauri dev`.\n  - [b5622882](https://www.github.com/tauri-apps/tauri/commit/b5622882cf3748e1e5a90915f415c0cd922aaaf8) fix(cli): exit on non-compilation Cargo errors, closes [#3930](https://www.github.com/tauri-apps/tauri/pull/3930) ([#3942](https://www.github.com/tauri-apps/tauri/pull/3942)) on 2022-04-22\n- Notify CLI update when running `tauri dev`.\n  - [a649aad7](https://www.github.com/tauri-apps/tauri/commit/a649aad7ad26d4578699370d6e63d80edeca1f97) feat(cli): check and notify about updates on `tauri dev`, closes [#3789](https://www.github.com/tauri-apps/tauri/pull/3789) ([#3960](https://www.github.com/tauri-apps/tauri/pull/3960)) on 2022-04-25\n- Kill the `beforeDevCommand` and app processes if the dev command returns an error.\n  - [485c9743](https://www.github.com/tauri-apps/tauri/commit/485c97438ac956d86bcf3794ceaa626bef968a4e) fix(cli): kill beforeDevCommand if dev code returns an error ([#3907](https://www.github.com/tauri-apps/tauri/pull/3907)) on 2022-04-19\n- Fix `info` command showing outdated text for latest versions.\n  - [73a4b74a](https://www.github.com/tauri-apps/tauri/commit/73a4b74aea8544e6fda51c1f6697630b0768072c) fix(cli.rs/info):  don't show outdated text for latest versions ([#3829](https://www.github.com/tauri-apps/tauri/pull/3829)) on 2022-04-02\n- **Breaking change:** Enable default Cargo features except `tauri/custom-protocol` on the dev command.\n  - [f2a30d8b](https://www.github.com/tauri-apps/tauri/commit/f2a30d8bc54fc3ba49e16f69a413eca5f61a9b1f) refactor(core): use ayatana appindicator by default, keep option to use gtk ([#3916](https://www.github.com/tauri-apps/tauri/pull/3916)) on 2022-04-19\n- Kill the `beforeDevCommand` process recursively on Unix.\n  - [e251e1b0](https://www.github.com/tauri-apps/tauri/commit/e251e1b0991d26ab10aea33cfb228f3e7f0f85b5) fix(cli): kill before dev command recursively on Unix, closes [#2794](https://www.github.com/tauri-apps/tauri/pull/2794) ([#3848](https://www.github.com/tauri-apps/tauri/pull/3848)) on 2022-04-03\n\n## \\[1.0.0-rc.8]\n\n- Allows the `tauri.conf.json` file to be git ignored on the path lookup function.\n  - [cc7c2d77](https://www.github.com/tauri-apps/tauri/commit/cc7c2d77da2e4a39ec2a97b080d41a719e6d0161) feat(cli): allow conf path to be gitignored, closes [#3636](https://www.github.com/tauri-apps/tauri/pull/3636) ([#3683](https://www.github.com/tauri-apps/tauri/pull/3683)) on 2022-03-13\n- Remove `minimumSystemVersion: null` from the application template configuration.\n  - [c81534eb](https://www.github.com/tauri-apps/tauri/commit/c81534ebd873c358e0346c7949aeb171803149a5) feat(cli): use default macOS minimum system version when it is empty ([#3658](https://www.github.com/tauri-apps/tauri/pull/3658)) on 2022-03-13\n- Improve readability of the `info` subcommand output.\n  - [49d2f13f](https://www.github.com/tauri-apps/tauri/commit/49d2f13fc07d763d5de9bf4b19d00c901776c11d) feat(cli): colorful cli ([#3635](https://www.github.com/tauri-apps/tauri/pull/3635)) on 2022-03-08\n- Fixes DMG bundling on macOS 12.3.\n  - [348a1ab5](https://www.github.com/tauri-apps/tauri/commit/348a1ab59d2697478a594016016f1fccbf1ac054) fix(bundler): DMG bundling on macOS 12.3 cannot use bless, closes [#3719](https://www.github.com/tauri-apps/tauri/pull/3719) ([#3721](https://www.github.com/tauri-apps/tauri/pull/3721)) on 2022-03-18\n- Fixes resources bundling on Windows when the path is on the root of the Tauri folder.\n  - [4c84559e](https://www.github.com/tauri-apps/tauri/commit/4c84559e1f3019e7aa2666b10a1a0bd97bb09d24) fix(cli): root resource bundling on Windows, closes [#3539](https://www.github.com/tauri-apps/tauri/pull/3539) ([#3685](https://www.github.com/tauri-apps/tauri/pull/3685)) on 2022-03-13\n\n## \\[1.0.0-rc.6]\n\n- Added `tsp` config option under `tauri > bundle > windows`, which enables Time-Stamp Protocol (RFC 3161) for the timestamping\n  server under code signing on Windows if set to `true`.\n  - [bdd5f7c2](https://www.github.com/tauri-apps/tauri/commit/bdd5f7c2f03af4af8b60a9527e55bb18525d989b) fix: add support for Time-Stamping Protocol for Windows codesigning (fix [#3563](https://www.github.com/tauri-apps/tauri/pull/3563)) ([#3570](https://www.github.com/tauri-apps/tauri/pull/3570)) on 2022-03-07\n- Added `i686-pc-windows-msvc` to the prebuilt targets.\n  - [fb6744da](https://www.github.com/tauri-apps/tauri/commit/fb6744daa45165c7e00e5c01f7df0880d69ca509) feat(cli.js): add 32bit cli for windows ([#3540](https://www.github.com/tauri-apps/tauri/pull/3540)) on 2022-02-24\n- Change the `plugin init` templates to use the new `tauri::plugin::Builder` syntax.\n  - [f7acb061](https://www.github.com/tauri-apps/tauri/commit/f7acb061e4d1ecdbfe182793587632d7ba6d8eff) feat(cli): use plugin::Builder syntax on the plugin template ([#3606](https://www.github.com/tauri-apps/tauri/pull/3606)) on 2022-03-03\n\n## \\[1.0.0-rc.5]\n\n- Improve \"waiting for your dev server to start\" message.\n  - [5999379f](https://www.github.com/tauri-apps/tauri/commit/5999379fb06052a115f04f99274ab46d1eefd659) chore(cli): improve \"waiting for dev server\" message, closes [#3491](https://www.github.com/tauri-apps/tauri/pull/3491) ([#3504](https://www.github.com/tauri-apps/tauri/pull/3504)) on 2022-02-18\n- Do not panic if the updater private key password is wrong.\n  - [17f17a80](https://www.github.com/tauri-apps/tauri/commit/17f17a80f818bcc20c387583a6ff00a8e07ec533) fix(cli): do not panic if private key password is wrong, closes [#3449](https://www.github.com/tauri-apps/tauri/pull/3449) ([#3495](https://www.github.com/tauri-apps/tauri/pull/3495)) on 2022-02-17\n- Check the current folder before checking the directories on the app and tauri dir path lookup function.\n  - [a06de376](https://www.github.com/tauri-apps/tauri/commit/a06de3760184caa71acfe7a2fe2189a033b565f5) fix(cli): path lookup should not check subfolder before the current one ([#3465](https://www.github.com/tauri-apps/tauri/pull/3465)) on 2022-02-15\n- Fixes the signature of the `signer sign` command to not have duplicated short flags.\n  - [a9755514](https://www.github.com/tauri-apps/tauri/commit/a975551461f3698db3f3b6afa5101189aaeeada9) fix(cli): duplicated short flag for `signer sign`, closes [#3483](https://www.github.com/tauri-apps/tauri/pull/3483) ([#3492](https://www.github.com/tauri-apps/tauri/pull/3492)) on 2022-02-17\n\n## \\[1.0.0-rc.4]\n\n- Change the `run` function to take a callback and run asynchronously instead of blocking the event loop.\n  - [cd9a20b9](https://www.github.com/tauri-apps/tauri/commit/cd9a20b9ab013759b4bdb742f358988022450795) refactor(cli.js): run on separate thread ([#3436](https://www.github.com/tauri-apps/tauri/pull/3436)) on 2022-02-13\n- Improve error message when the dev runner command fails.\n  - [759d1afb](https://www.github.com/tauri-apps/tauri/commit/759d1afb86f3657f6071a2ae39c9be21e20ed22c) feat(cli): improve error message when dev runner command fails ([#3447](https://www.github.com/tauri-apps/tauri/pull/3447)) on 2022-02-13\n- Show full error message from `cli.rs` instead of just the outermost underlying error message.\n  - [63826010](https://www.github.com/tauri-apps/tauri/commit/63826010d1f38544f36afd3aac67c45d4608d15b) feat(cli.js): show full error message ([#3442](https://www.github.com/tauri-apps/tauri/pull/3442)) on 2022-02-13\n- Increase `tauri.conf.json` directory lookup depth to `3` and allow changing it with the `TAURI_PATH_DEPTH` environment variable.\n  - [c6031c70](https://www.github.com/tauri-apps/tauri/commit/c6031c7070c6bb7539bbfdfe42cb73012829c910) feat(cli): increase lookup depth, add env var option ([#3451](https://www.github.com/tauri-apps/tauri/pull/3451)) on 2022-02-13\n- Added `tauri-build`, `tao` and `wry` version to the `info` command output.\n  - [16f1173f](https://www.github.com/tauri-apps/tauri/commit/16f1173f456b1db543d0160df2c9828708bfc68a) feat(cli): add tao and wry version to the `info` output ([#3443](https://www.github.com/tauri-apps/tauri/pull/3443)) on 2022-02-13\n\n## \\[1.0.0-rc.3]\n\n- Change default value for the `freezePrototype` configuration to `false`.\n  - Bumped due to a bump in cli.rs.\n  - [3a4c0160](https://www.github.com/tauri-apps/tauri/commit/3a4c01606184be762adee055ddac803de0d28527) fix(core): change default `freezePrototype` to false, closes [#3416](https://www.github.com/tauri-apps/tauri/pull/3416) [#3406](https://www.github.com/tauri-apps/tauri/pull/3406) ([#3423](https://www.github.com/tauri-apps/tauri/pull/3423)) on 2022-02-12\n\n## \\[1.0.0-rc.2]\n\n- Fixes Tauri path resolution on projects without Git or a `.gitignore` file.\n  - [d8acbe11](https://www.github.com/tauri-apps/tauri/commit/d8acbe11492bd990e6983c7e63e0f1a8f1ea5c7c) fix(cli.rs): app path resolution on projects without git, closes [#3409](https://www.github.com/tauri-apps/tauri/pull/3409) ([#3410](https://www.github.com/tauri-apps/tauri/pull/3410)) on 2022-02-11\n\n## \\[1.0.0-rc.1]\n\n- Fix `init` command prompting for values even if the argument has been provided on the command line.\n  - [def76840](https://www.github.com/tauri-apps/tauri/commit/def76840257a1447723ecda13c807cf0c881f083) fix(cli.rs): do not prompt for `init` values if arg set ([#3400](https://www.github.com/tauri-apps/tauri/pull/3400)) on 2022-02-11\n  - [41052dee](https://www.github.com/tauri-apps/tauri/commit/41052deeda2a00ee2b8ec2041c9c87c11de82ab2) fix(covector): add cli.js to change files on 2022-02-11\n- Fixes CLI freezing when running `light.exe` on Windows without the `--verbose` flag.\n  - [8beab636](https://www.github.com/tauri-apps/tauri/commit/8beab6363491e2a8757cc9fc0fa1eccc98ece916) fix(cli): build freezing on Windows, closes [#3399](https://www.github.com/tauri-apps/tauri/pull/3399) ([#3402](https://www.github.com/tauri-apps/tauri/pull/3402)) on 2022-02-11\n- Respect `.gitignore` configuration when looking for the folder with the `tauri.conf.json` file.\n  - [9c6c5a8c](https://www.github.com/tauri-apps/tauri/commit/9c6c5a8c52c6460d0b0a1a55300e1828262994ba) perf(cli.rs): improve performance of tauri dir lookup reading .gitignore ([#3405](https://www.github.com/tauri-apps/tauri/pull/3405)) on 2022-02-11\n  - [41052dee](https://www.github.com/tauri-apps/tauri/commit/41052deeda2a00ee2b8ec2041c9c87c11de82ab2) fix(covector): add cli.js to change files on 2022-02-11\n\n## \\[1.0.0-rc.0]\n\n- Do not force Tauri application code on `src-tauri` folder and use a glob pattern to look for a subfolder with a `tauri.conf.json` file.\n  - [a8cff6b3](https://www.github.com/tauri-apps/tauri/commit/a8cff6b3bc3288a53d7cdc5b3cb95d371309d2d6) feat(cli): do not enforce `src-tauri` folder structure, closes [#2643](https://www.github.com/tauri-apps/tauri/pull/2643) ([#2654](https://www.github.com/tauri-apps/tauri/pull/2654)) on 2021-09-27\n- Added CommonJS output to the `dist` folder.\n  - [205b0dc8](https://www.github.com/tauri-apps/tauri/commit/205b0dc8f30bf70902979a2c0a08c8bc8c8e5360) feat(cli.js): add CommonJS dist files ([#2646](https://www.github.com/tauri-apps/tauri/pull/2646)) on 2021-09-23\n- Fixes `.ico` icon generation.\n  - [11db96e4](https://www.github.com/tauri-apps/tauri/commit/11db96e440e6cadc1c70992d07bfea3c448208b1) fix(cli.js): `.ico` icon generation, closes [#2692](https://www.github.com/tauri-apps/tauri/pull/2692) ([#2694](https://www.github.com/tauri-apps/tauri/pull/2694)) on 2021-10-02\n- Automatically unplug `@tauri-apps/cli` in yarn 2+ installations to fix the download of the rust-cli.\n  - [1e336b68](https://www.github.com/tauri-apps/tauri/commit/1e336b6872c3b78caf7c2c6e71e03016c6abdacf) fix(cli.js): Fix package installation on yarn 2+ ([#3012](https://www.github.com/tauri-apps/tauri/pull/3012)) on 2021-12-09\n- Read `package.json` and check for a `tauri` object containing the `appPath` string, which points to the tauri crate path.\n  - [fb2b9a52](https://www.github.com/tauri-apps/tauri/commit/fb2b9a52f594830c0a68ea40ea429a09892f7ba7) feat(cli.js): allow configuring tauri app path on package.json [#2752](https://www.github.com/tauri-apps/tauri/pull/2752) ([#3035](https://www.github.com/tauri-apps/tauri/pull/3035)) on 2021-12-09\n- Removed the `icon` command, now exposed as a separate package, see https://github.com/tauri-apps/tauricon.\n  - [58030172](https://www.github.com/tauri-apps/tauri/commit/58030172eddb2403a84b56a21b5bdcebca42c265) feat(tauricon): remove from cli ([#3293](https://www.github.com/tauri-apps/tauri/pull/3293)) on 2022-02-07\n"
  },
  {
    "path": "packages/cli/Cargo.toml",
    "content": "[package]\nedition = \"2021\"\nname = \"tauri-cli-node\"\nversion = \"0.0.0\"\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nnapi = \"3\"\nnapi-derive = \"3\"\ntauri-cli = { path = \"../../crates/tauri-cli\", default-features = false }\nlog = \"0.4.21\"\n\n[build-dependencies]\nnapi-build = \"2.2\"\n\n[features]\ndefault = [\"tauri-cli/default\"]\nnative-tls = [\"tauri-cli/native-tls\"]\nnative-tls-vendored = [\"tauri-cli/native-tls-vendored\"]\n"
  },
  {
    "path": "packages/cli/LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "packages/cli/LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2017 - Present Tauri Apps Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/cli/README.md",
    "content": "# @tauri-apps/cli\n\n <img align=\"right\" src=\"https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png\" height=\"128\" width=\"128\">\n\n[![status](https://img.shields.io/badge/status-stable-blue.svg)](https://github.com/tauri-apps/tauri/tree/dev)\n[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri)\n[![test cli](https://img.shields.io/github/actions/workflow/status/tauri-apps/tauri/test-cli-js.yml?label=test%20cli&logo=github)](https://github.com/tauri-apps/tauri/actions/workflows/test-cli-js.yml)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield)\n[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S)\n[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app)\n[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation)\n[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri)\n\n| Component       | Version                                               |\n| --------------- | ----------------------------------------------------- |\n| @tauri-apps/cli | ![](https://img.shields.io/npm/v/@tauri-apps/cli.svg) |\n\n## About Tauri\n\nTauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.\n\nTauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.\n\n## This module\n\nWritten in Typescript and packaged such that it can be used with `npm`, `pnpm`, `yarn`, and `bun`, this library provides a node.js runner for common tasks when using Tauri, like `pnpm tauri dev`. For the most part it is a wrapper around [tauri-cli](https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-cli).\n\nTo learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.\n\n## Installation\n\nThe preferred method is to install this module locally as a development dependency:\n\n```\n$ pnpm add -D @tauri-apps/cli\n$ yarn add -D @tauri-apps/cli\n$ npm add -D @tauri-apps/cli\n```\n\n## Semver\n\n**tauri** is following [Semantic Versioning 2.0](https://semver.org/).\n\n## Licenses\n\nCode: (c) 2019 - 2021 - The Tauri Programme within The Commons Conservancy.\n\nMIT or MIT/Apache 2.0 where applicable.\n\nLogo: CC-BY-NC-ND\n\n- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)\n"
  },
  {
    "path": "packages/cli/__tests__/fixtures/empty/.gitignore",
    "content": "src-tauri\n"
  },
  {
    "path": "packages/cli/__tests__/fixtures/empty/dist/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <body>\n    <div></div>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/cli/__tests__/fixtures/empty/package.json",
    "content": "{\n  \"scripts\": {\n    \"build\": \"\"\n  }\n}\n"
  },
  {
    "path": "packages/cli/__tests__/template.spec.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nimport { resolve } from 'node:path'\nimport { spawnSync } from 'node:child_process'\nimport {\n  existsSync,\n  readFileSync,\n  writeFileSync,\n  rmSync,\n  renameSync\n} from 'node:fs'\nimport { beforeAll, describe, it } from 'vitest'\n\n// Build CLI before tests, for local usage only.\n// CI builds the CLI on different platforms and architectures\nif (!process.env.CI) {\n  beforeAll(() => {\n    const cliDir = resolve(__dirname, '..')\n    exec('pnpm', ['build:debug'], { cwd: cliDir })\n  })\n}\n\ndescribe('[CLI] @tauri-apps/cli template', () => {\n  it('init a project and builds it', { timeout: 15 * 60 * 1000 }, async () => {\n    const cwd = process.cwd()\n    const fixturePath = resolve(__dirname, './fixtures/empty')\n    const tauriFixturePath = resolve(fixturePath, 'src-tauri')\n    const outPath = resolve(tauriFixturePath, 'target')\n    const cacheOutPath = resolve(fixturePath, 'target')\n\n    process.chdir(fixturePath)\n\n    const outExists = existsSync(outPath)\n    if (outExists) {\n      if (existsSync(cacheOutPath)) {\n        rmSync(cacheOutPath, { recursive: true, force: true })\n      }\n      renameSync(outPath, cacheOutPath)\n    }\n\n    const cli = await import('../main.js')\n\n    await cli.run([\n      'init',\n      '-vvv',\n      '--directory',\n      process.cwd(),\n      '--force',\n      '--tauri-path',\n      resolve(__dirname, '../../..'),\n      '--before-build-command',\n      '',\n      '--before-dev-command',\n      '',\n      '--ci'\n    ])\n\n    if (outExists) {\n      renameSync(cacheOutPath, outPath)\n    }\n\n    process.chdir(tauriFixturePath)\n\n    const manifestPath = resolve(tauriFixturePath, 'Cargo.toml')\n    const manifestFile = readFileSync(manifestPath).toString()\n    writeFileSync(manifestPath, `workspace = { }\\n${manifestFile}`)\n\n    const configPath = resolve(tauriFixturePath, 'tauri.conf.json')\n    const config = readFileSync(configPath).toString()\n    writeFileSync(configPath, config.replace('com.tauri.dev', 'com.tauri.test'))\n\n    await cli.run(['build', '-vvv'])\n    process.chdir(cwd)\n  })\n})\n\nfunction exec(\n  bin: string,\n  args?: string[],\n  opts?: {\n    cwd?: string\n  }\n) {\n  process.platform === 'win32'\n    ? spawnSync('cmd', ['/c', bin, ...(args ?? [])], { cwd: opts?.cwd })\n    : spawnSync(bin, args, { cwd: opts?.cwd })\n}\n"
  },
  {
    "path": "packages/cli/append-headers.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nconst HEADERS = `// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT`\n\nconst fs = require('fs')\n\nfor (const file of ['index.js', 'index.d.ts']) {\n  const content = fs.readFileSync(file, 'utf8')\n  const newContent = `${HEADERS}\\n\\n${content}`\n  fs.writeFileSync(file, newContent, 'utf8')\n}\n"
  },
  {
    "path": "packages/cli/build.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nfn main() {\n  ::napi_build::setup();\n}\n"
  },
  {
    "path": "packages/cli/index.d.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/* auto-generated by NAPI-RS */\n/* eslint-disable */\nexport declare function logError(error: string): void\n\nexport declare function run(args: Array<string>, binName: string | undefined | null, callback: ((err: Error | null, arg: boolean) => any)): void\n"
  },
  {
    "path": "packages/cli/index.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n// prettier-ignore\n/* eslint-disable */\n// @ts-nocheck\n/* auto-generated by NAPI-RS */\n\nconst { readFileSync } = require('node:fs')\nlet nativeBinding = null\nconst loadErrors = []\n\nconst isMusl = () => {\n  let musl = false\n  if (process.platform === 'linux') {\n    musl = isMuslFromFilesystem()\n    if (musl === null) {\n      musl = isMuslFromReport()\n    }\n    if (musl === null) {\n      musl = isMuslFromChildProcess()\n    }\n  }\n  return musl\n}\n\nconst isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-')\n\nconst isMuslFromFilesystem = () => {\n  try {\n    return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl')\n  } catch {\n    return null\n  }\n}\n\nconst isMuslFromReport = () => {\n  let report = null\n  if (typeof process.report?.getReport === 'function') {\n    process.report.excludeNetwork = true\n    report = process.report.getReport()\n  }\n  if (!report) {\n    return null\n  }\n  if (report.header && report.header.glibcVersionRuntime) {\n    return false\n  }\n  if (Array.isArray(report.sharedObjects)) {\n    if (report.sharedObjects.some(isFileMusl)) {\n      return true\n    }\n  }\n  return false\n}\n\nconst isMuslFromChildProcess = () => {\n  try {\n    return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')\n  } catch (e) {\n    // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false\n    return false\n  }\n}\n\nfunction requireNative() {\n  if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {\n    try {\n      return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH);\n    } catch (err) {\n      loadErrors.push(err)\n    }\n  } else if (process.platform === 'android') {\n    if (process.arch === 'arm64') {\n      try {\n        return require('./cli.android-arm64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-android-arm64')\n        const bindingPackageVersion = require('@tauri-apps/cli-android-arm64/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm') {\n      try {\n        return require('./cli.android-arm-eabi.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-android-arm-eabi')\n        const bindingPackageVersion = require('@tauri-apps/cli-android-arm-eabi/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`))\n    }\n  } else if (process.platform === 'win32') {\n    if (process.arch === 'x64') {\n      if (process.config?.variables?.shlib_suffix === 'dll.a' || process.config?.variables?.node_target_type === 'shared_library') {\n        try {\n        return require('./cli.win32-x64-gnu.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-win32-x64-gnu')\n        const bindingPackageVersion = require('@tauri-apps/cli-win32-x64-gnu/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      } else {\n        try {\n        return require('./cli.win32-x64-msvc.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-win32-x64-msvc')\n        const bindingPackageVersion = require('@tauri-apps/cli-win32-x64-msvc/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      }\n    } else if (process.arch === 'ia32') {\n      try {\n        return require('./cli.win32-ia32-msvc.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-win32-ia32-msvc')\n        const bindingPackageVersion = require('@tauri-apps/cli-win32-ia32-msvc/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm64') {\n      try {\n        return require('./cli.win32-arm64-msvc.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-win32-arm64-msvc')\n        const bindingPackageVersion = require('@tauri-apps/cli-win32-arm64-msvc/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`))\n    }\n  } else if (process.platform === 'darwin') {\n    try {\n      return require('./cli.darwin-universal.node')\n    } catch (e) {\n      loadErrors.push(e)\n    }\n    try {\n      const binding = require('@tauri-apps/cli-darwin-universal')\n      const bindingPackageVersion = require('@tauri-apps/cli-darwin-universal/package.json').version\n      if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n        throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n      }\n      return binding\n    } catch (e) {\n      loadErrors.push(e)\n    }\n    if (process.arch === 'x64') {\n      try {\n        return require('./cli.darwin-x64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-darwin-x64')\n        const bindingPackageVersion = require('@tauri-apps/cli-darwin-x64/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm64') {\n      try {\n        return require('./cli.darwin-arm64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-darwin-arm64')\n        const bindingPackageVersion = require('@tauri-apps/cli-darwin-arm64/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`))\n    }\n  } else if (process.platform === 'freebsd') {\n    if (process.arch === 'x64') {\n      try {\n        return require('./cli.freebsd-x64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-freebsd-x64')\n        const bindingPackageVersion = require('@tauri-apps/cli-freebsd-x64/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm64') {\n      try {\n        return require('./cli.freebsd-arm64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-freebsd-arm64')\n        const bindingPackageVersion = require('@tauri-apps/cli-freebsd-arm64/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`))\n    }\n  } else if (process.platform === 'linux') {\n    if (process.arch === 'x64') {\n      if (isMusl()) {\n        try {\n          return require('./cli.linux-x64-musl.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tauri-apps/cli-linux-x64-musl')\n          const bindingPackageVersion = require('@tauri-apps/cli-linux-x64-musl/package.json').version\n          if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./cli.linux-x64-gnu.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tauri-apps/cli-linux-x64-gnu')\n          const bindingPackageVersion = require('@tauri-apps/cli-linux-x64-gnu/package.json').version\n          if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'arm64') {\n      if (isMusl()) {\n        try {\n          return require('./cli.linux-arm64-musl.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tauri-apps/cli-linux-arm64-musl')\n          const bindingPackageVersion = require('@tauri-apps/cli-linux-arm64-musl/package.json').version\n          if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./cli.linux-arm64-gnu.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tauri-apps/cli-linux-arm64-gnu')\n          const bindingPackageVersion = require('@tauri-apps/cli-linux-arm64-gnu/package.json').version\n          if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'arm') {\n      if (isMusl()) {\n        try {\n          return require('./cli.linux-arm-musleabihf.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tauri-apps/cli-linux-arm-musleabihf')\n          const bindingPackageVersion = require('@tauri-apps/cli-linux-arm-musleabihf/package.json').version\n          if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./cli.linux-arm-gnueabihf.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tauri-apps/cli-linux-arm-gnueabihf')\n          const bindingPackageVersion = require('@tauri-apps/cli-linux-arm-gnueabihf/package.json').version\n          if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'loong64') {\n      if (isMusl()) {\n        try {\n          return require('./cli.linux-loong64-musl.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tauri-apps/cli-linux-loong64-musl')\n          const bindingPackageVersion = require('@tauri-apps/cli-linux-loong64-musl/package.json').version\n          if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./cli.linux-loong64-gnu.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tauri-apps/cli-linux-loong64-gnu')\n          const bindingPackageVersion = require('@tauri-apps/cli-linux-loong64-gnu/package.json').version\n          if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'riscv64') {\n      if (isMusl()) {\n        try {\n          return require('./cli.linux-riscv64-musl.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tauri-apps/cli-linux-riscv64-musl')\n          const bindingPackageVersion = require('@tauri-apps/cli-linux-riscv64-musl/package.json').version\n          if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      } else {\n        try {\n          return require('./cli.linux-riscv64-gnu.node')\n        } catch (e) {\n          loadErrors.push(e)\n        }\n        try {\n          const binding = require('@tauri-apps/cli-linux-riscv64-gnu')\n          const bindingPackageVersion = require('@tauri-apps/cli-linux-riscv64-gnu/package.json').version\n          if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n            throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n          }\n          return binding\n        } catch (e) {\n          loadErrors.push(e)\n        }\n      }\n    } else if (process.arch === 'ppc64') {\n      try {\n        return require('./cli.linux-ppc64-gnu.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-linux-ppc64-gnu')\n        const bindingPackageVersion = require('@tauri-apps/cli-linux-ppc64-gnu/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 's390x') {\n      try {\n        return require('./cli.linux-s390x-gnu.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-linux-s390x-gnu')\n        const bindingPackageVersion = require('@tauri-apps/cli-linux-s390x-gnu/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`))\n    }\n  } else if (process.platform === 'openharmony') {\n    if (process.arch === 'arm64') {\n      try {\n        return require('./cli.openharmony-arm64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-openharmony-arm64')\n        const bindingPackageVersion = require('@tauri-apps/cli-openharmony-arm64/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'x64') {\n      try {\n        return require('./cli.openharmony-x64.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-openharmony-x64')\n        const bindingPackageVersion = require('@tauri-apps/cli-openharmony-x64/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else if (process.arch === 'arm') {\n      try {\n        return require('./cli.openharmony-arm.node')\n      } catch (e) {\n        loadErrors.push(e)\n      }\n      try {\n        const binding = require('@tauri-apps/cli-openharmony-arm')\n        const bindingPackageVersion = require('@tauri-apps/cli-openharmony-arm/package.json').version\n        if (bindingPackageVersion !== '2.10.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {\n          throw new Error(`Native binding package version mismatch, expected 2.10.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)\n        }\n        return binding\n      } catch (e) {\n        loadErrors.push(e)\n      }\n    } else {\n      loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`))\n    }\n  } else {\n    loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`))\n  }\n}\n\nnativeBinding = requireNative()\n\nif (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {\n  let wasiBinding = null\n  let wasiBindingError = null\n  try {\n    wasiBinding = require('./cli.wasi.cjs')\n    nativeBinding = wasiBinding\n  } catch (err) {\n    if (process.env.NAPI_RS_FORCE_WASI) {\n      wasiBindingError = err\n    }\n  }\n  if (!nativeBinding) {\n    try {\n      wasiBinding = require('@tauri-apps/cli-wasm32-wasi')\n      nativeBinding = wasiBinding\n    } catch (err) {\n      if (process.env.NAPI_RS_FORCE_WASI) {\n        wasiBindingError.cause = err\n        loadErrors.push(err)\n      }\n    }\n  }\n  if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) {\n    const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error')\n    error.cause = wasiBindingError\n    throw error\n  }\n}\n\nif (!nativeBinding) {\n  if (loadErrors.length > 0) {\n    throw new Error(\n      `Cannot find native binding. ` +\n        `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` +\n        'Please try `npm i` again after removing both package-lock.json and node_modules directory.',\n      {\n        cause: loadErrors.reduce((err, cur) => {\n          cur.cause = err\n          return cur\n        }),\n      },\n    )\n  }\n  throw new Error(`Failed to load native binding`)\n}\n\nmodule.exports = nativeBinding\nmodule.exports.logError = nativeBinding.logError\nmodule.exports.run = nativeBinding.run\n"
  },
  {
    "path": "packages/cli/main.d.ts",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/* tslint:disable */\n/* eslint-disable */\n\nexport function run(\n  args: Array<string>,\n  binName?: string | undefined | null\n): Promise<void>\n"
  },
  {
    "path": "packages/cli/main.js",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nconst { run, logError } = require('./index')\n\nmodule.exports.run = (args, binName) => {\n  return new Promise((resolve, reject) => {\n    run(args, binName, (error, res) => {\n      if (error) {\n        reject(error)\n      } else {\n        resolve(res)\n      }\n    })\n  })\n}\n\nmodule.exports.logError = logError\n"
  },
  {
    "path": "packages/cli/npm/darwin-arm64/README.md",
    "content": "# `@tauri-apps/cli-darwin-arm64`\n\nThis is the **aarch64-apple-darwin** binary for `@tauri-apps/cli`\n"
  },
  {
    "path": "packages/cli/npm/darwin-arm64/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/cli-darwin-arm64\",\n  \"version\": \"0.0.0\",\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"cli.darwin-arm64.node\",\n  \"files\": [\n    \"cli.darwin-arm64.node\"\n  ],\n  \"description\": \"Command line interface for building Tauri apps\",\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"os\": [\n    \"darwin\"\n  ]\n}\n"
  },
  {
    "path": "packages/cli/npm/darwin-x64/README.md",
    "content": "# `@tauri-apps/cli-darwin-x64`\n\nThis is the **x86_64-apple-darwin** binary for `@tauri-apps/cli`\n"
  },
  {
    "path": "packages/cli/npm/darwin-x64/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/cli-darwin-x64\",\n  \"version\": \"0.0.0\",\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"cli.darwin-x64.node\",\n  \"files\": [\n    \"cli.darwin-x64.node\"\n  ],\n  \"description\": \"Command line interface for building Tauri apps\",\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"os\": [\n    \"darwin\"\n  ]\n}\n"
  },
  {
    "path": "packages/cli/npm/linux-arm-gnueabihf/README.md",
    "content": "# `@tauri-apps/cli-linux-arm-gnueabihf`\n\nThis is the **armv7-unknown-linux-gnueabihf** binary for `@tauri-apps/cli`\n"
  },
  {
    "path": "packages/cli/npm/linux-arm-gnueabihf/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/cli-linux-arm-gnueabihf\",\n  \"version\": \"0.0.0\",\n  \"cpu\": [\n    \"arm\"\n  ],\n  \"main\": \"cli.linux-arm-gnueabihf.node\",\n  \"files\": [\n    \"cli.linux-arm-gnueabihf.node\"\n  ],\n  \"description\": \"Command line interface for building Tauri apps\",\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"os\": [\n    \"linux\"\n  ]\n}\n"
  },
  {
    "path": "packages/cli/npm/linux-arm64-gnu/README.md",
    "content": "# `@tauri-apps/cli-linux-arm64-gnu`\n\nThis is the **aarch64-unknown-linux-gnu** binary for `@tauri-apps/cli`\n"
  },
  {
    "path": "packages/cli/npm/linux-arm64-gnu/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/cli-linux-arm64-gnu\",\n  \"version\": \"0.0.0\",\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"cli.linux-arm64-gnu.node\",\n  \"files\": [\n    \"cli.linux-arm64-gnu.node\"\n  ],\n  \"description\": \"Command line interface for building Tauri apps\",\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"os\": [\n    \"linux\"\n  ],\n  \"libc\": [\n    \"glibc\"\n  ]\n}\n"
  },
  {
    "path": "packages/cli/npm/linux-arm64-musl/README.md",
    "content": "# `@tauri-apps/cli-linux-arm64-musl`\n\nThis is the **aarch64-unknown-linux-musl** binary for `@tauri-apps/cli`\n"
  },
  {
    "path": "packages/cli/npm/linux-arm64-musl/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/cli-linux-arm64-musl\",\n  \"version\": \"0.0.0\",\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"cli.linux-arm64-musl.node\",\n  \"files\": [\n    \"cli.linux-arm64-musl.node\"\n  ],\n  \"description\": \"Command line interface for building Tauri apps\",\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"os\": [\n    \"linux\"\n  ],\n  \"libc\": [\n    \"musl\"\n  ]\n}\n"
  },
  {
    "path": "packages/cli/npm/linux-riscv64-gnu/README.md",
    "content": "# `@tauri-apps/cli-linux-riscv64-gnu`\n\nThis is the **riscv64gc-unknown-linux-gnu** binary for `@tauri-apps/cli`\n"
  },
  {
    "path": "packages/cli/npm/linux-riscv64-gnu/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/cli-linux-riscv64-gnu\",\n  \"version\": \"0.0.0\",\n  \"cpu\": [\n    \"riscv64\"\n  ],\n  \"main\": \"cli.linux-riscv64-gnu.node\",\n  \"files\": [\n    \"cli.linux-riscv64-gnu.node\"\n  ],\n  \"description\": \"Command line interface for building Tauri apps\",\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"os\": [\n    \"linux\"\n  ],\n  \"libc\": [\n    \"glibc\"\n  ]\n}\n"
  },
  {
    "path": "packages/cli/npm/linux-x64-gnu/README.md",
    "content": "# `@tauri-apps/cli-linux-x64-gnu`\n\nThis is the **x86_64-unknown-linux-gnu** binary for `@tauri-apps/cli`\n"
  },
  {
    "path": "packages/cli/npm/linux-x64-gnu/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/cli-linux-x64-gnu\",\n  \"version\": \"0.0.0\",\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"cli.linux-x64-gnu.node\",\n  \"files\": [\n    \"cli.linux-x64-gnu.node\"\n  ],\n  \"description\": \"Command line interface for building Tauri apps\",\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"os\": [\n    \"linux\"\n  ],\n  \"libc\": [\n    \"glibc\"\n  ]\n}\n"
  },
  {
    "path": "packages/cli/npm/linux-x64-musl/README.md",
    "content": "# `@tauri-apps/cli-linux-x64-musl`\n\nThis is the **x86_64-unknown-linux-musl** binary for `@tauri-apps/cli`\n"
  },
  {
    "path": "packages/cli/npm/linux-x64-musl/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/cli-linux-x64-musl\",\n  \"version\": \"0.0.0\",\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"cli.linux-x64-musl.node\",\n  \"files\": [\n    \"cli.linux-x64-musl.node\"\n  ],\n  \"description\": \"Command line interface for building Tauri apps\",\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"os\": [\n    \"linux\"\n  ],\n  \"libc\": [\n    \"musl\"\n  ]\n}\n"
  },
  {
    "path": "packages/cli/npm/win32-arm64-msvc/README.md",
    "content": "# `@tauri-apps/cli-win32-arm64-msvc`\n\nThis is the **aarch64-pc-windows-msvc** binary for `@tauri-apps/cli`\n"
  },
  {
    "path": "packages/cli/npm/win32-arm64-msvc/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/cli-win32-arm64-msvc\",\n  \"version\": \"0.0.0\",\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"cli.win32-arm64-msvc.node\",\n  \"files\": [\n    \"cli.win32-arm64-msvc.node\"\n  ],\n  \"description\": \"Command line interface for building Tauri apps\",\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"os\": [\n    \"win32\"\n  ]\n}\n"
  },
  {
    "path": "packages/cli/npm/win32-ia32-msvc/README.md",
    "content": "# `@tauri-apps/cli-win32-ia32-msvc`\n\nThis is the **i686-pc-windows-msvc** binary for `@tauri-apps/cli`\n"
  },
  {
    "path": "packages/cli/npm/win32-ia32-msvc/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/cli-win32-ia32-msvc\",\n  \"version\": \"0.0.0\",\n  \"cpu\": [\n    \"ia32\"\n  ],\n  \"main\": \"cli.win32-ia32-msvc.node\",\n  \"files\": [\n    \"cli.win32-ia32-msvc.node\"\n  ],\n  \"description\": \"Command line interface for building Tauri apps\",\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"os\": [\n    \"win32\"\n  ]\n}\n"
  },
  {
    "path": "packages/cli/npm/win32-x64-msvc/README.md",
    "content": "# `@tauri-apps/cli-win32-x64-msvc`\n\nThis is the **x86_64-pc-windows-msvc** binary for `@tauri-apps/cli`\n"
  },
  {
    "path": "packages/cli/npm/win32-x64-msvc/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/cli-win32-x64-msvc\",\n  \"version\": \"0.0.0\",\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"cli.win32-x64-msvc.node\",\n  \"files\": [\n    \"cli.win32-x64-msvc.node\"\n  ],\n  \"description\": \"Command line interface for building Tauri apps\",\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"os\": [\n    \"win32\"\n  ]\n}\n"
  },
  {
    "path": "packages/cli/package.json",
    "content": "{\n  \"name\": \"@tauri-apps/cli\",\n  \"version\": \"2.10.1\",\n  \"description\": \"Command line interface for building Tauri apps\",\n  \"type\": \"commonjs\",\n  \"funding\": {\n    \"type\": \"opencollective\",\n    \"url\": \"https://opencollective.com/tauri\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/tauri-apps/tauri.git\"\n  },\n  \"contributors\": [\n    \"Tauri Programme within The Commons Conservancy\"\n  ],\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/tauri-apps/tauri/issues\"\n  },\n  \"homepage\": \"https://github.com/tauri-apps/tauri#readme\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"main\": \"main.js\",\n  \"types\": \"main.d.ts\",\n  \"napi\": {\n    \"binaryName\": \"cli\",\n    \"targets\": [\n      \"x86_64-unknown-linux-gnu\",\n      \"x86_64-pc-windows-msvc\",\n      \"x86_64-apple-darwin\",\n      \"aarch64-apple-darwin\",\n      \"aarch64-unknown-linux-gnu\",\n      \"aarch64-unknown-linux-musl\",\n      \"armv7-unknown-linux-gnueabihf\",\n      \"x86_64-unknown-linux-musl\",\n      \"riscv64gc-unknown-linux-gnu\",\n      \"i686-pc-windows-msvc\",\n      \"aarch64-pc-windows-msvc\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@napi-rs/cli\": \"^3.5.1\",\n    \"@types/node\": \"^24.11.0\",\n    \"cross-env\": \"10.1.0\",\n    \"vitest\": \"^4.0.18\"\n  },\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"bin\": {\n    \"tauri\": \"./tauri.js\"\n  },\n  \"scripts\": {\n    \"artifacts\": \"napi artifacts\",\n    \"build\": \"cross-env TARGET=node napi build --platform --profile release-size-optimized\",\n    \"postbuild\": \"node append-headers.js\",\n    \"build:debug\": \"cross-env TARGET=node napi build --platform\",\n    \"postbuild:debug\": \"node append-headers.js\",\n    \"prepublishOnly\": \"napi prepublish -t npm --gh-release-id $RELEASE_ID\",\n    \"prepack\": \"cp ../../crates/tauri-schema-generator/schemas/config.schema.json .\",\n    \"version\": \"napi version\",\n    \"test\": \"vitest run\",\n    \"tauri\": \"node ./tauri.js\"\n  }\n}\n"
  },
  {
    "path": "packages/cli/src/lib.rs",
    "content": "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![cfg(any(target_os = \"macos\", target_os = \"linux\", windows))]\n\nuse std::sync::Arc;\n\nuse napi::{\n  threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},\n  Error, Result, Status,\n};\n\n#[napi_derive::napi]\npub fn run(\n  args: Vec<String>,\n  bin_name: Option<String>,\n  callback: Arc<ThreadsafeFunction<bool>>,\n) -> Result<()> {\n  // we need to run in a separate thread so Node.js consumers\n  // can do work while `tauri dev` is running.\n  std::thread::spawn(move || {\n    let res = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n      tauri_cli::try_run(args, bin_name).inspect_err(|e| eprintln!(\"{e:#}\"))\n    })) {\n      Ok(t) => t,\n      Err(_) => {\n        return callback.call(\n          Err(Error::new(\n            Status::GenericFailure,\n            \"Tauri CLI unexpected panic\",\n          )),\n          ThreadsafeFunctionCallMode::Blocking,\n        );\n      }\n    };\n\n    match res {\n      Ok(_) => callback.call(Ok(true), ThreadsafeFunctionCallMode::Blocking),\n      Err(e) => callback.call(\n        Err(Error::new(Status::GenericFailure, format!(\"{e:#}\"))),\n        ThreadsafeFunctionCallMode::Blocking,\n      ),\n    }\n  });\n\n  Ok(())\n}\n\n#[napi_derive::napi]\npub fn log_error(error: String) {\n  log::error!(\"{}\", error);\n}\n"
  },
  {
    "path": "packages/cli/tauri.js",
    "content": "#!/usr/bin/env node\n\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nconst cli = require('./main')\nconst path = require('path')\n\nconst [bin, script, ...args] = process.argv\nconst binStem = path.parse(bin).name.toLowerCase()\n\n// We want to make a helpful binary name for the underlying CLI helper, if we\n// can successfully detect what command likely started the execution.\nlet binName\n\n// deno run -A npm:@tauri-apps/cli or deno task tauri\nif (globalThis.navigator?.userAgent?.includes('Deno')) {\n  binName = bin\n}\n// Even if started by a package manager, the binary will be NodeJS.\n// Some distribution still use \"nodejs\" as the binary name.\nelse if (binStem.match(/(nodejs|node|bun|electron)\\-?([0-9]*)*$/g)) {\n  const managerStem = process.env.npm_execpath\n    ? path.parse(process.env.npm_execpath).name.toLowerCase()\n    : null\n  if (managerStem) {\n    let manager\n    switch (managerStem) {\n      // Only supported package manager that has a different filename is npm.\n      case 'npm-cli':\n        manager = 'npm'\n        break\n\n      // Yarn, pnpm, and bun have the same stem name as their bin.\n      // We assume all unknown package managers do as well.\n      default:\n        manager = managerStem\n        break\n    }\n\n    binName = `${manager} run ${process.env.npm_lifecycle_event}`\n  } else {\n    // Assume running NodeJS if we didn't detect a manager from the env.\n    // We normalize the path to prevent the script's absolute path being used.\n    const scriptNormal = path.normalize(path.relative(process.cwd(), script))\n    binName = `${binStem} ${scriptNormal}`\n  }\n} else {\n  // We don't know what started it, assume it's already stripped.\n  args.unshift(bin)\n}\n\ncli.run(args, binName).catch((err) => {\n  cli.logError(err.message)\n  process.exit(1)\n})\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - packages/api\n  - packages/cli\n  - crates/tauri-schema-worker\n  - examples/api\n  - examples/resources\n  - examples/file-associations\n\nonlyBuiltDependencies:\n  - esbuild\n  - workerd\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\"config:recommended\"],\n  \"baseBranches\": [\"dev\"],\n  \"labels\": [\"type: chore\"],\n  \"enabledManagers\": [\"cargo\", \"npm\"],\n  \"rangeStrategy\": \"replace\",\n  \"packageRules\": [\n    {\n      \"matchPackageNames\": [\"*\"],\n      \"semanticCommitType\": \"chore\",\n      \"minimumReleaseAge\": \"3 days\"\n    },\n    {\n      \"description\": \"Disable node/pnpm version updates\",\n      \"matchPackageNames\": [\"node\", \"pnpm\"],\n      \"matchDepTypes\": [\"engines\", \"packageManager\"],\n      \"enabled\": false\n    },\n    {\n      \"description\": \"Disable oxc_* crates because of MSRV and PR spam\",\n      \"groupName\": \"oxc crates\",\n      \"matchPackageNames\": [\"oxc_*\"],\n      \"enabled\": false\n    },\n    {\n      \"description\": \"Group windows-rs / webview2-com crates\",\n      \"groupName\": \"windows-rs and webview2 crates\",\n      \"matchSourceUrls\": [\n        \"https://github.com/microsoft/windows-rs\",\n        \"https://github.com/wravery/webview2-rs\"\n      ]\n    },\n    {\n      \"description\": \"Group worker-rs crates\",\n      \"groupName\": \"worker-rs crates\",\n      \"matchSourceUrls\": [\"https://github.com/cloudflare/workers-rs\"]\n    }\n  ],\n  \"postUpdateOptions\": [\"pnpmDedupe\"]\n}\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "max_width = 100\nhard_tabs = false\ntab_spaces = 2\nnewline_style = \"Unix\"\nuse_small_heuristics = \"Default\"\nreorder_imports = true\nreorder_modules = true\nremove_nested_parens = true\nedition = \"2021\"\nmerge_derives = true\nuse_try_shorthand = false\nuse_field_init_shorthand = false\nforce_explicit_abi = true\n# normalize_comments = true\n# wrap_comments = true\n"
  },
  {
    "path": "supply-chain/audits.toml",
    "content": "\n# cargo-vet audits file\n\n[audits]\n\n[[trusted.aead]]\ncriteria = \"safe-to-deploy\"\nuser-id = 267               # Tony Arcieri (tarcieri)\nstart = \"2020-06-05\"\nend = \"2025-05-08\"\n\n[[trusted.aes]]\ncriteria = \"safe-to-deploy\"\nuser-id = 267               # Tony Arcieri (tarcieri)\nstart = \"2020-06-05\"\nend = \"2025-05-08\"\n\n[[trusted.aes-gcm]]\ncriteria = \"safe-to-deploy\"\nuser-id = 267               # Tony Arcieri (tarcieri)\nstart = \"2019-08-16\"\nend = \"2025-05-08\"\n\n[[trusted.aho-corasick]]\ncriteria = \"safe-to-deploy\"\nuser-id = 189               # Andrew Gallant (BurntSushi)\nstart = \"2019-03-28\"\nend = \"2025-03-06\"\n\n[[trusted.anyhow]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-10-05\"\nend = \"2025-03-06\"\n\n[[trusted.autocfg]]\ncriteria = \"safe-to-deploy\"\nuser-id = 539               # Josh Stone (cuviper)\nstart = \"2019-05-22\"\nend = \"2025-05-08\"\n\n[[trusted.backtrace]]\ncriteria = \"safe-to-deploy\"\nuser-id = 2915              # Amanieu d'Antras (Amanieu)\nstart = \"2023-06-29\"\nend = \"2025-03-06\"\n\n[[trusted.byteorder]]\ncriteria = \"safe-to-deploy\"\nuser-id = 189               # Andrew Gallant (BurntSushi)\nstart = \"2019-06-09\"\nend = \"2025-03-06\"\n\n[[trusted.bytes]]\ncriteria = \"safe-to-deploy\"\nuser-id = 359               # Sean McArthur (seanmonstar)\nstart = \"2019-11-27\"\nend = \"2025-03-06\"\n\n[[trusted.bytes]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6741              # Alice Ryhl (Darksonn)\nstart = \"2021-01-11\"\nend = \"2025-03-06\"\n\n[[trusted.cargo-platform]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-09-27\"\nend = \"2025-03-06\"\n\n[[trusted.cc]]\ncriteria = \"safe-to-deploy\"\nuser-id = 2915              # Amanieu d'Antras (Amanieu)\nstart = \"2024-02-20\"\nend = \"2025-03-06\"\n\n[[trusted.cipher]]\ncriteria = \"safe-to-deploy\"\nuser-id = 267               # Tony Arcieri (tarcieri)\nstart = \"2020-10-15\"\nend = \"2025-05-08\"\n\n[[trusted.cpufeatures]]\ncriteria = \"safe-to-deploy\"\nuser-id = 267               # Tony Arcieri (tarcieri)\nstart = \"2021-04-26\"\nend = \"2025-05-08\"\n\n[[trusted.ctr]]\ncriteria = \"safe-to-deploy\"\nuser-id = 267               # Tony Arcieri (tarcieri)\nstart = \"2020-06-06\"\nend = \"2025-05-08\"\n\n[[trusted.digest]]\ncriteria = \"safe-to-deploy\"\nuser-id = 267               # Tony Arcieri (tarcieri)\nstart = \"2020-06-10\"\nend = \"2025-05-08\"\n\n[[trusted.dtoa]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-05-02\"\nend = \"2025-03-06\"\n\n[[trusted.dyn-clone]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-12-23\"\nend = \"2025-03-06\"\n\n[[trusted.flate2]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-03-14\"\nend = \"2025-03-06\"\n\n[[trusted.ghash]]\ncriteria = \"safe-to-deploy\"\nuser-id = 267               # Tony Arcieri (tarcieri)\nstart = \"2019-09-19\"\nend = \"2025-05-08\"\n\n[[trusted.h2]]\ncriteria = \"safe-to-deploy\"\nuser-id = 359               # Sean McArthur (seanmonstar)\nstart = \"2019-03-13\"\nend = \"2025-03-06\"\n\n[[trusted.hashbrown]]\ncriteria = \"safe-to-deploy\"\nuser-id = 2915              # Amanieu d'Antras (Amanieu)\nstart = \"2019-04-02\"\nend = \"2025-03-06\"\n\n[[trusted.http]]\ncriteria = \"safe-to-deploy\"\nuser-id = 359               # Sean McArthur (seanmonstar)\nstart = \"2019-04-05\"\nend = \"2025-03-06\"\n\n[[trusted.http-body]]\ncriteria = \"safe-to-deploy\"\nuser-id = 359               # Sean McArthur (seanmonstar)\nstart = \"2019-10-01\"\nend = \"2025-03-06\"\n\n[[trusted.http-body-util]]\ncriteria = \"safe-to-deploy\"\nuser-id = 359               # Sean McArthur (seanmonstar)\nstart = \"2022-10-25\"\nend = \"2025-05-08\"\n\n[[trusted.httparse]]\ncriteria = \"safe-to-deploy\"\nuser-id = 359               # Sean McArthur (seanmonstar)\nstart = \"2019-07-03\"\nend = \"2025-03-06\"\n\n[[trusted.hyper]]\ncriteria = \"safe-to-deploy\"\nuser-id = 359               # Sean McArthur (seanmonstar)\nstart = \"2019-03-01\"\nend = \"2025-03-06\"\n\n[[trusted.hyper-tls]]\ncriteria = \"safe-to-deploy\"\nuser-id = 359               # Sean McArthur (seanmonstar)\nstart = \"2019-03-19\"\nend = \"2025-03-06\"\n\n[[trusted.hyper-util]]\ncriteria = \"safe-to-deploy\"\nuser-id = 359               # Sean McArthur (seanmonstar)\nstart = \"2022-01-15\"\nend = \"2025-05-08\"\n\n[[trusted.indexmap]]\ncriteria = \"safe-to-deploy\"\nuser-id = 539               # Josh Stone (cuviper)\nstart = \"2020-01-15\"\nend = \"2025-03-06\"\n\n[[trusted.itoa]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-05-02\"\nend = \"2025-03-06\"\n\n[[trusted.javascriptcore-rs]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-10-04\"\nend = \"2025-05-08\"\n\n[[trusted.javascriptcore-rs-sys]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-10-04\"\nend = \"2025-05-08\"\n\n[[trusted.js-sys]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-03-04\"\nend = \"2025-03-06\"\n\n[[trusted.libappindicator]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-10-11\"\nend = \"2025-05-08\"\n\n[[trusted.libappindicator-sys]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-10-11\"\nend = \"2025-05-08\"\n\n[[trusted.libc]]\ncriteria = \"safe-to-deploy\"\nuser-id = 2915              # Amanieu d'Antras (Amanieu)\nstart = \"2021-01-27\"\nend = \"2025-03-06\"\n\n[[trusted.libc]]\ncriteria = \"safe-to-deploy\"\nuser-id = 51017             # Yuki Okushi (JohnTitor)\nstart = \"2020-03-17\"\nend = \"2025-05-08\"\n\n[[trusted.libm]]\ncriteria = \"safe-to-deploy\"\nuser-id = 2915              # Amanieu d'Antras (Amanieu)\nstart = \"2022-02-06\"\nend = \"2025-03-06\"\n\n[[trusted.linux-raw-sys]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6825              # Dan Gohman (sunfishcode)\nstart = \"2021-06-12\"\nend = \"2025-03-06\"\n\n[[trusted.lock_api]]\ncriteria = \"safe-to-deploy\"\nuser-id = 2915              # Amanieu d'Antras (Amanieu)\nstart = \"2019-05-04\"\nend = \"2025-03-06\"\n\n[[trusted.loom]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6741              # Alice Ryhl (Darksonn)\nstart = \"2021-04-12\"\nend = \"2025-03-06\"\n\n[[trusted.memchr]]\ncriteria = \"safe-to-deploy\"\nuser-id = 189               # Andrew Gallant (BurntSushi)\nstart = \"2019-07-07\"\nend = \"2025-03-06\"\n\n[[trusted.mime]]\ncriteria = \"safe-to-deploy\"\nuser-id = 359               # Sean McArthur (seanmonstar)\nstart = \"2019-09-09\"\nend = \"2025-03-06\"\n\n[[trusted.mio]]\ncriteria = \"safe-to-deploy\"\nuser-id = 10                # Carl Lerche (carllerche)\nstart = \"2019-05-15\"\nend = \"2025-03-06\"\n\n[[trusted.muda]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2022-12-06\"\nend = \"2025-05-08\"\n\n[[trusted.new_debug_unreachable]]\ncriteria = \"safe-to-deploy\"\nuser-id = 2017              # Matt Brubeck (mbrubeck)\nstart = \"2019-02-26\"\nend = \"2025-05-08\"\n\n[[trusted.num_cpus]]\ncriteria = \"safe-to-deploy\"\nuser-id = 359               # Sean McArthur (seanmonstar)\nstart = \"2019-06-10\"\nend = \"2025-03-06\"\n\n[[trusted.openssl-src]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-02-25\"\nend = \"2025-03-06\"\n\n[[trusted.parking_lot]]\ncriteria = \"safe-to-deploy\"\nuser-id = 2915              # Amanieu d'Antras (Amanieu)\nstart = \"2019-05-04\"\nend = \"2025-03-06\"\n\n[[trusted.parking_lot_core]]\ncriteria = \"safe-to-deploy\"\nuser-id = 2915              # Amanieu d'Antras (Amanieu)\nstart = \"2019-05-04\"\nend = \"2025-03-06\"\n\n[[trusted.paste]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-03-19\"\nend = \"2025-05-08\"\n\n[[trusted.phf]]\ncriteria = \"safe-to-deploy\"\nuser-id = 51017             # Yuki Okushi (JohnTitor)\nstart = \"2021-06-17\"\nend = \"2025-05-08\"\n\n[[trusted.phf_codegen]]\ncriteria = \"safe-to-deploy\"\nuser-id = 51017             # Yuki Okushi (JohnTitor)\nstart = \"2021-06-17\"\nend = \"2025-05-08\"\n\n[[trusted.phf_generator]]\ncriteria = \"safe-to-deploy\"\nuser-id = 51017             # Yuki Okushi (JohnTitor)\nstart = \"2021-06-17\"\nend = \"2025-05-08\"\n\n[[trusted.phf_macros]]\ncriteria = \"safe-to-deploy\"\nuser-id = 51017             # Yuki Okushi (JohnTitor)\nstart = \"2021-06-17\"\nend = \"2025-05-08\"\n\n[[trusted.phf_shared]]\ncriteria = \"safe-to-deploy\"\nuser-id = 51017             # Yuki Okushi (JohnTitor)\nstart = \"2021-06-17\"\nend = \"2025-05-08\"\n\n[[trusted.polyval]]\ncriteria = \"safe-to-deploy\"\nuser-id = 267               # Tony Arcieri (tarcieri)\nstart = \"2019-08-13\"\nend = \"2025-05-08\"\n\n[[trusted.proc-macro-hack]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-04-16\"\nend = \"2025-03-06\"\n\n[[trusted.proc-macro2]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-04-23\"\nend = \"2025-03-06\"\n\n[[trusted.quickcheck]]\ncriteria = \"safe-to-deploy\"\nuser-id = 189               # Andrew Gallant (BurntSushi)\nstart = \"2019-05-13\"\nend = \"2025-03-06\"\n\n[[trusted.regex]]\ncriteria = \"safe-to-deploy\"\nuser-id = 189               # Andrew Gallant (BurntSushi)\nstart = \"2019-02-27\"\nend = \"2025-03-06\"\n\n[[trusted.regex-automata]]\ncriteria = \"safe-to-deploy\"\nuser-id = 189               # Andrew Gallant (BurntSushi)\nstart = \"2019-02-25\"\nend = \"2025-03-06\"\n\n[[trusted.regex-syntax]]\ncriteria = \"safe-to-deploy\"\nuser-id = 189               # Andrew Gallant (BurntSushi)\nstart = \"2019-03-30\"\nend = \"2025-03-06\"\n\n[[trusted.reqwest]]\ncriteria = \"safe-to-deploy\"\nuser-id = 359               # Sean McArthur (seanmonstar)\nstart = \"2019-03-04\"\nend = \"2025-03-06\"\n\n[[trusted.rustc-demangle]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-04-12\"\nend = \"2025-05-08\"\n\n[[trusted.rustix]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6825              # Dan Gohman (sunfishcode)\nstart = \"2021-10-29\"\nend = \"2025-03-06\"\n\n[[trusted.rustversion]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-07-08\"\nend = \"2025-05-08\"\n\n[[trusted.ryu]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-05-02\"\nend = \"2025-03-06\"\n\n[[trusted.same-file]]\ncriteria = \"safe-to-deploy\"\nuser-id = 189               # Andrew Gallant (BurntSushi)\nstart = \"2019-07-16\"\nend = \"2025-03-06\"\n\n[[trusted.scoped-tls]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-02-26\"\nend = \"2025-03-06\"\n\n[[trusted.scopeguard]]\ncriteria = \"safe-to-deploy\"\nuser-id = 2915              # Amanieu d'Antras (Amanieu)\nstart = \"2020-02-16\"\nend = \"2025-03-06\"\n\n[[trusted.semver]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2021-05-25\"\nend = \"2025-03-06\"\n\n[[trusted.serde]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-03-01\"\nend = \"2025-03-06\"\n\n[[trusted.serde_derive]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-03-01\"\nend = \"2025-05-08\"\n\n[[trusted.serde_derive_internals]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-09-08\"\nend = \"2025-03-06\"\n\n[[trusted.serde_json]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-02-28\"\nend = \"2025-03-06\"\n\n[[trusted.serde_repr]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-04-26\"\nend = \"2025-03-06\"\n\n[[trusted.serde_spanned]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6743              # Ed Page (epage)\nstart = \"2023-01-20\"\nend = \"2025-03-06\"\n\n[[trusted.serialize-to-javascript]]\ncriteria = \"safe-to-deploy\"\nuser-id = 28029             # chip (chippers)\nstart = \"2022-02-08\"\nend = \"2025-05-08\"\n\n[[trusted.serialize-to-javascript-impl]]\ncriteria = \"safe-to-deploy\"\nuser-id = 28029             # chip (chippers)\nstart = \"2022-02-08\"\nend = \"2025-05-08\"\n\n[[trusted.sha2]]\ncriteria = \"safe-to-deploy\"\nuser-id = 267               # Tony Arcieri (tarcieri)\nstart = \"2020-05-24\"\nend = \"2025-05-08\"\n\n[[trusted.slab]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6741              # Alice Ryhl (Darksonn)\nstart = \"2021-10-13\"\nend = \"2025-03-06\"\n\n[[trusted.smallvec]]\ncriteria = \"safe-to-deploy\"\nuser-id = 2017              # Matt Brubeck (mbrubeck)\nstart = \"2019-10-28\"\nend = \"2025-03-06\"\n\n[[trusted.socket2]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-05-06\"\nend = \"2025-03-06\"\n\n[[trusted.syn]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-03-01\"\nend = \"2025-03-06\"\n\n[[trusted.tao]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-04-30\"\nend = \"2025-05-08\"\n\n[[trusted.tao-macros]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2023-01-11\"\nend = \"2025-05-08\"\n\n[[trusted.target-lexicon]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6825              # Dan Gohman (sunfishcode)\nstart = \"2019-03-06\"\nend = \"2025-03-06\"\n\n[[trusted.tauri]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2020-07-20\"\nend = \"2025-05-08\"\n\n[[trusted.tauri-build]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-04-14\"\nend = \"2025-05-08\"\n\n[[trusted.tauri-codegen]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-04-14\"\nend = \"2025-05-08\"\n\n[[trusted.tauri-macros]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-04-14\"\nend = \"2025-05-08\"\n\n[[trusted.tauri-plugin]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2024-02-03\"\nend = \"2025-05-08\"\n\n[[trusted.tauri-runtime]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-05-10\"\nend = \"2025-05-08\"\n\n[[trusted.tauri-runtime-wry]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-05-10\"\nend = \"2025-05-08\"\n\n[[trusted.tauri-utils]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-04-14\"\nend = \"2025-05-08\"\n\n[[trusted.tauri-winres]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2023-01-19\"\nend = \"2025-05-08\"\n\n[[trusted.thiserror]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-10-09\"\nend = \"2025-03-06\"\n\n[[trusted.thiserror-impl]]\ncriteria = \"safe-to-deploy\"\nuser-id = 3618              # David Tolnay (dtolnay)\nstart = \"2019-10-09\"\nend = \"2025-03-06\"\n\n[[trusted.thread_local]]\ncriteria = \"safe-to-deploy\"\nuser-id = 2915              # Amanieu d'Antras (Amanieu)\nstart = \"2019-09-07\"\nend = \"2025-03-06\"\n\n[[trusted.tokio]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6741              # Alice Ryhl (Darksonn)\nstart = \"2020-12-25\"\nend = \"2025-03-06\"\n\n[[trusted.tokio-macros]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6741              # Alice Ryhl (Darksonn)\nstart = \"2020-10-26\"\nend = \"2025-03-06\"\n\n[[trusted.tokio-macros]]\ncriteria = \"safe-to-deploy\"\nuser-id = 10                # Carl Lerche (carllerche)\nstart = \"2019-04-24\"\nend = \"2025-03-06\"\n\n[[trusted.tokio-util]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6741              # Alice Ryhl (Darksonn)\nstart = \"2021-01-12\"\nend = \"2025-03-06\"\n\n[[trusted.toml]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-05-16\"\nend = \"2025-03-06\"\n\n[[trusted.toml]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6743              # Ed Page (epage)\nstart = \"2022-12-14\"\nend = \"2025-03-06\"\n\n[[trusted.toml_edit]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6743              # Ed Page (epage)\nstart = \"2021-09-13\"\nend = \"2025-03-06\"\n\n[[trusted.tower]]\ncriteria = \"safe-to-deploy\"\nuser-id = 10                # Carl Lerche (carllerche)\nstart = \"2019-04-27\"\nend = \"2025-05-08\"\n\n[[trusted.tower-layer]]\ncriteria = \"safe-to-deploy\"\nuser-id = 10                # Carl Lerche (carllerche)\nstart = \"2019-04-27\"\nend = \"2025-05-08\"\n\n[[trusted.tray-icon]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2022-12-01\"\nend = \"2025-05-08\"\n\n[[trusted.ucd-trie]]\ncriteria = \"safe-to-deploy\"\nuser-id = 189               # Andrew Gallant (BurntSushi)\nstart = \"2019-07-21\"\nend = \"2025-03-06\"\n\n[[trusted.walkdir]]\ncriteria = \"safe-to-deploy\"\nuser-id = 189               # Andrew Gallant (BurntSushi)\nstart = \"2019-06-09\"\nend = \"2025-03-06\"\n\n[[trusted.wasi]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6825              # Dan Gohman (sunfishcode)\nstart = \"2019-07-22\"\nend = \"2025-03-06\"\n\n[[trusted.wasi]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2020-06-03\"\nend = \"2025-03-06\"\n\n[[trusted.wasm-bindgen]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-03-04\"\nend = \"2025-03-06\"\n\n[[trusted.wasm-bindgen-backend]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-03-04\"\nend = \"2025-03-06\"\n\n[[trusted.wasm-bindgen-futures]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-03-04\"\nend = \"2025-03-06\"\n\n[[trusted.wasm-bindgen-macro]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-03-04\"\nend = \"2025-03-06\"\n\n[[trusted.wasm-bindgen-macro-support]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-03-04\"\nend = \"2025-03-06\"\n\n[[trusted.wasm-bindgen-shared]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-03-04\"\nend = \"2025-03-06\"\n\n[[trusted.web-sys]]\ncriteria = \"safe-to-deploy\"\nuser-id = 1                 # Alex Crichton (alexcrichton)\nstart = \"2019-03-04\"\nend = \"2025-03-06\"\n\n[[trusted.webkit2gtk]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-10-05\"\nend = \"2025-05-08\"\n\n[[trusted.webkit2gtk-sys]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-10-05\"\nend = \"2025-05-08\"\n\n[[trusted.winapi-util]]\ncriteria = \"safe-to-deploy\"\nuser-id = 189               # Andrew Gallant (BurntSushi)\nstart = \"2020-01-11\"\nend = \"2025-03-06\"\n\n[[trusted.window-vibrancy]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2022-03-05\"\nend = \"2025-05-08\"\n\n[[trusted.windows]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2021-01-15\"\nend = \"2025-03-06\"\n\n[[trusted.windows-core]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2021-11-15\"\nend = \"2025-03-06\"\n\n[[trusted.windows-implement]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2022-01-27\"\nend = \"2025-03-06\"\n\n[[trusted.windows-interface]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2022-02-18\"\nend = \"2025-03-06\"\n\n[[trusted.windows-result]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2024-02-02\"\nend = \"2025-03-06\"\n\n[[trusted.windows-sys]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2021-11-15\"\nend = \"2025-03-06\"\n\n[[trusted.windows-targets]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2022-09-09\"\nend = \"2025-03-06\"\n\n[[trusted.windows-version]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2023-03-07\"\nend = \"2025-03-06\"\n\n[[trusted.windows_aarch64_gnullvm]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2022-09-01\"\nend = \"2025-03-06\"\n\n[[trusted.windows_aarch64_msvc]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2021-11-05\"\nend = \"2025-03-06\"\n\n[[trusted.windows_i686_gnu]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2021-10-28\"\nend = \"2025-03-06\"\n\n[[trusted.windows_i686_gnullvm]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2024-04-02\"\nend = \"2025-05-08\"\n\n[[trusted.windows_i686_msvc]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2021-10-27\"\nend = \"2025-03-06\"\n\n[[trusted.windows_x86_64_gnu]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2021-10-28\"\nend = \"2025-03-06\"\n\n[[trusted.windows_x86_64_gnullvm]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2022-09-01\"\nend = \"2025-03-06\"\n\n[[trusted.windows_x86_64_msvc]]\ncriteria = \"safe-to-deploy\"\nuser-id = 64539             # Kenny Kerr (kennykerr)\nstart = \"2021-10-27\"\nend = \"2025-03-06\"\n\n[[trusted.winnow]]\ncriteria = \"safe-to-deploy\"\nuser-id = 6743              # Ed Page (epage)\nstart = \"2023-02-22\"\nend = \"2025-03-06\"\n\n[[trusted.wry]]\ncriteria = \"safe-to-deploy\"\nuser-id = 95389             # tauri (tauri-bot)\nstart = \"2021-03-11\"\nend = \"2025-05-08\"\n\n[[trusted.zeroize]]\ncriteria = \"safe-to-deploy\"\nuser-id = 267               # Tony Arcieri (tarcieri)\nstart = \"2021-11-05\"\nend = \"2025-05-08\"\n"
  },
  {
    "path": "supply-chain/config.toml",
    "content": "\n# cargo-vet config file\n\n[cargo-vet]\nversion = \"0.9\"\n\n[imports.bytecode-alliance]\nurl = \"https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-chain/audits.toml\"\n\n[imports.embark-studios]\nurl = \"https://raw.githubusercontent.com/EmbarkStudios/rust-ecosystem/main/audits.toml\"\n\n[imports.google]\nurl = \"https://raw.githubusercontent.com/google/supply-chain/main/audits.toml\"\n\n[imports.isrg]\nurl = \"https://raw.githubusercontent.com/divviup/libprio-rs/main/supply-chain/audits.toml\"\n\n[imports.mozilla]\nurl = \"https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml\"\n\n[imports.zcash]\nurl = \"https://raw.githubusercontent.com/zcash/rust-ecosystem/main/supply-chain/audits.toml\"\n\n[policy.tauri]\naudit-as-crates-io = true\n\n[policy.tauri-build]\naudit-as-crates-io = true\n\n[policy.tauri-codegen]\naudit-as-crates-io = true\n\n[policy.tauri-macros]\naudit-as-crates-io = true\n\n[policy.tauri-plugin]\naudit-as-crates-io = true\n\n[policy.tauri-runtime]\naudit-as-crates-io = true\n\n[policy.tauri-runtime-wry]\naudit-as-crates-io = true\n\n[policy.tauri-utils]\naudit-as-crates-io = true\n\n[[exemptions.addr2line]]\nversion = \"0.21.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.alloc-no-stdlib]]\nversion = \"2.0.4\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.alloc-stdlib]]\nversion = \"0.2.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.android-tzdata]]\nversion = \"0.1.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.as-raw-xcb-connection]]\nversion = \"1.0.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.atk]]\nversion = \"0.18.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.atk-sys]]\nversion = \"0.18.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.base64]]\nversion = \"0.21.7\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.bitflags]]\nversion = \"1.3.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.block]]\nversion = \"0.1.6\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.brotli]]\nversion = \"3.4.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.brotli-decompressor]]\nversion = \"2.5.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.bytemuck]]\nversion = \"1.14.3\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.bytemuck_derive]]\nversion = \"1.5.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.cairo-rs]]\nversion = \"0.18.5\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.cairo-sys-rs]]\nversion = \"0.18.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.camino]]\nversion = \"1.1.6\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.cargo-platform]]\nversion = \"0.1.7\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.cargo_toml]]\nversion = \"0.17.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.cesu8]]\nversion = \"1.1.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.cfb]]\nversion = \"0.7.3\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.cfg_aliases]]\nversion = \"0.2.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.chrono]]\nversion = \"0.4.34\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.cocoa]]\nversion = \"0.25.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.cocoa-foundation]]\nversion = \"0.1.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.combine]]\nversion = \"4.6.6\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.console]]\nversion = \"0.15.8\"\ncriteria = \"safe-to-run\"\n\n[[exemptions.core-foundation]]\nversion = \"0.9.4\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.core-graphics-types]]\nversion = \"0.1.3\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.crc32fast]]\nversion = \"1.4.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.crossbeam-channel]]\nversion = \"0.5.12\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.crossbeam-utils]]\nversion = \"0.8.19\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.cssparser]]\nversion = \"0.27.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.ctor]]\nversion = \"0.2.7\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.ctr]]\nversion = \"0.9.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.darling]]\nversion = \"0.20.8\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.darling_core]]\nversion = \"0.20.8\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.darling_macro]]\nversion = \"0.20.8\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.data-url]]\nversion = \"0.3.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.deranged]]\nversion = \"0.3.11\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.dirs-sys-next]]\nversion = \"0.1.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.dispatch]]\nversion = \"0.2.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.dlib]]\nversion = \"0.5.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.dlopen2]]\nversion = \"0.7.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.dlopen2_derive]]\nversion = \"0.4.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.downcast-rs]]\nversion = \"1.2.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.drm]]\nversion = \"0.11.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.drm-ffi]]\nversion = \"0.7.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.drm-fourcc]]\nversion = \"2.2.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.drm-sys]]\nversion = \"0.6.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.dtoa-short]]\nversion = \"0.3.4\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.dunce]]\nversion = \"1.0.4\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.embed-resource]]\nversion = \"2.4.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.embed_plist]]\nversion = \"1.2.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.encode_unicode]]\nversion = \"0.3.6\"\ncriteria = \"safe-to-run\"\n\n[[exemptions.fdeflate]]\nversion = \"0.3.4\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.field-offset]]\nversion = \"0.3.6\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.flate2]]\nversion = \"1.0.28\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.futf]]\nversion = \"0.1.5\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.futures-executor]]\nversion = \"0.3.30\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.futures-io]]\nversion = \"0.3.30\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.futures-macro]]\nversion = \"0.3.30\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.futures-sink]]\nversion = \"0.3.30\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.futures-task]]\nversion = \"0.3.30\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.futures-util]]\nversion = \"0.3.30\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gdk]]\nversion = \"0.18.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gdk-pixbuf]]\nversion = \"0.18.5\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gdk-pixbuf-sys]]\nversion = \"0.18.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gdk-sys]]\nversion = \"0.18.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gdkwayland-sys]]\nversion = \"0.18.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gdkx11]]\nversion = \"0.18.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gdkx11-sys]]\nversion = \"0.18.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.generator]]\nversion = \"0.7.6\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.generic-array]]\nversion = \"0.14.7\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gethostname]]\nversion = \"0.4.3\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.getrandom]]\nversion = \"0.1.16\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.getrandom]]\nversion = \"0.2.12\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gimli]]\nversion = \"0.28.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gio]]\nversion = \"0.18.4\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gio-sys]]\nversion = \"0.18.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.glib]]\nversion = \"0.18.5\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.glib-macros]]\nversion = \"0.18.5\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.glib-sys]]\nversion = \"0.18.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gobject-sys]]\nversion = \"0.18.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gtk]]\nversion = \"0.18.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gtk-sys]]\nversion = \"0.18.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.gtk3-macros]]\nversion = \"0.18.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.hermit-abi]]\nversion = \"0.3.8\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.html5ever]]\nversion = \"0.26.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.http-range]]\nversion = \"0.1.5\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.hyper-rustls]]\nversion = \"0.24.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.iana-time-zone]]\nversion = \"0.1.60\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.ico]]\nversion = \"0.3.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.image]]\nversion = \"0.24.9\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.infer]]\nversion = \"0.15.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.insta]]\nversion = \"1.35.1\"\ncriteria = \"safe-to-run\"\n\n[[exemptions.instant]]\nversion = \"0.1.12\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.ipnet]]\nversion = \"2.9.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.jni-sys]]\nversion = \"0.3.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.json-patch]]\nversion = \"1.2.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.json5]]\nversion = \"0.4.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.keyboard-types]]\nversion = \"0.7.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.kuchikiki]]\nversion = \"0.8.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.libloading]]\nversion = \"0.7.4\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.libloading]]\nversion = \"0.8.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.libredox]]\nversion = \"0.0.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.libxdo]]\nversion = \"0.6.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.libxdo-sys]]\nversion = \"0.11.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.mac]]\nversion = \"0.1.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.markup5ever]]\nversion = \"0.11.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.memmap2]]\nversion = \"0.9.4\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.memoffset]]\nversion = \"0.9.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.miniz_oxide]]\nversion = \"0.7.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.mio]]\nversion = \"0.8.11\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.ndk]]\nversion = \"0.7.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.ndk-sys]]\nversion = \"0.4.1+23.1.7779620\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.nodrop]]\nversion = \"0.1.14\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.num-conv]]\nversion = \"0.1.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.objc]]\nversion = \"0.2.7\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.objc_exception]]\nversion = \"0.1.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.objc_id]]\nversion = \"0.1.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.object]]\nversion = \"0.32.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.once_cell]]\nversion = \"1.19.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.openssl]]\nversion = \"0.10.64\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.openssl-sys]]\nversion = \"0.9.101\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.pango]]\nversion = \"0.18.3\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.pango-sys]]\nversion = \"0.18.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.pest]]\nversion = \"2.7.7\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.pest_derive]]\nversion = \"2.7.7\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.pest_generator]]\nversion = \"2.7.7\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.pest_meta]]\nversion = \"2.7.7\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.phf]]\nversion = \"0.8.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.phf_codegen]]\nversion = \"0.8.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.phf_generator]]\nversion = \"0.8.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.phf_macros]]\nversion = \"0.8.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.phf_shared]]\nversion = \"0.8.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.pkg-config]]\nversion = \"0.3.30\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.plist]]\nversion = \"1.6.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.png]]\nversion = \"0.17.13\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.powerfmt]]\nversion = \"0.2.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.ppv-lite86]]\nversion = \"0.2.17\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.proc-macro-crate]]\nversion = \"1.3.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.proc-macro-crate]]\nversion = \"2.0.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.proc-macro-error]]\nversion = \"1.0.4\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.proptest]]\nversion = \"1.4.0\"\ncriteria = \"safe-to-run\"\n\n[[exemptions.quick-error]]\nversion = \"1.2.3\"\ncriteria = \"safe-to-run\"\n\n[[exemptions.quick-xml]]\nversion = \"0.31.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.rand]]\nversion = \"0.7.3\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.rand]]\nversion = \"0.8.5\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.rand_chacha]]\nversion = \"0.2.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.rand_core]]\nversion = \"0.5.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.rand_hc]]\nversion = \"0.2.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.rand_pcg]]\nversion = \"0.2.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.redox_syscall]]\nversion = \"0.4.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.redox_users]]\nversion = \"0.4.4\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.ring]]\nversion = \"0.17.8\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.rustls]]\nversion = \"0.21.10\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.rustls-pemfile]]\nversion = \"1.0.4\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.rustls-webpki]]\nversion = \"0.101.7\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.rusty-fork]]\nversion = \"0.3.0\"\ncriteria = \"safe-to-run\"\n\n[[exemptions.safemem]]\nversion = \"0.3.3\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.schannel]]\nversion = \"0.1.23\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.schemars]]\nversion = \"0.8.16\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.schemars_derive]]\nversion = \"0.8.16\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.sct]]\nversion = \"0.7.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.security-framework]]\nversion = \"2.9.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.security-framework-sys]]\nversion = \"2.9.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.serde_urlencoded]]\nversion = \"0.7.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.serde_with]]\nversion = \"3.6.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.serde_with_macros]]\nversion = \"3.6.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.sha2]]\nversion = \"0.10.8\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.simd-adler32]]\nversion = \"0.3.7\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.similar]]\nversion = \"2.4.0\"\ncriteria = \"safe-to-run\"\n\n[[exemptions.siphasher]]\nversion = \"0.3.11\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.socket2]]\nversion = \"0.5.6\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.softbuffer]]\nversion = \"0.4.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.soup3]]\nversion = \"0.5.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.soup3-sys]]\nversion = \"0.5.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.spin]]\nversion = \"0.9.8\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.stable_deref_trait]]\nversion = \"1.2.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.state]]\nversion = \"0.6.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.string_cache]]\nversion = \"0.8.7\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.string_cache_codegen]]\nversion = \"0.5.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.swift-rs]]\nversion = \"1.0.6\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.sync_wrapper]]\nversion = \"0.1.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.system-configuration]]\nversion = \"0.5.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.system-configuration-sys]]\nversion = \"0.5.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.system-deps]]\nversion = \"6.2.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.tempfile]]\nversion = \"3.10.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.tendril]]\nversion = \"0.4.3\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.thin-slice]]\nversion = \"0.1.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.time]]\nversion = \"0.3.34\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.time-macros]]\nversion = \"0.2.17\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.tiny-xlib]]\nversion = \"0.2.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.tokio-rustls]]\nversion = \"0.24.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.tower-service]]\nversion = \"0.3.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.tracing]]\nversion = \"0.1.40\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.tracing-attributes]]\nversion = \"0.1.27\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.tracing-core]]\nversion = \"0.1.32\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.tracing-log]]\nversion = \"0.2.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.treediff]]\nversion = \"4.0.3\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.typenum]]\nversion = \"1.17.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.unarray]]\nversion = \"0.1.4\"\ncriteria = \"safe-to-run\"\n\n[[exemptions.untrusted]]\nversion = \"0.9.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.utf-8]]\nversion = \"0.7.6\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.uuid]]\nversion = \"1.7.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.vswhom]]\nversion = \"0.1.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.vswhom-sys]]\nversion = \"0.1.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.wait-timeout]]\nversion = \"0.2.0\"\ncriteria = \"safe-to-run\"\n\n[[exemptions.wasm-streams]]\nversion = \"0.4.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.wayland-backend]]\nversion = \"0.3.3\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.wayland-client]]\nversion = \"0.31.2\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.wayland-scanner]]\nversion = \"0.31.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.wayland-sys]]\nversion = \"0.31.1\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.webpki-roots]]\nversion = \"0.25.4\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.webview2-com]]\nversion = \"0.28.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.webview2-com-macros]]\nversion = \"0.7.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.webview2-com-sys]]\nversion = \"0.28.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.winapi]]\nversion = \"0.3.9\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.winapi-i686-pc-windows-gnu]]\nversion = \"0.4.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.winapi-x86_64-pc-windows-gnu]]\nversion = \"0.4.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.winreg]]\nversion = \"0.50.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.winreg]]\nversion = \"0.51.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.x11]]\nversion = \"2.21.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.x11-dl]]\nversion = \"2.21.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.x11rb]]\nversion = \"0.13.0\"\ncriteria = \"safe-to-deploy\"\n\n[[exemptions.x11rb-protocol]]\nversion = \"0.13.0\"\ncriteria = \"safe-to-deploy\"\n"
  }
]