diff --git a/.gitattributes b/.gitattributes index f36b1615d06..d01bb0791af 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,3 +6,6 @@ # patches/*.patch with `\n`-joined patterns. Normalize to LF in the working # copy on every platform; the stored blobs are already LF. * text=auto eol=lf + +# Patched crates.io copies (nil-safe NSOpenPanel). Not first-party source. +desktop/src-tauri/vendor/** linguist-vendored diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index 081e345edb7..84c9b8ddad4 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -210,7 +210,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -221,7 +221,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -264,7 +264,7 @@ dependencies = [ "objc2-foundation", "parking_lot", "percent-encoding", - "windows-sys 0.60.2", + "windows-sys 0.52.0", "wl-clipboard-rs", "x11rb", ] @@ -1624,7 +1624,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2291,7 +2291,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.118", ] [[package]] @@ -2469,7 +2469,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3268,7 +3268,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.1.3", + "windows-link 0.2.1", "windows-result 0.4.1", ] @@ -5726,7 +5726,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5885,7 +5885,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6398,7 +6398,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7102,7 +7102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] @@ -8610,8 +8610,6 @@ checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "rfd" version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" dependencies = [ "block2", "dispatch2", @@ -8846,7 +8844,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -9815,7 +9813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -10854,7 +10852,7 @@ dependencies = [ "parking_lot", "rustix 1.1.4", "signal-hook 0.3.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -11596,7 +11594,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -11697,7 +11695,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -12548,7 +12546,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -13199,8 +13197,6 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wry" version = "0.55.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" dependencies = [ "base64 0.22.1", "block2", diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index 2c3686814f5..6853b451c13 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -53,7 +53,7 @@ webkit2gtk = { version = "=2.0.2", features = ["v2_22"] } [target.'cfg(target_os = "macos")'.dependencies] block2 = { version = "0.6", default-features = false, features = ["std"] } objc2 = { version = "0.6.4", default-features = false } -objc2-app-kit = { version = "0.3.2", default-features = false, features = ["NSEvent", "NSHapticFeedback", "NSMenu", "NSMenuItem", "NSStatusItem", "block2"] } +objc2-app-kit = { version = "0.3.2", default-features = false, features = ["NSApplication", "NSEvent", "NSHapticFeedback", "NSMenu", "NSMenuItem", "NSStatusItem", "block2"] } objc2-foundation = { version = "0.3.2", default-features = false, features = ["NSDictionary", "NSError", "NSBundle", "NSObject", "NSProcessInfo", "NSString"] } objc2-user-notifications = { version = "0.3.2", default-features = false, features = ["block2", "UNNotification", "UNNotificationContent", "UNNotificationRequest", "UNNotificationResponse", "UNNotificationSettings", "UNNotificationTrigger", "UNUserNotificationCenter"] } keyring = { version = "3.6.3", default-features = false, features = ["apple-native", "vendored"], optional = true } @@ -155,3 +155,11 @@ tokio = { version = "1", features = ["test-util"] } # The relay's media validation, so the snapshot-sharing tests can prove the # full export → sanitize → relay-accept → import contract end to end. buzz_media_pkg = { package = "buzz-media", path = "../../crates/buzz-media" } + +# wry 0.55.1 / rfd 0.16.0 still panic when +[NSOpenPanel openPanel] returns +# nil (macOS 26, ad-hoc local builds). Upstream wry#1716 is unmerged and +# wry 0.56.1 still calls the typed binding. Patch in-tree; drop when a +# crates.io release retains nil and cancels instead of aborting. +[patch.crates-io] +wry = { path = "vendor/wry" } +rfd = { path = "vendor/rfd" } diff --git a/desktop/src-tauri/Info.plist b/desktop/src-tauri/Info.plist index aa6a5e0945b..b23ba20abfa 100644 --- a/desktop/src-tauri/Info.plist +++ b/desktop/src-tauri/Info.plist @@ -6,6 +6,13 @@ Hula Buzz CFBundleName Hula Buzz + + LSUIElement + + NSPrincipalClass + NSApplication NSMicrophoneUsageDescription Buzz needs microphone access for voice huddles. NSCameraUsageDescription diff --git a/desktop/src-tauri/src/macos_file_panel.rs b/desktop/src-tauri/src/macos_file_panel.rs new file mode 100644 index 00000000000..165420e7456 --- /dev/null +++ b/desktop/src-tauri/src/macos_file_panel.rs @@ -0,0 +1,70 @@ +//! macOS file-panel crash guard. +//! +//! Composer attach uses a hidden ``. WKWebView asks Wry's +//! `run_file_upload_panel`, which called `NSOpenPanel::openPanel()`. On macOS +//! 26 Tahoe — especially ad-hoc/unsigned local `.app` builds — that class +//! method can return nil. The typed objc2 binding treats nil as a hard fail +//! (`none_fail`) and the process SIGABRTs before a picker appears. +//! +//! Native `tauri-plugin-dialog` / `rfd` hits the same constructor. Both crates +//! are patched in `vendor/` to retain the raw return and cancel instead of +//! aborting. This module keeps the process a regular activating app so AppKit +//! is willing to present the panel at all (crash reports showed Role: +//! Background). + +#[cfg(target_os = "macos")] +pub(crate) fn ensure_regular_activation() { + use objc2::MainThreadMarker; + use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy}; + + let Some(mtm) = MainThreadMarker::new() else { + return; + }; + let app = NSApplication::sharedApplication(mtm); + let _ = app.setActivationPolicy(NSApplicationActivationPolicy::Regular); + NSApplication::activate(&app); +} + +#[cfg(test)] +mod tests { + const WRY_UI_DELEGATE: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/vendor/wry/src/wkwebview/class/wry_web_view_ui_delegate.rs" + )); + const RFD_PANEL: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/vendor/rfd/src/backend/macos/file_dialog/panel_ffi.rs" + )); + + #[test] + fn patched_wry_cancels_when_nsopenpanel_is_nil() { + assert!( + WRY_UI_DELEGATE.contains("Retained::retain(ptr)"), + "wry must retain +[NSOpenPanel openPanel] without the typed nil panic" + ); + assert!( + WRY_UI_DELEGATE.contains("(*handler).call((null_mut(),))"), + "nil panel must cancel the WebKit upload instead of aborting" + ); + assert!( + !WRY_UI_DELEGATE.contains("NSOpenPanel::openPanel(mtm)"), + "typed NSOpenPanel::openPanel panics on nil" + ); + } + + #[test] + fn patched_rfd_does_not_use_typed_open_panel() { + assert!( + RFD_PANEL.contains("fn try_open_panel"), + "rfd must go through a nil-safe open-panel helper" + ); + assert!( + !RFD_PANEL.contains("NSOpenPanel::openPanel(mtm)"), + "typed NSOpenPanel::openPanel panics on nil" + ); + assert!( + !RFD_PANEL.contains("NSSavePanel::savePanel(mtm)"), + "typed NSSavePanel::savePanel panics on nil" + ); + } +} diff --git a/desktop/src-tauri/src/tray_menu.rs b/desktop/src-tauri/src/tray_menu.rs index 3f9fe49fe08..9a55bcaba4c 100644 --- a/desktop/src-tauri/src/tray_menu.rs +++ b/desktop/src-tauri/src/tray_menu.rs @@ -3,8 +3,11 @@ //! The webview owns the live agent-turn state. It sends the small display //! projection here so the native menu can remain useful while Buzz is hidden. -// Mouse back/forward (X1/X2 buttons and swipe) is also macOS-only native I/O; -// group it here so both platform-layer init paths share one call site in lib.rs. +// macOS-only native I/O lives beside the tray init so lib.rs stays one call +// site (file-size ratchet). Mouse back/forward and the NSOpenPanel crash +// guard both run from `init`. +#[path = "macos_file_panel.rs"] +mod macos_file_panel; #[path = "mouse_nav.rs"] pub(crate) mod mouse_nav; @@ -474,6 +477,7 @@ fn handle_menu_event(app: &AppHandle, id: &str) { /// Installs the persistent Buzz tray icon with the initial empty activity menu. pub fn init(app: &AppHandle) -> tauri::Result<()> { + macos_file_panel::ensure_regular_activation(); let preview_activities = preview_activities(); let preview_recent_activities = preview_recent_activities(); let activities = preview_activities.as_deref().unwrap_or(&[]); diff --git a/desktop/src-tauri/src/util.rs b/desktop/src-tauri/src/util.rs index 7fa4dc6768e..dc3d54cfc12 100644 --- a/desktop/src-tauri/src/util.rs +++ b/desktop/src-tauri/src/util.rs @@ -1,5 +1,12 @@ use chrono::Utc; +// tray_menu (and its file-panel child) is macOS-only. Include the same +// module under test so Linux CI still checks the vendored nil-NSOpenPanel +// guards. +#[cfg(test)] +#[path = "macos_file_panel.rs"] +mod macos_file_panel; + pub fn now_iso() -> String { Utc::now().to_rfc3339() } diff --git a/desktop/src-tauri/vendor/README.md b/desktop/src-tauri/vendor/README.md new file mode 100644 index 00000000000..ba9484b1ad1 --- /dev/null +++ b/desktop/src-tauri/vendor/README.md @@ -0,0 +1,17 @@ +# Vendored desktop patches + +Pinned copies of crates.io `wry` 0.55.1 and `rfd` 0.16.0 with a nil-safe +`NSOpenPanel` / `NSSavePanel` constructor. + +`+[NSOpenPanel openPanel]` can return nil (macOS 26 Tahoe, ad-hoc/unsigned +local `.app` builds, code-signature mismatch after an in-place replace). +The typed `objc2-app-kit` binding treats that as a programming error and +aborts the process. Composer attach goes through Wry's WKWebView upload +panel; `pick_and_upload_*` goes through rfd. + +The patches retain the raw Objective-C return, cancel the picker when it +is nil, and promote the app to `NSApplicationActivationPolicyRegular` +before asking AppKit for the panel. + +Replace with a crates.io bump when tauri-apps/wry#1716 (or equivalent) is +released and rfd does the same. diff --git a/desktop/src-tauri/vendor/rfd/.cargo_vcs_info.json b/desktop/src-tauri/vendor/rfd/.cargo_vcs_info.json new file mode 100644 index 00000000000..9afe6d6f726 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "5d32eec3a7930eb43b7e864eb773831bbd3d91b4" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/desktop/src-tauri/vendor/rfd/.editorconifg b/desktop/src-tauri/vendor/rfd/.editorconifg new file mode 100644 index 00000000000..b01431d6591 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/.editorconifg @@ -0,0 +1,19 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +max_line_length = 100 + +[*.{rs, toml}] +indent_size = 4 + +[*.md] +trim_trailing_whitespace = true +indent_size = 4 + +[Dockerfile] +indent_size = 4 \ No newline at end of file diff --git a/desktop/src-tauri/vendor/rfd/.github/workflows/cargo-deny.yml b/desktop/src-tauri/vendor/rfd/.github/workflows/cargo-deny.yml new file mode 100644 index 00000000000..cca9ae564c4 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/.github/workflows/cargo-deny.yml @@ -0,0 +1,42 @@ +# https://github.com/EmbarkStudios/cargo-deny +# To run locally: `cargo install cargo-deny && cargo deny check` + +name: cargo-deny + +on: [push, pull_request] + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - name: Ubuntu GTK + target: x86_64-unknown-linux-gnu + flags: "--no-default-features --features gtk3" + - name: Ubuntu XDG + target: x86_64-unknown-linux-gnu + flags: "--no-default-features --features xdg-portal --exclude syn" + - name: Windows + target: x86_64-pc-windows-msvc + flags: "" + - name: Windows CC6 + target: x86_64-pc-windows-msvc + flags: "--features common-controls-v6" + - name: macOS + target: x86_64-apple-darwin + flags: "" + - name: WASM32 + target: wasm32-unknown-unknown + flags: "" + + name: ${{ matrix.name }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: EmbarkStudios/cargo-deny-action@v2 + with: + log-level: error + command: check + arguments: ${{ matrix.flags }} --target ${{ matrix.target }} diff --git a/desktop/src-tauri/vendor/rfd/.github/workflows/changelog.yml b/desktop/src-tauri/vendor/rfd/.github/workflows/changelog.yml new file mode 100644 index 00000000000..5b91fd7720e --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/.github/workflows/changelog.yml @@ -0,0 +1,15 @@ +name: Changelog check + +on: + pull_request: + branches: [ master ] + types: [ opened, synchronize, reopened, labeled, unlabeled ] + +jobs: + Changelog-Entry-Check: + name: Check Changelog Action + runs-on: ubuntu-latest + steps: + - uses: tarides/changelog-check-action@v2 + with: + changelog: CHANGELOG.md diff --git a/desktop/src-tauri/vendor/rfd/.github/workflows/rust.yml b/desktop/src-tauri/vendor/rfd/.github/workflows/rust.yml new file mode 100644 index 00000000000..204fea2cb02 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/.github/workflows/rust.yml @@ -0,0 +1,59 @@ +name: Rust + +on: + push: + branches: + - master + pull_request: + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - name: Ubuntu GTK + os: ubuntu-latest + target: x86_64-unknown-linux-gnu + flags: '--no-default-features --features gtk3' + - name: Ubuntu XDG + os: ubuntu-latest + target: x86_64-unknown-linux-gnu + flags: '--no-default-features --features xdg-portal,tokio' + - name: Windows + os: windows-latest + target: x86_64-pc-windows-msvc + flags: '' + - name: Windows CC6 + os: windows-latest + target: x86_64-pc-windows-msvc + flags: '--features common-controls-v6' + - name: macOS + os: macos-latest + target: aarch64-apple-darwin + flags: '' + - name: WASM32 + os: ubuntu-latest + target: wasm32-unknown-unknown + flags: '' + + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: "[Ubuntu GTK] install dependencies" + if: matrix.name == 'Ubuntu GTK' + run: sudo apt update && sudo apt install libgtk-3-dev + - name: "[Ubuntu XDG] install dependencies" + if: matrix.name == 'Ubuntu XDG' + run: sudo apt update && sudo apt install libwayland-dev + - name: "[WASM] rustup" + if: matrix.name == 'WASM32' + run: rustup target add wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + - name: Build + run: cargo build --target ${{ matrix.target }} ${{ matrix.flags }} + - name: Test + # FIXME + if: matrix.name != 'WASM32' + run: cargo test --target ${{ matrix.target }} ${{ matrix.flags }} diff --git a/desktop/src-tauri/vendor/rfd/.gitignore b/desktop/src-tauri/vendor/rfd/.gitignore new file mode 100644 index 00000000000..2674b3d6835 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/.gitignore @@ -0,0 +1,3 @@ +/target +.idea +.vscode \ No newline at end of file diff --git a/desktop/src-tauri/vendor/rfd/CHANGELOG.md b/desktop/src-tauri/vendor/rfd/CHANGELOG.md new file mode 100644 index 00000000000..6e6eec38572 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/CHANGELOG.md @@ -0,0 +1,159 @@ +# Change Log + +## Unreleased + +## 0.16.0 + +- Fix regressions on Wayland due to `ashpd` upgrade (#255). +- The `pick_file()` method of file dialog targeted WASM now can return `None` correctly when cancelled (#258) +- Update `windows-sys` to 0.60. +- Make `ashpd` Wayland APIs optional. These are now gated behind the `wayland` feature, which is enabled by default. + +### Changed items in the public API +```diff +-pub fn AsyncFileDialog::set_parent(self, parent: &W) -> Self ++pub fn AsyncFileDialog::set_parent(self, parent: &W) -> Self +-pub fn AsyncMessageDialog::set_parent(self, parent: &W) -> Self ++pub fn MessageDialog::set_parent(self, parent: &W) -> Self +-pub fn FileDialog::set_parent(self, parent: &W) -> Self ++pub fn rfd::FileDialog::set_parent(self, parent: &W) -> Self +-pub fn MessageDialog::set_parent(self, parent: &W) -> Self ++pub fn MessageDialog::set_parent(self, parent: &W) -> Self +``` + +## 0.15.3 + +- Update `objc2` to v0.6. +- Update `ashpd` to 0.11. + +## 0.15.1 + +- Update `ashpd` to 0.10. +- Fix issue where with no filter added no files are selectable on Windows (#211). + +## 0.15.0 + +- Move from `objc` crates to `objc2` crates. +- Fix `AsyncFileDialog` blocking the executor on Windows (#191) +- Add `TDF_SIZE_TO_CONTENT` to `TaskDialogIndirect` config so that it can display longer text without truncating/wrapping (80 characters instead of 55) (#202) +- Fix `xdg-portal` backend not accepting special characters in message dialogs +- Make `set_parent` require `HasWindowHandle + HasDisplayHandle` +- Add support for `set_parent` in XDG Portals +- Update `ashpd` to 0.9. +- Add support for files without an extension in XDG Portal filters +- Derive `Clone` for `FileHandle` + +## 0.14.0 + +- i18n for GTK and XDG Portal +- Use XDG Portal as default +- Use zenity as a fallback for XDG Portal +- Update `raw-window-handle` to 0.6. +- Update `winit` in example to 0.29. +- Update `ashpd` to 0.8. +- Update wasm CSS to respect the color scheme (including dark mode) +- Fix macOS sync backend incorrectly setting the parent window +- Add `FileDialog/AsyncFileDialog::set_can_create_directories`, supported on macOS only. + +## 0.13.0 + +- **[Breaking]** Users of the `xdg-portal` feature must now also select the `tokio` + or `async-std` feature +- [macOS] Use NSOpenPanel.message instead of title #166 + +## 0.12.1 + +- Fix `FileHandle::inner` (under feature `file-handle-inner`) on wasm + +## 0.12.0 + +- Add title support for WASM (#132) +- Add Create folder button to `pick_folder` on macOS (#127) +- Add support for Yes/No/Cancel buttons (#123) +- Change a string method signatures #117 +- WASM `save_file` (#134) +- Update `gtk-sys` to `0.18` (#143) +- Update `ashpd` to `0.6` (#133) +- Replace windows with `windows-sys` (#118) +- Make zenity related deps optional (#141) + +## 0.11.3 + +- Zenity message dialogs for xdg portal backend + +## 0.10.1 + +- Update `gtk-sys` to `0.16` and `windows-rs` to `0.44` + +## 0.10.0 + +- fix(FileDialog::set_directory): fallback to default if path is empty + +## 0.9.0 + +- feat: customize button text, Close #74 +- feat: Add support for selecting multiple folders, fixes #73 + +## 0.8.4 + +- XDG: decode URI before converting to PathBuf #70 + +## 0.8.3 + +- Windows-rs update 0.37 + +## 0.8.2 + +- Windows-rs update 0.35 + +## 0.8.1 + +- Macos parent for sync FileDialog (#58) +- Windows-rs update 0.33 + +## 0.8.0 + +- `parent` feature was removed, it is always on now +- New feature `xdg-portal` +- Now you have to choose one of the features `gtk3` or `xdg-portal`, gtk is on by default +- `window` crate got updated to 0.32 + +## 0.7.0 + +- Safe Rust XDG Desktop Portal support + +## 0.6.3 + +- Update `windows` crate to 0.30. + +## 0.6.2 + +- Strip Win32 namespaces from directory paths + +## 0.6.0 + +- FreeBSD support +- Port to windows-rs +- Update RawWindowHandle to 0.4 + +## 0.4.4 + +- Fix `set_directory` on some windows setups (#22) +- Implement `set_file_name` on MacOS (#21) + +## 0.4.3 + +- `set_parent` support for `MessageDialog` on windows + +## 0.4.2 + +- GTK save dialog now sets current_name correctly (#18) + +## 0.4.1 + +- Update gtk + +## 0.4.0 + +- **[Breaking]** Fix misspeled `OkCancel` in `MessageButtons` (#12) +- `set_parent` support for Windows (#14) diff --git a/desktop/src-tauri/vendor/rfd/Cargo.lock b/desktop/src-tauri/vendor/rfd/Cargo.lock new file mode 100644 index 00000000000..d0f2fb04020 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/Cargo.lock @@ -0,0 +1,2052 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ashpd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand", + "raw-window-handle", + "serde", + "serde_repr", + "tokio", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 0.38.44", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 0.38.44", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.44", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "block2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "cc" +version = "1.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 0.38.44", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rfd" +version = "0.16.0" +dependencies = [ + "ashpd", + "block2", + "dispatch2", + "futures", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "pollster", + "raw-window-handle", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix 1.0.7", + "windows-sys 0.59.0", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "toml" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +dependencies = [ + "cc", + "downcast-rs", + "rustix 0.38.44", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +dependencies = [ + "bitflags", + "rustix 0.38.44", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2522b82023923eecb0b366da727ec883ace092e7887b61d3da5139f26b44da58" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tokio", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d2e12843c75108c00c618c2e8ef9675b50b6ec095b36dc965f2e5aed463c15" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zvariant" +version = "5.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557e89d54880377a507c94cd5452f20e35d14325faf9d2958ebeadce0966c1b2" +dependencies = [ + "endi", + "enumflags2", + "serde", + "url", + "winnow", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757779842a0d242061d24c28be589ce392e45350dfb9186dfd7a042a2e19870c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn", + "winnow", +] diff --git a/desktop/src-tauri/vendor/rfd/Cargo.toml b/desktop/src-tauri/vendor/rfd/Cargo.toml new file mode 100644 index 00000000000..6fe293917df --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/Cargo.toml @@ -0,0 +1,203 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "rfd" +version = "0.16.0" +authors = ["Poly "] +build = "build.rs" +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Rusty File Dialog" +documentation = "https://docs.rs/rfd" +readme = "README.md" +keywords = [ + "file", + "ui", + "dialog", +] +license = "MIT" +repository = "https://github.com/PolyMeilex/rfd" + +[package.metadata.docs.rs] +features = ["file-handle-inner"] + +[features] +async-std = ["ashpd?/async-std"] +common-controls-v6 = ["windows-sys/Win32_UI_Controls"] +default = [ + "xdg-portal", + "wayland", + "async-std", +] +file-handle-inner = [] +gtk3 = [ + "gtk-sys", + "glib-sys", + "gobject-sys", +] +tokio = ["ashpd?/tokio"] +wayland = ["ashpd?/wayland"] +xdg-portal = [ + "ashpd", + "urlencoding", + "pollster", +] + +[lib] +name = "rfd" +path = "src/lib.rs" + +[[example]] +name = "async" +path = "examples/async.rs" + +[[example]] +name = "msg" +path = "examples/msg.rs" + +[[example]] +name = "save" +path = "examples/save.rs" + +[[example]] +name = "simple" +path = "examples/simple.rs" + +[dependencies.log] +version = "0.4" + +[dependencies.raw-window-handle] +version = "0.6" + +[dev-dependencies.futures] +version = "0.3.12" + +[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))'.dependencies.ashpd] +version = "0.11" +features = ["raw_handle"] +optional = true +default-features = false + +[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))'.dependencies.glib-sys] +version = "0.18.0" +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))'.dependencies.gobject-sys] +version = "0.18.0" +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))'.dependencies.gtk-sys] +version = "0.18.0" +features = ["v3_24"] +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))'.dependencies.pollster] +version = "0.4" +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))'.dependencies.urlencoding] +version = "2.1.0" +optional = true + +[target.'cfg(target_arch = "wasm32")'.dependencies.js-sys] +version = "0.3.46" + +[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen] +version = "0.2.69" + +[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen-futures] +version = "0.4.19" + +[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] +version = "0.3.46" +features = [ + "Document", + "Element", + "HtmlInputElement", + "HtmlButtonElement", + "HtmlAnchorElement", + "Window", + "Navigator", + "UserActivation", + "File", + "FileList", + "FileReader", + "Blob", + "BlobPropertyBag", + "Url", +] + +[target.'cfg(target_os = "macos")'.dependencies.block2] +version = "0.6.0" + +[target.'cfg(target_os = "macos")'.dependencies.dispatch2] +version = "0.3.0" + +[target.'cfg(target_os = "macos")'.dependencies.objc2] +version = "0.6.0" + +[target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit] +version = "0.3.0" +features = [ + "std", + "block2", + "NSAlert", + "NSApplication", + "NSButton", + "NSControl", + "NSOpenPanel", + "NSPanel", + "NSResponder", + "NSRunningApplication", + "NSSavePanel", + "NSView", + "NSWindow", +] +default-features = false + +[target.'cfg(target_os = "macos")'.dependencies.objc2-core-foundation] +version = "0.3.0" +features = [ + "std", + "CFBase", + "CFDate", + "CFString", + "CFURL", + "CFUserNotification", +] +default-features = false + +[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation] +version = "0.3.0" +features = [ + "std", + "NSArray", + "NSEnumerator", + "NSString", + "NSThread", + "NSURL", +] +default-features = false + +[target.'cfg(target_os = "windows")'.dependencies.windows-sys] +version = "0.60" +features = [ + "Win32_Foundation", + "Win32_System_Com", + "Win32_UI_Shell_Common", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", +] diff --git a/desktop/src-tauri/vendor/rfd/Cargo.toml.orig b/desktop/src-tauri/vendor/rfd/Cargo.toml.orig new file mode 100644 index 00000000000..175e8de3afa --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/Cargo.toml.orig @@ -0,0 +1,117 @@ +[package] +name = "rfd" +version = "0.16.0" +edition = "2021" + +authors = ["Poly "] +description = "Rusty File Dialog" +keywords = ["file", "ui", "dialog"] +license = "MIT" +repository = "https://github.com/PolyMeilex/rfd" +documentation = "https://docs.rs/rfd" + +[features] +default = ["xdg-portal", "wayland", "async-std"] +file-handle-inner = [] +gtk3 = ["gtk-sys", "glib-sys", "gobject-sys"] +xdg-portal = ["ashpd", "urlencoding", "pollster"] +# Use async-std for xdg-portal +async-std = ["ashpd?/async-std"] +# Use tokio for xdg-portal +tokio = ["ashpd?/tokio"] +# Enable wayland support for xdg-portal +wayland = ["ashpd?/wayland"] +common-controls-v6 = ["windows-sys/Win32_UI_Controls"] + +[dev-dependencies] +futures = "0.3.12" + +[dependencies] +raw-window-handle = "0.6" +log = "0.4" + +[target.'cfg(target_os = "macos")'.dependencies] +block2 = "0.6.0" +dispatch2 = "0.3.0" +objc2 = "0.6.0" +objc2-foundation = { version = "0.3.0", default-features = false, features = [ + "std", + "NSArray", + "NSEnumerator", + "NSString", + "NSThread", + "NSURL", +] } +objc2-app-kit = { version = "0.3.0", default-features = false, features = [ + "std", + "block2", + "NSAlert", + "NSApplication", + "NSButton", + "NSControl", + "NSOpenPanel", + "NSPanel", + "NSResponder", + "NSRunningApplication", + "NSSavePanel", + "NSView", + "NSWindow", +] } +objc2-core-foundation = { version = "0.3.0", default-features = false, features = [ + "std", + "CFBase", + "CFDate", + "CFString", + "CFURL", + "CFUserNotification", +] } + +[target.'cfg(target_os = "windows")'.dependencies] +windows-sys = { version = "0.60", features = [ + "Win32_Foundation", + "Win32_System_Com", + "Win32_UI_Shell_Common", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", +] } + +[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))'.dependencies] +# XDG Desktop Portal +ashpd = { version = "0.11", optional = true, default-features = false, features = [ + "raw_handle", +] } +urlencoding = { version = "2.1.0", optional = true } +pollster = { version = "0.4", optional = true } +# GTK +gtk-sys = { version = "0.18.0", features = ["v3_24"], optional = true } +glib-sys = { version = "0.18.0", optional = true } +gobject-sys = { version = "0.18.0", optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2.69" +js-sys = "0.3.46" +web-sys = { version = "0.3.46", features = [ + 'Document', + 'Element', + 'HtmlInputElement', + 'HtmlButtonElement', + 'HtmlAnchorElement', + 'Window', + 'Navigator', + 'UserActivation', + 'File', + 'FileList', + 'FileReader', + 'Blob', + 'BlobPropertyBag', + 'Url', +] } +wasm-bindgen-futures = "0.4.19" + +[[example]] +name = "simple" +[[example]] +name = "async" + +[package.metadata.docs.rs] +features = ["file-handle-inner"] diff --git a/desktop/src-tauri/vendor/rfd/LICENSE b/desktop/src-tauri/vendor/rfd/LICENSE new file mode 100644 index 00000000000..cef2babb568 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Bartłomiej Maryńczak + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/desktop/src-tauri/vendor/rfd/README.md b/desktop/src-tauri/vendor/rfd/README.md new file mode 100644 index 00000000000..6584cb5b035 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/README.md @@ -0,0 +1,22 @@ +![img](https://github.com/PolyMeilex/rfd/assets/20758186/9bef59fa-60f0-448c-b9db-44ab436ee611) + + +[![version](https://img.shields.io/crates/v/rfd.svg)](https://crates.io/crates/rfd) +[![Documentation](https://docs.rs/rfd/badge.svg)](https://docs.rs/rfd) +[![dependency status](https://deps.rs/crate/rfd/0.15.1/status.svg)](https://deps.rs/crate/rfd/0.15.3) + +Rusty File Dialogs is a cross platform Rust library for using native file open/save dialogs. +It provides both asynchronous and synchronous APIs. Supported platforms: + + * Windows + * macOS + * Linux & BSDs (GTK3 or XDG Desktop Portal) + * WASM32 (async only) + +Refer to the [documentation](https://docs.rs/rfd) for more details. + + +## Platform-specific notes + +### Linux +Please refer to [Linux & BSD backends](https://docs.rs/rfd/latest/rfd/#linux--bsd-backends) for information about the needed dependencies to be able to compile on Linux. diff --git a/desktop/src-tauri/vendor/rfd/build.rs b/desktop/src-tauri/vendor/rfd/build.rs new file mode 100644 index 00000000000..558ea124b40 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/build.rs @@ -0,0 +1,26 @@ +fn main() { + let target_os = std::env::var("CARGO_CFG_TARGET_OS").expect("target OS not detected"); + + match target_os.as_str() { + "macos" => println!("cargo:rustc-link-lib=framework=AppKit"), + "windows" => {} + _ => { + let gtk = std::env::var_os("CARGO_FEATURE_GTK3").is_some(); + let xdg = std::env::var_os("CARGO_FEATURE_XDG_PORTAL").is_some(); + + if gtk && xdg { + panic!("You can't enable both `gtk3` and `xdg-portal` features at once"); + } else if !gtk && !xdg { + panic!("You need to choose at least one backend: `gtk3` or `xdg-portal` features"); + } + + if xdg { + let tokio = std::env::var_os("CARGO_FEATURE_TOKIO").is_some(); + let async_std = std::env::var_os("CARGO_FEATURE_ASYNC_STD").is_some(); + if !tokio && !async_std { + panic!("One of the `tokio` or `async-std` features must be enabled to use `xdg-portal`"); + } + } + } + } +} diff --git a/desktop/src-tauri/vendor/rfd/deny.toml b/desktop/src-tauri/vendor/rfd/deny.toml new file mode 100644 index 00000000000..233f74e6afd --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/deny.toml @@ -0,0 +1,36 @@ +# https://embarkstudios.github.io/cargo-deny/ + +[graph] +targets = [ + { triple = "aarch64-apple-darwin" }, + { triple = "aarch64-linux-android" }, + { triple = "wasm32-unknown-unknown" }, + { triple = "x86_64-apple-darwin" }, + { triple = "x86_64-pc-windows-msvc" }, + { triple = "x86_64-unknown-linux-gnu" }, + { triple = "x86_64-unknown-linux-musl" }, +] +exclude = ["gtk-sys"] + +[advisories] +version = 2 +yanked = "deny" +ignore = [] + +[bans] +multiple-versions = "deny" +wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed +deny = [] +skip = [] +skip-tree = [] + +[licenses] +version = 2 +unused-allowed-license = "allow" +allow = [ + "MIT", + "Unicode-3.0", + "ISC", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", +] diff --git a/desktop/src-tauri/vendor/rfd/examples/async.rs b/desktop/src-tauri/vendor/rfd/examples/async.rs new file mode 100644 index 00000000000..c14a56d7308 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/examples/async.rs @@ -0,0 +1,32 @@ +fn main() { + // Spawn dialog on main thread + let task = rfd::AsyncFileDialog::new().pick_file(); + + // Await somewhere else + execute(async { + let file = task.await; + + if let Some(file) = file { + // If you are on native platform you can just get the path + #[cfg(not(target_arch = "wasm32"))] + println!("{:?}", file.path()); + + // If you care about wasm support you just read() the file + file.read().await; + } + }); + + std::thread::park(); +} + +use std::future::Future; + +#[cfg(not(target_arch = "wasm32"))] +fn execute + Send + 'static>(f: F) { + // this is stupid... use any executor of your choice instead + std::thread::spawn(move || futures::executor::block_on(f)); +} +#[cfg(target_arch = "wasm32")] +fn execute + 'static>(f: F) { + wasm_bindgen_futures::spawn_local(f); +} diff --git a/desktop/src-tauri/vendor/rfd/examples/msg.rs b/desktop/src-tauri/vendor/rfd/examples/msg.rs new file mode 100644 index 00000000000..c4f49e6d476 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/examples/msg.rs @@ -0,0 +1,78 @@ +use std::io::{self, Read}; + +fn main() { + #[cfg(all( + any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd" + ), + not(feature = "gtk3") + ))] + let res = ""; + #[cfg(any( + target_os = "windows", + target_os = "macos", + all( + any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd" + ), + feature = "gtk3" + ) + ))] + let res = rfd::MessageDialog::new() + .set_title("Msg!") + .set_description("Description!") + .set_buttons(rfd::MessageButtons::OkCancel) + .set_level(rfd::MessageLevel::Error) + .show(); + println!("res: {res}"); + + #[cfg(windows)] + println!("Ctrl+Z"); + #[cfg(not(windows))] + println!("Ctrl+D"); + + let mut stdin = io::stdin(); + let mut buffer: Vec = vec![]; + stdin.read_to_end(&mut buffer).unwrap(); + + #[cfg(any( + target_os = "windows", + target_os = "macos", + all( + any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd" + ), + feature = "gtk3" + ) + ))] + futures::executor::block_on(async move { + let res = rfd::AsyncMessageDialog::new() + .set_title("Msg!") + .set_description("Description!") + .set_buttons(rfd::MessageButtons::OkCancel) + .show() + .await; + println!("res: {res}"); + }); + + #[cfg(windows)] + println!("Ctrl+Z"); + #[cfg(not(windows))] + println!("Ctrl+D"); + let mut stdin = io::stdin(); + let mut buffer: Vec = vec![]; + stdin.read_to_end(&mut buffer).unwrap(); + println!(); +} diff --git a/desktop/src-tauri/vendor/rfd/examples/save.rs b/desktop/src-tauri/vendor/rfd/examples/save.rs new file mode 100644 index 00000000000..a73c24475bc --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/examples/save.rs @@ -0,0 +1,16 @@ +#[cfg(not(target_arch = "wasm32"))] +fn main() { + let path = std::env::current_dir().unwrap(); + + let res = rfd::FileDialog::new() + .set_file_name("foo.txt") + .set_directory(&path) + .save_file(); + + println!("The user choose: {:#?}", res); +} + +#[cfg(target_arch = "wasm32")] +fn main() { + // On wasm only async dialogs are possible +} diff --git a/desktop/src-tauri/vendor/rfd/examples/simple.rs b/desktop/src-tauri/vendor/rfd/examples/simple.rs new file mode 100644 index 00000000000..fca64c21897 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/examples/simple.rs @@ -0,0 +1,17 @@ +#[cfg(not(target_arch = "wasm32"))] +fn main() { + let path = std::env::current_dir().unwrap(); + + let res = rfd::FileDialog::new() + .add_filter("text", &["txt", "rs"]) + .add_filter("rust", &["rs", "toml"]) + .set_directory(&path) + .pick_files(); + + println!("The user choose: {:#?}", res); +} + +#[cfg(target_arch = "wasm32")] +fn main() { + // On wasm only async dialogs are possible +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend.rs b/desktop/src-tauri/vendor/rfd/src/backend.rs new file mode 100644 index 00000000000..98c7fa084bd --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend.rs @@ -0,0 +1,107 @@ +use crate::message_dialog::MessageDialogResult; +use crate::FileHandle; +use std::future::Future; +#[cfg(not(target_arch = "wasm32"))] +use std::path::PathBuf; +use std::pin::Pin; + +#[cfg(all( + any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd" + ), + not(feature = "gtk3") +))] +mod linux; + +#[cfg(all( + any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd" + ), + feature = "gtk3" +))] +mod gtk3; +#[cfg(target_os = "macos")] +mod macos; +#[cfg(target_arch = "wasm32")] +mod wasm; +#[cfg(target_os = "windows")] +mod win_cid; +#[cfg(all( + any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd" + ), + not(feature = "gtk3") +))] +mod xdg_desktop_portal; + +// +// Sync +// + +/// Dialog used to pick file/files +#[cfg(not(target_arch = "wasm32"))] +pub trait FilePickerDialogImpl { + fn pick_file(self) -> Option; + fn pick_files(self) -> Option>; +} + +/// Dialog used to save file +#[cfg(not(target_arch = "wasm32"))] +pub trait FileSaveDialogImpl { + fn save_file(self) -> Option; +} + +/// Dialog used to pick folder +#[cfg(not(target_arch = "wasm32"))] +pub trait FolderPickerDialogImpl { + fn pick_folder(self) -> Option; + fn pick_folders(self) -> Option>; +} + +pub trait MessageDialogImpl { + fn show(self) -> MessageDialogResult; +} + +// +// Async +// + +// Return type of async dialogs: +#[cfg(not(target_arch = "wasm32"))] +pub type DialogFutureType = Pin + Send>>; +#[cfg(target_arch = "wasm32")] +pub type DialogFutureType = Pin>>; + +/// Dialog used to pick file/files +pub trait AsyncFilePickerDialogImpl { + fn pick_file_async(self) -> DialogFutureType>; + fn pick_files_async(self) -> DialogFutureType>>; +} + +/// Dialog used to pick folder +#[cfg(not(target_arch = "wasm32"))] +pub trait AsyncFolderPickerDialogImpl { + fn pick_folder_async(self) -> DialogFutureType>; + fn pick_folders_async(self) -> DialogFutureType>>; +} + +/// Dialog used to pick folder +pub trait AsyncFileSaveDialogImpl { + fn save_file_async(self) -> DialogFutureType>; +} + +pub trait AsyncMessageDialogImpl { + fn show_async(self) -> DialogFutureType; +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/gtk3.rs b/desktop/src-tauri/vendor/rfd/src/backend/gtk3.rs new file mode 100644 index 00000000000..8da5ec85857 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/gtk3.rs @@ -0,0 +1,11 @@ +mod file_dialog; +mod message_dialog; + +mod gtk_future; + +mod utils; + +pub(self) trait AsGtkDialog { + fn gtk_dialog_ptr(&self) -> *mut gtk_sys::GtkDialog; + unsafe fn show(&self); +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/gtk3/file_dialog.rs b/desktop/src-tauri/vendor/rfd/src/backend/gtk3/file_dialog.rs new file mode 100644 index 00000000000..bd454d555e1 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/gtk3/file_dialog.rs @@ -0,0 +1,183 @@ +pub mod dialog_ffi; + +use dialog_ffi::GtkFileDialog; + +use std::path::PathBuf; + +use super::utils::GtkGlobalThread; +use crate::backend::DialogFutureType; +use crate::{FileDialog, FileHandle}; + +use super::gtk_future::GtkDialogFuture; + +// +// File Picker +// + +use crate::backend::FilePickerDialogImpl; +impl FilePickerDialogImpl for FileDialog { + fn pick_file(self) -> Option { + GtkGlobalThread::instance().run_blocking(move || { + let dialog = GtkFileDialog::build_pick_file(&self); + + if dialog.run() == gtk_sys::GTK_RESPONSE_ACCEPT { + dialog.get_result() + } else { + None + } + }) + } + + fn pick_files(self) -> Option> { + GtkGlobalThread::instance().run_blocking(move || { + let dialog = GtkFileDialog::build_pick_files(&self); + + if dialog.run() == gtk_sys::GTK_RESPONSE_ACCEPT { + Some(dialog.get_results()) + } else { + None + } + }) + } +} + +use crate::backend::AsyncFilePickerDialogImpl; +impl AsyncFilePickerDialogImpl for FileDialog { + fn pick_file_async(self) -> DialogFutureType> { + let builder = move || GtkFileDialog::build_pick_file(&self); + + let future = GtkDialogFuture::new(builder, |dialog, res_id| { + if res_id == gtk_sys::GTK_RESPONSE_ACCEPT { + dialog.get_result().map(FileHandle::wrap) + } else { + None + } + }); + + Box::pin(future) + } + + fn pick_files_async(self) -> DialogFutureType>> { + let builder = move || GtkFileDialog::build_pick_files(&self); + + let future = GtkDialogFuture::new(builder, |dialog, res_id| { + if res_id == gtk_sys::GTK_RESPONSE_ACCEPT { + Some( + dialog + .get_results() + .into_iter() + .map(FileHandle::wrap) + .collect(), + ) + } else { + None + } + }); + + Box::pin(future) + } +} + +// +// Folder Picker +// + +use crate::backend::FolderPickerDialogImpl; +impl FolderPickerDialogImpl for FileDialog { + fn pick_folder(self) -> Option { + GtkGlobalThread::instance().run_blocking(move || { + let dialog = GtkFileDialog::build_pick_folder(&self); + + if dialog.run() == gtk_sys::GTK_RESPONSE_ACCEPT { + dialog.get_result() + } else { + None + } + }) + } + + fn pick_folders(self) -> Option> { + GtkGlobalThread::instance().run_blocking(move || { + let dialog = GtkFileDialog::build_pick_folders(&self); + + if dialog.run() == gtk_sys::GTK_RESPONSE_ACCEPT { + Some(dialog.get_results()) + } else { + None + } + }) + } +} + +use crate::backend::AsyncFolderPickerDialogImpl; +impl AsyncFolderPickerDialogImpl for FileDialog { + fn pick_folder_async(self) -> DialogFutureType> { + let builder = move || GtkFileDialog::build_pick_folder(&self); + + let future = GtkDialogFuture::new(builder, |dialog, res_id| { + if res_id == gtk_sys::GTK_RESPONSE_ACCEPT { + dialog.get_result().map(FileHandle::wrap) + } else { + None + } + }); + + Box::pin(future) + } + + fn pick_folders_async(self) -> DialogFutureType>> { + let builder = move || GtkFileDialog::build_pick_folders(&self); + + let future = GtkDialogFuture::new(builder, |dialog, res_id| { + if res_id == gtk_sys::GTK_RESPONSE_ACCEPT { + Some( + dialog + .get_results() + .into_iter() + .map(FileHandle::wrap) + .collect(), + ) + } else { + None + } + }); + + Box::pin(future) + } +} + +// +// File Save +// + +use crate::backend::FileSaveDialogImpl; +impl FileSaveDialogImpl for FileDialog { + fn save_file(self) -> Option { + GtkGlobalThread::instance().run_blocking(move || { + let dialog = GtkFileDialog::build_save_file(&self); + + if dialog.run() == gtk_sys::GTK_RESPONSE_ACCEPT { + dialog.get_result() + } else { + None + } + }) + } +} + +use crate::backend::AsyncFileSaveDialogImpl; +impl AsyncFileSaveDialogImpl for FileDialog { + fn save_file_async(self) -> DialogFutureType> { + let builder = move || GtkFileDialog::build_save_file(&self); + + let future = GtkDialogFuture::new(builder, |dialog, res_id| { + if res_id == gtk_sys::GTK_RESPONSE_ACCEPT { + dialog.get_result().map(FileHandle::wrap) + } else { + None + } + }); + + Box::pin(future) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/gtk3/file_dialog/dialog_ffi.rs b/desktop/src-tauri/vendor/rfd/src/backend/gtk3/file_dialog/dialog_ffi.rs new file mode 100644 index 00000000000..17f7aab0b39 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/gtk3/file_dialog/dialog_ffi.rs @@ -0,0 +1,286 @@ +use super::super::AsGtkDialog; +use crate::FileDialog; +use gtk_sys::GtkFileChooserNative; + +use std::{ + ffi::{CStr, CString}, + ops::Deref, + path::{Path, PathBuf}, + ptr, +}; + +#[repr(i32)] +pub enum GtkFileChooserAction { + Open = 0, + Save = 1, + SelectFolder = 2, + // CreateFolder = 3, +} + +pub struct GtkFileDialog { + pub ptr: *mut GtkFileChooserNative, +} + +impl GtkFileDialog { + fn new(title: &str, action: GtkFileChooserAction) -> Self { + let title = CString::new(title).unwrap(); + + let ptr = unsafe { + let dialog = gtk_sys::gtk_file_chooser_native_new( + title.as_ptr(), + ptr::null_mut(), + action as i32, + // passing null for the texts will use the default text, which has full support for i18n + std::ptr::null(), + std::ptr::null(), + ); + dialog as _ + }; + + Self { ptr } + } + + fn add_filters(&mut self, filters: &[crate::file_dialog::Filter]) { + for f in filters.iter() { + if let Ok(name) = CString::new(f.name.as_str()) { + unsafe { + let filter = gtk_sys::gtk_file_filter_new(); + + let paterns: Vec<_> = f + .extensions + .iter() + .filter_map(|e| CString::new(format!("*.{}", e)).ok()) + .collect(); + + gtk_sys::gtk_file_filter_set_name(filter, name.as_ptr()); + + for p in paterns.iter() { + gtk_sys::gtk_file_filter_add_pattern(filter, p.as_ptr()); + } + + gtk_sys::gtk_file_chooser_add_filter(self.ptr as _, filter); + } + } + } + } + + fn set_file_name(&self, name: Option<&str>) { + if let Some(name) = name { + if let Ok(name) = CString::new(name) { + unsafe { + gtk_sys::gtk_file_chooser_set_filename(self.ptr as _, name.as_ptr()); + } + } + } + } + + fn set_current_name(&self, name: Option<&str>) { + if let Some(name) = name { + if let Ok(name) = CString::new(name) { + unsafe { + gtk_sys::gtk_file_chooser_set_current_name(self.ptr as _, name.as_ptr()); + } + } + } + } + + fn set_path(&self, path: Option<&Path>) { + if let Some(path) = path { + if let Some(path) = path.to_str() { + if let Ok(path) = CString::new(path) { + unsafe { + gtk_sys::gtk_file_chooser_set_current_folder(self.ptr as _, path.as_ptr()); + } + } + } + } + } + + pub fn get_result(&self) -> Option { + let cstr = unsafe { + let chosen_filename = gtk_sys::gtk_file_chooser_get_filename(self.ptr as _); + CStr::from_ptr(chosen_filename).to_str() + }; + + if let Ok(cstr) = cstr { + Some(PathBuf::from(cstr.to_owned())) + } else { + None + } + } + + pub fn get_results(&self) -> Vec { + #[derive(Debug)] + struct FileList(*mut glib_sys::GSList); + + impl Iterator for FileList { + type Item = glib_sys::GSList; + fn next(&mut self) -> Option { + let curr_ptr = self.0; + + if !curr_ptr.is_null() { + let curr = unsafe { *curr_ptr }; + + self.0 = curr.next; + + Some(curr) + } else { + None + } + } + } + + let chosen_filenames = + unsafe { gtk_sys::gtk_file_chooser_get_filenames(self.ptr as *mut _) }; + + let paths: Vec = FileList(chosen_filenames) + .filter_map(|item| { + let cstr = unsafe { CStr::from_ptr(item.data as _).to_str() }; + + if let Ok(cstr) = cstr { + Some(PathBuf::from(cstr.to_owned())) + } else { + None + } + }) + .collect(); + + paths + } + + pub fn run(&self) -> i32 { + unsafe { gtk_sys::gtk_native_dialog_run(self.ptr as *mut _) } + } +} + +impl GtkFileDialog { + pub fn build_pick_file(opt: &FileDialog) -> Self { + let mut dialog = GtkFileDialog::new( + opt.title.as_deref().unwrap_or("Open File"), + GtkFileChooserAction::Open, + ); + + dialog.add_filters(&opt.filters); + dialog.set_path(opt.starting_directory.as_deref()); + + if let (Some(mut path), Some(file_name)) = + (opt.starting_directory.to_owned(), opt.file_name.as_deref()) + { + path.push(file_name); + dialog.set_file_name(path.deref().to_str()); + } else { + dialog.set_file_name(opt.file_name.as_deref()); + } + + dialog + } + + pub fn build_save_file(opt: &FileDialog) -> Self { + let mut dialog = GtkFileDialog::new( + opt.title.as_deref().unwrap_or("Save File"), + GtkFileChooserAction::Save, + ); + + unsafe { gtk_sys::gtk_file_chooser_set_do_overwrite_confirmation(dialog.ptr as _, 1) }; + + dialog.add_filters(&opt.filters); + dialog.set_path(opt.starting_directory.as_deref()); + + if let (Some(mut path), Some(file_name)) = + (opt.starting_directory.to_owned(), opt.file_name.as_deref()) + { + path.push(file_name); + if path.exists() { + // the user edited an existing document + dialog.set_file_name(path.deref().to_str()); + } else { + // the user just created a new document + dialog.set_current_name(opt.file_name.as_deref()); + } + } else { + // the user just created a new document + dialog.set_current_name(opt.file_name.as_deref()); + } + + dialog + } + + pub fn build_pick_folder(opt: &FileDialog) -> Self { + let dialog = GtkFileDialog::new( + opt.title.as_deref().unwrap_or("Select Folder"), + GtkFileChooserAction::SelectFolder, + ); + dialog.set_path(opt.starting_directory.as_deref()); + + if let (Some(mut path), Some(file_name)) = + (opt.starting_directory.to_owned(), opt.file_name.as_deref()) + { + path.push(file_name); + dialog.set_file_name(path.deref().to_str()); + } else { + dialog.set_file_name(opt.file_name.as_deref()); + } + + dialog + } + + pub fn build_pick_folders(opt: &FileDialog) -> Self { + let dialog = GtkFileDialog::new( + opt.title.as_deref().unwrap_or("Select Folder"), + GtkFileChooserAction::SelectFolder, + ); + unsafe { gtk_sys::gtk_file_chooser_set_select_multiple(dialog.ptr as _, 1) }; + dialog.set_path(opt.starting_directory.as_deref()); + + if let (Some(mut path), Some(file_name)) = + (opt.starting_directory.to_owned(), opt.file_name.as_deref()) + { + path.push(file_name); + dialog.set_file_name(path.deref().to_str()); + } else { + dialog.set_file_name(opt.file_name.as_deref()); + } + + dialog + } + + pub fn build_pick_files(opt: &FileDialog) -> Self { + let mut dialog = GtkFileDialog::new( + opt.title.as_deref().unwrap_or("Open File"), + GtkFileChooserAction::Open, + ); + + unsafe { gtk_sys::gtk_file_chooser_set_select_multiple(dialog.ptr as _, 1) }; + dialog.add_filters(&opt.filters); + dialog.set_path(opt.starting_directory.as_deref()); + + if let (Some(mut path), Some(file_name)) = + (opt.starting_directory.to_owned(), opt.file_name.as_deref()) + { + path.push(file_name); + dialog.set_file_name(path.deref().to_str()); + } else { + dialog.set_file_name(opt.file_name.as_deref()); + } + + dialog + } +} + +impl AsGtkDialog for GtkFileDialog { + fn gtk_dialog_ptr(&self) -> *mut gtk_sys::GtkDialog { + self.ptr as *mut _ + } + + unsafe fn show(&self) { + gtk_sys::gtk_native_dialog_show(self.ptr as *mut _); + } +} + +impl Drop for GtkFileDialog { + fn drop(&mut self) { + unsafe { + gtk_sys::gtk_native_dialog_destroy(self.ptr as _); + } + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/gtk3/gtk_future.rs b/desktop/src-tauri/vendor/rfd/src/backend/gtk3/gtk_future.rs new file mode 100644 index 00000000000..b0fb7219ebf --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/gtk3/gtk_future.rs @@ -0,0 +1,140 @@ +use super::utils::GtkGlobalThread; + +use std::pin::Pin; +use std::sync::{Arc, Mutex}; + +use std::task::{Context, Poll, Waker}; + +use super::AsGtkDialog; + +struct FutureState { + waker: Option, + data: Option, + dialog: Option, +} + +unsafe impl Send for FutureState {} + +pub(super) struct GtkDialogFuture { + state: Arc>>, +} + +unsafe impl Send for GtkDialogFuture {} + +impl GtkDialogFuture { + pub fn new(build: B, cb: F) -> Self + where + B: FnOnce() -> D + Send + 'static, + F: Fn(&mut D, i32) -> R + Send + 'static, + { + let state = Arc::new(Mutex::new(FutureState { + waker: None, + data: None, + dialog: None, + })); + + { + let state = state.clone(); + let callback = { + let state = state.clone(); + + move |res_id| { + let mut state = state.lock().unwrap(); + + if let Some(mut dialog) = state.dialog.take() { + state.data = Some(cb(&mut dialog, res_id)); + } + + if let Some(waker) = state.waker.take() { + waker.wake(); + } + } + }; + + GtkGlobalThread::instance().run(move || { + let mut state = state.lock().unwrap(); + state.dialog = Some(build()); + + unsafe { + let dialog = state.dialog.as_ref().unwrap(); + dialog.show(); + + let ptr = dialog.gtk_dialog_ptr(); + connect_response(ptr as *mut _, callback); + } + }); + } + + Self { state } + } +} + +impl std::future::Future for GtkDialogFuture { + type Output = R; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut state = self.state.lock().unwrap(); + + if state.data.is_some() { + Poll::Ready(state.data.take().unwrap()) + } else { + state.waker = Some(cx.waker().clone()); + Poll::Pending + } + } +} + +use gobject_sys::GCallback; +use gtk_sys::{GtkDialog, GtkResponseType}; +use std::ffi::c_void; +use std::os::raw::c_char; + +unsafe fn connect_raw( + receiver: *mut gobject_sys::GObject, + signal_name: *const c_char, + trampoline: GCallback, + closure: *mut F, +) { + use std::mem; + + use glib_sys::gpointer; + + unsafe extern "C" fn destroy_closure(ptr: *mut c_void, _: *mut gobject_sys::GClosure) { + // destroy + let _ = Box::::from_raw(ptr as *mut _); + } + assert_eq!(mem::size_of::<*mut F>(), mem::size_of::()); + assert!(trampoline.is_some()); + let handle = gobject_sys::g_signal_connect_data( + receiver, + signal_name, + trampoline, + closure as *mut _, + Some(destroy_closure::), + 0, + ); + assert!(handle > 0); +} + +unsafe fn connect_response(dialog: *mut GtkDialog, f: F) { + use std::mem::transmute; + + unsafe extern "C" fn response_trampoline( + _this: *mut gtk_sys::GtkDialog, + res: GtkResponseType, + f: glib_sys::gpointer, + ) { + let f: &F = &*(f as *const F); + + f(res); + } + let f: Box = Box::new(f); + connect_raw( + dialog as *mut _, + b"response\0".as_ptr() as *const _, + Some(transmute::<_, unsafe extern "C" fn()>( + response_trampoline:: as *const (), + )), + Box::into_raw(f), + ); +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/gtk3/message_dialog.rs b/desktop/src-tauri/vendor/rfd/src/backend/gtk3/message_dialog.rs new file mode 100644 index 00000000000..234969c641d --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/gtk3/message_dialog.rs @@ -0,0 +1,217 @@ +use std::ffi::CString; +use std::ptr; + +use super::gtk_future::GtkDialogFuture; +use super::utils::GtkGlobalThread; +use super::AsGtkDialog; + +use crate::message_dialog::{MessageButtons, MessageDialog, MessageLevel}; +use crate::MessageDialogResult; + +pub struct GtkMessageDialog { + buttons: MessageButtons, + ptr: *mut gtk_sys::GtkDialog, +} + +impl GtkMessageDialog { + pub fn new(opt: MessageDialog) -> Self { + let level = match opt.level { + MessageLevel::Info => gtk_sys::GTK_MESSAGE_INFO, + MessageLevel::Warning => gtk_sys::GTK_MESSAGE_WARNING, + MessageLevel::Error => gtk_sys::GTK_MESSAGE_ERROR, + }; + + let buttons = match opt.buttons { + MessageButtons::Ok => gtk_sys::GTK_BUTTONS_OK, + MessageButtons::OkCancel => gtk_sys::GTK_BUTTONS_OK_CANCEL, + MessageButtons::YesNo => gtk_sys::GTK_BUTTONS_YES_NO, + MessageButtons::YesNoCancel => gtk_sys::GTK_BUTTONS_NONE, + MessageButtons::OkCustom(_) => gtk_sys::GTK_BUTTONS_NONE, + MessageButtons::OkCancelCustom(_, _) => gtk_sys::GTK_BUTTONS_NONE, + MessageButtons::YesNoCancelCustom(_, _, _) => gtk_sys::GTK_BUTTONS_NONE, + }; + + let custom_buttons = match &opt.buttons { + MessageButtons::YesNoCancel => vec![ + Some((CString::new("Yes").unwrap(), gtk_sys::GTK_RESPONSE_YES)), + Some((CString::new("No").unwrap(), gtk_sys::GTK_RESPONSE_NO)), + Some(( + CString::new("Cancel").unwrap(), + gtk_sys::GTK_RESPONSE_CANCEL, + )), + None, + ], + MessageButtons::OkCustom(ok_text) => vec![ + Some(( + CString::new(ok_text.as_bytes()).unwrap(), + gtk_sys::GTK_RESPONSE_OK, + )), + None, + ], + MessageButtons::OkCancelCustom(ok_text, cancel_text) => vec![ + Some(( + CString::new(ok_text.as_bytes()).unwrap(), + gtk_sys::GTK_RESPONSE_OK, + )), + Some(( + CString::new(cancel_text.as_bytes()).unwrap(), + gtk_sys::GTK_RESPONSE_CANCEL, + )), + ], + MessageButtons::YesNoCancelCustom(yes_text, no_text, cancel_text) => vec![ + Some(( + CString::new(yes_text.as_bytes()).unwrap(), + gtk_sys::GTK_RESPONSE_YES, + )), + Some(( + CString::new(no_text.as_bytes()).unwrap(), + gtk_sys::GTK_RESPONSE_NO, + )), + Some(( + CString::new(cancel_text.as_bytes()).unwrap(), + gtk_sys::GTK_RESPONSE_CANCEL, + )), + None, + ], + _ => vec![], + }; + + let s: &str = &opt.title; + let title = CString::new(s).unwrap(); + let s: &str = &opt.description; + let description = CString::new(s).unwrap(); + + let ptr = unsafe { + let dialog = gtk_sys::gtk_message_dialog_new( + ptr::null_mut(), + gtk_sys::GTK_DIALOG_MODAL, + level, + buttons, + b"%s\0".as_ptr() as *mut _, + title.as_ptr(), + ) as *mut gtk_sys::GtkDialog; + + set_child_labels_selectable(dialog); + // Also set the window title, otherwise it would be empty + gtk_sys::gtk_window_set_title(dialog as _, title.as_ptr()); + + for custom_button in custom_buttons { + if let Some((custom_button_cstr, response_id)) = custom_button { + gtk_sys::gtk_dialog_add_button( + dialog, + custom_button_cstr.as_ptr(), + response_id, + ); + } + } + + dialog + }; + + unsafe { + gtk_sys::gtk_message_dialog_format_secondary_text(ptr as *mut _, description.as_ptr()); + } + + Self { + ptr, + buttons: opt.buttons, + } + } + + pub fn run(self) -> MessageDialogResult { + let res = unsafe { gtk_sys::gtk_dialog_run(self.ptr) }; + + use MessageButtons::*; + match (&self.buttons, res) { + (Ok | OkCancel, gtk_sys::GTK_RESPONSE_OK) => MessageDialogResult::Ok, + (Ok | OkCancel | YesNoCancel, gtk_sys::GTK_RESPONSE_CANCEL) => { + MessageDialogResult::Cancel + } + (YesNo | YesNoCancel, gtk_sys::GTK_RESPONSE_YES) => MessageDialogResult::Yes, + (YesNo | YesNoCancel, gtk_sys::GTK_RESPONSE_NO) => MessageDialogResult::No, + (OkCustom(custom), gtk_sys::GTK_RESPONSE_OK) => { + MessageDialogResult::Custom(custom.to_owned()) + } + (OkCancelCustom(custom, _), gtk_sys::GTK_RESPONSE_OK) => { + MessageDialogResult::Custom(custom.to_owned()) + } + (OkCancelCustom(_, custom), gtk_sys::GTK_RESPONSE_CANCEL) => { + MessageDialogResult::Custom(custom.to_owned()) + } + (YesNoCancelCustom(custom, _, _), gtk_sys::GTK_RESPONSE_YES) => { + MessageDialogResult::Custom(custom.to_owned()) + } + (YesNoCancelCustom(_, custom, _), gtk_sys::GTK_RESPONSE_NO) => { + MessageDialogResult::Custom(custom.to_owned()) + } + (YesNoCancelCustom(_, _, custom), gtk_sys::GTK_RESPONSE_CANCEL) => { + MessageDialogResult::Custom(custom.to_owned()) + } + _ => MessageDialogResult::Cancel, + } + } +} + +unsafe fn is_label(type_instance: *const gobject_sys::GTypeInstance) -> bool { + (*(*type_instance).g_class).g_type == gtk_sys::gtk_label_get_type() +} + +/// Sets the child labels of a widget selectable +unsafe fn set_child_labels_selectable(dialog: *mut gtk_sys::GtkDialog) { + let area = gtk_sys::gtk_message_dialog_get_message_area(dialog as _); + let mut children = gtk_sys::gtk_container_get_children(area as _); + while !children.is_null() { + let child = (*children).data; + if is_label(child as _) { + gtk_sys::gtk_label_set_selectable(child as _, 1); + } + children = (*children).next; + } +} + +impl Drop for GtkMessageDialog { + fn drop(&mut self) { + unsafe { + gtk_sys::gtk_widget_destroy(self.ptr as *mut _); + } + } +} + +impl AsGtkDialog for GtkMessageDialog { + fn gtk_dialog_ptr(&self) -> *mut gtk_sys::GtkDialog { + self.ptr as *mut _ + } + unsafe fn show(&self) { + gtk_sys::gtk_widget_show_all(self.ptr as *mut _); + } +} + +use crate::backend::MessageDialogImpl; + +impl MessageDialogImpl for MessageDialog { + fn show(self) -> MessageDialogResult { + GtkGlobalThread::instance().run_blocking(move || { + let dialog = GtkMessageDialog::new(self); + dialog.run() + }) + } +} + +use crate::backend::AsyncMessageDialogImpl; +use crate::backend::DialogFutureType; + +impl AsyncMessageDialogImpl for MessageDialog { + fn show_async(self) -> DialogFutureType { + let builder = move || GtkMessageDialog::new(self); + + let future = GtkDialogFuture::new(builder, |_, res| match res { + gtk_sys::GTK_RESPONSE_OK => MessageDialogResult::Ok, + gtk_sys::GTK_RESPONSE_CANCEL => MessageDialogResult::Cancel, + gtk_sys::GTK_RESPONSE_YES => MessageDialogResult::Yes, + gtk_sys::GTK_RESPONSE_NO => MessageDialogResult::No, + gtk_sys::GTK_RESPONSE_DELETE_EVENT => MessageDialogResult::Cancel, + _ => unreachable!(), + }); + Box::pin(future) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/gtk3/utils.rs b/desktop/src-tauri/vendor/rfd/src/backend/gtk3/utils.rs new file mode 100644 index 00000000000..08655a424f9 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/gtk3/utils.rs @@ -0,0 +1,125 @@ +use std::ptr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::OnceLock; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread::spawn; + +static GTK_THREAD: OnceLock = OnceLock::new(); + +/// GTK functions are not thread-safe, and must all be called from the thread that initialized GTK. To ensure this, we +/// spawn one thread the first time a GTK dialog is opened and keep it open for the entire lifetime of the application, +/// as GTK cannot be de-initialized or re-initialized on another thread. You're stuck on the thread on which you first +/// initialize GTK. +pub struct GtkGlobalThread { + running: Arc, +} + +impl GtkGlobalThread { + /// Return the global, lazily-initialized instance of the global GTK thread. + pub(super) fn instance() -> &'static Self { + GTK_THREAD.get_or_init(|| Self::new()) + } + + fn new() -> Self { + // When the GtkGlobalThread is eventually dropped, we will set `running` to false and wake up the loop so + // gtk_main_iteration unblocks and we exit the thread on the next iteration. + let running = Arc::new(AtomicBool::new(true)); + let thread_running = Arc::clone(&running); + + spawn(move || { + let initialized = + unsafe { gtk_sys::gtk_init_check(ptr::null_mut(), ptr::null_mut()) == 1 }; + if !initialized { + return; + } + + loop { + if !thread_running.load(Ordering::Acquire) { + break; + } + + unsafe { + gtk_sys::gtk_main_iteration(); + } + } + }); + + Self { + running: Arc::new(AtomicBool::new(true)), + } + } + + /// Run a function on the GTK thread, blocking on the result which is then passed back. + pub(super) fn run_blocking< + T: Send + Clone + std::fmt::Debug + 'static, + F: FnOnce() -> T + Send + 'static, + >( + &self, + cb: F, + ) -> T { + let data: Arc<(Mutex>, _)> = Arc::new((Mutex::new(None), Condvar::new())); + let thread_data = Arc::clone(&data); + let mut cb = Some(cb); + unsafe { + connect_idle(move || { + // connect_idle takes a FnMut; convert our FnOnce into that by ensuring we only call it once + let res = cb.take().expect("Callback should only be called once")(); + + // pass the result back to the main thread + let (lock, cvar) = &*thread_data; + *lock.lock().unwrap() = Some(res); + cvar.notify_all(); + + glib_sys::GFALSE + }); + }; + + // wait for GTK thread to execute the callback and place the result into `data` + let lock_res = data + .1 + .wait_while(data.0.lock().unwrap(), |res| res.is_none()) + .unwrap(); + lock_res.as_ref().unwrap().clone() + } + + /// Launch a function on the GTK thread without blocking. + pub(super) fn run(&self, cb: F) { + let mut cb = Some(cb); + unsafe { + connect_idle(move || { + cb.take().expect("Callback should only be called once")(); + glib_sys::GFALSE + }); + }; + } +} + +impl Drop for GtkGlobalThread { + fn drop(&mut self) { + self.running.store(false, Ordering::Release); + unsafe { glib_sys::g_main_context_wakeup(std::ptr::null_mut()) }; + } +} + +unsafe fn connect_idle glib_sys::gboolean + Send + 'static>(f: F) { + unsafe extern "C" fn response_trampoline glib_sys::gboolean + Send + 'static>( + f: glib_sys::gpointer, + ) -> glib_sys::gboolean { + let f: &mut F = &mut *(f as *mut F); + + f() + } + let f_box: Box = Box::new(f); + + unsafe extern "C" fn destroy_closure(ptr: *mut std::ffi::c_void) { + // destroy + let _ = Box::::from_raw(ptr as *mut _); + } + + glib_sys::g_idle_add_full( + glib_sys::G_PRIORITY_DEFAULT_IDLE, + Some(response_trampoline::), + Box::into_raw(f_box) as glib_sys::gpointer, + Some(destroy_closure::), + ); +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/linux/async_command.rs b/desktop/src-tauri/vendor/rfd/src/backend/linux/async_command.rs new file mode 100644 index 00000000000..a21c545f98a --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/linux/async_command.rs @@ -0,0 +1,55 @@ +use std::{ + io, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Waker}, +}; + +struct State { + waker: Option, + data: Option>, +} + +pub struct AsyncCommand { + state: Arc>, +} + +impl AsyncCommand { + pub fn spawn(mut command: std::process::Command) -> Self { + let state = Arc::new(Mutex::new(State { + waker: None, + data: None, + })); + + std::thread::spawn({ + let state = state.clone(); + move || { + let output = command.output(); + + let mut state = state.lock().unwrap(); + state.data = Some(output); + + if let Some(waker) = state.waker.take() { + waker.wake(); + } + } + }); + + Self { state } + } +} + +impl std::future::Future for AsyncCommand { + type Output = io::Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut state = self.state.lock().unwrap(); + + if state.data.is_some() { + Poll::Ready(state.data.take().unwrap()) + } else { + state.waker = Some(cx.waker().clone()); + Poll::Pending + } + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/linux/mod.rs b/desktop/src-tauri/vendor/rfd/src/backend/linux/mod.rs new file mode 100644 index 00000000000..8bb2b7c9124 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/linux/mod.rs @@ -0,0 +1,2 @@ +mod async_command; +pub(crate) mod zenity; diff --git a/desktop/src-tauri/vendor/rfd/src/backend/linux/zenity.rs b/desktop/src-tauri/vendor/rfd/src/backend/linux/zenity.rs new file mode 100644 index 00000000000..cd5d9dd40bd --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/linux/zenity.rs @@ -0,0 +1,312 @@ +use std::{error::Error, fmt::Display, path::PathBuf, process::Command}; + +use crate::{ + file_dialog::Filter, + message_dialog::{MessageButtons, MessageLevel}, + FileDialog, MessageDialogResult, +}; + +#[derive(Debug)] +pub enum ZenityError { + Io(std::io::Error), + FromUtf8Error(std::string::FromUtf8Error), +} + +impl Error for ZenityError {} + +impl Display for ZenityError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ZenityError::Io(io) => write!(f, "{io}"), + ZenityError::FromUtf8Error(err) => err.fmt(f), + } + } +} + +impl From for ZenityError { + fn from(value: std::io::Error) -> Self { + Self::Io(value) + } +} + +impl From for ZenityError { + fn from(value: std::string::FromUtf8Error) -> Self { + Self::FromUtf8Error(value) + } +} + +pub type ZenityResult = Result; + +fn command() -> Command { + let mut cmd = Command::new("zenity"); + cmd.arg("--no-markup"); + cmd +} + +fn add_filters(command: &mut Command, filters: &[Filter]) { + for f in filters.iter() { + command.arg("--file-filter"); + + let extensions: Vec<_> = f + .extensions + .iter() + .map(|ext| format!("*.{}", ext)) + .collect(); + + command.arg(format!("{} | {}", f.name, extensions.join(" "))); + } +} + +fn add_filename(command: &mut Command, file_name: &Option) { + if let Some(name) = file_name.as_ref() { + command.arg("--filename"); + command.arg(name); + } +} + +async fn run(command: Command) -> ZenityResult> { + let res = super::async_command::AsyncCommand::spawn(command).await?; + let buffer = String::from_utf8(res.stdout)?; + + Ok((res.status.success() || !buffer.is_empty()).then_some(buffer)) +} + +pub async fn pick_file(dialog: &FileDialog) -> ZenityResult> { + let mut command = command(); + command.arg("--file-selection"); + + add_filters(&mut command, &dialog.filters); + add_filename(&mut command, &dialog.file_name); + + run(command).await.map(|res| { + res.map(|buffer| { + let trimed = buffer.trim(); + trimed.into() + }) + }) +} + +pub async fn pick_files(dialog: &FileDialog) -> ZenityResult> { + let mut command = command(); + command.args(["--file-selection", "--multiple"]); + + add_filters(&mut command, &dialog.filters); + add_filename(&mut command, &dialog.file_name); + + run(command).await.map(|res| { + res.map(|buffer| { + let list = buffer.trim().split('|').map(PathBuf::from).collect(); + list + }) + .unwrap_or_default() + }) +} + +pub async fn pick_folder(dialog: &FileDialog) -> ZenityResult> { + let mut command = command(); + command.args(["--file-selection", "--directory"]); + + add_filters(&mut command, &dialog.filters); + add_filename(&mut command, &dialog.file_name); + + run(command).await.map(|res| { + res.map(|buffer| { + let trimed = buffer.trim(); + trimed.into() + }) + }) +} + +pub async fn pick_folders(dialog: &FileDialog) -> ZenityResult> { + let mut command = command(); + command.args(["--file-selection", "--directory", "--multiple"]); + + add_filters(&mut command, &dialog.filters); + add_filename(&mut command, &dialog.file_name); + + run(command).await.map(|res| { + res.map(|buffer| { + let list = buffer.trim().split('|').map(PathBuf::from).collect(); + list + }) + .unwrap_or_default() + }) +} + +pub async fn save_file(dialog: &FileDialog) -> ZenityResult> { + let mut command = command(); + command.args(["--file-selection", "--save", "--confirm-overwrite"]); + + add_filters(&mut command, &dialog.filters); + add_filename(&mut command, &dialog.file_name); + + run(command).await.map(|res| { + res.map(|buffer| { + let trimed = buffer.trim(); + trimed.into() + }) + }) +} + +pub async fn message( + level: &MessageLevel, + btns: &MessageButtons, + title: &str, + description: &str, +) -> ZenityResult { + let cmd = match level { + MessageLevel::Info => "--info", + MessageLevel::Warning => "--warning", + MessageLevel::Error => "--error", + }; + + let ok_label = match btns { + MessageButtons::Ok => None, + MessageButtons::OkCustom(ok) => Some(ok), + _ => None, + }; + + let mut command = command(); + command.args([cmd, "--title", title, "--text", description]); + + if let Some(ok) = ok_label { + command.args(["--ok-label", ok]); + } + + run(command).await.map(|res| match res { + Some(_) => MessageDialogResult::Ok, + None => MessageDialogResult::Cancel, + }) +} + +pub async fn question( + btns: &MessageButtons, + title: &str, + description: &str, +) -> ZenityResult { + let mut command = command(); + command.args(["--question", "--title", title, "--text", description]); + + match btns { + MessageButtons::OkCancel => { + command.args(["--ok-label", "Ok"]); + command.args(["--cancel-label", "Cancel"]); + } + MessageButtons::OkCancelCustom(ok, cancel) => { + command.args(["--ok-label", ok.as_str()]); + command.args(["--cancel-label", cancel.as_str()]); + } + MessageButtons::YesNoCancel => { + command.args(["--extra-button", "No"]); + command.args(["--cancel-label", "Cancel"]); + } + MessageButtons::YesNoCancelCustom(yes, no, cancel) => { + command.args(["--ok-label", yes.as_str()]); + command.args(["--cancel-label", cancel.as_str()]); + command.args(["--extra-button", no.as_str()]); + } + _ => {} + } + + run(command).await.map(|res| match btns { + MessageButtons::OkCancel => match res { + Some(_) => MessageDialogResult::Ok, + None => MessageDialogResult::Cancel, + }, + MessageButtons::YesNo => match res { + Some(_) => MessageDialogResult::Yes, + None => MessageDialogResult::No, + }, + MessageButtons::OkCancelCustom(ok, cancel) => match res { + Some(_) => MessageDialogResult::Custom(ok.clone()), + None => MessageDialogResult::Custom(cancel.clone()), + }, + MessageButtons::YesNoCancel => match res { + Some(output) if output.is_empty() => MessageDialogResult::Yes, + Some(_) => MessageDialogResult::No, + None => MessageDialogResult::Cancel, + }, + MessageButtons::YesNoCancelCustom(yes, no, cancel) => match res { + Some(output) if output.is_empty() => MessageDialogResult::Custom(yes.clone()), + Some(_) => MessageDialogResult::Custom(no.clone()), + None => MessageDialogResult::Custom(cancel.clone()), + }, + _ => MessageDialogResult::Cancel, + }) +} + +#[cfg(test)] +mod tests { + use crate::FileDialog; + + #[test] + #[ignore] + fn message() { + pollster::block_on(super::message( + &crate::message_dialog::MessageLevel::Info, + &crate::message_dialog::MessageButtons::Ok, + "hi", + "me", + )) + .unwrap(); + pollster::block_on(super::message( + &crate::message_dialog::MessageLevel::Warning, + &crate::message_dialog::MessageButtons::Ok, + "hi", + "me", + )) + .unwrap(); + pollster::block_on(super::message( + &crate::message_dialog::MessageLevel::Error, + &crate::message_dialog::MessageButtons::Ok, + "hi", + "me", + )) + .unwrap(); + } + + #[test] + #[ignore] + fn question() { + pollster::block_on(super::question( + &crate::message_dialog::MessageButtons::OkCancel, + "hi", + "me", + )) + .unwrap(); + pollster::block_on(super::question( + &crate::message_dialog::MessageButtons::YesNo, + "hi", + "me", + )) + .unwrap(); + } + + #[test] + #[ignore] + fn pick_file() { + let path = pollster::block_on(super::pick_file(&FileDialog::default())).unwrap(); + dbg!(path); + } + + #[test] + #[ignore] + fn pick_files() { + let path = pollster::block_on(super::pick_files(&FileDialog::default())).unwrap(); + dbg!(path); + } + + #[test] + #[ignore] + fn pick_folder() { + let path = pollster::block_on(super::pick_folder(&FileDialog::default())).unwrap(); + dbg!(path); + } + + #[test] + #[ignore] + fn save_file() { + let path = pollster::block_on(super::save_file(&FileDialog::default())).unwrap(); + dbg!(path); + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/macos.rs b/desktop/src-tauri/vendor/rfd/src/backend/macos.rs new file mode 100644 index 00000000000..f0ffe1fa553 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/macos.rs @@ -0,0 +1,6 @@ +mod file_dialog; +mod message_dialog; + +mod modal_future; + +mod utils; diff --git a/desktop/src-tauri/vendor/rfd/src/backend/macos/file_dialog.rs b/desktop/src-tauri/vendor/rfd/src/backend/macos/file_dialog.rs new file mode 100644 index 00000000000..777f38a8a0f --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/macos/file_dialog.rs @@ -0,0 +1,210 @@ +use objc2::rc::autoreleasepool; +use objc2_app_kit::NSModalResponseOK; +use std::path::PathBuf; + +mod panel_ffi; + +use self::panel_ffi::Panel; +use super::modal_future::ModalFuture; +use super::utils::{run_on_main, window_from_raw_window_handle}; +use crate::backend::DialogFutureType; +use crate::{FileDialog, FileHandle}; + +// +// File Picker +// + +use crate::backend::FilePickerDialogImpl; +impl FilePickerDialogImpl for FileDialog { + fn pick_file(self) -> Option { + autoreleasepool(move |_| { + run_on_main(move |mtm| { + let panel = Panel::build_pick_file(&self, mtm)?; + + if panel.run_modal() == NSModalResponseOK { + Some(panel.get_result()) + } else { + None + } + }) + }) + } + + fn pick_files(self) -> Option> { + autoreleasepool(move |_| { + run_on_main(move |mtm| { + let panel = Panel::build_pick_files(&self, mtm)?; + + if panel.run_modal() == NSModalResponseOK { + Some(panel.get_results()) + } else { + None + } + }) + }) + } +} + +use crate::backend::AsyncFilePickerDialogImpl; +impl AsyncFilePickerDialogImpl for FileDialog { + fn pick_file_async(self) -> DialogFutureType> { + let win = self.parent.as_ref().map(window_from_raw_window_handle); + + let future = ModalFuture::new( + win, + move |mtm| Panel::build_pick_file(&self, mtm), + |panel, res_id| { + if res_id == NSModalResponseOK { + Some(panel.get_result().into()) + } else { + None + } + }, + ); + + Box::pin(future) + } + + fn pick_files_async(self) -> DialogFutureType>> { + let win = self.parent.as_ref().map(window_from_raw_window_handle); + + let future = ModalFuture::new( + win, + move |mtm| Panel::build_pick_files(&self, mtm), + |panel, res_id| { + if res_id == NSModalResponseOK { + Some( + panel + .get_results() + .into_iter() + .map(FileHandle::wrap) + .collect(), + ) + } else { + None + } + }, + ); + + Box::pin(future) + } +} + +// +// Folder Picker +// + +use crate::backend::FolderPickerDialogImpl; +impl FolderPickerDialogImpl for FileDialog { + fn pick_folder(self) -> Option { + autoreleasepool(move |_| { + run_on_main(move |mtm| { + let panel = Panel::build_pick_folder(&self, mtm)?; + if panel.run_modal() == NSModalResponseOK { + Some(panel.get_result()) + } else { + None + } + }) + }) + } + + fn pick_folders(self) -> Option> { + autoreleasepool(move |_| { + run_on_main(move |mtm| { + let panel = Panel::build_pick_folders(&self, mtm)?; + if panel.run_modal() == NSModalResponseOK { + Some(panel.get_results()) + } else { + None + } + }) + }) + } +} + +use crate::backend::AsyncFolderPickerDialogImpl; +impl AsyncFolderPickerDialogImpl for FileDialog { + fn pick_folder_async(self) -> DialogFutureType> { + let win = self.parent.as_ref().map(window_from_raw_window_handle); + + let future = ModalFuture::new( + win, + move |mtm| Panel::build_pick_folder(&self, mtm), + |panel, res_id| { + if res_id == NSModalResponseOK { + Some(panel.get_result().into()) + } else { + None + } + }, + ); + + Box::pin(future) + } + + fn pick_folders_async(self) -> DialogFutureType>> { + let win = self.parent.as_ref().map(window_from_raw_window_handle); + + let future = ModalFuture::new( + win, + move |mtm| Panel::build_pick_folders(&self, mtm), + |panel, res_id| { + if res_id == NSModalResponseOK { + Some( + panel + .get_results() + .into_iter() + .map(FileHandle::wrap) + .collect(), + ) + } else { + None + } + }, + ); + + Box::pin(future) + } +} + +// +// File Save +// + +use crate::backend::FileSaveDialogImpl; +impl FileSaveDialogImpl for FileDialog { + fn save_file(self) -> Option { + autoreleasepool(move |_| { + run_on_main(move |mtm| { + let panel = Panel::build_save_file(&self, mtm)?; + if panel.run_modal() == NSModalResponseOK { + Some(panel.get_result()) + } else { + None + } + }) + }) + } +} + +use crate::backend::AsyncFileSaveDialogImpl; +impl AsyncFileSaveDialogImpl for FileDialog { + fn save_file_async(self) -> DialogFutureType> { + let win = self.parent.as_ref().map(window_from_raw_window_handle); + + let future = ModalFuture::new( + win, + move |mtm| Panel::build_save_file(&self, mtm), + |panel, res_id| { + if res_id == NSModalResponseOK { + Some(panel.get_result().into()) + } else { + None + } + }, + ); + + Box::pin(future) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/macos/file_dialog/panel_ffi.rs b/desktop/src-tauri/vendor/rfd/src/backend/macos/file_dialog/panel_ffi.rs new file mode 100644 index 00000000000..8751257bd14 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/macos/file_dialog/panel_ffi.rs @@ -0,0 +1,315 @@ +use core::str; +use std::path::Path; +use std::path::PathBuf; + +use block2::Block; +use objc2::rc::Retained; +use objc2::{msg_send, ClassType, MainThreadMarker}; +use objc2_app_kit::{ + NSApplication, NSApplicationActivationPolicy, NSModalResponse, NSOpenPanel, NSSavePanel, + NSWindow, NSWindowLevel, +}; +use objc2_foundation::{NSArray, NSString, NSURL}; +use raw_window_handle::RawWindowHandle; + +use super::super::{ + modal_future::{AsModal, InnerModal}, + utils::{FocusManager, PolicyManager}, +}; +use crate::backend::macos::utils::window_from_raw_window_handle; +use crate::FileDialog; + +extern "C" { + pub fn CGShieldingWindowLevel() -> i32; +} + +fn prepare_appkit_for_panel(mtm: MainThreadMarker) { + let app = NSApplication::sharedApplication(mtm); + let _ = app.setActivationPolicy(NSApplicationActivationPolicy::Regular); + NSApplication::activate(&app); +} + +/// `+[NSOpenPanel openPanel]` can return nil. The typed objc2 binding panics +/// on that, so retain the raw return instead (tauri-apps/tauri#13047). +fn try_open_panel(mtm: MainThreadMarker) -> Option> { + prepare_appkit_for_panel(mtm); + unsafe { + let ptr: *mut NSOpenPanel = msg_send![NSOpenPanel::class(), openPanel]; + Retained::retain(ptr) + } +} + +fn try_save_panel(mtm: MainThreadMarker) -> Option> { + prepare_appkit_for_panel(mtm); + unsafe { + let ptr: *mut NSSavePanel = msg_send![NSSavePanel::class(), savePanel]; + Retained::retain(ptr) + } +} + +pub struct Panel { + // Either `NSSavePanel` or the subclass `NSOpenPanel` + pub(crate) panel: Retained, + parent: Option>, + _focus_manager: FocusManager, + _policy_manager: PolicyManager, +} + +impl AsModal for Panel { + fn inner_modal(&self) -> &(impl InnerModal + 'static) { + &*self.panel + } +} + +impl InnerModal for NSSavePanel { + fn begin_modal(&self, window: &NSWindow, handler: &Block) { + unsafe { self.beginSheetModalForWindow_completionHandler(window, handler) } + } + + fn run_modal(&self) -> NSModalResponse { + unsafe { self.runModal() } + } +} + +impl Panel { + fn as_open_panel(&self) -> Option<&NSOpenPanel> { + self.panel.downcast_ref::() + } + + pub fn new(panel: Retained, parent: Option<&RawWindowHandle>) -> Self { + let _policy_manager = PolicyManager::new(MainThreadMarker::from(&*panel)); + + let _focus_manager = FocusManager::new(MainThreadMarker::from(&*panel)); + + panel.setLevel(unsafe { CGShieldingWindowLevel() } as NSWindowLevel); + Self { + panel, + parent: parent.map(window_from_raw_window_handle), + _focus_manager, + _policy_manager, + } + } + + pub fn run_modal(&self) -> NSModalResponse { + if let Some(parent) = self.parent.clone() { + let completion = block2::StackBlock::new(|_: isize| {}); + + unsafe { + self.panel + .beginSheetModalForWindow_completionHandler(&parent, &completion) + } + } + + unsafe { self.panel.runModal() } + } + + pub fn get_result(&self) -> PathBuf { + unsafe { + let url = self.panel.URL().unwrap(); + url.path().unwrap().to_string().into() + } + } + + pub fn get_results(&self) -> Vec { + unsafe { + let urls = self.as_open_panel().unwrap().URLs(); + + let mut res = Vec::new(); + for url in urls { + res.push(url.path().unwrap().to_string().into()); + } + + res + } + } +} + +trait PanelExt { + fn panel(&self) -> &NSSavePanel; + + fn set_can_create_directories(&self, can: bool) { + unsafe { self.panel().setCanCreateDirectories(can) } + } + + fn add_filters(&self, opt: &FileDialog) { + let mut exts: Vec = Vec::new(); + + for filter in opt.filters.iter() { + exts.append(&mut filter.extensions.to_vec()); + } + + let f_raw: Vec<_> = exts.iter().map(|ext| NSString::from_str(ext)).collect(); + let array = NSArray::from_retained_slice(&f_raw); + + unsafe { + #[allow(deprecated)] + self.panel().setAllowedFileTypes(Some(&array)); + } + } + + fn set_path(&self, path: &Path, file_name: Option<&str>) { + // if file_name is some, and path is a dir + let path = if let (Some(name), true) = (file_name, path.is_dir()) { + let mut path = path.to_owned(); + // add a name to the end of path + path.push(name); + path + } else { + path.to_owned() + }; + + if let Some(path) = path.to_str() { + unsafe { + let url = NSURL::fileURLWithPath_isDirectory(&NSString::from_str(path), true); + self.panel().setDirectoryURL(Some(&url)); + } + } + } + + fn set_file_name(&self, file_name: &str) { + unsafe { + self.panel() + .setNameFieldStringValue(&NSString::from_str(file_name)) + } + } + + fn set_title(&self, title: &str) { + unsafe { self.panel().setMessage(Some(&NSString::from_str(title))) } + } +} + +impl PanelExt for Retained { + fn panel(&self) -> &NSSavePanel { + &self + } +} + +impl PanelExt for Retained { + fn panel(&self) -> &NSSavePanel { + &self + } +} + +impl Panel { + pub fn build_pick_file(opt: &FileDialog, mtm: MainThreadMarker) -> Option { + let panel = try_open_panel(mtm)?; + + if !opt.filters.is_empty() { + panel.add_filters(opt); + } + + if let Some(path) = &opt.starting_directory { + panel.set_path(path, opt.file_name.as_deref()); + } + + if let Some(file_name) = &opt.file_name { + panel.set_file_name(file_name); + } + + if let Some(title) = &opt.title { + panel.set_title(title); + } + + if let Some(can) = opt.can_create_directories { + panel.set_can_create_directories(can); + } + + unsafe { panel.setCanChooseDirectories(false) }; + unsafe { panel.setCanChooseFiles(true) }; + + Some(Self::new(Retained::into_super(panel), opt.parent.as_ref())) + } + + pub fn build_save_file(opt: &FileDialog, mtm: MainThreadMarker) -> Option { + let panel = try_save_panel(mtm)?; + + if !opt.filters.is_empty() { + panel.add_filters(opt); + } + + if let Some(path) = &opt.starting_directory { + panel.set_path(path, opt.file_name.as_deref()); + } + + if let Some(file_name) = &opt.file_name { + panel.set_file_name(file_name); + } + + if let Some(title) = &opt.title { + panel.set_title(title); + } + + if let Some(can) = opt.can_create_directories { + panel.set_can_create_directories(can); + } + + Some(Self::new(panel, opt.parent.as_ref())) + } + + pub fn build_pick_folder(opt: &FileDialog, mtm: MainThreadMarker) -> Option { + let panel = try_open_panel(mtm)?; + + if let Some(path) = &opt.starting_directory { + panel.set_path(path, opt.file_name.as_deref()); + } + + if let Some(title) = &opt.title { + panel.set_title(title); + } + + let can = opt.can_create_directories.unwrap_or(true); + panel.set_can_create_directories(can); + + unsafe { panel.setCanChooseDirectories(true) }; + unsafe { panel.setCanChooseFiles(false) }; + + Some(Self::new(Retained::into_super(panel), opt.parent.as_ref())) + } + + pub fn build_pick_folders(opt: &FileDialog, mtm: MainThreadMarker) -> Option { + let panel = try_open_panel(mtm)?; + + if let Some(path) = &opt.starting_directory { + panel.set_path(path, opt.file_name.as_deref()); + } + + if let Some(title) = &opt.title { + panel.set_title(title); + } + + let can = opt.can_create_directories.unwrap_or(true); + panel.set_can_create_directories(can); + + unsafe { panel.setCanChooseDirectories(true) }; + unsafe { panel.setCanChooseFiles(false) }; + unsafe { panel.setAllowsMultipleSelection(true) }; + + Some(Self::new(Retained::into_super(panel), opt.parent.as_ref())) + } + + pub fn build_pick_files(opt: &FileDialog, mtm: MainThreadMarker) -> Option { + let panel = try_open_panel(mtm)?; + + if !opt.filters.is_empty() { + panel.add_filters(opt); + } + + if let Some(path) = &opt.starting_directory { + panel.set_path(path, opt.file_name.as_deref()); + } + + if let Some(title) = &opt.title { + panel.set_title(title); + } + + if let Some(can) = opt.can_create_directories { + panel.set_can_create_directories(can); + } + + unsafe { panel.setCanChooseDirectories(false) }; + unsafe { panel.setCanChooseFiles(true) }; + unsafe { panel.setAllowsMultipleSelection(true) }; + + Some(Self::new(Retained::into_super(panel), opt.parent.as_ref())) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/macos/message_dialog.rs b/desktop/src-tauri/vendor/rfd/src/backend/macos/message_dialog.rs new file mode 100644 index 00000000000..2c1bcaedf71 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/macos/message_dialog.rs @@ -0,0 +1,184 @@ +use crate::backend::DialogFutureType; +use crate::message_dialog::{MessageButtons, MessageDialog, MessageDialogResult, MessageLevel}; + +use super::modal_future::AsModal; +use super::{ + modal_future::{InnerModal, ModalFuture}, + utils::{self, run_on_main, FocusManager, PolicyManager}, +}; + +use super::utils::window_from_raw_window_handle; +use block2::Block; +use objc2::rc::{autoreleasepool, Retained}; +use objc2::MainThreadMarker; +use objc2_app_kit::{ + NSAlert, NSAlertFirstButtonReturn, NSAlertSecondButtonReturn, NSAlertStyle, + NSAlertThirdButtonReturn, NSApplication, NSModalResponse, NSWindow, +}; +use objc2_foundation::NSString; + +pub struct Alert { + buttons: MessageButtons, + alert: Retained, + parent: Option>, + _focus_manager: FocusManager, + _policy_manager: PolicyManager, +} + +impl Alert { + pub fn new(opt: MessageDialog, mtm: MainThreadMarker) -> Self { + let _policy_manager = PolicyManager::new(mtm); + + let alert = unsafe { NSAlert::new(mtm) }; + + let level = match opt.level { + MessageLevel::Info => NSAlertStyle::Informational, + MessageLevel::Warning => NSAlertStyle::Warning, + MessageLevel::Error => NSAlertStyle::Critical, + }; + + unsafe { alert.setAlertStyle(level) }; + + let buttons = match &opt.buttons { + MessageButtons::Ok => vec!["OK".to_owned()], + MessageButtons::OkCancel => vec!["OK".to_owned(), "Cancel".to_owned()], + MessageButtons::YesNo => vec!["Yes".to_owned(), "No".to_owned()], + MessageButtons::YesNoCancel => { + vec!["Yes".to_owned(), "No".to_owned(), "Cancel".to_owned()] + } + MessageButtons::OkCustom(ok_text) => vec![ok_text.to_owned()], + MessageButtons::OkCancelCustom(ok_text, cancel_text) => { + vec![ok_text.to_owned(), cancel_text.to_owned()] + } + MessageButtons::YesNoCancelCustom(yes_text, no_text, cancel_text) => { + vec![ + yes_text.to_owned(), + no_text.to_owned(), + cancel_text.to_owned(), + ] + } + }; + + for button in buttons { + let label = NSString::from_str(&button); + unsafe { alert.addButtonWithTitle(&label) }; + } + + unsafe { + let text = NSString::from_str(&opt.title); + alert.setMessageText(&text); + let text = NSString::from_str(&opt.description); + alert.setInformativeText(&text); + } + + let _focus_manager = FocusManager::new(mtm); + + Self { + alert, + parent: opt.parent.map(|x| window_from_raw_window_handle(&x)), + buttons: opt.buttons, + _focus_manager, + _policy_manager, + } + } + + pub fn run(mut self) -> MessageDialogResult { + let mtm = MainThreadMarker::from(&*self.alert); + + if let Some(parent) = self.parent.take() { + let completion = { + block2::StackBlock::new(move |result| unsafe { + NSApplication::sharedApplication(mtm).stopModalWithCode(result); + }) + }; + + unsafe { + self.alert + .beginSheetModalForWindow_completionHandler(&parent, Some(&completion)) + } + } + + dialog_result(&self.buttons, unsafe { self.alert.runModal() }) + } +} + +fn dialog_result(buttons: &MessageButtons, ret: NSModalResponse) -> MessageDialogResult { + match buttons { + MessageButtons::Ok if ret == NSAlertFirstButtonReturn => MessageDialogResult::Ok, + MessageButtons::OkCancel if ret == NSAlertFirstButtonReturn => MessageDialogResult::Ok, + MessageButtons::OkCancel if ret == NSAlertSecondButtonReturn => MessageDialogResult::Cancel, + MessageButtons::YesNo if ret == NSAlertFirstButtonReturn => MessageDialogResult::Yes, + MessageButtons::YesNo if ret == NSAlertSecondButtonReturn => MessageDialogResult::No, + MessageButtons::YesNoCancel if ret == NSAlertFirstButtonReturn => MessageDialogResult::Yes, + MessageButtons::YesNoCancel if ret == NSAlertSecondButtonReturn => MessageDialogResult::No, + MessageButtons::YesNoCancel if ret == NSAlertThirdButtonReturn => { + MessageDialogResult::Cancel + } + MessageButtons::OkCustom(custom) if ret == NSAlertFirstButtonReturn => { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::OkCancelCustom(custom, _) if ret == NSAlertFirstButtonReturn => { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::OkCancelCustom(_, custom) if ret == NSAlertSecondButtonReturn => { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::YesNoCancelCustom(custom, _, _) if ret == NSAlertFirstButtonReturn => { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::YesNoCancelCustom(_, custom, _) if ret == NSAlertSecondButtonReturn => { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::YesNoCancelCustom(_, _, custom) if ret == NSAlertThirdButtonReturn => { + MessageDialogResult::Custom(custom.to_owned()) + } + _ => MessageDialogResult::Cancel, + } +} + +impl AsModal for Alert { + fn inner_modal(&self) -> &(impl InnerModal + 'static) { + &*self.alert + } +} + +impl InnerModal for NSAlert { + fn begin_modal(&self, window: &NSWindow, handler: &Block) { + unsafe { self.beginSheetModalForWindow_completionHandler(window, Some(handler)) } + } + + fn run_modal(&self) -> NSModalResponse { + unsafe { self.runModal() } + } +} + +use crate::backend::MessageDialogImpl; +impl MessageDialogImpl for MessageDialog { + fn show(self) -> MessageDialogResult { + autoreleasepool(move |_| { + run_on_main(move |mtm| { + if self.parent.is_none() { + utils::sync_pop_dialog(self, mtm) + } else { + Alert::new(self, mtm).run() + } + }) + }) + } +} + +use crate::backend::AsyncMessageDialogImpl; + +impl AsyncMessageDialogImpl for MessageDialog { + fn show_async(self) -> DialogFutureType { + if self.parent.is_none() { + utils::async_pop_dialog(self) + } else { + Box::pin(ModalFuture::new( + self.parent.as_ref().map(window_from_raw_window_handle), + move |mtm| Some(Alert::new(self, mtm)), + |dialog, ret| dialog_result(&dialog.buttons, ret), + )) + } + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/macos/modal_future.rs b/desktop/src-tauri/vendor/rfd/src/backend/macos/modal_future.rs new file mode 100644 index 00000000000..0a43be110ca --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/macos/modal_future.rs @@ -0,0 +1,144 @@ +use block2::Block; +use dispatch2::run_on_main; +use objc2::rc::Retained; +use objc2::{ClassType, MainThreadMarker, MainThreadOnly, Message}; +use objc2_app_kit::{NSApplication, NSModalResponse, NSModalResponseCancel, NSWindow}; + +use std::pin::Pin; +use std::sync::{Arc, Mutex}; + +use std::task::{Context, Poll, Waker}; + +use super::utils::activate_cocoa_multithreading; + +pub(super) trait AsModal { + fn inner_modal(&self) -> &(impl InnerModal + 'static); +} + +pub(super) trait InnerModal: ClassType + MainThreadOnly { + fn begin_modal(&self, window: &NSWindow, handler: &Block); + fn run_modal(&self) -> NSModalResponse; +} + +struct FutureState { + waker: Option, + data: Option, + modal: Option, +} + +unsafe impl Send for FutureState {} + +pub(super) struct ModalFuture { + state: Arc>>, +} + +unsafe impl Send for ModalFuture {} + +impl ModalFuture { + pub fn new Option + Send>( + win: Option>, + build_modal: DBULD, + cb: F, + ) -> Self + where + F: Fn(&mut D, isize) -> R + Send + 'static, + { + activate_cocoa_multithreading(); + + let state = Arc::new(Mutex::new(FutureState { + waker: None, + data: None, + modal: None, + })); + + let dialog_callback = move |state: Arc>>, + result: NSModalResponse| { + let mut state = state.lock().unwrap(); + // take() to drop it when it's safe to do so + state.data = if let Some(mut modal) = state.modal.take() { + Some((cb)(&mut modal, result)) + } else { + Some(Default::default()) + }; + if let Some(waker) = state.waker.take() { + waker.wake(); + } + }; + + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + let app = NSApplication::sharedApplication(mtm); + + let win = if let Some(win) = win { + Some(win) + } else { + unsafe { app.mainWindow() }.or_else(|| app.windows().firstObject()) + }; + + // if async exec is possible start sheet modal + // otherwise fallback to sync + if unsafe { app.isRunning() } && win.is_some() { + let state = state.clone(); + + // Hack to work around us getting the window above + struct WindowWrapper(Retained); + unsafe impl Send for WindowWrapper {} + let window = WindowWrapper(win.unwrap()); + + run_on_main(move |mtm| { + let window = window; + + let completion = { + let state = state.clone(); + block2::RcBlock::new(move |result| { + dialog_callback(state.clone(), result); + }) + }; + + let Some(modal) = build_modal(mtm) else { + dialog_callback(state, NSModalResponseCancel); + return; + }; + let inner = modal.inner_modal().retain(); + + state.lock().unwrap().modal = Some(modal); + + inner.begin_modal(&window.0, &completion); + }); + } else { + eprintln!("\n Hi! It looks like you are running async dialog in unsupported environment, I will fallback to sync dialog for you. \n"); + + if let Some(mtm) = MainThreadMarker::new() { + let Some(modal) = build_modal(mtm) else { + dialog_callback(state, NSModalResponseCancel); + return; + }; + let inner = modal.inner_modal().retain(); + + state.lock().unwrap().modal = Some(modal); + + let ret = inner.run_modal(); + + dialog_callback(state.clone(), ret); + } else { + panic!("Fallback Sync Dialog Must Be Spawned On Main Thread (Why? If async dialog is unsupported in this env, it also means that spawning dialogs outside of main thread is also inpossible"); + } + } + + Self { state } + } +} + +impl std::future::Future for ModalFuture { + type Output = R; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut state = self.state.lock().unwrap(); + + if state.data.is_some() { + Poll::Ready(state.data.take().unwrap()) + } else { + state.waker = Some(cx.waker().clone()); + Poll::Pending + } + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/macos/utils.rs b/desktop/src-tauri/vendor/rfd/src/backend/macos/utils.rs new file mode 100644 index 00000000000..cee53d30684 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/macos/utils.rs @@ -0,0 +1,45 @@ +mod focus_manager; +mod policy_manager; +mod user_alert; + +pub use focus_manager::FocusManager; +pub use policy_manager::PolicyManager; +pub use user_alert::{async_pop_dialog, sync_pop_dialog}; + +use objc2::rc::Retained; +use objc2::MainThreadMarker; +use objc2_app_kit::{NSApplication, NSView, NSWindow}; +use objc2_foundation::NSThread; +use raw_window_handle::RawWindowHandle; + +pub fn activate_cocoa_multithreading() { + let thread = NSThread::new(); + unsafe { thread.start() }; +} + +pub fn run_on_main R + Send>(run: F) -> R { + if let Some(mtm) = MainThreadMarker::new() { + run(mtm) + } else { + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + let app = NSApplication::sharedApplication(mtm); + if unsafe { app.isRunning() } { + dispatch2::run_on_main(run) + } else { + panic!("You are running RFD in NonWindowed environment, it is impossible to spawn dialog from thread different than main in this env."); + } + } +} + +pub fn window_from_raw_window_handle(h: &RawWindowHandle) -> Retained { + // TODO: Move this requirement up + let _mtm = unsafe { MainThreadMarker::new_unchecked() }; + match h { + RawWindowHandle::AppKit(h) => { + let view = h.ns_view.as_ptr() as *mut NSView; + let view = unsafe { Retained::retain(view).unwrap() }; + view.window().expect("NSView to be inside a NSWindow") + } + _ => unreachable!("unsupported window handle, expected: MacOS"), + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/macos/utils/focus_manager.rs b/desktop/src-tauri/vendor/rfd/src/backend/macos/utils/focus_manager.rs new file mode 100644 index 00000000000..c4fc666c2c5 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/macos/utils/focus_manager.rs @@ -0,0 +1,24 @@ +use objc2::rc::Retained; +use objc2::MainThreadMarker; +use objc2_app_kit::{NSApplication, NSWindow}; + +pub struct FocusManager { + key_window: Option>, +} + +impl FocusManager { + pub fn new(mtm: MainThreadMarker) -> Self { + let app = NSApplication::sharedApplication(mtm); + let key_window = app.keyWindow(); + + Self { key_window } + } +} + +impl Drop for FocusManager { + fn drop(&mut self) { + if let Some(win) = &self.key_window { + win.makeKeyAndOrderFront(None); + } + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/macos/utils/policy_manager.rs b/desktop/src-tauri/vendor/rfd/src/backend/macos/utils/policy_manager.rs new file mode 100644 index 00000000000..a48994a5e27 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/macos/utils/policy_manager.rs @@ -0,0 +1,31 @@ +use objc2::rc::Retained; +use objc2::MainThreadMarker; +use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy}; + +pub struct PolicyManager { + app: Retained, + initial_policy: NSApplicationActivationPolicy, +} + +impl PolicyManager { + pub fn new(mtm: MainThreadMarker) -> Self { + let app = NSApplication::sharedApplication(mtm); + let initial_policy = unsafe { app.activationPolicy() }; + + if initial_policy != NSApplicationActivationPolicy::Regular { + app.setActivationPolicy(NSApplicationActivationPolicy::Regular); + } + + Self { + app, + initial_policy, + } + } +} + +impl Drop for PolicyManager { + fn drop(&mut self) { + // Restore initial policy + self.app.setActivationPolicy(self.initial_policy); + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/macos/utils/user_alert.rs b/desktop/src-tauri/vendor/rfd/src/backend/macos/utils/user_alert.rs new file mode 100644 index 00000000000..d2669fc2a71 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/macos/utils/user_alert.rs @@ -0,0 +1,196 @@ +use crate::{ + backend::{ + macos::utils::{FocusManager, PolicyManager}, + DialogFutureType, + }, + message_dialog::{MessageButtons, MessageDialog, MessageDialogResult, MessageLevel}, +}; + +use objc2::MainThreadMarker; +use objc2_core_foundation::{ + kCFUserNotificationAlternateResponse, kCFUserNotificationCancelResponse, + kCFUserNotificationCautionAlertLevel, kCFUserNotificationDefaultResponse, + kCFUserNotificationNoteAlertLevel, kCFUserNotificationOtherResponse, + kCFUserNotificationStopAlertLevel, CFOptionFlags, CFRetained, CFString, CFTimeInterval, + CFUserNotificationDisplayAlert, CFURL, +}; + +use std::{mem::MaybeUninit, thread}; + +struct UserAlert { + timeout: CFTimeInterval, + flags: CFOptionFlags, + icon_url: Option>, + sound_url: Option>, + localization_url: Option>, + alert_header: String, + alert_message: String, + default_button_title: Option, + alternate_button_title: Option, + other_button_title: Option, + buttons: MessageButtons, + _focus_manager: Option, + _policy_manager: Option, +} + +impl UserAlert { + fn new(opt: MessageDialog, mtm: Option) -> Self { + let mut buttons: [Option; 3] = match &opt.buttons { + MessageButtons::Ok => [None, None, None], + MessageButtons::OkCancel => [None, Some("Cancel".to_string()), None], + MessageButtons::YesNo => [Some("Yes".to_string()), Some("No".to_string()), None], + MessageButtons::YesNoCancel => [ + Some("Yes".to_string()), + Some("No".to_string()), + Some("Cancel".to_string()), + ], + MessageButtons::OkCustom(ok_text) => [Some(ok_text.to_string()), None, None], + MessageButtons::OkCancelCustom(ok_text, cancel_text) => [ + Some(ok_text.to_string()), + Some(cancel_text.to_string()), + None, + ], + MessageButtons::YesNoCancelCustom(yes_text, no_text, cancel_text) => [ + Some(yes_text.to_string()), + Some(no_text.to_string()), + Some(cancel_text.to_string()), + ], + }; + UserAlert { + timeout: 0_f64, + icon_url: None, + sound_url: None, + localization_url: None, + flags: match opt.level { + MessageLevel::Info => kCFUserNotificationNoteAlertLevel, + MessageLevel::Warning => kCFUserNotificationCautionAlertLevel, + MessageLevel::Error => kCFUserNotificationStopAlertLevel, + }, + alert_header: opt.title, + alert_message: opt.description, + default_button_title: buttons[0].take(), + alternate_button_title: buttons[1].take(), + other_button_title: buttons[2].take(), + buttons: opt.buttons, + _policy_manager: mtm.map(PolicyManager::new), + _focus_manager: mtm.map(FocusManager::new), + } + } + + fn run(self) -> MessageDialogResult { + let alert_header = CFString::from_str(&self.alert_header[..]); + let alert_message = CFString::from_str(&self.alert_message[..]); + let default_button_title = self + .default_button_title + .map(|string| CFString::from_str(&string[..])); + let alternate_button_title = self + .alternate_button_title + .map(|value| CFString::from_str(&value[..])); + let other_button_title = self + .other_button_title + .map(|value| CFString::from_str(&value[..])); + let mut response_flags = MaybeUninit::::uninit(); + let is_cancel = unsafe { + CFUserNotificationDisplayAlert( + self.timeout, + self.flags, + self.icon_url.as_deref(), + self.sound_url.as_deref(), + self.localization_url.as_deref(), + Some(&alert_header), + Some(&alert_message), + default_button_title.as_deref(), + alternate_button_title.as_deref(), + other_button_title.as_deref(), + response_flags.as_mut_ptr(), + ) + }; + if is_cancel != 0 { + return MessageDialogResult::Cancel; + } + let response = unsafe { response_flags.assume_init() }; + if response == kCFUserNotificationCancelResponse { + return MessageDialogResult::Cancel; + } + match self.buttons { + MessageButtons::Ok if response == kCFUserNotificationDefaultResponse => { + MessageDialogResult::Ok + } + MessageButtons::OkCancel if response == kCFUserNotificationDefaultResponse => { + MessageDialogResult::Ok + } + MessageButtons::OkCancel if response == kCFUserNotificationAlternateResponse => { + MessageDialogResult::Cancel + } + MessageButtons::YesNo if response == kCFUserNotificationDefaultResponse => { + MessageDialogResult::Yes + } + MessageButtons::YesNo if response == kCFUserNotificationAlternateResponse => { + MessageDialogResult::No + } + MessageButtons::YesNoCancel if response == kCFUserNotificationDefaultResponse => { + MessageDialogResult::Yes + } + MessageButtons::YesNoCancel if response == kCFUserNotificationAlternateResponse => { + MessageDialogResult::No + } + MessageButtons::YesNoCancel if response == kCFUserNotificationOtherResponse => { + MessageDialogResult::Cancel + } + MessageButtons::OkCustom(custom) if response == kCFUserNotificationDefaultResponse => { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::OkCancelCustom(custom, _) + if response == kCFUserNotificationDefaultResponse => + { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::OkCancelCustom(_, custom) + if response == kCFUserNotificationAlternateResponse => + { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::YesNoCancelCustom(custom, _, _) + if response == kCFUserNotificationDefaultResponse => + { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::YesNoCancelCustom(_, custom, _) + if response == kCFUserNotificationAlternateResponse => + { + MessageDialogResult::Custom(custom.to_owned()) + } + MessageButtons::YesNoCancelCustom(_, _, custom) + if response == kCFUserNotificationOtherResponse => + { + MessageDialogResult::Custom(custom.to_owned()) + } + _ => MessageDialogResult::Cancel, + } + } +} + +pub fn sync_pop_dialog(opt: MessageDialog, mtm: MainThreadMarker) -> MessageDialogResult { + UserAlert::new(opt, Some(mtm)).run() +} + +pub fn async_pop_dialog(opt: MessageDialog) -> DialogFutureType { + let (tx, rx) = crate::oneshot::channel(); + + thread::spawn(move || { + let message_dialog_result = UserAlert::new(opt.clone(), None).run(); + if let Err(err) = tx.send(message_dialog_result) { + log::error!("UserAler result send error: {err}"); + } + }); + + Box::pin(async { + match rx.await { + Ok(res) => res, + Err(err) => { + log::error!("UserAler error: {err}"); + MessageDialogResult::Cancel + } + } + }) +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/wasm.rs b/desktop/src-tauri/vendor/rfd/src/backend/wasm.rs new file mode 100644 index 00000000000..8ef22e964e0 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/wasm.rs @@ -0,0 +1,396 @@ +mod file_dialog; + +use crate::{ + file_dialog::FileDialog, file_handle::WasmFileHandleKind, FileHandle, MessageDialogResult, +}; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use web_sys::{Element, HtmlAnchorElement, HtmlButtonElement, HtmlElement, HtmlInputElement}; + +#[derive(Clone, Debug)] +pub enum FileKind<'a> { + In(FileDialog), + Out(FileDialog, &'a [u8]), +} + +#[derive(Clone, Debug)] +enum HtmlIoElement<'a> { + Input(HtmlInputElement), + Output { + element: HtmlAnchorElement, + name: String, + data: &'a [u8], + }, +} + +pub struct WasmDialog<'a> { + overlay: Element, + card: Element, + title: Option, + io: HtmlIoElement<'a>, + ok_button: HtmlButtonElement, + cancel_button: HtmlButtonElement, + + style: Element, +} + +impl<'a> WasmDialog<'a> { + pub fn new(opt: &FileKind<'a>) -> Self { + let window = web_sys::window().expect("Window not found"); + let document = window.document().expect("Document not found"); + + let overlay = document.create_element("div").unwrap(); + overlay.set_id("rfd-overlay"); + + let card = { + let card = document.create_element("div").unwrap(); + card.set_id("rfd-card"); + overlay.append_child(&card).unwrap(); + + card + }; + + let title = match opt { + FileKind::In(dialog) => &dialog.title, + FileKind::Out(dialog, _) => &dialog.title, + } + .as_ref() + .map(|title| { + let title_el: HtmlElement = document.create_element("div").unwrap().dyn_into().unwrap(); + + title_el.set_id("rfd-title"); + title_el.set_inner_html(title); + + card.append_child(&title_el).unwrap(); + title_el + }); + + let io = match opt { + FileKind::In(dialog) => { + let input_el = document.create_element("input").unwrap(); + let input: HtmlInputElement = wasm_bindgen::JsCast::dyn_into(input_el).unwrap(); + + input.set_id("rfd-input"); + input.set_type("file"); + + let mut accept: Vec = Vec::new(); + + for filter in dialog.filters.iter() { + accept.append(&mut filter.extensions.to_vec()); + } + + accept.iter_mut().for_each(|ext| ext.insert_str(0, ".")); + + input.set_accept(&accept.join(",")); + + card.append_child(&input).unwrap(); + HtmlIoElement::Input(input) + } + FileKind::Out(dialog, data) => { + let output_el = document.create_element("a").unwrap(); + let output: HtmlAnchorElement = wasm_bindgen::JsCast::dyn_into(output_el).unwrap(); + + output.set_id("rfd-output"); + output.set_inner_text("click here to download your file"); + + card.append_child(&output).unwrap(); + HtmlIoElement::Output { + element: output, + name: dialog.file_name.clone().unwrap_or_default(), + data, + } + } + }; + + let ok_button = { + let btn_el = document.create_element("button").unwrap(); + let btn: HtmlButtonElement = wasm_bindgen::JsCast::dyn_into(btn_el).unwrap(); + + btn.set_class_name("rfd-button"); + btn.set_inner_text("Ok"); + + card.append_child(&btn).unwrap(); + btn + }; + + let cancel_button = { + let btn_el = document.create_element("button").unwrap(); + let btn: HtmlButtonElement = wasm_bindgen::JsCast::dyn_into(btn_el).unwrap(); + + btn.set_class_name("rfd-button"); + btn.set_inner_text("Cancel"); + + card.append_child(&btn).unwrap(); + btn + }; + + let style = document.create_element("style").unwrap(); + style.set_inner_html(include_str!("./wasm/style.css")); + overlay.append_child(&style).unwrap(); + + Self { + overlay, + card, + title, + ok_button, + cancel_button, + io, + + style, + } + } + + async fn show(&self) { + let window = web_sys::window().expect("Window not found"); + let document = window.document().expect("Document not found"); + let body = document.body().expect("Document should have a body"); + + let overlay = self.overlay.clone(); + let ok_button = self.ok_button.clone(); + let cancel_button = self.cancel_button.clone(); + + let promise = match &self.io { + HtmlIoElement::Input(input) => js_sys::Promise::new(&mut move |res, rej| { + let resolve_promise = Closure::wrap(Box::new(move || { + res.call0(&JsValue::undefined()).unwrap(); + }) as Box); + + let body_for_cancel = body.clone(); + let overlay_for_cancel = overlay.clone(); + + let reject_promise = Closure::wrap(Box::new({ + let input = input.clone(); + move || { + rej.call0(&JsValue::undefined()).unwrap(); + input.set_value(""); + body_for_cancel.remove_child(&overlay_for_cancel).unwrap(); + } + }) as Box); + + ok_button.set_onclick(Some(resolve_promise.as_ref().unchecked_ref())); + cancel_button.set_onclick(Some(reject_promise.as_ref().unchecked_ref())); + + body.append_child(&overlay).ok(); + + input.add_event_listener_with_callback( + "change", + resolve_promise.as_ref().unchecked_ref(), + ).unwrap(); + + input.add_event_listener_with_callback( + "cancel", + reject_promise.as_ref().unchecked_ref(), + ).unwrap(); + + if window.navigator().user_activation().is_active() { + // Browsers require transient user activation to open the file picker from JS. + // If we have it, we can click the input to immediately show the file picker + // instead of showing the popup. + + overlay.set_class_name("hidden"); + + // click on the input element to open the file picker + input.click(); + } + + resolve_promise.forget(); + reject_promise.forget(); + }), + HtmlIoElement::Output { + element, + name, + data, + } => { + js_sys::Promise::new(&mut |res, rej| { + // Moved to keep closure as FnMut + let output = element.clone(); + let file_name = name.clone(); + + let resolve_promise = Closure::wrap(Box::new(move || { + res.call1(&JsValue::undefined(), &JsValue::from(true)) + .unwrap(); + }) as Box); + + let reject_promise = Closure::wrap(Box::new(move || { + rej.call1(&JsValue::undefined(), &JsValue::from(true)) + .unwrap(); + }) as Box); + + // Resolve the promise once the user clicks the download link or the button. + output.set_onclick(Some(resolve_promise.as_ref().unchecked_ref())); + ok_button.set_onclick(Some(resolve_promise.as_ref().unchecked_ref())); + cancel_button.set_onclick(Some(reject_promise.as_ref().unchecked_ref())); + + resolve_promise.forget(); + reject_promise.forget(); + + let set_download_link = move |in_array: &[u8], name: &str| { + // See + let array = js_sys::Array::new(); + let uint8arr = js_sys::Uint8Array::new( + // Safety: No wasm allocations happen between creating the view and consuming it in the array.push + &unsafe { js_sys::Uint8Array::view(&in_array) }.into(), + ); + array.push(&uint8arr.buffer()); + + let blob_property = web_sys::BlobPropertyBag::new(); + blob_property.set_type("application/octet-stream"); + + let blob = web_sys::Blob::new_with_u8_array_sequence_and_options( + &array, + &blob_property, + ) + .unwrap(); + let download_url = + web_sys::Url::create_object_url_with_blob(&blob).unwrap(); + + output.set_href(&download_url); + output.set_download(&name); + }; + + set_download_link(&*data, &file_name); + + body.append_child(&overlay).ok(); + }) + } + }; + + let future = wasm_bindgen_futures::JsFuture::from(promise); + future.await.ok(); + } + + fn get_results(&self) -> Option> { + let input = match &self.io { + HtmlIoElement::Input(input) => input, + _ => panic!("Internal Error: Results only exist for input dialog"), + }; + if let Some(files) = input.files() { + let len = files.length(); + if len > 0 { + let mut file_handles = Vec::new(); + for id in 0..len { + let file = files.get(id).unwrap(); + file_handles.push(FileHandle::wrap(file)); + } + Some(file_handles) + } else { + None + } + } else { + None + } + } + + fn get_result(&self) -> Option { + let files = self.get_results(); + files.and_then(|mut f| f.pop()) + } + + async fn pick_files(self) -> Option> { + if let HtmlIoElement::Input(input) = &self.io { + input.set_multiple(true); + } else { + panic!("Internal error: Pick files only on input wasm dialog") + } + + self.show().await; + + self.get_results() + } + + async fn pick_file(self) -> Option { + if let HtmlIoElement::Input(input) = &self.io { + input.set_multiple(false); + } else { + panic!("Internal error: Pick file only on input wasm dialog") + } + + self.show().await; + + self.get_result() + } + + fn io_element(&self) -> Element { + match self.io.clone() { + HtmlIoElement::Input(element) => element.unchecked_into(), + HtmlIoElement::Output { element, .. } => element.unchecked_into(), + } + } +} + +impl<'a> Drop for WasmDialog<'a> { + fn drop(&mut self) { + self.ok_button.remove(); + self.cancel_button.remove(); + self.io_element().remove(); + self.title.as_ref().map(|elem| elem.remove()); + self.card.remove(); + + self.style.remove(); + self.overlay.remove(); + } +} + +use super::{AsyncFilePickerDialogImpl, DialogFutureType}; + +impl AsyncFilePickerDialogImpl for FileDialog { + fn pick_file_async(self) -> DialogFutureType> { + let dialog = WasmDialog::new(&FileKind::In(self)); + Box::pin(dialog.pick_file()) + } + fn pick_files_async(self) -> DialogFutureType>> { + let dialog = WasmDialog::new(&FileKind::In(self)); + Box::pin(dialog.pick_files()) + } +} + +#[wasm_bindgen] +extern "C" { + fn alert(s: &str); + fn confirm(s: &str) -> bool; +} + +use crate::backend::MessageDialogImpl; +use crate::message_dialog::{MessageButtons, MessageDialog}; + +impl MessageDialogImpl for MessageDialog { + fn show(self) -> MessageDialogResult { + let text = format!("{}\n{}", self.title, self.description); + match self.buttons { + MessageButtons::Ok | MessageButtons::OkCustom(_) => { + alert(&text); + MessageDialogResult::Ok + } + MessageButtons::OkCancel + | MessageButtons::OkCancelCustom(..) + | MessageButtons::YesNo + | MessageButtons::YesNoCancel + | MessageButtons::YesNoCancelCustom(..) => { + if confirm(&text) { + MessageDialogResult::Ok + } else { + MessageDialogResult::Cancel + } + } + } + } +} + +impl crate::backend::AsyncMessageDialogImpl for MessageDialog { + fn show_async(self) -> DialogFutureType { + let val = MessageDialogImpl::show(self); + Box::pin(std::future::ready(val)) + } +} + +impl FileHandle { + pub async fn write(&self, data: &[u8]) -> std::io::Result<()> { + let dialog = match &self.0 { + WasmFileHandleKind::Writable(dialog) => dialog, + _ => panic!("This File Handle doesn't support writing. Use `save_file` to get a writeable FileHandle in Wasm"), + }; + let dialog = WasmDialog::new(&FileKind::Out(dialog.clone(), data)); + dialog.show().await; + Ok(()) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/wasm/file_dialog.rs b/desktop/src-tauri/vendor/rfd/src/backend/wasm/file_dialog.rs new file mode 100644 index 00000000000..b2d1eccc3cc --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/wasm/file_dialog.rs @@ -0,0 +1,16 @@ +// +// File Save +// + +use crate::{ + backend::{AsyncFileSaveDialogImpl, DialogFutureType}, + file_dialog::FileDialog, + FileHandle, +}; +use std::future::ready; +impl AsyncFileSaveDialogImpl for FileDialog { + fn save_file_async(self) -> DialogFutureType> { + let file = FileHandle::writable(self); + Box::pin(ready(Some(file))) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/wasm/style.css b/desktop/src-tauri/vendor/rfd/src/backend/wasm/style.css new file mode 100644 index 00000000000..3431d7ccb3c --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/wasm/style.css @@ -0,0 +1,49 @@ +#rfd-overlay { + z-index: 1000; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + + animation: init 0.5s; + + display: flex; + justify-content: center; + align-items: center; +} +#rfd-overlay.hidden { + display: none; +} +#rfd-card { + padding: 20px; + border-radius: 5px; + + box-shadow: 0 24px 38px 3px rgba(0, 0, 0, 0.14), + 0 9px 46px 8px rgba(0, 0, 0, 0.12), 0 11px 15px -7px rgba(0, 0, 0, 0.2); + + color-scheme: light dark; + background-color: canvas; + color: canvastext; +} +#rfd-title { + line-height: 1.6; +} +#rfd-input,#rfd-output { + text-align: center; +} +.rfd-button { + display: block; + margin-top: 5px; + width: 100%; +} + +@keyframes init { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/win_cid.rs b/desktop/src-tauri/vendor/rfd/src/backend/win_cid.rs new file mode 100644 index 00000000000..80911c7d2ba --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/win_cid.rs @@ -0,0 +1,9 @@ +//! Windows Common Item Dialog +//! Win32 Vista + +mod utils; + +mod file_dialog; +mod message_dialog; + +mod thread_future; diff --git a/desktop/src-tauri/vendor/rfd/src/backend/win_cid/file_dialog.rs b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/file_dialog.rs new file mode 100644 index 00000000000..d5c414a87a6 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/file_dialog.rs @@ -0,0 +1,126 @@ +mod com; +pub mod dialog_ffi; +mod dialog_future; + +use dialog_ffi::{IDialog, Result}; +use dialog_future::{multiple_return_future, single_return_future}; + +use crate::backend::DialogFutureType; +use crate::FileDialog; +use crate::FileHandle; + +use std::path::PathBuf; + +use super::utils::init_com; + +// +// File Picker +// + +use crate::backend::FilePickerDialogImpl; +impl FilePickerDialogImpl for FileDialog { + fn pick_file(self) -> Option { + fn run(opt: FileDialog) -> Result { + init_com(|| { + let dialog = IDialog::build_pick_file(&opt)?; + dialog.show()?; + dialog.get_result() + })? + } + run(self).ok() + } + + fn pick_files(self) -> Option> { + fn run(opt: FileDialog) -> Result> { + init_com(|| { + let dialog = IDialog::build_pick_files(&opt)?; + dialog.show()?; + dialog.get_results() + })? + } + run(self).ok() + } +} + +use crate::backend::AsyncFilePickerDialogImpl; +impl AsyncFilePickerDialogImpl for FileDialog { + fn pick_file_async(self) -> DialogFutureType> { + let ret = single_return_future(move || IDialog::build_pick_file(&self)); + Box::pin(ret) + } + + fn pick_files_async(self) -> DialogFutureType>> { + let ret = multiple_return_future(move || IDialog::build_pick_files(&self)); + Box::pin(ret) + } +} + +// +// Folder Picker +// + +use crate::backend::FolderPickerDialogImpl; +impl FolderPickerDialogImpl for FileDialog { + fn pick_folder(self) -> Option { + fn run(opt: FileDialog) -> Result { + init_com(|| { + let dialog = IDialog::build_pick_folder(&opt)?; + dialog.show()?; + dialog.get_result() + })? + } + + run(self).ok() + } + + fn pick_folders(self) -> Option> { + fn run(opt: FileDialog) -> Result> { + init_com(|| { + let dialog = IDialog::build_pick_folders(&opt)?; + dialog.show()?; + dialog.get_results() + })? + } + run(self).ok() + } +} + +use crate::backend::AsyncFolderPickerDialogImpl; +impl AsyncFolderPickerDialogImpl for FileDialog { + fn pick_folder_async(self) -> DialogFutureType> { + let ret = single_return_future(move || IDialog::build_pick_folder(&self)); + Box::pin(ret) + } + + fn pick_folders_async(self) -> DialogFutureType>> { + let ret = multiple_return_future(move || IDialog::build_pick_folders(&self)); + Box::pin(ret) + } +} + +// +// File Save +// + +use crate::backend::FileSaveDialogImpl; +impl FileSaveDialogImpl for FileDialog { + fn save_file(self) -> Option { + fn run(opt: FileDialog) -> Result { + init_com(|| { + let dialog = IDialog::build_save_file(&opt)?; + dialog.show()?; + dialog.get_result() + })? + } + + run(self).ok() + } +} + +use crate::backend::AsyncFileSaveDialogImpl; +impl AsyncFileSaveDialogImpl for FileDialog { + fn save_file_async(self) -> DialogFutureType> { + let ret = single_return_future(move || IDialog::build_save_file(&self)); + Box::pin(ret) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/win_cid/file_dialog/com.rs b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/file_dialog/com.rs new file mode 100644 index 00000000000..acb192c4e17 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/file_dialog/com.rs @@ -0,0 +1,268 @@ +#![allow(non_snake_case)] + +use std::ffi::c_void; +use windows_sys::core::{HRESULT, PCWSTR, PWSTR}; +pub use windows_sys::{ + core::GUID, + Win32::{ + Foundation::HWND, + UI::Shell::{Common::COMDLG_FILTERSPEC, FILEOPENDIALOGOPTIONS, SIGDN, SIGDN_FILESYSPATH}, + }, +}; + +pub(crate) type Result = std::result::Result; + +#[inline] +pub(super) fn wrap_err(hresult: HRESULT) -> Result<()> { + if hresult >= 0 { + Ok(()) + } else { + Err(hresult) + } +} + +#[inline] +unsafe fn read_to_string(ptr: *const u16) -> String { + let mut cursor = ptr; + + while *cursor != 0 { + cursor = cursor.add(1); + } + + let slice = std::slice::from_raw_parts(ptr, cursor.offset_from(ptr) as usize); + String::from_utf16(slice).unwrap() +} + +#[repr(C)] +pub(super) struct Interface { + vtable: *mut T, +} + +impl Interface { + #[inline] + pub(super) fn vtbl(&self) -> &T { + unsafe { &*self.vtable } + } +} + +#[repr(C)] +pub(super) struct IUnknownV { + __query_interface: usize, + __add_ref: usize, + pub(super) release: unsafe extern "system" fn(this: *mut c_void) -> u32, +} + +pub(super) type IUnknown = Interface; + +#[inline] +fn drop_impl(ptr: *mut c_void) { + unsafe { + ((*ptr.cast::()).vtbl().release)(ptr); + } +} + +#[repr(C)] +pub(super) struct IShellItemV { + base: IUnknownV, + BindToHandler: unsafe extern "system" fn( + this: *mut c_void, + pbc: *mut c_void, + bhid: *const GUID, + riid: *const GUID, + ppv: *mut *mut c_void, + ) -> HRESULT, + GetParent: unsafe extern "system" fn(this: *mut c_void, ppsi: *mut *mut c_void) -> HRESULT, + GetDisplayName: unsafe extern "system" fn( + this: *mut c_void, + sigdnname: SIGDN, + ppszname: *mut PWSTR, + ) -> HRESULT, + GetAttributes: usize, + Compare: unsafe extern "system" fn( + this: *mut c_void, + psi: *mut c_void, + hint: u32, + piorder: *mut i32, + ) -> HRESULT, +} + +#[repr(transparent)] +pub(super) struct IShellItem(pub(super) *mut Interface); + +impl IShellItem { + pub(super) fn get_path(&self) -> Result { + let filename = unsafe { + let mut dname = std::mem::MaybeUninit::uninit(); + wrap_err(((*self.0).vtbl().GetDisplayName)( + self.0.cast(), + SIGDN_FILESYSPATH, + dname.as_mut_ptr(), + ))?; + + let dname = dname.assume_init(); + let fname = read_to_string(dname); + windows_sys::Win32::System::Com::CoTaskMemFree(dname.cast()); + fname + }; + + Ok(filename.into()) + } +} + +impl Drop for IShellItem { + fn drop(&mut self) { + drop_impl(self.0.cast()); + } +} + +#[repr(C)] +struct IShellItemArrayV { + base: IUnknownV, + BindToHandler: unsafe extern "system" fn( + this: *mut c_void, + pbc: *mut c_void, + bhid: *const GUID, + riid: *const GUID, + ppvout: *mut *mut c_void, + ) -> HRESULT, + GetPropertyStore: usize, + GetPropertyDescriptionList: usize, + GetAttributes: usize, + GetCount: unsafe extern "system" fn(this: *mut c_void, pdwnumitems: *mut u32) -> HRESULT, + GetItemAt: unsafe extern "system" fn( + this: *mut c_void, + dwindex: u32, + ppsi: *mut IShellItem, + ) -> HRESULT, + EnumItems: + unsafe extern "system" fn(this: *mut c_void, ppenumshellitems: *mut *mut c_void) -> HRESULT, +} + +#[repr(transparent)] +pub(super) struct IShellItemArray(*mut Interface); + +impl IShellItemArray { + #[inline] + pub(super) fn get_count(&self) -> Result { + let mut count = 0; + unsafe { + wrap_err(((*self.0).vtbl().GetCount)(self.0.cast(), &mut count))?; + } + Ok(count) + } + + #[inline] + pub(super) fn get_item_at(&self, index: u32) -> Result { + let mut item = std::mem::MaybeUninit::uninit(); + unsafe { + wrap_err(((*self.0).vtbl().GetItemAt)( + self.0.cast(), + index, + item.as_mut_ptr(), + ))?; + Ok(item.assume_init()) + } + } +} + +impl Drop for IShellItemArray { + fn drop(&mut self) { + drop_impl(self.0.cast()); + } +} + +#[repr(C)] +pub(super) struct IModalWindowV { + base: IUnknownV, + pub(super) Show: unsafe extern "system" fn(this: *mut c_void, owner: HWND) -> HRESULT, +} + +/// +#[repr(C)] +pub(super) struct IFileDialogV { + pub(super) base: IModalWindowV, + pub(super) SetFileTypes: unsafe extern "system" fn( + this: *mut c_void, + cfiletypes: u32, + rgfilterspec: *const COMDLG_FILTERSPEC, + ) -> HRESULT, + SetFileTypeIndex: unsafe extern "system" fn(this: *mut c_void, ifiletype: u32) -> HRESULT, + GetFileTypeIndex: unsafe extern "system" fn(this: *mut c_void, pifiletype: *mut u32) -> HRESULT, + Advise: unsafe extern "system" fn( + this: *mut c_void, + pfde: *mut c_void, + pdwcookie: *mut u32, + ) -> HRESULT, + Unadvise: unsafe extern "system" fn(this: *mut c_void, dwcookie: u32) -> HRESULT, + pub(super) SetOptions: + unsafe extern "system" fn(this: *mut c_void, fos: FILEOPENDIALOGOPTIONS) -> HRESULT, + GetOptions: + unsafe extern "system" fn(this: *mut c_void, pfos: *mut FILEOPENDIALOGOPTIONS) -> HRESULT, + SetDefaultFolder: unsafe extern "system" fn(this: *mut c_void, psi: *mut c_void) -> HRESULT, + pub(super) SetFolder: unsafe extern "system" fn(this: *mut c_void, psi: *mut c_void) -> HRESULT, + GetFolder: unsafe extern "system" fn(this: *mut c_void, ppsi: *mut *mut c_void) -> HRESULT, + GetCurrentSelection: + unsafe extern "system" fn(this: *mut c_void, ppsi: *mut *mut c_void) -> HRESULT, + pub(super) SetFileName: + unsafe extern "system" fn(this: *mut c_void, pszname: PCWSTR) -> HRESULT, + GetFileName: unsafe extern "system" fn(this: *mut c_void, pszname: *mut PWSTR) -> HRESULT, + pub(super) SetTitle: unsafe extern "system" fn(this: *mut c_void, psztitle: PCWSTR) -> HRESULT, + SetOkButtonLabel: unsafe extern "system" fn(this: *mut c_void, psztext: PCWSTR) -> HRESULT, + SetFileNameLabel: unsafe extern "system" fn(this: *mut c_void, pszlabel: PCWSTR) -> HRESULT, + pub(super) GetResult: + unsafe extern "system" fn(this: *mut c_void, ppsi: *mut IShellItem) -> HRESULT, + AddPlace: unsafe extern "system" fn( + this: *mut c_void, + psi: *mut c_void, + fdap: windows_sys::Win32::UI::Shell::FDAP, + ) -> HRESULT, + pub(super) SetDefaultExtension: + unsafe extern "system" fn(this: *mut c_void, pszdefaultextension: PCWSTR) -> HRESULT, + Close: unsafe extern "system" fn(this: *mut c_void, hr: HRESULT) -> HRESULT, + SetClientGuid: unsafe extern "system" fn(this: *mut c_void, guid: *const GUID) -> HRESULT, + ClearClientData: unsafe extern "system" fn(this: *mut c_void) -> HRESULT, + SetFilter: unsafe extern "system" fn(this: *mut c_void, pfilter: *mut c_void) -> HRESULT, +} + +#[repr(transparent)] +pub(super) struct IFileDialog(pub(super) *mut Interface); + +impl Drop for IFileDialog { + fn drop(&mut self) { + drop_impl(self.0.cast()); + } +} + +/// +#[repr(C)] +pub(super) struct IFileOpenDialogV { + pub(super) base: IFileDialogV, + /// + GetResults: + unsafe extern "system" fn(this: *mut c_void, ppenum: *mut IShellItemArray) -> HRESULT, + GetSelectedItems: + unsafe extern "system" fn(this: *mut c_void, ppsai: *mut *mut c_void) -> HRESULT, +} + +#[repr(transparent)] +pub(super) struct IFileOpenDialog(pub(super) *mut Interface); + +impl IFileOpenDialog { + #[inline] + pub(super) fn get_results(&self) -> Result { + let mut res = std::mem::MaybeUninit::uninit(); + unsafe { + wrap_err((((*self.0).vtbl()).GetResults)( + self.0.cast(), + res.as_mut_ptr(), + ))?; + Ok(res.assume_init()) + } + } +} + +impl Drop for IFileOpenDialog { + fn drop(&mut self) { + drop_impl(self.0.cast()); + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/win_cid/file_dialog/dialog_ffi.rs b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/file_dialog/dialog_ffi.rs new file mode 100644 index 00000000000..29e9dd33025 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/file_dialog/dialog_ffi.rs @@ -0,0 +1,353 @@ +use super::super::utils::str_to_vec_u16; +pub(crate) use super::com::Result; +use super::com::{ + wrap_err, IFileDialog, IFileDialogV, IFileOpenDialog, IShellItem, COMDLG_FILTERSPEC, + FILEOPENDIALOGOPTIONS, HWND, +}; +use crate::FileDialog; + +use windows_sys::{ + core::GUID, + Win32::{ + System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER}, + UI::Shell::{ + FileOpenDialog, FileSaveDialog, SHCreateItemFromParsingName, FOS_ALLOWMULTISELECT, + FOS_PICKFOLDERS, + }, + }, +}; + +use std::{ffi::c_void, path::PathBuf}; + +use raw_window_handle::RawWindowHandle; + +enum DialogInner { + Open(IFileOpenDialog), + Save(IFileDialog), +} + +impl DialogInner { + unsafe fn new(open: bool) -> Result { + const FILE_OPEN_DIALOG_IID: GUID = GUID::from_u128(0xd57c7288_d4ad_4768_be02_9d969532d960); + const FILE_SAVE_DIALOG_IID: GUID = GUID::from_u128(0x84bccd23_5fde_4cdb_aea4_af64b83d78ab); + + unsafe { + let (cls_id, iid) = if open { + (&FileOpenDialog, &FILE_OPEN_DIALOG_IID) + } else { + (&FileSaveDialog, &FILE_SAVE_DIALOG_IID) + }; + + let mut iptr = std::mem::MaybeUninit::uninit(); + wrap_err(CoCreateInstance( + cls_id, + std::ptr::null_mut(), + CLSCTX_INPROC_SERVER, + iid, + iptr.as_mut_ptr(), + ))?; + + let iptr = iptr.assume_init(); + + Ok(if open { + Self::Open(IFileOpenDialog(iptr.cast())) + } else { + Self::Save(IFileDialog(iptr.cast())) + }) + } + } + + #[inline] + unsafe fn open() -> Result { + unsafe { Self::new(true) } + } + + #[inline] + unsafe fn save() -> Result { + unsafe { Self::new(false) } + } + + #[inline] + unsafe fn fd(&self) -> (*mut c_void, &IFileDialogV) { + match self { + Self::Save(s) => unsafe { (s.0.cast(), (*s.0).vtbl()) }, + Self::Open(o) => unsafe { (o.0.cast(), &(*o.0).vtbl().base) }, + } + } + + #[inline] + unsafe fn set_options(&self, opts: FILEOPENDIALOGOPTIONS) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.SetOptions)(d, opts)) + } + + #[inline] + unsafe fn set_title(&self, title: &[u16]) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.SetTitle)(d, title.as_ptr())) + } + + #[inline] + unsafe fn set_default_extension(&self, extension: &[u16]) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.SetDefaultExtension)(d, extension.as_ptr())) + } + + #[inline] + unsafe fn set_file_types(&self, specs: &[COMDLG_FILTERSPEC]) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.SetFileTypes)(d, specs.len() as _, specs.as_ptr())) + } + + #[inline] + unsafe fn set_filename(&self, fname: &[u16]) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.SetFileName)(d, fname.as_ptr())) + } + + #[inline] + unsafe fn set_folder(&self, folder: &IShellItem) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.SetFolder)(d, folder.0.cast())) + } + + #[inline] + unsafe fn show(&self, parent: Option) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.base.Show)(d, parent.unwrap_or(std::ptr::null_mut()))) + } + + #[inline] + unsafe fn get_result(&self) -> Result { + let (d, v) = self.fd(); + let mut res = std::mem::MaybeUninit::uninit(); + wrap_err((v.GetResult)(d, res.as_mut_ptr()))?; + let res = res.assume_init(); + res.get_path() + } + + #[inline] + unsafe fn get_results(&self) -> Result> { + let Self::Open(od) = self else { unreachable!() }; + + let items = od.get_results()?; + let count = items.get_count()?; + + let mut paths = Vec::with_capacity(count as usize); + for index in 0..count { + let item = items.get_item_at(index)?; + + let path = item.get_path()?; + paths.push(path); + } + + Ok(paths) + } +} + +pub struct IDialog(DialogInner, Option); + +impl IDialog { + fn new_open_dialog(opt: &FileDialog) -> Result { + let dialog = unsafe { DialogInner::open()? }; + + let parent = match opt.parent { + Some(RawWindowHandle::Win32(handle)) => Some(handle.hwnd.get() as _), + None => None, + _ => unreachable!("unsupported window handle, expected: Windows"), + }; + + Ok(Self(dialog, parent)) + } + + fn new_save_dialog(opt: &FileDialog) -> Result { + let dialog = unsafe { DialogInner::save()? }; + + let parent = match opt.parent { + Some(RawWindowHandle::Win32(handle)) => Some(handle.hwnd.get() as _), + None => None, + _ => unreachable!("unsupported window handle, expected: Windows"), + }; + + Ok(Self(dialog, parent)) + } + + fn add_filters(&self, filters: &[crate::file_dialog::Filter]) -> Result<()> { + if let Some(first_filter) = filters.first() { + if let Some(first_extension) = first_filter.extensions.first() { + let extension = str_to_vec_u16(first_extension); + unsafe { self.0.set_default_extension(&extension)? } + } + } + + let mut f_list = { + let mut f_list = Vec::new(); + let mut ext_string = String::new(); + + for f in filters.iter() { + let name = str_to_vec_u16(&f.name); + ext_string.clear(); + + for ext in &f.extensions { + use std::fmt::Write; + // This is infallible for String (barring OOM) + let _ = write!(&mut ext_string, "*.{ext};"); + } + + // pop trailing ; + ext_string.pop(); + + f_list.push((name, str_to_vec_u16(&ext_string))); + } + f_list + }; + + if f_list.is_empty() { + f_list.push((str_to_vec_u16("All Files"), str_to_vec_u16("*.*"))); + } + + let spec: Vec<_> = f_list + .iter() + .map(|(name, ext)| COMDLG_FILTERSPEC { + pszName: name.as_ptr(), + pszSpec: ext.as_ptr(), + }) + .collect(); + + unsafe { + self.0.set_file_types(&spec)?; + } + Ok(()) + } + + fn set_path(&self, path: &Option) -> Result<()> { + const SHELL_ITEM_IID: GUID = GUID::from_u128(0x43826d1e_e718_42ee_bc55_a1e261c37bfe); + + let Some(path) = path.as_ref().and_then(|p| p.to_str()) else { + return Ok(()); + }; + + // Strip Win32 namespace prefix from the path + let path = path.strip_prefix(r"\\?\").unwrap_or(path); + + let wide_path = str_to_vec_u16(path); + + unsafe { + let mut item = std::mem::MaybeUninit::uninit(); + if wrap_err(SHCreateItemFromParsingName( + wide_path.as_ptr(), + std::ptr::null_mut(), + &SHELL_ITEM_IID, + item.as_mut_ptr(), + )) + .is_ok() + { + let item = IShellItem(item.assume_init().cast()); + // For some reason SetDefaultFolder(), does not guarantees default path, so we use SetFolder + self.0.set_folder(&item)?; + } + } + + Ok(()) + } + + fn set_file_name(&self, file_name: &Option) -> Result<()> { + if let Some(path) = file_name { + let wide_path = str_to_vec_u16(path); + + unsafe { + self.0.set_filename(&wide_path)?; + } + } + Ok(()) + } + + fn set_title(&self, title: &Option) -> Result<()> { + if let Some(title) = title { + let wide_title = str_to_vec_u16(title); + + unsafe { + self.0.set_title(&wide_title)?; + } + } + Ok(()) + } + + pub fn get_results(&self) -> Result> { + unsafe { self.0.get_results() } + } + + pub fn get_result(&self) -> Result { + unsafe { self.0.get_result() } + } + + pub fn show(&self) -> Result<()> { + unsafe { self.0.show(self.1) } + } +} + +impl IDialog { + pub fn build_pick_file(opt: &FileDialog) -> Result { + let dialog = IDialog::new_open_dialog(opt)?; + + dialog.add_filters(&opt.filters)?; + dialog.set_path(&opt.starting_directory)?; + dialog.set_file_name(&opt.file_name)?; + dialog.set_title(&opt.title)?; + + Ok(dialog) + } + + pub fn build_save_file(opt: &FileDialog) -> Result { + let dialog = IDialog::new_save_dialog(opt)?; + + dialog.add_filters(&opt.filters)?; + dialog.set_path(&opt.starting_directory)?; + dialog.set_file_name(&opt.file_name)?; + dialog.set_title(&opt.title)?; + + Ok(dialog) + } + + pub fn build_pick_folder(opt: &FileDialog) -> Result { + let dialog = IDialog::new_open_dialog(opt)?; + + dialog.set_path(&opt.starting_directory)?; + dialog.set_title(&opt.title)?; + + unsafe { + dialog.0.set_options(FOS_PICKFOLDERS)?; + } + + Ok(dialog) + } + + pub fn build_pick_folders(opt: &FileDialog) -> Result { + let dialog = IDialog::new_open_dialog(opt)?; + + dialog.set_path(&opt.starting_directory)?; + dialog.set_title(&opt.title)?; + let opts = FOS_PICKFOLDERS | FOS_ALLOWMULTISELECT; + + unsafe { + dialog.0.set_options(opts)?; + } + + Ok(dialog) + } + + pub fn build_pick_files(opt: &FileDialog) -> Result { + let dialog = IDialog::new_open_dialog(opt)?; + + dialog.add_filters(&opt.filters)?; + dialog.set_path(&opt.starting_directory)?; + dialog.set_file_name(&opt.file_name)?; + dialog.set_title(&opt.title)?; + + unsafe { + dialog.0.set_options(FOS_ALLOWMULTISELECT)?; + } + + Ok(dialog) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/win_cid/file_dialog/dialog_future.rs b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/file_dialog/dialog_future.rs new file mode 100644 index 00000000000..2ebf72c31f0 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/file_dialog/dialog_future.rs @@ -0,0 +1,52 @@ +use super::super::thread_future::ThreadFuture; +use super::super::utils::init_com; +use super::dialog_ffi::IDialog; + +use crate::file_handle::FileHandle; + +pub fn single_return_future Result + Send + 'static>( + build: F, +) -> ThreadFuture> { + ThreadFuture::new(move |data| { + let ret: Result<(), i32> = (|| { + init_com(|| { + let dialog = build()?; + dialog.show()?; + + let path = dialog.get_result().ok().map(FileHandle::wrap); + *data = Some(path); + + Ok(()) + })? + })(); + + if ret.is_err() { + *data = Some(None); + } + }) +} + +pub fn multiple_return_future Result + Send + 'static>( + build: F, +) -> ThreadFuture>> { + ThreadFuture::new(move |data| { + let ret: Result<(), i32> = (|| { + init_com(|| { + let dialog = build()?; + dialog.show()?; + + let list = dialog + .get_results() + .ok() + .map(|r| r.into_iter().map(FileHandle::wrap).collect()); + *data = Some(list); + + Ok(()) + })? + })(); + + if ret.is_err() { + *data = Some(None); + } + }) +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/win_cid/message_dialog.rs b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/message_dialog.rs new file mode 100644 index 00000000000..4fb24e88996 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/message_dialog.rs @@ -0,0 +1,247 @@ +use super::thread_future::ThreadFuture; +use super::utils::str_to_vec_u16; +use crate::message_dialog::{MessageButtons, MessageDialog, MessageDialogResult, MessageLevel}; + +use windows_sys::Win32::{ + Foundation::HWND, + UI::WindowsAndMessaging::{IDCANCEL, IDNO, IDOK, IDYES}, +}; + +#[cfg(not(feature = "common-controls-v6"))] +use windows_sys::Win32::UI::WindowsAndMessaging::{ + MessageBoxW, MB_ICONERROR, MB_ICONINFORMATION, MB_ICONWARNING, MB_OK, MB_OKCANCEL, MB_YESNO, + MB_YESNOCANCEL, MESSAGEBOX_STYLE, +}; + +use raw_window_handle::RawWindowHandle; + +pub struct WinMessageDialog { + parent: Option, + text: Vec, + caption: Vec, + #[cfg(not(feature = "common-controls-v6"))] + flags: MESSAGEBOX_STYLE, + #[cfg(feature = "common-controls-v6")] + opt: MessageDialog, +} + +// Oh god, I don't like sending RawWindowHandle between threads but here we go anyways... +// fingers crossed +unsafe impl Send for WinMessageDialog {} + +impl WinMessageDialog { + pub fn new(opt: MessageDialog) -> Self { + let text: Vec = str_to_vec_u16(&opt.description); + let caption: Vec = str_to_vec_u16(&opt.title); + + #[cfg(not(feature = "common-controls-v6"))] + let level = match opt.level { + MessageLevel::Info => MB_ICONINFORMATION, + MessageLevel::Warning => MB_ICONWARNING, + MessageLevel::Error => MB_ICONERROR, + }; + + #[cfg(not(feature = "common-controls-v6"))] + let buttons = match opt.buttons { + MessageButtons::Ok | MessageButtons::OkCustom(_) => MB_OK, + MessageButtons::OkCancel | MessageButtons::OkCancelCustom(_, _) => MB_OKCANCEL, + MessageButtons::YesNo => MB_YESNO, + MessageButtons::YesNoCancel | MessageButtons::YesNoCancelCustom(_, _, _) => { + MB_YESNOCANCEL + } + }; + + let parent = match opt.parent { + Some(RawWindowHandle::Win32(handle)) => Some(handle.hwnd.get() as _), + None => None, + _ => unreachable!("unsupported window handle, expected: Windows"), + }; + + Self { + parent, + text, + caption, + #[cfg(not(feature = "common-controls-v6"))] + flags: level | buttons, + #[cfg(feature = "common-controls-v6")] + opt, + } + } + + #[cfg(feature = "common-controls-v6")] + pub fn run(self) -> MessageDialogResult { + use windows_sys::{ + core::BOOL, + Win32::UI::Controls::{ + TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOGCONFIG_0, TASKDIALOGCONFIG_1, + TASKDIALOG_BUTTON, TDCBF_CANCEL_BUTTON, TDCBF_NO_BUTTON, TDCBF_OK_BUTTON, + TDCBF_YES_BUTTON, TDF_ALLOW_DIALOG_CANCELLATION, TDF_SIZE_TO_CONTENT, + TD_ERROR_ICON, TD_INFORMATION_ICON, TD_WARNING_ICON, + }, + }; + + let mut pf_verification_flag_checked = 0; + let mut pn_button = 0; + let mut pn_radio_button = 0; + + const ID_CUSTOM_OK: i32 = 1000; + const ID_CUSTOM_CANCEL: i32 = 1001; + const ID_CUSTOM_YES: i32 = 1004; + const ID_CUSTOM_NO: i32 = 1008; + + let main_icon_ptr = match self.opt.level { + MessageLevel::Warning => TD_WARNING_ICON, + MessageLevel::Error => TD_ERROR_ICON, + MessageLevel::Info => TD_INFORMATION_ICON, + }; + + let (system_buttons, custom_buttons) = match &self.opt.buttons { + MessageButtons::Ok => (TDCBF_OK_BUTTON, vec![]), + MessageButtons::OkCancel => (TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON, vec![]), + MessageButtons::YesNo => (TDCBF_YES_BUTTON | TDCBF_NO_BUTTON, vec![]), + MessageButtons::YesNoCancel => ( + TDCBF_YES_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON, + vec![], + ), + MessageButtons::OkCustom(ok_text) => ( + Default::default(), + vec![(ID_CUSTOM_OK, str_to_vec_u16(ok_text))], + ), + MessageButtons::OkCancelCustom(ok_text, cancel_text) => ( + Default::default(), + vec![ + (ID_CUSTOM_OK, str_to_vec_u16(ok_text)), + (ID_CUSTOM_CANCEL, str_to_vec_u16(cancel_text)), + ], + ), + MessageButtons::YesNoCancelCustom(yes_text, no_text, cancel_text) => ( + Default::default(), + vec![ + (ID_CUSTOM_YES, str_to_vec_u16(yes_text)), + (ID_CUSTOM_NO, str_to_vec_u16(no_text)), + (ID_CUSTOM_CANCEL, str_to_vec_u16(cancel_text)), + ], + ), + }; + + let p_buttons = custom_buttons + .iter() + .map(|(id, text)| TASKDIALOG_BUTTON { + nButtonID: *id, + pszButtonText: text.as_ptr(), + }) + .collect::>(); + + let task_dialog_config = TASKDIALOGCONFIG { + cbSize: core::mem::size_of::() as u32, + hwndParent: self.parent.unwrap_or(std::ptr::null_mut()), + dwFlags: TDF_ALLOW_DIALOG_CANCELLATION | TDF_SIZE_TO_CONTENT, + pszWindowTitle: self.caption.as_ptr(), + pszContent: self.text.as_ptr(), + Anonymous1: TASKDIALOGCONFIG_0 { + pszMainIcon: main_icon_ptr, + }, + Anonymous2: TASKDIALOGCONFIG_1 { + pszFooterIcon: std::ptr::null(), + }, + dwCommonButtons: system_buttons, + pButtons: p_buttons.as_ptr(), + cButtons: custom_buttons.len() as u32, + pRadioButtons: std::ptr::null(), + cRadioButtons: 0, + cxWidth: 0, + hInstance: std::ptr::null_mut(), + pfCallback: None, + lpCallbackData: 0, + nDefaultButton: 0, + nDefaultRadioButton: 0, + pszCollapsedControlText: std::ptr::null(), + pszExpandedControlText: std::ptr::null(), + pszExpandedInformation: std::ptr::null(), + pszMainInstruction: std::ptr::null(), + pszVerificationText: std::ptr::null(), + pszFooter: std::ptr::null(), + }; + + let ret = unsafe { + TaskDialogIndirect( + &task_dialog_config, + &mut pn_button as *mut i32, + &mut pn_radio_button as *mut i32, + &mut pf_verification_flag_checked as *mut BOOL, + ) + }; + + if ret != 0 { + return MessageDialogResult::Cancel; + } + + match pn_button { + IDOK => MessageDialogResult::Ok, + IDYES => MessageDialogResult::Yes, + IDCANCEL => MessageDialogResult::Cancel, + IDNO => MessageDialogResult::No, + custom => match self.opt.buttons { + MessageButtons::OkCustom(ok_text) => match custom { + ID_CUSTOM_OK => MessageDialogResult::Custom(ok_text), + _ => MessageDialogResult::Cancel, + }, + MessageButtons::OkCancelCustom(ok_text, cancel_text) => match custom { + ID_CUSTOM_OK => MessageDialogResult::Custom(ok_text), + ID_CUSTOM_CANCEL => MessageDialogResult::Custom(cancel_text), + _ => MessageDialogResult::Cancel, + }, + MessageButtons::YesNoCancelCustom(yes_text, no_text, cancel_text) => match custom { + ID_CUSTOM_YES => MessageDialogResult::Custom(yes_text), + ID_CUSTOM_NO => MessageDialogResult::Custom(no_text), + ID_CUSTOM_CANCEL => MessageDialogResult::Custom(cancel_text), + _ => MessageDialogResult::Cancel, + }, + _ => MessageDialogResult::Cancel, + }, + } + } + + #[cfg(not(feature = "common-controls-v6"))] + pub fn run(self) -> MessageDialogResult { + let ret = unsafe { + MessageBoxW( + self.parent.unwrap_or(std::ptr::null_mut()), + self.text.as_ptr(), + self.caption.as_ptr(), + self.flags, + ) + }; + + match ret { + IDOK => MessageDialogResult::Ok, + IDYES => MessageDialogResult::Yes, + IDCANCEL => MessageDialogResult::Cancel, + IDNO => MessageDialogResult::No, + _ => MessageDialogResult::Cancel, + } + } + + pub fn run_async(self) -> ThreadFuture { + ThreadFuture::new(move |data| *data = Some(self.run())) + } +} + +use crate::backend::MessageDialogImpl; + +impl MessageDialogImpl for MessageDialog { + fn show(self) -> MessageDialogResult { + let dialog = WinMessageDialog::new(self); + dialog.run() + } +} + +use crate::backend::AsyncMessageDialogImpl; +use crate::backend::DialogFutureType; + +impl AsyncMessageDialogImpl for MessageDialog { + fn show_async(self) -> DialogFutureType { + let dialog = WinMessageDialog::new(self); + Box::pin(dialog.run_async()) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/win_cid/thread_future.rs b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/thread_future.rs new file mode 100644 index 00000000000..fd149b4ffc9 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/thread_future.rs @@ -0,0 +1,63 @@ +use std::pin::Pin; +use std::sync::{Arc, Mutex, TryLockError}; + +use std::task::{Context, Poll, Waker}; + +struct FutureState { + waker: Mutex>, + data: Mutex>, +} + +pub struct ThreadFuture { + state: Arc>, +} + +unsafe impl Send for ThreadFuture {} + +impl ThreadFuture { + pub fn new) + Send + 'static>(f: F) -> Self { + let state = Arc::new(FutureState { + waker: Mutex::new(None), + data: Mutex::new(None), + }); + + { + let state = state.clone(); + std::thread::spawn(move || { + f(&mut state.data.lock().unwrap()); + + if let Some(waker) = state.waker.lock().unwrap().take() { + waker.wake(); + } + }); + } + + Self { state } + } +} + +impl std::future::Future for ThreadFuture { + type Output = R; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let state = &self.state; + let data = state.data.try_lock(); + + match data { + Ok(mut data) => match data.take() { + Some(data) => Poll::Ready(data), + None => { + *state.waker.lock().unwrap() = Some(cx.waker().clone()); + Poll::Pending + } + }, + Err(TryLockError::Poisoned(err)) => { + panic!("{}", err); + } + Err(TryLockError::WouldBlock) => { + *state.waker.lock().unwrap() = Some(cx.waker().clone()); + Poll::Pending + } + } + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/win_cid/utils.rs b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/utils.rs new file mode 100644 index 00000000000..222a2cc3096 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/win_cid/utils.rs @@ -0,0 +1,35 @@ +use windows_sys::{ + core::HRESULT, + Win32::System::Com::{ + CoInitializeEx, CoUninitialize, COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE, + }, +}; + +#[inline] +pub(crate) fn str_to_vec_u16(s: &str) -> Vec { + let mut v: Vec<_> = s.encode_utf16().collect(); + v.push(0); + v +} + +/// Makes sure that COM lib is initialized long enough +pub fn init_com T>(f: F) -> Result { + let res = unsafe { + CoInitializeEx( + std::ptr::null(), + (COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE) as u32, + ) + }; + + if res < 0 { + return Err(res); + } + + let out = f(); + + unsafe { + CoUninitialize(); + } + + Ok(out) +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/win_xp.rs.old b/desktop/src-tauri/vendor/rfd/src/backend/win_xp.rs.old new file mode 100644 index 00000000000..15a12e2a367 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/win_xp.rs.old @@ -0,0 +1,164 @@ +//! Windows commdlg.h dialogs +//! Win32 XP +use crate::DialogOptions; + +use std::path::PathBuf; + +use winapi::um::commdlg::{ + GetOpenFileNameW, GetSaveFileNameW, OFN_ALLOWMULTISELECT, OFN_EXPLORER, OFN_FILEMUSTEXIST, + OFN_NOCHANGEDIR, OFN_OVERWRITEPROMPT, OFN_PATHMUSTEXIST, +}; + +extern "C" { + fn wcslen(buf: *const u16) -> usize; +} + +mod utils { + use crate::DialogOptions; + + use std::{ffi::OsStr, iter::once, mem, os::windows::ffi::OsStrExt}; + + use winapi::um::commdlg::OPENFILENAMEW; + + pub unsafe fn build_ofn( + path: &mut Vec, + filters: Option<&Vec>, + flags: u32, + ) -> OPENFILENAMEW { + let mut ofn: OPENFILENAMEW = std::mem::zeroed(); + ofn.lStructSize = mem::size_of::() as u32; + ofn.hwndOwner = std::mem::zeroed(); + + ofn.lpstrFile = path.as_mut_ptr(); + ofn.nMaxFile = path.len() as _; + + if let Some(filters) = filters { + ofn.lpstrFilter = filters.as_ptr(); + ofn.nFilterIndex = 1; + } + + ofn.Flags = flags; + + ofn + } + + pub fn build_filters(params: &DialogParams) -> Option> { + let mut filters = String::new(); + + for f in params.filters.iter() { + filters += &format!("{}\0{}\0", f.0, f.1); + } + + let filter: Option> = if !params.filters.is_empty() { + Some(OsStr::new(&filters).encode_wide().chain(once(0)).collect()) + } else { + None + }; + + filter + } +} + +use utils::*; + +pub fn open_file_with_params(params: DialogOptions) -> Option { + let filters = build_filters(¶ms); + + unsafe { + // This vec needs to be initialized with zeros, so we do not use `Vec::with_capacity` here + let mut path: Vec = vec![0; 260]; + + let flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; + + let mut ofn = build_ofn(&mut path, filters.as_ref(), flags); + let out = GetOpenFileNameW(&mut ofn); + + if out == 1 { + let l = wcslen(ofn.lpstrFile); + + // Trim string + path.set_len(l); + + String::from_utf16(&path).ok().map(PathBuf::from) + } else { + None + } + } +} + +pub fn save_file_with_params(params: DialogOptions) -> Option { + let filters = build_filters(¶ms); + + unsafe { + // This vec needs to be initialized with zeros, so we do not use `Vec::with_capacity` here + let mut path: Vec = vec![0; 260]; + + let flags = OFN_EXPLORER + | OFN_OVERWRITEPROMPT + | OFN_PATHMUSTEXIST + | OFN_FILEMUSTEXIST + | OFN_NOCHANGEDIR; + + let mut ofn = build_ofn(&mut path, filters.as_ref(), flags); + let out = GetSaveFileNameW(&mut ofn); + + if out == 1 { + let l = wcslen(ofn.lpstrFile); + // Trim string + path.set_len(l); + + String::from_utf16(&path).ok().map(PathBuf::from) + } else { + None + } + } +} + +pub fn pick_folder_with_params(params: DialogOptions) -> Option { + unimplemented!("pick_folder"); +} + +pub fn open_multiple_files_with_params(params: DialogOptions) -> Option> { + let filters = build_filters(¶ms); + + unsafe { + // This vec needs to be initialized with zeros, so we do not use `Vec::with_capacity` here + let mut path: Vec = vec![0; 260]; + + let flags = OFN_EXPLORER + | OFN_ALLOWMULTISELECT + | OFN_PATHMUSTEXIST + | OFN_FILEMUSTEXIST + | OFN_NOCHANGEDIR; + + let mut ofn = build_ofn(&mut path, filters.as_ref(), flags); + let out = GetOpenFileNameW(&mut ofn); + + if out == 1 { + String::from_utf16(&path).ok().map(|s| { + let mut res = Vec::new(); + + let split = s.split("\u{0}"); + for elm in split { + if !elm.is_empty() { + res.push(PathBuf::from(elm)); + } else { + break; + } + } + + if res.len() == 1 { + // 0th element is path to a files + res + } else { + // 0th element is base path of all files + let dir = res.remove(0); + // Add base path to all files + res.into_iter().map(|i| dir.clone().join(i)).collect() + } + }) + } else { + None + } + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/backend/xdg_desktop_portal.rs b/desktop/src-tauri/vendor/rfd/src/backend/xdg_desktop_portal.rs new file mode 100644 index 00000000000..16f0ae2fff9 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/backend/xdg_desktop_portal.rs @@ -0,0 +1,324 @@ +use std::path::PathBuf; + +use super::linux::zenity; +use crate::backend::DialogFutureType; +use crate::file_dialog::Filter; +use crate::message_dialog::MessageDialog; +use crate::{FileDialog, FileHandle, MessageButtons, MessageDialogResult}; + +use ashpd::desktop::file_chooser::{FileFilter, OpenFileRequest, SaveFileRequest}; +use ashpd::WindowIdentifier; + +use log::error; +use pollster::block_on; +use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; + +fn to_window_identifier( + window: Option, + display: Option, +) -> Option { + window.map(|window| { + block_on(Box::pin(async move { + WindowIdentifier::from_raw_handle(&window, display.as_ref()).await + })) + })? +} + +impl From<&Filter> for FileFilter { + fn from(filter: &Filter) -> Self { + let mut ashpd_filter = FileFilter::new(&filter.name); + for file_extension in &filter.extensions { + if file_extension == "*" || file_extension.is_empty() { + ashpd_filter = ashpd_filter.glob("*"); + } else { + ashpd_filter = ashpd_filter.glob(&format!("*.{file_extension}")); + } + } + ashpd_filter + } +} + +// +// File Picker +// + +use crate::backend::FilePickerDialogImpl; +impl FilePickerDialogImpl for FileDialog { + fn pick_file(self) -> Option { + block_on(self.pick_file_async()).map(PathBuf::from) + } + + fn pick_files(self) -> Option> { + block_on(self.pick_files_async()) + .map(|vec_file_handle| vec_file_handle.iter().map(PathBuf::from).collect()) + } +} + +use crate::backend::AsyncFilePickerDialogImpl; +impl AsyncFilePickerDialogImpl for FileDialog { + fn pick_file_async(self) -> DialogFutureType> { + Box::pin(async move { + let res = OpenFileRequest::default() + .identifier(to_window_identifier(self.parent, self.parent_display)) + .multiple(false) + .title(self.title.as_deref().or(None)) + .filters(self.filters.iter().map(From::from)) + .current_folder::<&PathBuf>(&self.starting_directory) + .expect("File path should not be nul-terminated") + .send() + .await; + + if let Err(err) = res { + error!("Failed to pick file: {err}"); + match zenity::pick_file(&self).await { + Ok(res) => res, + Err(err) => { + error!("Failed to pick file with zenity: {err}"); + return None; + } + } + } else { + res.ok() + .and_then(|request| request.response().ok()) + .and_then(|response| { + response + .uris() + .first() + .and_then(|uri| uri.to_file_path().ok()) + }) + } + .map(FileHandle::from) + }) + } + + fn pick_files_async(self) -> DialogFutureType>> { + Box::pin(async move { + let res = OpenFileRequest::default() + .identifier(to_window_identifier(self.parent, self.parent_display)) + .multiple(true) + .title(self.title.as_deref().or(None)) + .filters(self.filters.iter().map(From::from)) + .current_folder::<&PathBuf>(&self.starting_directory) + .expect("File path should not be nul-terminated") + .send() + .await; + + if let Err(err) = res { + error!("Failed to pick files: {err}"); + match zenity::pick_files(&self).await { + Ok(res) => Some(res.into_iter().map(FileHandle::from).collect::>()), + Err(err) => { + error!("Failed to pick files with zenity: {err}"); + None + } + } + } else { + res.ok() + .and_then(|request| request.response().ok()) + .map(|response| { + response + .uris() + .iter() + .filter_map(|uri| uri.to_file_path().ok()) + .map(FileHandle::from) + .collect::>() + }) + } + }) + } +} + +// +// Folder Picker +// + +use crate::backend::FolderPickerDialogImpl; +impl FolderPickerDialogImpl for FileDialog { + fn pick_folder(self) -> Option { + block_on(self.pick_folder_async()).map(PathBuf::from) + } + + fn pick_folders(self) -> Option> { + block_on(self.pick_folders_async()) + .map(|vec_file_handle| vec_file_handle.iter().map(PathBuf::from).collect()) + } +} + +use crate::backend::AsyncFolderPickerDialogImpl; +impl AsyncFolderPickerDialogImpl for FileDialog { + fn pick_folder_async(self) -> DialogFutureType> { + Box::pin(async move { + let res = OpenFileRequest::default() + .identifier(to_window_identifier(self.parent, self.parent_display)) + .multiple(false) + .directory(true) + .title(self.title.as_deref().or(None)) + .filters(self.filters.iter().map(From::from)) + .current_folder::<&PathBuf>(&self.starting_directory) + .expect("File path should not be nul-terminated") + .send() + .await; + + if let Err(err) = res { + error!("Failed to pick folder: {err}"); + match zenity::pick_folder(&self).await { + Ok(res) => res, + Err(err) => { + error!("Failed to pick folder with zenity: {err}"); + return None; + } + } + } else { + res.ok() + .and_then(|request| request.response().ok()) + .and_then(|response| { + response + .uris() + .first() + .and_then(|uri| uri.to_file_path().ok()) + }) + } + .map(FileHandle::from) + }) + } + + fn pick_folders_async(self) -> DialogFutureType>> { + Box::pin(async move { + let res = OpenFileRequest::default() + .identifier(to_window_identifier(self.parent, self.parent_display)) + .multiple(true) + .directory(true) + .title(self.title.as_deref().or(None)) + .filters(self.filters.iter().map(From::from)) + .current_folder::<&PathBuf>(&self.starting_directory) + .expect("File path should not be nul-terminated") + .send() + .await; + + if let Err(err) = res { + error!("Failed to pick folders: {err}"); + match zenity::pick_folders(&self).await { + Ok(res) => Some(res.into_iter().map(FileHandle::from).collect::>()), + Err(err) => { + error!("Failed to pick folders with zenity: {err}"); + None + } + } + } else { + res.ok() + .and_then(|request| request.response().ok()) + .map(|response| { + response + .uris() + .iter() + .filter_map(|uri| uri.to_file_path().ok()) + .map(FileHandle::from) + .collect::>() + }) + } + }) + } +} + +// +// File Save +// + +use crate::backend::FileSaveDialogImpl; +impl FileSaveDialogImpl for FileDialog { + fn save_file(self) -> Option { + block_on(self.save_file_async()).map(PathBuf::from) + } +} + +use crate::backend::AsyncFileSaveDialogImpl; +impl AsyncFileSaveDialogImpl for FileDialog { + fn save_file_async(self) -> DialogFutureType> { + Box::pin(async move { + let res = SaveFileRequest::default() + .identifier(to_window_identifier(self.parent, self.parent_display)) + .title(self.title.as_deref().or(None)) + .current_name(self.file_name.as_deref()) + .filters(self.filters.iter().map(From::from)) + .current_folder::<&PathBuf>(&self.starting_directory) + .expect("File path should not be nul-terminated") + .send() + .await; + + if let Err(err) = res { + error!("Failed to save file: {err}"); + match zenity::save_file(&self).await { + Ok(res) => res, + Err(err) => { + error!("Failed to save file with zenity: {err}"); + return None; + } + } + } else { + res.ok() + .and_then(|request| request.response().ok()) + .and_then(|response| { + response + .uris() + .first() + .and_then(|uri| uri.to_file_path().ok()) + }) + } + .map(FileHandle::from) + }) + } +} + +use crate::backend::MessageDialogImpl; +impl MessageDialogImpl for MessageDialog { + fn show(self) -> MessageDialogResult { + block_on(self.show_async()) + } +} + +use crate::backend::AsyncMessageDialogImpl; +impl AsyncMessageDialogImpl for MessageDialog { + fn show_async(self) -> DialogFutureType { + Box::pin(async move { + match &self.buttons { + MessageButtons::Ok | MessageButtons::OkCustom(_) => { + let res = crate::backend::linux::zenity::message( + &self.level, + &self.buttons, + &self.title, + &self.description, + ) + .await; + + match res { + Ok(res) => res, + Err(err) => { + error!("Failed to open zenity dialog: {err}"); + MessageDialogResult::Cancel + } + } + } + MessageButtons::OkCancel + | MessageButtons::YesNo + | MessageButtons::OkCancelCustom(..) + | MessageButtons::YesNoCancel + | MessageButtons::YesNoCancelCustom(..) => { + let res = crate::backend::linux::zenity::question( + &self.buttons, + &self.title, + &self.description, + ) + .await; + + match res { + Ok(res) => res, + Err(err) => { + error!("Failed to open zenity dialog: {err}"); + MessageDialogResult::Cancel + } + } + } + } + }) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/file_dialog.rs b/desktop/src-tauri/vendor/rfd/src/file_dialog.rs new file mode 100644 index 00000000000..4d6fdf5a0d7 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/file_dialog.rs @@ -0,0 +1,294 @@ +use crate::FileHandle; + +use std::path::Path; +use std::path::PathBuf; + +use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; + +#[derive(Debug, Clone)] +pub(crate) struct Filter { + #[allow(dead_code)] + pub name: String, + pub extensions: Vec, +} + +/// Synchronous File Dialog. Supported platforms: +/// * Linux +/// * Windows +/// * Mac +#[derive(Default, Debug, Clone)] +pub struct FileDialog { + pub(crate) filters: Vec, + pub(crate) starting_directory: Option, + pub(crate) file_name: Option, + pub(crate) title: Option, + pub(crate) parent: Option, + pub(crate) parent_display: Option, + pub(crate) can_create_directories: Option, +} + +// Oh god, I don't like sending RawWindowHandle between threads but here we go anyways... +// fingers crossed +unsafe impl Send for FileDialog {} +unsafe impl Sync for FileDialog {} + +impl FileDialog { + /// New file dialog builder + #[cfg(not(target_arch = "wasm32"))] + pub fn new() -> Self { + Default::default() + } + + /// Add file extension filter. + /// + /// Takes in the name of the filter, and list of extensions + /// + /// The name of the filter will be displayed on supported platforms: + /// * Windows + /// * Linux + /// + /// On platforms that don't support filter names, all filters will be merged into one filter + pub fn add_filter(mut self, name: impl Into, extensions: &[impl ToString]) -> Self { + self.filters.push(Filter { + name: name.into(), + extensions: extensions.iter().map(|e| e.to_string()).collect(), + }); + self + } + + /// Set starting directory of the dialog. Supported platforms: + /// * Linux + /// * Windows + /// * Mac + pub fn set_directory>(mut self, path: P) -> Self { + let path = path.as_ref(); + if path.to_str().map(|p| p.is_empty()).unwrap_or(false) { + self.starting_directory = None; + } else { + self.starting_directory = Some(path.into()); + } + self + } + + /// Set starting file name of the dialog. Supported platforms: + /// * Windows + /// * Linux + /// * Mac + pub fn set_file_name(mut self, file_name: impl Into) -> Self { + self.file_name = Some(file_name.into()); + self + } + + /// Set the title of the dialog. Supported platforms: + /// * Windows + /// * Linux + /// * Mac + pub fn set_title(mut self, title: impl Into) -> Self { + self.title = Some(title.into()); + self + } + + /// Set parent windows explicitly (optional). + /// Supported platforms: + /// * Windows + /// * Mac + /// * Linux (XDG only) + pub fn set_parent( + mut self, + parent: &W, + ) -> Self { + self.parent = parent.window_handle().ok().map(|x| x.as_raw()); + self.parent_display = parent.display_handle().ok().map(|x| x.as_raw()); + self + } + + /// Set can create directories in the dialog. + /// Supported in: `macos`. + pub fn set_can_create_directories(mut self, can: bool) -> Self { + self.can_create_directories.replace(can); + self + } +} + +#[cfg(not(target_arch = "wasm32"))] +use crate::backend::{FilePickerDialogImpl, FileSaveDialogImpl, FolderPickerDialogImpl}; + +#[cfg(not(target_arch = "wasm32"))] +impl FileDialog { + /// Pick one file + pub fn pick_file(self) -> Option { + FilePickerDialogImpl::pick_file(self) + } + + /// Pick multiple files + pub fn pick_files(self) -> Option> { + FilePickerDialogImpl::pick_files(self) + } + + /// Pick one folder + pub fn pick_folder(self) -> Option { + FolderPickerDialogImpl::pick_folder(self) + } + + /// Pick multiple folders + pub fn pick_folders(self) -> Option> { + FolderPickerDialogImpl::pick_folders(self) + } + + /// Opens save file dialog + /// + /// #### Platform specific notes regarding save dialog filters: + /// - On macOS + /// - If filter is set, all files will be grayed out (no matter the extension sadly) + /// - If user does not type an extension MacOs will append first available extension from filters list + /// - If user types in filename with extension MacOs will check if it exists in filters list, if not it will display appropriate message + /// - On GTK + /// - It only filters which already existing files get shown to the user + /// - It does not append extensions automatically + /// - It does not prevent users from adding any unsupported extension + /// - On Win: + /// - If no extension was provided it will just add currently selected one + /// - If selected extension was typed in by the user it will just return + /// - If unselected extension was provided it will append selected one at the end, example: `test.png.txt` + pub fn save_file(self) -> Option { + FileSaveDialogImpl::save_file(self) + } +} + +/// Asynchronous File Dialog. Supported platforms: +/// * Linux +/// * Windows +/// * Mac +/// * WASM32 +#[derive(Default, Debug, Clone)] +pub struct AsyncFileDialog { + file_dialog: FileDialog, +} + +impl AsyncFileDialog { + /// New file dialog builder + pub fn new() -> Self { + Default::default() + } + + /// Add file extension filter. + /// + /// Takes in the name of the filter, and list of extensions + /// + /// The name of the filter will be displayed on supported platforms: + /// * Windows + /// * Linux + /// + /// On platforms that don't support filter names, all filters will be merged into one filter + pub fn add_filter(mut self, name: impl Into, extensions: &[impl ToString]) -> Self { + self.file_dialog = self.file_dialog.add_filter(name, extensions); + self + } + + /// Set starting directory of the dialog. Supported platforms: + /// * Linux + /// * Windows + /// * Mac + pub fn set_directory>(mut self, path: P) -> Self { + self.file_dialog = self.file_dialog.set_directory(path); + self + } + + /// Set starting file name of the dialog. Supported platforms: + /// * Windows + /// * Linux + /// * Mac + /// * WASM32 (otherwise defaults to a random string with no extension) + pub fn set_file_name(mut self, file_name: impl Into) -> Self { + self.file_dialog = self.file_dialog.set_file_name(file_name); + self + } + + /// Set the title of the dialog. Supported platforms: + /// * Windows + /// * Linux + /// * Mac + /// * WASM32 + pub fn set_title(mut self, title: impl Into) -> Self { + self.file_dialog = self.file_dialog.set_title(title); + self + } + + /// Set parent windows explicitly (optional). + /// Supported platforms: + /// * Windows + /// * Mac + /// * Linux (XDG only) + pub fn set_parent( + mut self, + parent: &W, + ) -> Self { + self.file_dialog = self.file_dialog.set_parent(parent); + self + } + + /// Set can create directories in the dialog. + /// Supported in: `macos`. + pub fn set_can_create_directories(mut self, can: bool) -> Self { + self.file_dialog = self.file_dialog.set_can_create_directories(can); + self + } +} + +use crate::backend::AsyncFilePickerDialogImpl; +use crate::backend::AsyncFileSaveDialogImpl; +#[cfg(not(target_arch = "wasm32"))] +use crate::backend::AsyncFolderPickerDialogImpl; + +use std::future::Future; + +impl AsyncFileDialog { + /// Pick one file + pub fn pick_file(self) -> impl Future> { + AsyncFilePickerDialogImpl::pick_file_async(self.file_dialog) + } + + /// Pick multiple files + pub fn pick_files(self) -> impl Future>> { + AsyncFilePickerDialogImpl::pick_files_async(self.file_dialog) + } + + #[cfg(not(target_arch = "wasm32"))] + /// Pick one folder + /// + /// Does not exist in `WASM32` + pub fn pick_folder(self) -> impl Future> { + AsyncFolderPickerDialogImpl::pick_folder_async(self.file_dialog) + } + + #[cfg(not(target_arch = "wasm32"))] + /// Pick multiple folders + /// + /// Does not exist in `WASM32` + pub fn pick_folders(self) -> impl Future>> { + AsyncFolderPickerDialogImpl::pick_folders_async(self.file_dialog) + } + + /// Opens save file dialog + /// + /// #### Platform specific notes regarding save dialog filters: + /// - On MacOs + /// - If filter is set, all files will be grayed out (no matter the extension sadly) + /// - If user does not type an extension MacOs will append first available extension from filters list + /// - If user types in filename with extension MacOs will check if it exists in filters list, if not it will display appropriate message + /// - On GTK + /// - It only filters which already existing files get shown to the user + /// - It does not append extensions automatically + /// - It does not prevent users from adding any unsupported extension + /// - On Win: + /// - If no extension was provided it will just add currently selected one + /// - If selected extension was typed in by the user it will just return + /// - If unselected extension was provided it will append selected one at the end, example: `test.png.txt` + /// - On Wasm32: + /// - No filtering is applied. + /// - `save_file` returns immediately without a dialog prompt. + /// Instead the user is prompted by their browser on where to save the file when [`FileHandle::write`] is used. + pub fn save_file(self) -> impl Future> { + AsyncFileSaveDialogImpl::save_file_async(self.file_dialog) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/file_handle/mod.rs b/desktop/src-tauri/vendor/rfd/src/file_handle/mod.rs new file mode 100644 index 00000000000..f7e4b9e063d --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/file_handle/mod.rs @@ -0,0 +1,33 @@ +//! FileHandle is a way of abstracting over a file returned by a dialog +//! +//! On native targets it just wraps a path of a file. +//! In web browsers it wraps `File` js object +//! +//! It should allow a user to treat web browser files same way as native files + +#[cfg(not(target_arch = "wasm32"))] +mod native; +#[cfg(not(target_arch = "wasm32"))] +pub use native::FileHandle; + +#[cfg(target_arch = "wasm32")] +mod web; +#[cfg(target_arch = "wasm32")] +pub use web::FileHandle; +#[cfg(target_arch = "wasm32")] +pub(crate) use web::WasmFileHandleKind; + +#[cfg(test)] +mod tests { + use super::FileHandle; + + #[test] + fn fn_def_check() { + let _ = FileHandle::wrap; + let _ = FileHandle::read; + #[cfg(feature = "file-handle-inner")] + let _ = FileHandle::inner; + #[cfg(not(target_arch = "wasm32"))] + let _ = FileHandle::path; + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/file_handle/native.rs b/desktop/src-tauri/vendor/rfd/src/file_handle/native.rs new file mode 100644 index 00000000000..79fa179cf5f --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/file_handle/native.rs @@ -0,0 +1,195 @@ +use std::{ + future::Future, + path::{Path, PathBuf}, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Waker}, +}; + +#[derive(Default)] +struct ReaderState { + res: Option>>, + waker: Option, +} + +struct Reader { + state: Arc>, +} + +impl Reader { + fn new(path: &Path) -> Self { + let state: Arc> = Arc::new(Mutex::new(Default::default())); + + { + let path = path.to_owned(); + let state = state.clone(); + std::thread::Builder::new() + .name("rfd_file_read".into()) + .spawn(move || { + let res = std::fs::read(path); + + let mut state = state.lock().unwrap(); + state.res.replace(res); + + if let Some(waker) = state.waker.take() { + waker.wake(); + } + }) + .unwrap(); + } + + Self { state } + } +} + +impl Future for Reader { + type Output = Vec; + + fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { + let mut state = self.state.lock().unwrap(); + if let Some(res) = state.res.take() { + Poll::Ready(res.unwrap()) + } else { + state.waker.replace(ctx.waker().clone()); + Poll::Pending + } + } +} + +struct WriterState { + waker: Option, + res: Option>, +} + +struct Writer { + state: Arc>, +} + +impl Writer { + fn new(path: &Path, bytes: &[u8]) -> Self { + let state = Arc::new(Mutex::new(WriterState { + waker: None, + res: None, + })); + + { + let path = path.to_owned(); + let bytes = bytes.to_owned(); + let state = state.clone(); + std::thread::Builder::new() + .name("rfd_file_write".into()) + .spawn(move || { + let res = std::fs::write(path, bytes); + + let mut state = state.lock().unwrap(); + state.res.replace(res); + + if let Some(waker) = state.waker.take() { + waker.wake(); + } + }) + .unwrap(); + } + + Self { state } + } +} + +impl Future for Writer { + type Output = std::io::Result<()>; + + fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { + let mut state = self.state.lock().unwrap(); + if let Some(res) = state.res.take() { + Poll::Ready(res) + } else { + state.waker.replace(ctx.waker().clone()); + Poll::Pending + } + } +} + +/// FileHandle is a way of abstracting over a file returned by a dialog +#[derive(Clone)] +pub struct FileHandle(PathBuf); + +impl FileHandle { + /// On native platforms it wraps path. + /// + /// On `WASM32` it wraps JS `File` object. + pub(crate) fn wrap(path_buf: PathBuf) -> Self { + Self(path_buf) + } + + /// Get name of a file + pub fn file_name(&self) -> String { + self.0 + .file_name() + .and_then(|f| f.to_str()) + .map(|f| f.to_string()) + .unwrap_or_default() + } + + /// Gets path to a file. + /// + /// Does not exist in `WASM32` + pub fn path(&self) -> &Path { + &self.0 + } + + /// Reads a file asynchronously. + /// + /// On native platforms it spawns a `std::thread` in the background. + /// + /// `This fn exists solely to keep native api in pair with async only web api.` + pub async fn read(&self) -> Vec { + Reader::new(&self.0).await + } + + /// Writes a file asynchronously. + /// + /// On native platforms it spawns a `std::thread` in the background. + /// + /// `This fn exists solely to keep native api in pair with async only web api.` + pub async fn write(&self, data: &[u8]) -> std::io::Result<()> { + Writer::new(&self.0, data).await + } + + /// Unwraps a `FileHandle` and returns inner type. + /// + /// It should be used, if user wants to handle file read themselves + /// + /// On native platforms returns path. + /// + /// On `WASM32` it returns JS `File` object. + /// + /// #### Behind a `file-handle-inner` feature flag + #[cfg(feature = "file-handle-inner")] + pub fn inner(&self) -> &Path { + &self.0 + } +} + +impl std::fmt::Debug for FileHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.path()) + } +} + +impl From for FileHandle { + fn from(path: PathBuf) -> Self { + Self::wrap(path) + } +} + +impl From for PathBuf { + fn from(file_handle: FileHandle) -> Self { + file_handle.0 + } +} + +impl From<&FileHandle> for PathBuf { + fn from(file_handle: &FileHandle) -> Self { + PathBuf::from(file_handle.path()) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/file_handle/web.rs b/desktop/src-tauri/vendor/rfd/src/file_handle/web.rs new file mode 100644 index 00000000000..371a4c8e235 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/file_handle/web.rs @@ -0,0 +1,85 @@ +use crate::file_dialog::FileDialog; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +#[derive(Clone, Debug)] +pub(crate) enum WasmFileHandleKind { + Readable(web_sys::File), + Writable(FileDialog), +} + +#[derive(Clone)] +pub struct FileHandle(pub(crate) WasmFileHandleKind); + +impl FileHandle { + /// Wrap a [`web_sys::File`] for reading. Use with [`FileHandle::read`] + pub(crate) fn wrap(file: web_sys::File) -> Self { + Self(WasmFileHandleKind::Readable(file)) + } + + /// Create a dummy `FileHandle`. Use with [`FileHandle::write`]. + pub(crate) fn writable(dialog: FileDialog) -> Self { + FileHandle(WasmFileHandleKind::Writable(dialog)) + } + + pub fn file_name(&self) -> String { + match &self.0 { + WasmFileHandleKind::Readable(x) => x.name(), + WasmFileHandleKind::Writable(x) => x.file_name.clone().unwrap_or_default(), + } + } + + // Path is not supported in browsers. + // Use read() instead. + // pub fn path(&self) -> &Path { + // // compile_error!(); + // unimplemented!("Path is not supported in browsers"); + // } + + pub async fn read(&self) -> Vec { + let promise = js_sys::Promise::new(&mut move |res, _rej| { + let file_reader = web_sys::FileReader::new().unwrap(); + + let fr = file_reader.clone(); + let closure = Closure::wrap(Box::new(move || { + res.call1(&JsValue::undefined(), &fr.result().unwrap()) + .unwrap(); + }) as Box); + + file_reader.set_onload(Some(closure.as_ref().unchecked_ref())); + + closure.forget(); + + if let WasmFileHandleKind::Readable(reader) = &self.0 { + file_reader.read_as_array_buffer(reader).unwrap(); + } else { + panic!("This File Handle doesn't support reading. Use `pick_file` to get a readable FileHandle"); + } + }); + + let future = wasm_bindgen_futures::JsFuture::from(promise); + + let res = future.await.unwrap(); + + let buffer: js_sys::Uint8Array = js_sys::Uint8Array::new(&res); + let mut vec = vec![0; buffer.length() as usize]; + buffer.copy_to(&mut vec[..]); + + vec + } + + #[cfg(feature = "file-handle-inner")] + pub fn inner(&self) -> &web_sys::File { + if let WasmFileHandleKind::Readable(reader) = &self.0 { + reader + } else { + panic!("This File Handle doesn't support reading. Use `pick_file` to get a readable FileHandle"); + } + } +} + +impl std::fmt::Debug for FileHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.file_name()) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/lib.rs b/desktop/src-tauri/vendor/rfd/src/lib.rs new file mode 100644 index 00000000000..b0d6fe70829 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/lib.rs @@ -0,0 +1,147 @@ +//! Rusty File Dialogs is a cross platform library for using native file open/save dialogs. +//! It provides both asynchronous and synchronous APIs. Supported platforms: +//! +//! * Windows +//! * macOS +//! * Linux & BSDs (GTK3 or XDG Desktop Portal) +//! * WASM32 (async only) +//! +//! # Examples +//! +//! ## Synchronous +//! ```no_run +//! use rfd::FileDialog; +//! +//! let files = FileDialog::new() +//! .add_filter("text", &["txt", "rs"]) +//! .add_filter("rust", &["rs", "toml"]) +//! .set_directory("/") +//! .pick_file(); +//! ``` +//! +//! ## Asynchronous +//! ```no_run +//! use rfd::AsyncFileDialog; +//! +//! let future = async { +//! let file = AsyncFileDialog::new() +//! .add_filter("text", &["txt", "rs"]) +//! .add_filter("rust", &["rs", "toml"]) +//! .set_directory("/") +//! .pick_file() +//! .await; +//! +//! let data = file.unwrap().read().await; +//! }; +//! ``` +//! +//! # Linux & BSD backends +//! +//! On Linux & BSDs, two backends are available, one using the [GTK3 Rust bindings](https://gtk-rs.org/) +//! and the other using the [XDG Desktop Portal](https://github.com/flatpak/xdg-desktop-portal) +//! D-Bus API through [ashpd](https://github.com/bilelmoussaoui/ashpd), +//! [zbus](https://gitlab.freedesktop.org/dbus/zbus/) and [zenity](https://gitlab.gnome.org/GNOME/zenity). +//! +//! ## GTK backend +//! The GTK backend is used when the `xdg-portal` feature is disabled with the [`default-features = false`](https://doc.rust-lang.org/cargo/reference/features.html#dependency-features), and `gtk3` is enabled instead. The GTK3 +//! backend requires the C library and development headers to be installed to build RFD. The package +//! names on various distributions are: +//! +//! | Distribution | Installation Command | +//! | --------------- | ------------ | +//! | Fedora | dnf install gtk3-devel | +//! | Arch | pacman -S gtk3 | +//! | Debian & Ubuntu | apt install libgtk-3-dev | +//! +//! ## XDG Desktop Portal backend +//! The XDG Desktop Portal backend is used with the `xdg-portal` Cargo feature which is enabled by default. Either the `tokio` or `async-std` feature must be enabled. This backend will use either the GTK or KDE file dialog depending on the desktop environment +//! in use at runtime. +//! +//! It does not have any non-Rust +//! build dependencies, however it requires the user to have either the +//! [GTK](https://github.com/flatpak/xdg-desktop-portal-gtk), +//! [GNOME](https://gitlab.gnome.org/GNOME/xdg-desktop-portal-gnome), or +//! [KDE](https://invent.kde.org/plasma/xdg-desktop-portal-kde/) XDG Desktop Portal backend installed +//! at runtime. These are typically installed by the distribution together with the desktop environment. +//! +//! The +//! [wlroots portal backend](https://github.com/emersion/xdg-desktop-portal-wlr) does not implement the +//! D-Bus API that RFD requires (it does not interfere with the other portal implementations; +//! they can all be installed simultaneously). +//! +//! [Zenity](https://gitlab.gnome.org/GNOME/zenity) is also required to display message dialogs, +//! and is used for file dialogs if [ashpd] cannot. +//! +//! If you are packaging an application that uses RFD, ensure that both a supported portal backend and +//! Zenity are installed with the package. +//! +//! # macOS non-windowed applications, async, and threading +//! +//! macOS async dialogs require an `NSApplication` instance, so the dialog is only truly async when +//! opened in windowed environment like `winit` or `SDL2`. Otherwise, it will fallback to sync dialog. +//! It is also recommended to spawn dialogs on your main thread. RFD can run dialogs from any thread +//! but it is only possible in a windowed app and it adds a little bit of overhead. So it is recommended +//! to [spawn on main and await in other thread](https://github.com/PolyMeilex/rfd/blob/master/examples/async.rs). +//! Non-windowed apps will never be able to spawn async dialogs or from threads other than the main thread. +//! +//! # Customize button texts of message dialog in Windows +//! +//! `TaskDialogIndirect` API is used for showing message dialog which can have customized button texts. +//! It is only provided by ComCtl32.dll v6 but Windows use v5 by default. +//! If you want to customize button texts or just need a modern dialog style (aka *visual styles*), you will need to: +//! +//! 1. Enable cargo feature `common-controls-v6`. +//! 2. Add an application manifest to use ComCtl32.dll v5. See [Windows Controls / Enabling Visual Styles](https://docs.microsoft.com/en-us/windows/win32/controls/cookbook-overview) +//! +//! +//! Here is an [example](https://github.com/PolyMeilex/rfd/tree/master/examples/message-custom-buttons) using [embed-resource](https://docs.rs/embed-resource/latest/embed_resource/). +//! +//! # Cargo features +//! * `gtk3`: Uses GTK for dialogs on Linux & BSDs; has no effect on Windows and macOS +//! * `xdg-portal`: Uses XDG Desktop Portal instead of GTK on Linux & BSDs +//! * `common-controls-v6`: Use `TaskDialogIndirect` API from ComCtl32.dll v6 for showing message dialog. This is necessary if you need to customize dialog button texts. +//! +//! # State +//! +//! | API Stability | +//! | ------------- | +//! | 🚧 | +//! +//! | Feature | Linux | Windows | MacOS | Wasm32 | +//! | ------------ | ----- | ------- | --------- | ------ | +//! | SingleFile | ✔ | ✔ | ✔ | ✔ | +//! | MultipleFile | ✔ | ✔ | ✔ | ✔ | +//! | PickFolder | ✔ | ✔ | ✔ | ✖ | +//! | SaveFile | ✔ | ✔ | ✔ | ✖ | +//! | Filters | ✔ | ✔ | ✔ | ✔ | +//! | StartingPath | ✔ | ✔ | ✔ | ✖ | +//! | Async | ✔ | ✔ | ✔ | ✔ | +//! +//! # rfd-extras +//! +//! AKA features that are not file related +//! +//! | Feature | Linux | Windows | MacOS | Wasm32 | +//! | ------------- | ----- | ------- | ----- | ------ | +//! | MessageDialog | ✔ | ✔ | ✔ | ✔ | +//! | PromptDialog | | | | | +//! | ColorPicker | | | | | + +mod backend; + +mod file_handle; +pub use file_handle::FileHandle; + +mod file_dialog; +#[cfg(target_os = "macos")] +mod oneshot; + +#[cfg(not(target_arch = "wasm32"))] +pub use file_dialog::FileDialog; + +pub use file_dialog::AsyncFileDialog; + +mod message_dialog; +pub use message_dialog::{ + AsyncMessageDialog, MessageButtons, MessageDialog, MessageDialogResult, MessageLevel, +}; diff --git a/desktop/src-tauri/vendor/rfd/src/message_dialog.rs b/desktop/src-tauri/vendor/rfd/src/message_dialog.rs new file mode 100644 index 00000000000..964af0de32a --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/message_dialog.rs @@ -0,0 +1,213 @@ +use crate::backend::AsyncMessageDialogImpl; +use crate::backend::MessageDialogImpl; +use std::fmt::{Display, Formatter}; + +use std::future::Future; + +use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; + +/// Synchronous Message Dialog. Supported platforms: +/// * Windows +/// * macOS +/// * Linux +/// * WASM +#[derive(Default, Debug, Clone)] +pub struct MessageDialog { + pub(crate) title: String, + pub(crate) description: String, + pub(crate) level: MessageLevel, + pub(crate) buttons: MessageButtons, + pub(crate) parent: Option, + pub(crate) parent_display: Option, +} + +// Oh god, I don't like sending RawWindowHandle between threads but here we go anyways... +// fingers crossed +unsafe impl Send for MessageDialog {} + +impl MessageDialog { + pub fn new() -> Self { + Default::default() + } + + /// Set level of a dialog + /// + /// Depending on the system it can result in level specific icon to show up, + /// the will inform user it message is a error, warning or just information. + pub fn set_level(mut self, level: MessageLevel) -> Self { + self.level = level; + self + } + + /// Set title of a dialog + pub fn set_title(mut self, text: impl Into) -> Self { + self.title = text.into(); + self + } + + /// Set description of a dialog + /// + /// Description is a content of a dialog + pub fn set_description(mut self, text: impl Into) -> Self { + self.description = text.into(); + self + } + + /// Set the set of button that will be displayed on the dialog + /// + /// - `Ok` dialog is a single `Ok` button + /// - `OkCancel` dialog, will display 2 buttons: ok and cancel. + /// - `YesNo` dialog, will display 2 buttons: yes and no. + /// - `YesNoCancel` dialog, will display 3 buttons: yes, no, and cancel. + pub fn set_buttons(mut self, btn: MessageButtons) -> Self { + self.buttons = btn; + self + } + + /// Set parent windows explicitly (optional). + /// Supported platforms: + /// * Windows + /// * Mac + /// * Linux (XDG only) + pub fn set_parent( + mut self, + parent: &W, + ) -> Self { + self.parent = parent.window_handle().ok().map(|x| x.as_raw()); + self.parent_display = parent.display_handle().ok().map(|x| x.as_raw()); + self + } + + /// Shows a message dialog and returns the button that was pressed. + pub fn show(self) -> MessageDialogResult { + MessageDialogImpl::show(self) + } +} + +/// Asynchronous Message Dialog. Supported platforms: +/// * Windows +/// * macOS +/// * Linux +/// * WASM +#[derive(Default, Debug, Clone)] +pub struct AsyncMessageDialog(MessageDialog); + +impl AsyncMessageDialog { + pub fn new() -> Self { + Default::default() + } + + /// Set level of a dialog + /// + /// Depending on the system it can result in level specific icon to show up, + /// the will inform user it message is a error, warning or just information. + pub fn set_level(mut self, level: MessageLevel) -> Self { + self.0 = self.0.set_level(level); + self + } + + /// Set title of a dialog + pub fn set_title(mut self, text: impl Into) -> Self { + self.0 = self.0.set_title(text); + self + } + + /// Set description of a dialog + /// + /// Description is a content of a dialog + pub fn set_description(mut self, text: impl Into) -> Self { + self.0 = self.0.set_description(text); + self + } + + /// Set the set of button that will be displayed on the dialog + /// + /// - `Ok` dialog is a single `Ok` button + /// - `OkCancel` dialog, will display 2 buttons ok and cancel. + /// - `YesNo` dialog, will display 2 buttons yes and no. + /// - `YesNoCancel` dialog, will display 3 buttons: yes, no, and cancel. + pub fn set_buttons(mut self, btn: MessageButtons) -> Self { + self.0 = self.0.set_buttons(btn); + self + } + + /// Set parent windows explicitly (optional). + /// Supported platforms: + /// * Windows + /// * Mac + /// * Linux (XDG only) + pub fn set_parent( + mut self, + parent: &W, + ) -> Self { + self.0 = self.0.set_parent(parent); + self + } + + /// Shows a message dialog and returns the button that was pressed. + pub fn show(self) -> impl Future { + AsyncMessageDialogImpl::show_async(self.0) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum MessageLevel { + Info, + Warning, + Error, +} + +impl Default for MessageLevel { + fn default() -> Self { + Self::Info + } +} + +#[derive(Debug, Clone)] +pub enum MessageButtons { + Ok, + OkCancel, + YesNo, + YesNoCancel, + /// One customizable button. + /// Notice that in Windows, this only works with the feature *common-controls-v6* enabled + OkCustom(String), + /// Two customizable buttons. + /// Notice that in Windows, this only works with the feature *common-controls-v6* enabled + OkCancelCustom(String, String), + /// Three customizable buttons. + /// Notice that in Windows, this only works with the feature *common-controls-v6* enabled + YesNoCancelCustom(String, String, String), +} + +impl Default for MessageButtons { + fn default() -> Self { + Self::Ok + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub enum MessageDialogResult { + Yes, + No, + Ok, + #[default] + Cancel, + Custom(String), +} + +impl Display for MessageDialogResult { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Yes => "Yes".to_string(), + Self::No => "No".to_string(), + Self::Ok => "Ok".to_string(), + Self::Cancel => "Cancel".to_string(), + Self::Custom(custom) => format!("Custom({custom})"), + } + ) + } +} diff --git a/desktop/src-tauri/vendor/rfd/src/oneshot.rs b/desktop/src-tauri/vendor/rfd/src/oneshot.rs new file mode 100644 index 00000000000..bfabb1ce940 --- /dev/null +++ b/desktop/src-tauri/vendor/rfd/src/oneshot.rs @@ -0,0 +1,258 @@ +use std::fmt; +use std::future::Future; +use std::pin::Pin; +use std::sync::{ + atomic::{AtomicBool, Ordering::SeqCst}, + Arc, Mutex, +}; +use std::task::{Context, Poll, Waker}; + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Receiver { + inner: Arc>, +} + +pub struct Sender { + inner: Arc>, +} + +// The channels do not ever project Pin to the inner T +impl Unpin for Receiver {} +impl Unpin for Sender {} + +/// Internal state of the `Receiver`/`Sender` pair above. This is all used as +/// the internal synchronization between the two for send/recv operations. +struct Inner { + /// Indicates whether this oneshot is complete yet. This is filled in both + /// by `Sender::drop` and by `Receiver::drop`, and both sides interpret it + /// appropriately. + /// + /// For `Receiver`, if this is `true`, then it's guaranteed that `data` is + /// unlocked and ready to be inspected. + /// + /// For `Sender` if this is `true` then the oneshot has gone away and it + /// can return ready from `poll_canceled`. + complete: AtomicBool, + + /// The actual data being transferred as part of this `Receiver`. This is + /// filled in by `Sender::complete` and read by `Receiver::poll`. + /// + /// Note that this is protected by `Lock`, but it is in theory safe to + /// replace with an `UnsafeCell` as it's actually protected by `complete` + /// above. I wouldn't recommend doing this, however, unless someone is + /// supremely confident in the various atomic orderings here and there. + data: Mutex>, + + /// Field to store the task which is blocked in `Receiver::poll`. + /// + /// This is filled in when a oneshot is polled but not ready yet. Note that + /// the `Lock` here, unlike in `data` above, is important to resolve races. + /// Both the `Receiver` and the `Sender` halves understand that if they + /// can't acquire the lock then some important interference is happening. + rx_task: Mutex>, + + /// Like `rx_task` above, except for the task blocked in + /// `Sender::poll_canceled`. Additionally, `Lock` cannot be `UnsafeCell`. + tx_task: Mutex>, +} + +pub fn channel() -> (Sender, Receiver) { + let inner = Arc::new(Inner::new()); + let receiver = Receiver { + inner: inner.clone(), + }; + let sender = Sender { inner }; + (sender, receiver) +} + +impl Inner { + fn new() -> Self { + Self { + complete: AtomicBool::new(false), + data: Mutex::new(None), + rx_task: Mutex::new(None), + tx_task: Mutex::new(None), + } + } + + fn send(&self, t: T) -> Result<(), T> { + if self.complete.load(SeqCst) { + return Err(t); + } + + // Note that this lock acquisition may fail if the receiver + // is closed and sets the `complete` flag to `true`, whereupon + // the receiver may call `poll()`. + if let Ok(mut slot) = self.data.try_lock() { + assert!(slot.is_none()); + *slot = Some(t); + drop(slot); + + // If the receiver called `close()` between the check at the + // start of the function, and the lock being released, then + // the receiver may not be around to receive it, so try to + // pull it back out. + if self.complete.load(SeqCst) { + // If lock acquisition fails, then receiver is actually + // receiving it, so we're good. + if let Ok(mut slot) = self.data.try_lock() { + if let Some(t) = slot.take() { + return Err(t); + } + } + } + Ok(()) + } else { + // Must have been closed + Err(t) + } + } + + fn drop_tx(&self) { + // Flag that we're a completed `Sender` and try to wake up a receiver. + // Whether or not we actually stored any data will get picked up and + // translated to either an item or cancellation. + // + // Note that if we fail to acquire the `rx_task` lock then that means + // we're in one of two situations: + // + // 1. The receiver is trying to block in `poll` + // 2. The receiver is being dropped + // + // In the first case it'll check the `complete` flag after it's done + // blocking to see if it succeeded. In the latter case we don't need to + // wake up anyone anyway. So in both cases it's ok to ignore the `None` + // case of `try_lock` and bail out. + // + // The first case crucially depends on `Lock` using `SeqCst` ordering + // under the hood. If it instead used `Release` / `Acquire` ordering, + // then it would not necessarily synchronize with `inner.complete` + // and deadlock might be possible, as was observed in + // https://github.com/rust-lang/futures-rs/pull/219. + self.complete.store(true, SeqCst); + + if let Ok(mut slot) = self.rx_task.try_lock() { + if let Some(task) = slot.take() { + drop(slot); + task.wake(); + } + } + + // If we registered a task for cancel notification drop it to reduce + // spurious wakeups + if let Ok(mut slot) = self.tx_task.try_lock() { + drop(slot.take()); + } + } + + fn recv(&self, cx: &mut Context<'_>) -> Poll> { + // Check to see if some data has arrived. If it hasn't then we need to + // block our task. + // + // Note that the acquisition of the `rx_task` lock might fail below, but + // the only situation where this can happen is during `Sender::drop` + // when we are indeed completed already. If that's happening then we + // know we're completed so keep going. + let done = if self.complete.load(SeqCst) { + true + } else { + let task = cx.waker().clone(); + match self.rx_task.try_lock() { + Ok(mut slot) => { + *slot = Some(task); + false + } + Err(_) => true, + } + }; + + // If we're `done` via one of the paths above, then look at the data and + // figure out what the answer is. If, however, we stored `rx_task` + // successfully above we need to check again if we're completed in case + // a message was sent while `rx_task` was locked and couldn't notify us + // otherwise. + // + // If we're not done, and we're not complete, though, then we've + // successfully blocked our task and we return `Pending`. + if done || self.complete.load(SeqCst) { + // If taking the lock fails, the sender will realise that the we're + // `done` when it checks the `complete` flag on the way out, and + // will treat the send as a failure. + if let Ok(mut slot) = self.data.try_lock() { + if let Some(data) = slot.take() { + return Poll::Ready(Ok(data)); + } + } + Poll::Ready(Err(Canceled)) + } else { + Poll::Pending + } + } + + fn drop_rx(&self) { + // Indicate to the `Sender` that we're done, so any future calls to + // `poll_canceled` are weeded out. + self.complete.store(true, SeqCst); + + // If we've blocked a task then there's no need for it to stick around, + // so we need to drop it. If this lock acquisition fails, though, then + // it's just because our `Sender` is trying to take the task, so we + // let them take care of that. + if let Ok(mut slot) = self.rx_task.try_lock() { + let task = slot.take(); + drop(slot); + drop(task); + } + + // Finally, if our `Sender` wants to get notified of us going away, it + // would have stored something in `tx_task`. Here we try to peel that + // out and unpark it. + // + // Note that the `try_lock` here may fail, but only if the `Sender` is + // in the process of filling in the task. If that happens then we + // already flagged `complete` and they'll pick that up above. + if let Ok(mut handle) = self.tx_task.try_lock() { + if let Some(task) = handle.take() { + drop(handle); + task.wake() + } + } + } +} + +impl Sender { + pub fn send(self, t: T) -> Result<(), T> { + self.inner.send(t) + } +} + +impl Drop for Sender { + fn drop(&mut self) { + self.inner.drop_tx() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Canceled; + +impl fmt::Display for Canceled { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "oneshot canceled") + } +} + +impl std::error::Error for Canceled {} + +impl Future for Receiver { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.inner.recv(cx) + } +} + +impl Drop for Receiver { + fn drop(&mut self) { + self.inner.drop_rx() + } +} diff --git a/desktop/src-tauri/vendor/wry/.cargo/config.toml b/desktop/src-tauri/vendor/wry/.cargo/config.toml new file mode 100644 index 00000000000..3f6a4c76b19 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/.cargo/config.toml @@ -0,0 +1,4 @@ +# [build] +# target = "aarch64-linux-android" +[target.x86_64-apple-darwin] +rustflags = ["-C", "link-arg=-mmacosx-version-min=10.12"] diff --git a/desktop/src-tauri/vendor/wry/.cargo_vcs_info.json b/desktop/src-tauri/vendor/wry/.cargo_vcs_info.json new file mode 100644 index 00000000000..974b290a4ab --- /dev/null +++ b/desktop/src-tauri/vendor/wry/.cargo_vcs_info.json @@ -0,0 +1,7 @@ +{ + "git": { + "sha1": "a5bf203a1c8dbb3583588382538d6521655222a8", + "dirty": true + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/desktop/src-tauri/vendor/wry/.gitignore b/desktop/src-tauri/vendor/wry/.gitignore new file mode 100644 index 00000000000..0a015927102 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/.gitignore @@ -0,0 +1,9 @@ +# Copyright 2020-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +target +gh-pages +.DS_Store +examples/test_video.mp4 +.vscode/ diff --git a/desktop/src-tauri/vendor/wry/.license_template b/desktop/src-tauri/vendor/wry/.license_template new file mode 100644 index 00000000000..9601f8a1b49 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/.license_template @@ -0,0 +1,3 @@ +// Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/desktop/src-tauri/vendor/wry/CHANGELOG.md b/desktop/src-tauri/vendor/wry/CHANGELOG.md new file mode 100644 index 00000000000..5aea0d9ca10 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/CHANGELOG.md @@ -0,0 +1,1270 @@ +# Changelog + +## \[0.55.1] + +- [`3860359`](https://github.com/tauri-apps/wry/commit/3860359e3dc75260fb9865f7a8eae00a9ec4733d) ([#1721](https://github.com/tauri-apps/wry/pull/1721) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Tweak ProGuard rules to keep the getId required activity method. + +## \[0.55.0] + +- [`a535fd9`](https://github.com/tauri-apps/wry/commit/a535fd95025d126dd1d6ad10b101b81ed9ae40fe) ([#1699](https://github.com/tauri-apps/wry/pull/1699) by [@FabianLars](https://github.com/tauri-apps/wry/../../FabianLars)) On Android, fixed panics on custom protocol timeouts. Increased timeout for non-internal requests to 30 seconds. +- [`781f371`](https://github.com/tauri-apps/wry/commit/781f371893d58c9dc81f2adce84adfad9e390833) ([#1633](https://github.com/tauri-apps/wry/pull/1633) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Update to latest tao to support multi-window on Android and iOS. +- [`848aff7`](https://github.com/tauri-apps/wry/commit/848aff722a72cc30be01c992734000933f74c2d0) ([#1660](https://github.com/tauri-apps/wry/pull/1660) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Run `new_window_req_handler` on main thread and remove the `Send + Sync` restriction on it +- [`92114b2`](https://github.com/tauri-apps/wry/commit/92114b2efb531ec8191734817ebcda952e204210) ([#1634](https://github.com/tauri-apps/wry/pull/1634) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Call `onNewIntent` for tao's 0.35 Opened event. +- [`05440b8`](https://github.com/tauri-apps/wry/commit/05440b80ed79a84faa264a91dbcd952d9e77f78f) ([#1688](https://github.com/tauri-apps/wry/pull/1688) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Removed `drag-drop` feature, previously gated `WebViewAttributes::drag_drop_handler` and `WebViewBuilder::with_drag_drop_handler` are now always available +- [`222c63d`](https://github.com/tauri-apps/wry/commit/222c63d69f03639bbd1f456ae2d9d2f26a2d549c) ([#1698](https://github.com/tauri-apps/wry/pull/1698) by [@FabianLars](https://github.com/tauri-apps/wry/../../FabianLars)) Remove `WebViewAttributes`, `WebView::new` and `WebView::new_as_child` from public API. Use `WebViewBuilder` instead. + +## \[0.54.4] + +- [`47d9470`](https://github.com/tauri-apps/wry/commit/47d9470e40160af8209da094364141917ec677a7) ([#1693](https://github.com/tauri-apps/wry/pull/1693) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Updated dependency dom_query to 0.27.0, this fixed a bug where initialization scripts were escaped incorrectly on Android +- [`1540788`](https://github.com/tauri-apps/wry/commit/1540788a0161235e1b89c70912725fd2f18cb813) ([#1692](https://github.com/tauri-apps/wry/pull/1692) by [@Sbenazar](https://github.com/tauri-apps/wry/../../Sbenazar)) fix(webkitgtk): normalize background color values to 0.0–1.0 + +## \[0.54.3] + +- [`40a7032`](https://github.com/tauri-apps/wry/commit/40a703214d3e66942edf6f6a96074580798d5046) ([#1677](https://github.com/tauri-apps/wry/pull/1677) by [@russellmcc](https://github.com/tauri-apps/wry/../../russellmcc)) Fix bug where wry would crash when loaded by multiple dylibs in the same process on macOS. + +### Dependencies + +- [`146e36e`](https://github.com/tauri-apps/wry/commit/146e36e68b5da3c8e4488a67c8661695e76e60eb) ([#1671](https://github.com/tauri-apps/wry/pull/1671) by [@thomaseizinger](https://github.com/tauri-apps/wry/../../thomaseizinger)) Refactors HTML manipulation on Android to use `dom_query` instead of `kuchikiki`. + +### enhance + +- [`e430558`](https://github.com/tauri-apps/wry/commit/e430558f4be90096a1bf786cdb59c49a0754beec) ([#1649](https://github.com/tauri-apps/wry/pull/1649) by [@aoxiangtianyu-go](https://github.com/tauri-apps/wry/../../aoxiangtianyu-go)) Add an option to control browser-level autofill behavior on Windows. + +### bug + +- [`a7de137`](https://github.com/tauri-apps/wry/commit/a7de137ecc321bf3c3a3a10c857b5839741b4b5c) ([#1680](https://github.com/tauri-apps/wry/pull/1680) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Fix infinite loop on main thread if creating the webview failed or getting cookie failed + +## \[0.54.2] + +- [`1d1b870`](https://github.com/tauri-apps/wry/commit/1d1b8707665b4638e040dde7a88cd33d77ba2ebc) ([#1662](https://github.com/tauri-apps/wry/pull/1662) by [@JSKitty](https://github.com/tauri-apps/wry/../../JSKitty)) On macOS, implement `background_color` support for WKWebView behind the `transparent` feature. Disables the default white background via the `drawsBackground` KVC key at init and applies `underPageBackgroundColor` on macOS 12+ for both initial creation and runtime `set_background_color` calls. +- [`e3761e5`](https://github.com/tauri-apps/wry/commit/e3761e5e5ab684ae277dfa7df931ed9ff4c5bc38) ([#1668](https://github.com/tauri-apps/wry/pull/1668) by [@zeta-gundam](https://github.com/tauri-apps/wry/../../zeta-gundam)) Fixed an issue that caused "Symbol not found: \_NSHTTPCookieSameSite..." panics on macOS `10.13` & `10.14`. +- [`e3761e5`](https://github.com/tauri-apps/wry/commit/e3761e5e5ab684ae277dfa7df931ed9ff4c5bc38) ([#1668](https://github.com/tauri-apps/wry/pull/1668) by [@zeta-gundam](https://github.com/tauri-apps/wry/../../zeta-gundam)) Fixed an issue that caused "\[\ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key drawsBackground." panics on macOS `10.13` + +## \[0.54.1] + +- [`6704b20`](https://github.com/tauri-apps/wry/commit/6704b207ac985e968be0b5c26b898ca152ae47d9) ([#1655](https://github.com/tauri-apps/wry/pull/1655)) Adds a `PartialEq` derive to `Rect`. + +## \[0.54.0] + +- [`5fdd1a9`](https://github.com/tauri-apps/wry/commit/5fdd1a9840e24ce77ea4a57ce3746c3dbee28c9c) ([#1625](https://github.com/tauri-apps/wry/pull/1625)) Use OnBackPressedCallback instead of the deprecated onKeyDown for back navigation on Android. +- [`51d06d0`](https://github.com/tauri-apps/wry/commit/51d06d0cecdf69652a0ae636b1b875e427534bc3) ([#1646](https://github.com/tauri-apps/wry/pull/1646)) Remove redundant clones in WebView and download handling. No user facing changes. +- [`7b77322`](https://github.com/tauri-apps/wry/commit/7b77322f886c440d035674b8efa586671dc0a3db) ([#1647](https://github.com/tauri-apps/wry/pull/1647)) Fix clippy io_other_error. No user facing changes. +- [`ff8876d`](https://github.com/tauri-apps/wry/commit/ff8876d92b9681b139d6637220cefe3248b7d3fb) ([#1652](https://github.com/tauri-apps/wry/pull/1652)) Improve wkwebview performance. No user-facing changes. +- [`27ba73f`](https://github.com/tauri-apps/wry/commit/27ba73f07720b80a21b6b876f1229cad8871aa33) Add handler for web content process termination. +- [`4fcff14`](https://github.com/tauri-apps/wry/commit/4fcff147b5022af3ee006ed077759b1648b32aa6) ([#1547](https://github.com/tauri-apps/wry/pull/1547)) Set `WebsiteDataManagerBuilder::base_cache_directory` with the same path as `base_data_directory`. + + This change allows the cache directory to be changed instead of using the default one [from WebKitGTK](https://webkitgtk.org/reference/webkit2gtk/stable/property.WebsiteDataManager.base-cache-directory.html). +- [`64d9296`](https://github.com/tauri-apps/wry/commit/64d92960b960a4b2fdf9205b6188f55650ddd62f) ([#1640](https://github.com/tauri-apps/wry/pull/1640)) Update webkit2gtk-rs crates to `2.0.2`. + +## \[0.53.5] + +- [#1622](https://github.com/tauri-apps/wry/pull/1626) Fixed an issue that caused docs.rs builds to fail. No user facing changes. + +## \[0.53.4] + +- [`093856a`](https://github.com/tauri-apps/wry/commit/093856a2a53a6fc1aaa759e048c7e1fe31bb09fa) ([#1622](https://github.com/tauri-apps/wry/pull/1622) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Add flag to opt out of automatic back navigation handling on Android via `WryActivity#handleBackNavigation`. +- [`0f51d67`](https://github.com/tauri-apps/wry/commit/0f51d67485d84fd9c72391379a67567eea3cbbfe) ([#1605](https://github.com/tauri-apps/wry/pull/1605) by [@dgerhardt](https://github.com/tauri-apps/wry/../../dgerhardt)) On Linux, removed a workaround which forced inital requests for multiple webviews to be handled sequentially. + The workaround was intended to fix a concurrency bug with loading multiple URIs at the same time on WebKitGTK. + But it prevented parallelization and could cause a deadlock in certain situations. + It is no longer needed with newer WebKitGTK versions. + +## \[0.53.3] + +- [`6aa5854`](https://github.com/tauri-apps/wry/commit/6aa5854b0371a4828638cd722e18d9b1ab235a8b) ([#1609](https://github.com/tauri-apps/wry/pull/1609) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Enhance error handling of the `webview_version` function on macOS. + +## \[0.53.2] + +- [`1743e7f`](https://github.com/tauri-apps/wry/commit/1743e7f9c1113db1639d544f1f12b8835f91e20b) ([#1608](https://github.com/tauri-apps/wry/pull/1608) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fix new_window_req_handler craashing when creating new window for webview that has a custom data_store_identifier. + +## \[0.53.1] + +- [`ccdf912`](https://github.com/tauri-apps/wry/commit/ccdf912621f5e12698bdb2cb75af81dd794ab751) ([#1603](https://github.com/tauri-apps/wry/pull/1603) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fix the build when not enabling the `x11` feature. + +## \[0.53.0] + +- [`1456f8e`](https://github.com/tauri-apps/wry/commit/1456f8e33ad3d05239c2ee71f64e49ef32d48f03) ([#1602](https://github.com/tauri-apps/wry/pull/1602) by [@FabianLars](https://github.com/tauri-apps/wry/../../FabianLars)) Enabled all downloads by default to match WebView2 and browser behavior on all platforms. To disable this, provide a custom `download_started_handler`. +- [`43e78ff`](https://github.com/tauri-apps/wry/commit/43e78ff6b09fe9cac84ff7d73460f170a8c32081) ([#1588](https://github.com/tauri-apps/wry/pull/1588) by [@zphrs](https://github.com/tauri-apps/wry/../../zphrs)) Add `WebViewBuilder::with_limit_navigations_to_app_bound_domains` only on iOS. + Function is a no-op if iOS version is less than iOS 14. +- [`60dba38`](https://github.com/tauri-apps/wry/commit/60dba38ddcc01c428feccea2957adf69128373ef) ([#1569](https://github.com/tauri-apps/wry/pull/1569) by [@WSH032](https://github.com/tauri-apps/wry/../../WSH032)) Add `WebView::set_cookie` and `WebView::delete_cookie` APIs. +- [`88cbb01`](https://github.com/tauri-apps/wry/commit/88cbb019a8d38fbcf7ad015d5f34f9a838a88f98) ([#1561](https://github.com/tauri-apps/wry/pull/1561) by [@dgerhardt](https://github.com/tauri-apps/wry/../../dgerhardt)) On Linux, fix a deadlock, which could occur when destroying a WebView before loading has finished. +- [`eb562ca`](https://github.com/tauri-apps/wry/commit/eb562ca99346fabf2c28d65252d176e2612f3178) ([#1596](https://github.com/tauri-apps/wry/pull/1596) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Do not fire `new_window_req_handler` on navigation events on macOS and iOS. +- [`78634b3`](https://github.com/tauri-apps/wry/commit/78634b3d4dd31651f9e6a3959cbb5f994502a92b) ([#1594](https://github.com/tauri-apps/wry/pull/1594) by [@FabianLars](https://github.com/tauri-apps/wry/../../FabianLars)) On Linux, macOS and iOS, the `download_started_handler` will now get the correct suggested destination path. +- [`5399823`](https://github.com/tauri-apps/wry/commit/5399823a1ecfca4476e7c585c694380c41951766) ([#1600](https://github.com/tauri-apps/wry/pull/1600) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fixes `new_window_req_handler` not creating the webview on Linux when executing `window.open()`. +- [`eb562ca`](https://github.com/tauri-apps/wry/commit/eb562ca99346fabf2c28d65252d176e2612f3178) ([#1596](https://github.com/tauri-apps/wry/pull/1596) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fixes `new_window_req_handler` not fired on macOS when executing `window.open()`. +- [`3f978d3`](https://github.com/tauri-apps/wry/commit/3f978d3290aa5cebd6b5feea506557ac0f6e07a7) ([#1601](https://github.com/tauri-apps/wry/pull/1601) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Refactor `WebViewBuilder::with_new_window_req_handler` to allow creating the webview manually. +- [`3f978d3`](https://github.com/tauri-apps/wry/commit/3f978d3290aa5cebd6b5feea506557ac0f6e07a7) ([#1601](https://github.com/tauri-apps/wry/pull/1601) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Disable new window creation by default on Windows to match behavior of other platforms. Use `WebViewBuilder::with_new_window_req_handler` to enable. + +## \[0.52.1] + +- [`63eaab8`](https://github.com/tauri-apps/wry/commit/63eaab80bad7c5f888893c79690c5b626d015eb3) ([#1573](https://github.com/tauri-apps/wry/pull/1573) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Fix `wry::DragDropEvent::Drop::paths` returns random data on Windows + +## \[0.52.0] + +- [`5e6b0e6`](https://github.com/tauri-apps/wry/commit/5e6b0e689e38068c817ed1beb87af60b0fcbe0e2) ([#1555](https://github.com/tauri-apps/wry/pull/1555) by [@FabianLars](https://github.com/tauri-apps/wry/../../FabianLars)) Added iOS support for `set_background_color`. +- [`99dab51`](https://github.com/tauri-apps/wry/commit/99dab51fef0dfd49647a8e568f383553bbab6551) ([#1570](https://github.com/tauri-apps/wry/pull/1570) by [@renovate](https://github.com/tauri-apps/wry/../../renovate)) Updated `webview2-com` to `0.38`. +- [`41f4a3a`](https://github.com/tauri-apps/wry/commit/41f4a3a65e0c2ec8e63eedaee9b7fa4d6d3f4fa7) ([#1572](https://github.com/tauri-apps/wry/pull/1572) by [@FabianLars](https://github.com/tauri-apps/wry/../../FabianLars)) On Windows on systems running WebView2 v137+ wry now uses a new default background color API which should reduce white flashes. The use of the `RemoveRedirectionBitmap` browser flag (v134+) was removed due to crashes on Insider builds. +- [`eb40ac8`](https://github.com/tauri-apps/wry/commit/eb40ac8b2ab691bbc0e75bc9c27ba53e2f645e03) ([#1528](https://github.com/tauri-apps/wry/pull/1528) by [@aurelj](https://github.com/tauri-apps/wry/../../aurelj)) Added `x11` feature flag (enabled by default). + +### breaking + +- [`1567635`](https://github.com/tauri-apps/wry/commit/1567635ba5f660827d4ae20f4e226ed7fa595f12) ([#1558](https://github.com/tauri-apps/wry/pull/1558) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) Rename `WebViewBuilder` methods for consistency and clarity: + + - Renamed `WebViewBuilder::with_web_context` to `WebViewBuilder::new_with_web_context` + - Renamed `WebViewBuilder::with_attributes` to `WebViewBuilder::new_with_attributes` +- [`f868658`](https://github.com/tauri-apps/wry/commit/f868658d6ffbf0a6b944faebb3b7565726c82f57) ([#1556](https://github.com/tauri-apps/wry/pull/1556) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) `WebContext::is_custom_protocol_registered` now takes `&str` instead of `String` + +## \[0.51.2] + +- [`f7781a7`](https://github.com/tauri-apps/wry/commit/f7781a788dbb0d07bea27f18daf844d70a3958a3) ([#1544](https://github.com/tauri-apps/wry/pull/1544) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Allow modifying or removing the input accessory view on iOS via `WebViewBuilderExtIos::with_input_accessory_view_builder`. + +## \[0.51.1] + +- [`3e24d6b`](https://github.com/tauri-apps/wry/commit/3e24d6b7b191ce37298ad6be99614ccdf04c7d3f) ([#1543](https://github.com/tauri-apps/wry/pull/1543) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fix async custom protocol not responding. + +## \[0.51.0] + +- [`f99bbd9`](https://github.com/tauri-apps/wry/commit/f99bbd97c6f46492a2f8b34894532629ce17550d) ([#1507](https://github.com/tauri-apps/wry/pull/1507) by [@Simon-Laux](https://github.com/tauri-apps/wry/../../Simon-Laux)) Renamed `Error::UrlPrase` to `Error::UrlParse` to fix typo. +- [`2d753c6`](https://github.com/tauri-apps/wry/commit/2d753c64822e05ed28621009ef9383ffb70b1b94) ([#1531](https://github.com/tauri-apps/wry/pull/1531) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) **Breaking Change**: `WebViewAttributes::initialization_scripts` takes `Vec::` now instead of `Vec::<(String, bool)>` +- [`78b83a0`](https://github.com/tauri-apps/wry/commit/78b83a0d8a5db3c58140d7a3f00a52616049251e) ([#1537](https://github.com/tauri-apps/wry/pull/1537) by [@Brendonovich](https://github.com/tauri-apps/wry/../../Brendonovich)) Moved protocol handler functions to a thread local instead of storing them as ivars to prevent a race condition between webview close and custom protocol handling. +- [`5f1e4ba`](https://github.com/tauri-apps/wry/commit/5f1e4bafeee6aab8cdc1d7ebc5d13bb6cfeb94ad) ([#1538](https://github.com/tauri-apps/wry/pull/1538) by [@syrel](https://github.com/tauri-apps/wry/../../syrel)) macOS: Handle flipped coordinates when adding a WebView as a child +- [`4ec951a`](https://github.com/tauri-apps/wry/commit/4ec951a7b5d0484e19f2d94665c79380171ec9ba) ([#1526](https://github.com/tauri-apps/wry/pull/1526) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Updated `webview2-com` to `0.37`, `windows` to `0.61`. +- [`2d753c6`](https://github.com/tauri-apps/wry/commit/2d753c64822e05ed28621009ef9383ffb70b1b94) ([#1531](https://github.com/tauri-apps/wry/pull/1531) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Init scripts are always executed on all frames on Windows WebView2 +- [`2d753c6`](https://github.com/tauri-apps/wry/commit/2d753c64822e05ed28621009ef9383ffb70b1b94) ([#1531](https://github.com/tauri-apps/wry/pull/1531) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Fix init script sometimes get executed too late on Windows WebView2 + +### feat + +- [`ecbced2`](https://github.com/tauri-apps/wry/commit/ecbced25e5e41c6a9f7816ea6517763d30c5b060) ([#1534](https://github.com/tauri-apps/wry/pull/1534) by [@Simon-Laux](https://github.com/tauri-apps/wry/../../Simon-Laux)) macOS/iOS: add option to disable link previews when building a webview (the webkit API has it enabled by default) + + - `WebViewBuilderExtDarwin.with_allow_link_preview(bool)` + +## \[0.50.5] + +### enhance + +- [`353bd95`](https://github.com/tauri-apps/wry/commit/353bd9573a1ff80aa1e547e82f8c32ab2f984f9f) ([#1517](https://github.com/tauri-apps/wry/pull/1517) by [@FabianLars](https://github.com/tauri-apps/wry/../../FabianLars)) Added a Windows-only setting to disable all Webview2 context menus. + +### bug + +- [`4f4ade3`](https://github.com/tauri-apps/wry/commit/4f4ade3c9564bb33b6e54488c228a4c5b054204f) ([#1520](https://github.com/tauri-apps/wry/pull/1520) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fix crash setting macOS traffic light buttons inset when window is undecorated or buttons have been removed. + +## \[0.50.4] + +- [`349dfe3`](https://github.com/tauri-apps/wry/commit/349dfe37d25f71ed60143f7ec36153bedc296cfa) ([#1512](https://github.com/tauri-apps/wry/pull/1512) by [@Simon-Laux](https://github.com/tauri-apps/wry/../../Simon-Laux)) Added `WebViewExtDarwin` to expose WebView functions available to both macOS and iOS. +- [`349dfe3`](https://github.com/tauri-apps/wry/commit/349dfe37d25f71ed60143f7ec36153bedc296cfa) ([#1512](https://github.com/tauri-apps/wry/pull/1512) by [@Simon-Laux](https://github.com/tauri-apps/wry/../../Simon-Laux)) fix: crash when using `WebViewBuilderExtDarwin.with_data_store_identifier` +- [`349dfe3`](https://github.com/tauri-apps/wry/commit/349dfe37d25f71ed60143f7ec36153bedc296cfa) ([#1512](https://github.com/tauri-apps/wry/pull/1512) by [@Simon-Laux](https://github.com/tauri-apps/wry/../../Simon-Laux)) feat: macOS: add `WebViewExtDarwin::fetch_data_store_identifiers` and `WebViewExtDarwin::remove_data_store`. +- [`d0b16a7`](https://github.com/tauri-apps/wry/commit/d0b16a7b89dfed084405bd7058020da1a4b6708a) ([#1491](https://github.com/tauri-apps/wry/pull/1491) by [@neilmcguire](https://github.com/tauri-apps/wry/../../neilmcguire)) On Windows, Add support for iframe requests in custom protocols. Requires WebView2 1.0.2365.46 or higher. +- [`148d7cd`](https://github.com/tauri-apps/wry/commit/148d7cdd23de91aba541f935aa23d2faf5558199) ([#1513](https://github.com/tauri-apps/wry/pull/1513) by [@FabianLars](https://github.com/tauri-apps/wry/../../FabianLars)) Wry by default now passes `--enable-features=RemoveRedirectionBitmap` to WebView2 to hide the initial white flash of newly created webviews. Only takes effect on WebView2 Runtime versions 134 and above. + +## \[0.50.3] + +- [`94ecadb`](https://github.com/tauri-apps/wry/commit/94ecadbc73d6357484a4df7358c96e72111ce4be) ([#1496](https://github.com/tauri-apps/wry/pull/1496) by [@Simon-Laux](https://github.com/tauri-apps/wry/../../Simon-Laux)) Add `WebViewBuilder.with_javascript_disabled` api to disable JavaScript. +- [`5120a5c`](https://github.com/tauri-apps/wry/commit/5120a5cc0fd4b9d1df32812d3df121c632821395) ([#1486](https://github.com/tauri-apps/wry/pull/1486) by [@charrondev](https://github.com/tauri-apps/wry/../../charrondev)) Fix `Webview::cookies` and `Webview::cookies_for_url` deadlock on macOS. +- [`8dfeb76`](https://github.com/tauri-apps/wry/commit/8dfeb7650213fc10edbf7b03acc9a95107c7e342) ([#1500](https://github.com/tauri-apps/wry/pull/1500) by [@Simon-Laux](https://github.com/tauri-apps/wry/../../Simon-Laux)) feat: add `Webview.reload` + +## \[0.50.2] + +- [`cef818f`](https://github.com/tauri-apps/wry/commit/cef818f63f36b9acfc4d31f17b5e9fe7c3058612) ([#1505](https://github.com/tauri-apps/wry/pull/1505) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fix `already mutably borrowed: BorrowError` panic on webview initialization on Android. + +## \[0.50.1] + +- [`f9abf6b`](https://github.com/tauri-apps/wry/commit/f9abf6b4464acc91926236366a347f04d741b15d) ([#1501](https://github.com/tauri-apps/wry/pull/1501) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Fix webview not resized with parent when it gets maximized on Windows + +## \[0.50.0] + +- [`933de78`](https://github.com/tauri-apps/wry/commit/933de788bc0fbab1914b7c5ce29c209281996b03) ([#1492](https://github.com/tauri-apps/wry/pull/1492) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Updated `webview2-com` to `0.36`, `windows` to `0.60`. + +## \[0.49.0] + +- [`0395df5`](https://github.com/tauri-apps/wry/commit/0395df504ffaea2320d706501c89859a6e319d30) ([#1484](https://github.com/tauri-apps/wry/pull/1484) by [@alexmoon](https://github.com/tauri-apps/wry/../../alexmoon)) Removed `obj-exception` feature. +- [`c27b4ff`](https://github.com/tauri-apps/wry/commit/c27b4ffc05b6bc9bb3748a1203116342cfec680d) ([#1468](https://github.com/tauri-apps/wry/pull/1468) by [@madsmtm](https://github.com/tauri-apps/wry/../../madsmtm)) Update to `objc2` v0.6. + + This bumps MSRV on macOS/iOS to 1.71. +- [`95a9319`](https://github.com/tauri-apps/wry/commit/95a9319f24cb62add69d468ec0f60530b608fe6b) ([#1454](https://github.com/tauri-apps/wry/pull/1454) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Updated `webview2-com` to `0.35`, `windows` to `0.59`. +- [`9df094a`](https://github.com/tauri-apps/wry/commit/9df094aa79210c6743b5d295069931afaee596db) ([#1483](https://github.com/tauri-apps/wry/pull/1483) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) On Windows, fix webview slightly larger than the host window causing a pixel or two to be obscured. +- [`ce98c34`](https://github.com/tauri-apps/wry/commit/ce98c3401d6bc3079900ced9007f212f4f30099c) ([#1480](https://github.com/tauri-apps/wry/pull/1480) by [@ahqsoftwares](https://github.com/tauri-apps/wry/../../ahqsoftwares)) Fixed an issue that could cause `Return type mismatch: expected 'kotlin.String', actual 'kotlin.String?'` errors. + +### enhance + +- [`0185644`](https://github.com/tauri-apps/wry/commit/0185644040184c43084814cb8692acb4e1004d86) ([#1452](https://github.com/tauri-apps/wry/pull/1452) by [@mzdk100](https://github.com/tauri-apps/wry/../../mzdk100)) Allow the use of TAB to cycle through focus elements in an HTML document. + +## \[0.48.1] + +- [`cbbcccc`](https://github.com/tauri-apps/wry/commit/cbbcccc38af7d900a0f8f7fa5ea5e6667765ed81) ([#1446](https://github.com/tauri-apps/wry/pull/1446) by [@FabianLars](https://github.com/tauri-apps/wry/../../FabianLars)) Add functionality to set the traffic light inset on macOS. This is required to prevent flickers if the WebView is injected via `build()` instead of `build_as_child()`. + +### enhance + +- [`26abf63`](https://github.com/tauri-apps/wry/commit/26abf63d79c4a0fcff8ea39d0ff677686f5b546c) ([#1445](https://github.com/tauri-apps/wry/pull/1445) by [@bastiankistner](https://github.com/tauri-apps/wry/../../bastiankistner)) Add an option to change the default background throttling policy (currently for WebKit only). + +## \[0.48.0] + +- [`eb0c816`](https://github.com/tauri-apps/wry/commit/eb0c8163f1e2a11115ef1928b5e4bf0a8f083f3b) ([#1423](https://github.com/tauri-apps/wry/pull/1423) by [@SpikeHD](https://github.com/tauri-apps/wry/../../SpikeHD)) Rename `{WebViewBuilderExtWindows, WebViewBuilderExtUnix}::with_extension_path` to `with_extensions_path`. +- [`b4ba5b4`](https://github.com/tauri-apps/wry/commit/b4ba5b47f235df503085150671ff16189d3d9ea9) ([#1426](https://github.com/tauri-apps/wry/pull/1426) by [@FabianLars](https://github.com/tauri-apps/wry/../../FabianLars)) On Windows, Wry will now log PostMessage errors instead of panicking. +- [`f0c6b94`](https://github.com/tauri-apps/wry/commit/f0c6b947dddc8f1b208157a664a6cc940f123b3c) ([#1441](https://github.com/tauri-apps/wry/pull/1441) by [@renovate](https://github.com/tauri-apps/wry/../../renovate)) Updated `webview2-com` to `0.34`. + +## \[0.47.2] + +- [`7bb4f49`](https://github.com/tauri-apps/wry/commit/7bb4f4929eddbde8f36472a55ec3713d6d51c0e3) ([#1421](https://github.com/tauri-apps/wry/pull/1421) by [@SpikeHD](https://github.com/tauri-apps/wry/../../SpikeHD)) Fix extension loading on Windows. + +## \[0.47.1] + +- [`59c1eef`](https://github.com/tauri-apps/wry/commit/59c1eef0805ecbb1f70a7c78578ff4e03b09a204) ([#1418](https://github.com/tauri-apps/wry/pull/1418) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) Fix initialization scripts running twice on Windows. + +## \[0.47.0] + +- [`7221256`](https://github.com/tauri-apps/wry/commit/72212568cb4d815463fc035969f9cac60fe28ba6) ([#1365](https://github.com/tauri-apps/wry/pull/1365) by [@Norbiros](https://github.com/tauri-apps/wry/../../Norbiros)) Add `WebViewBuilder::with_initialization_script_for_main_only` to enable injecting JavaScript code into main frame only or all subframes. +- [`c1b26b9`](https://github.com/tauri-apps/wry/commit/c1b26b9612bf5c5a9e4e0185f73739a2444343cd) ([#1394](https://github.com/tauri-apps/wry/pull/1394) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) Add `WebView::cookies` and `WebView::cookies_for_url` APIs. +- [`c193e2a`](https://github.com/tauri-apps/wry/commit/c193e2a04c369b7699cd0d73049e84b6851b5c06) ([#1408](https://github.com/tauri-apps/wry/pull/1408) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) Fix `DragDropEvent::Drop` event never fired on Wayland (and sometimes on X11). +- [`1d63fa3`](https://github.com/tauri-apps/wry/commit/1d63fa325327a02a0a8be9ee50ce1eb7a0e8e04f) ([#1403](https://github.com/tauri-apps/wry/pull/1403) by [@SpikeHD](https://github.com/tauri-apps/wry/../../SpikeHD)) Add `WebViewBuilder::with_extension_path` API to Windows and Linux. +- [`0c192f4`](https://github.com/tauri-apps/wry/commit/0c192f4fda1d9c0020bd3ad09a9090bca25ef04f) ([#1414](https://github.com/tauri-apps/wry/pull/1414) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fix Android static handlers not being replaced when the application UI is relaunched while still running in the foreground. +- [`9a2a2d4`](https://github.com/tauri-apps/wry/commit/9a2a2d42b635a1e27bd6a8f67f7f7b1c59acc7db) ([#1412](https://github.com/tauri-apps/wry/pull/1412) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) Fix icons of dragged items getting stuck when using `WebViewBuilder::with_drag_drop_handler` on some distros like Gnome. +- [`fa9875b`](https://github.com/tauri-apps/wry/commit/fa9875bb16dd967520c41e50c562a5aabccc2cbc) ([#1409](https://github.com/tauri-apps/wry/pull/1409) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) On Windows, disable Webview2's file drop when using `WebViewBuilder::with_drag_drop_handler` which fix drag events for files from "Recent files" view. +- [`6007608`](https://github.com/tauri-apps/wry/commit/600760827735696e4099eff1ad500baa69d84d2f) ([#1400](https://github.com/tauri-apps/wry/pull/1400) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) On Windows, fix webview slightly larger than the window inner size, which resulted in a hidden 1px in the right and bottom borders of the webview + +## \[0.46.3] + +- [`be122f6`](https://github.com/tauri-apps/wry/commit/be122f667f9f5516b4bc25f9e8c61cb99dbe1440) ([#1397](https://github.com/tauri-apps/wry/pull/1397) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fix `with_user_agent` regression. + +## \[0.46.2] + +- [`1189e2a`](https://github.com/tauri-apps/wry/commit/1189e2a2d5ee18ad83281eea92b05a93f9184ebf) ([#1392](https://github.com/tauri-apps/wry/pull/1392) by [@chrox](https://github.com/tauri-apps/wry/../../chrox)) Fix malformed headers in custom protocol response on macOS. + +## \[0.46.1] + +### bug + +- [`33c0193`](https://github.com/tauri-apps/wry/commit/33c01931a08b2178bb37b03b762d82f20f10aa3b) ([#1389](https://github.com/tauri-apps/wry/pull/1389) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) Fix crash on macOS versions below 14. +- [`a57719e`](https://github.com/tauri-apps/wry/commit/a57719e6d63c8dbb002905e54c19d9d2f82c12de) ([#1385](https://github.com/tauri-apps/wry/pull/1385) by [@huacnlee](https://github.com/tauri-apps/wry/../../huacnlee)) Add `WebView::focus_parent` method. + +## \[0.46.0] + +- [`8cc2a7f`](https://github.com/tauri-apps/wry/commit/8cc2a7f6570085da5d6a5ea57ad7f127520b778f) ([#1384](https://github.com/tauri-apps/wry/pull/1384) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) This release contains quite the breaking changes, because even though wry@0.44, ignored duplicate custom protocols, On Linux when using a shared web context, the custom protocol handler can only be registered once so we are bringing the duplicate custom protocols on Linux again, Windows and macOS are not affected. If using a shared web context, make sure to register a protocol only once on Linux (other platforms should be registed multiple times), use `WebContext::is_custom_protocol_registered` with `#[cfg(target_os = "linux")]`. + + We also noticed that it is hard to know which webview made a request to the custom protocol so we added a method to attach an ID to a webview, and changed relevant custom protocol APIs to take a new argument that passes the specified id back to protocol handler. + + We also made a few changes to the builder, specifically `WebViewBuilder::new` and `WebViewBuilder::build` methods to make them more ergonomic to work with. + + - Added `Error::DuplicateCustomProtocol` enum variant. + - Added `Error::ContextDuplicateCustomProtocol` enum variant. + - On Linux, return an error in `WebViewBuilder::build` if registering a custom protocol multiple times. + - Added `WebContext::is_custom_protocol_registered` to check if a protocol has been regsterd for this web context. + - Added `WebViewId` alias type. + - **Breaking** Changed `WebViewAttributes` to have a lifetime parameter. + - Added `WebViewAttributes.id` field to specify an id for the webview. + - Added `WebViewBuilder::with_id` method to specify an id for the webview. + - Added `WebViewAttributes.context` field to specify a shared context for the webview. + - **Breaking** Changed `WebViewAttributes.custom_protocols` field,`WebViewBuilder::with_custom_protocol` method and `WebViewBuilder::with_asynchronous_custom_protocol` method handler function to take `WebViewId` as the first argument to check which webview made the request to the protocol. + - **Breaking** Changed `WebViewBuilder::with_web_context` to be a static method to create a builder with a webcontext, instead of it being a setter method. It is now an alternative to `WebviewBuilder::new` + - Added `WebViewBuilder::with_attributes` to create a webview builder with provided attributes. + - **Breaking** Changed `WebViewBuilder::new` to take no arguments. + - **Breaking** Changed `WebViewBuilder::build` method to take a reference to a window to create the webview in it. + - **Breaking** Removed `WebViewBuilder::new_as_child`. + - Added `WebViewBuilder::build_as_child` method, which takes a reference to a window to create the webview in it. + - **Breaking** Removed `WebViewBuilderExtUnix::new_gtk`. + - Added `WebViewBuilderExtUnix::build_gtk`. +- [`0abc221`](https://github.com/tauri-apps/wry/commit/0abc221ca0edf3482518a68af024f9988b10f50b) ([#1316](https://github.com/tauri-apps/wry/pull/1316) by [@pewsheen](https://github.com/tauri-apps/wry/../../pewsheen)) Migrate to obj2. +- [`b01eac3`](https://github.com/tauri-apps/wry/commit/b01eac35f521e4e763fc15753e2fbee68aa31a02) ([#1386](https://github.com/tauri-apps/wry/pull/1386) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Use unescaped Android package identifier for the proguard rules. + +## \[0.45.0] + +- [`0fd1229`](https://github.com/tauri-apps/wry/commit/0fd12297997f598e4893e8f5b6e235b09cedec09) ([#1369](https://github.com/tauri-apps/wry/pull/1369) by [@lloydzhou](https://github.com/tauri-apps/wry/../../lloydzhou)) On Linux, fixed incorrect path for indexeddb database directory which made apps using `wry@0.24` and `tauri@1` migrating to `wry@>=0.38` and `tauri@2` lose their indexeddb data. +- [`e332eff`](https://github.com/tauri-apps/wry/commit/e332eff6ac41ff0f4ed6cf5196c3a78c776912d3) ([#1368](https://github.com/tauri-apps/wry/pull/1368) by [@zephraph](https://github.com/tauri-apps/wry/../../zephraph)) Add `Webview::load_html`. + +## \[0.44.1] + +- [`5111eb0`](https://github.com/tauri-apps/wry/commit/5111eb013e1b049d12aad38b96b2017a4fc54c72) ([#1362](https://github.com/tauri-apps/wry/pull/1362) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fixes `WebView::clear_all_browsing_data` crashing with a segfault on macOS. + +## \[0.44.0] + +- [`b863d38`](https://github.com/tauri-apps/wry/commit/b863d38ff705e037b297c1651e17775d3dbe473c) ([#1356](https://github.com/tauri-apps/wry/pull/1356) by [@SpikeHD](https://github.com/tauri-apps/wry/../../SpikeHD)) Expose ability to enable browser extensions in WebView2 +- [`9220793`](https://github.com/tauri-apps/wry/commit/92207933c9de4a0c1ef65ecbacb0c303619ced4c) ([#1361](https://github.com/tauri-apps/wry/pull/1361) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) Ignore duplicate custom protocols in `WebviewBuilder::with_custom_protocol` and `WebviewBuilder::with_async_custom_protocol` and use the last registered one. +- [`9220793`](https://github.com/tauri-apps/wry/commit/92207933c9de4a0c1ef65ecbacb0c303619ced4c) ([#1361](https://github.com/tauri-apps/wry/pull/1361) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) Removed `Error::DuplicateCustomProtocol` variant. +- [`5915341`](https://github.com/tauri-apps/wry/commit/59153413d87549964b885ad3ed84473acf4b70df) ([#1354](https://github.com/tauri-apps/wry/pull/1354) by [@millermk](https://github.com/tauri-apps/wry/../../millermk)) Fixes Android webview error page flashing when a redirect to the app is performed. +- [`170095b`](https://github.com/tauri-apps/wry/commit/170095b9a5829ac9279cfeea3fac0da2c922d448) ([#1360](https://github.com/tauri-apps/wry/pull/1360) by [@Steve-xmh](https://github.com/tauri-apps/wry/../../Steve-xmh)) Fix web resource loading in android binding by skip duplicate Content-Type/Content-Length headers. +- [`5915341`](https://github.com/tauri-apps/wry/commit/59153413d87549964b885ad3ed84473acf4b70df) ([#1354](https://github.com/tauri-apps/wry/pull/1354) by [@millermk](https://github.com/tauri-apps/wry/../../millermk)) Fix navigation error handling to trigger custom protocol on Android. + +## \[0.43.1] + +- [`5cea504`](https://github.com/tauri-apps/wry/commit/5cea5045c9e646ace1f94767d91b746c2afd3c14) ([#1352](https://github.com/tauri-apps/wry/pull/1352) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fixes Android file picker result processing. + +## \[0.43.0] + +- [`7b1c26a`](https://github.com/tauri-apps/wry/commit/7b1c26adff5fdf13a3364b6fc26c8444c120d5c1) ([#1344](https://github.com/tauri-apps/wry/pull/1344) by [@Themayu](https://github.com/tauri-apps/wry/../../Themayu)) Windows: Implement `WebViewBuilderExtWindows::with_scroll_bar_style` to allow opting into Fluent Overlay style scrollbars. +- [`98d1a83`](https://github.com/tauri-apps/wry/commit/98d1a835e2c818e9f733a55b074e7de3c0cd725f) ([#1326](https://github.com/tauri-apps/wry/pull/1326) by [@ollpu](https://github.com/tauri-apps/wry/../../ollpu)) Fix Linux IPC handler and initialization scripts when sharing a WebContext between multiple WebViews. + +## \[0.42.0] + +- [`556a359`](https://github.com/tauri-apps/wry/commit/556a359d3739ac3a3e671ab2d1c3dda2784c511e) ([#1301](https://github.com/tauri-apps/wry/pull/1301) by [@pewsheen](https://github.com/tauri-apps/wry/../../pewsheen)) On macOS, emit an error when the URL scheme registration fails. +- [`19d83d7`](https://github.com/tauri-apps/wry/commit/19d83d7d01ef9704197c09e6197a6da31a4eec6c) ([#1332](https://github.com/tauri-apps/wry/pull/1332) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fixes a crash on the Android custom protocol handler when the request URL is invalid. +- [`38abcb9`](https://github.com/tauri-apps/wry/commit/38abcb95090a3917ad5092f4933acb95c721f893) ([#1340](https://github.com/tauri-apps/wry/pull/1340) by [@lucasfernog](https://github.com/tauri-apps/wry/../../lucasfernog)) Fixes custom protocols not triggered on Android on external redirects. +- [`d1f1e7e`](https://github.com/tauri-apps/wry/commit/d1f1e7e6fa82722d9848fbb540e6f9a3d5925cdd) ([#1299](https://github.com/tauri-apps/wry/pull/1299) by [@kanatapple](https://github.com/tauri-apps/wry/../../kanatapple)) Fix `Webview::bounds` returning logical values where it should have been physical. +- [`03cdf93`](https://github.com/tauri-apps/wry/commit/03cdf93f1e39469a1cd73565ba61138c58567fb6) ([#1311](https://github.com/tauri-apps/wry/pull/1311) by [@bukowa](https://github.com/tauri-apps/wry/../../bukowa)) Handle `webkit2gtk` close signal (when `window.close` is called from js) +- [`68413e8`](https://github.com/tauri-apps/wry/commit/68413e837f81a88e8de0df39d12ba2b405b89e5e) ([#1296](https://github.com/tauri-apps/wry/pull/1296) by [@MarijnS95](https://github.com/tauri-apps/wry/../../MarijnS95)) **Breaking change**: Upgrade `ndk` crate to `0.9` and delete unused `ndk-sys` and `ndk-context` dependencies. Types from the `ndk` crate are used in public API surface. + **Breaking change**: The public `android_setup()` function now takes `&ThreadLooper` instead of `&ForeignLooper`, signifying that the setup function must be called on the thread where the looper is attached (and the `JNIEnv` argument is already thread-local as well). +- [`39fc82c`](https://github.com/tauri-apps/wry/commit/39fc82c9276bd039a9130919645db149f067719a) ([#1306](https://github.com/tauri-apps/wry/pull/1306) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Support WebView2 version older than 101.0.1210.39 and document `incognito` and `theme` will not work for versions before it +- [`5231a37`](https://github.com/tauri-apps/wry/commit/5231a379d09c9eea27f490b3062129dd474cb44c) ([#1322](https://github.com/tauri-apps/wry/pull/1322) by [@Legend-Master](https://github.com/tauri-apps/wry/../../Legend-Master)) Support WebView2 version older than 86.0.616.0 and document version requirements for `back_forward_navigation_gestures`, `with_user_agent`, `with_hotkeys_zoom`, `with_browser_accelerator_keys` +- [`a23a28d`](https://github.com/tauri-apps/wry/commit/a23a28d32a01a0a4c8e62e83d34813ec7db4a004) ([#1341](https://github.com/tauri-apps/wry/pull/1341) by [@amrbashir](https://github.com/tauri-apps/wry/../../amrbashir)) Updated `windows` to 0.58. + +## \[0.41.0] + +- [`8b691df`](https://github.com/tauri-apps/wry/commit/8b691df1ac57eb5eb15082c5f6d72e871965c61e) ([#1285](https://github.com/tauri-apps/wry/pull/1285) by [@pewsheen](https://github.com/tauri-apps/wry/../../pewsheen)) On macOS, fix an issue that could cause a panic when running an async command. +- [`6c7f45e`](https://github.com/tauri-apps/wry/commit/6c7f45e1e3f89805a9fd6b58a9382a6bbc2b0c28) ([#1287](https://github.com/tauri-apps/wry/pull/1287) by [@FabianLars](https://github.com/tauri-apps/wry/../../FabianLars)) Fixed a regression causing autoplay on windows to require user gestures. +- [`24a7d27`](https://github.com/tauri-apps/wry/commit/24a7d275c9059dd9e54544b75a5996c9d0762f26) ([#1289](https://github.com/tauri-apps/wry/pull/1289) by [@renovate](https://github.com/tauri-apps/wry/../../renovate)) Update `windows` crate to `0.57` and `webview2-com` crate to `0.31` + +## \[0.40.1] + +- [`b6863ed`](https://github.com/tauri-apps/wry/commit/b6863ed1884fb190ae46f37ed72dcdd92de700cd)([#1275](https://github.com/tauri-apps/wry/pull/1275)) On Android, set `RustWebViewClient.currentUrl` field early in `onPageStarted` method instead of `onPageFinished` +- [`f089964`](https://github.com/tauri-apps/wry/commit/f089964a3cf3014987aca24a7e7d6cae83e67d8a)([#1276](https://github.com/tauri-apps/wry/pull/1276)) Fixes `with_asynchronous_custom_protocol` crashing when sending the response on Linux. +- [`637289d`](https://github.com/tauri-apps/wry/commit/637289dfb36150635177eb629a12b40fdaac1afe)([#1272](https://github.com/tauri-apps/wry/pull/1272)) On Android, make `WryActivity.setWebview` method public to prevent JNI crashes. + +## \[0.40.0] + +- [`a424a0b`](https://github.com/tauri-apps/wry/commit/a424a0b234cb20b3ca7305d87e82aba3c8b2bd41)([#1270](https://github.com/tauri-apps/wry/pull/1270)) On Windows, fix child webview invisible after creation because it was created with `0,0` size +- [`d6f8dd7`](https://github.com/tauri-apps/wry/commit/d6f8dd7b6c0485fbb96fed34717969540eef2b96)([#1271](https://github.com/tauri-apps/wry/pull/1271)) On Windows, create child webview at the top of z-order to align with other platforms. +- [`03d2535`](https://github.com/tauri-apps/wry/commit/03d25357d2c20a21640871cfca9d5f6a39c7afc8)([#1269](https://github.com/tauri-apps/wry/pull/1269)) On macOS, disable initialization script injection into subframes. +- [`1e65049`](https://github.com/tauri-apps/wry/commit/1e65049d4842947ced6a807b93211542c46ca771)([#1267](https://github.com/tauri-apps/wry/pull/1267)) On macOS, fixed a crash when sending empty body by IPC. +- [`0f3c886`](https://github.com/tauri-apps/wry/commit/0f3c886a224a1b52980ef90667860e58a6ad669a)([#1260](https://github.com/tauri-apps/wry/pull/1260)) On macOS, fixed an issue of not being able to listen to the cmd+key event in javascript in single WebView. +- [`0f14e2a`](https://github.com/tauri-apps/wry/commit/0f14e2a540a1d54f82bdee2a3c2f93c43c593959)([#1259](https://github.com/tauri-apps/wry/pull/1259)) Default the margin when printing on MacOS to 0 so it is closer to the behavior of when printing on the web. +- [`0f14e2a`](https://github.com/tauri-apps/wry/commit/0f14e2a540a1d54f82bdee2a3c2f93c43c593959)([#1259](https://github.com/tauri-apps/wry/pull/1259)) Add `WebViewExtMacOS::print_with_options` which allows to modify the margins that will be used on the print dialog. +- [`f516122`](https://github.com/tauri-apps/wry/commit/f5161225940c545dd457af1178c73f36dfe63710)([#1262](https://github.com/tauri-apps/wry/pull/1262)) On Windows, enable webview2 [non client region support](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2settings9?view=webview2-1.0.2478.35#get_isnonclientregionsupportenabled) which allows using `app-region` CSS style. + +## \[0.39.5] + +- [`4c88c66`](https://github.com/tauri-apps/wry/commit/4c88c66fb79fc3742f4592252c260e7e012d5fcf)([#1247](https://github.com/tauri-apps/wry/pull/1247)) Force the IPC and custom protocol tracing spans to have no parent. +- [`2d43d62`](https://github.com/tauri-apps/wry/commit/2d43d62a8e61514ade27ae63fa33c1dee2de6744)([#1254](https://github.com/tauri-apps/wry/pull/1254)) On Windows, fix webview having a bigger size than the actual window size after creation and until the window is resized. + +## \[0.39.4] + +- [`8bbc2bf`](https://github.com/tauri-apps/wry/commit/8bbc2bf388113af2e7d91250abe1569070b351a9)([#1237](https://github.com/tauri-apps/wry/pull/1237)) Fix `WebviewBuilder::with_transparent`, `WebviewBuilder::with_background_color`, and `Webview::set_background_color` always failing and causing the webview to fail to load. +- [`130c469`](https://github.com/tauri-apps/wry/commit/130c46965d0cd0ae2389d2fa9b683488a16e0cc8)([#1238](https://github.com/tauri-apps/wry/pull/1238)) Add `WebViewBuilderExtDarwin::with_data_store_identifier`. +- [`203604c`](https://github.com/tauri-apps/wry/commit/203604c519e4acb169676b20ddf5956ba21b4d57)([#1233](https://github.com/tauri-apps/wry/pull/1233)) On Windows, fix the webview not filling up the whole window if the parent window was resized during the webview initialization. + +## \[0.39.3] + +- [`c7ca3db`](https://github.com/tauri-apps/wry/commit/c7ca3db581bbeb4f16a28f47c3a1fd59889c0978)([#1221](https://github.com/tauri-apps/wry/pull/1221)) On Windows, fix data directory created next to the executable with a gibberish name even if it was explicitly provided in `WebConext::new` + +## \[0.39.2] + +- [`3e3d59c`](https://github.com/tauri-apps/wry/commit/3e3d59cd4f79c21571e503a5bf80d4d54a654a38)([#1215](https://github.com/tauri-apps/wry/pull/1215)) On macOS, prevent NSExceptions and invalid memory access panics when dropping the WebView while custom protocols handlers may still be running. +- [`ca6b5fb`](https://github.com/tauri-apps/wry/commit/ca6b5fbef6e5a5efe43b5cbebe6bfc4bc13930d3)([#1224](https://github.com/tauri-apps/wry/pull/1224)) Update `windows` crate to `0.56` + +## \[0.39.1] + +- [`f0e82d3`](https://github.com/tauri-apps/wry/commit/f0e82d3aa2da9da2b935d97c9a9b5e2dbd65b6ea)([#1217](https://github.com/tauri-apps/wry/pull/1217)) Fix target detection on build script to enhance cross compiling capabilities. +- [`ed9fa9b`](https://github.com/tauri-apps/wry/commit/ed9fa9b3950206548cdaf0bcdb6c2d5fb72619b3)([#1210](https://github.com/tauri-apps/wry/pull/1210)) On iOS, allows media plays inline. + +## \[0.39.0] + +- [`ddda455`](https://github.com/tauri-apps/wry/commit/ddda4556b36a41b1c6f3f4d200eb16612d5f3f12)([#1207](https://github.com/tauri-apps/wry/pull/1207)) Disable deprecated applicationCache web api. This api was completely removed upstream in webkitgtk 2.44. +- [`d7031ae`](https://github.com/tauri-apps/wry/commit/d7031aed8eebc6324e4b3db46ee53120ce24930b)([#1206](https://github.com/tauri-apps/wry/pull/1206)) On Windows, fix a crash due to a double-free when the host window is destroyed before the webview is dropped. +- [`34ae1ca`](https://github.com/tauri-apps/wry/commit/34ae1ca3af75c471f77b90fd342bbcc79ac7189a)([#1202](https://github.com/tauri-apps/wry/pull/1202)) Add `dpi` module which is a re-export of `dpi` crate. +- [`fdbd3d3`](https://github.com/tauri-apps/wry/commit/fdbd3d3c614acd42dddb49583d16de6b3f02e62d)([#1081](https://github.com/tauri-apps/wry/pull/1081)) Update `http` dependency to `1` +- [`34ae1ca`](https://github.com/tauri-apps/wry/commit/34ae1ca3af75c471f77b90fd342bbcc79ac7189a)([#1202](https://github.com/tauri-apps/wry/pull/1202)) **Breaking Change**: Removed `x`, `y`, `with` and `height` fields from `Rect` struct and replaced it with `size` and `position` fields. +- [`c033bd2`](https://github.com/tauri-apps/wry/commit/c033bd27f23953537520d17493c7b77ea146e7d5)([#1156](https://github.com/tauri-apps/wry/pull/1156)) On `macOS`, fix menu keyboard shortcuts when added `webview` as `child`. + +## \[0.38.2] + +- [`3e84a0e`](https://github.com/tauri-apps/wry/commit/3e84a0e276dfac0b28fb01f42460f9367fff9f22)([#1200](https://github.com/tauri-apps/wry/pull/1200)) Fixes compilation for 32bit Linux targets. + +## \[0.38.1] + +- [`7c9e71f`](https://github.com/tauri-apps/wry/commit/7c9e71f4692e94fd401ad3508ff3912d43880e2c)([#1192](https://github.com/tauri-apps/wry/pull/1192)) Fixes compilation failing on Windows with the `tracing` feature enabled. + +## \[0.38.0] + +- [`e6f0fbd`](https://github.com/tauri-apps/wry/commit/e6f0fbd33365070af46361605a922ba24e542fb5)([#1180](https://github.com/tauri-apps/wry/pull/1180)) Fixes a null pointer exception when running `window.ipc.postMessage(null)` on Android. +- [`5789bf7`](https://github.com/tauri-apps/wry/commit/5789bf759ce94e4dad5ff26a08fe81521658a4e4)([#1187](https://github.com/tauri-apps/wry/pull/1187)) **Breaking change**: Refactored the file-drop handling on the webview for better representation of the actual drag and drop operation: + + - Renamed `file-drop` cargo feature flag to `drag-drop`. + - Removed `FileDropEvent` enum and replaced with a new `DragDropEvent` enum. + - Renamed `WebViewAttributes::file_drop_handler` field to `WebViewAttributes::drag_drop_handler`. + - Renamed `WebViewAttributes::with_file_drop_handler` method to `WebViewAttributes::with_drag_drop_handler`. +- [`b8fea39`](https://github.com/tauri-apps/wry/commit/b8fea396c2eca289e2f930ad635a15397b7c0dda)([#1183](https://github.com/tauri-apps/wry/pull/1183)) Changed `WebViewBuilder::with_ipc_handler` closure to take `http::Request` instead of `String` so the request URL is available. +- [`3a2026b`](https://github.com/tauri-apps/wry/commit/3a2026b37be67dea53535f0a7d78b32452ac8b40)([#1182](https://github.com/tauri-apps/wry/pull/1182)) **Breaking changes**: Changed a few methods on `WebView` type to return a `Result`: + + - `Webview::url` + - `Webview::zoom` + - `Webview::load_url` + - `Webview::load_url_with_headers` + - `Webview::bounds` + - `Webview::set_bounds` + - `Webview::set_visible` + - `WebviewExtWindows::set_theme` + - `WebviewExtWindows::set_memory_usage_level` + - `WebviewExtWindows::reparent` + - `WebviewExtUnix::reparent` + - `WebviewExtMacOS::reparent` +- [`e1e2e07`](https://github.com/tauri-apps/wry/commit/e1e2e071e5329bc1a94864e368fdaa3041e79427)([#1190](https://github.com/tauri-apps/wry/pull/1190)) Update `webview2-com` crate to `0.29` +- [`e1e2e07`](https://github.com/tauri-apps/wry/commit/e1e2e071e5329bc1a94864e368fdaa3041e79427)([#1190](https://github.com/tauri-apps/wry/pull/1190)) Update `windows` crate to `0.54` +- [`00bc96d`](https://github.com/tauri-apps/wry/commit/00bc96d115879c841fc47242271db3761d19f746)([#1179](https://github.com/tauri-apps/wry/pull/1179)) Added `WryActivity::onWebViewCreate(android.webkit.WebView)` on Android. + +## \[0.37.0] + +- [`8c86fba`](https://github.com/tauri-apps/wry/commit/8c86fbaf51cd970737cc070583318d4b532349d2) **Breaking change**: Removed `data:` url support, as its native support in Windows and macOS are buggy and unreliable, use `Webview::with_html` instead. +- [`8c86fba`](https://github.com/tauri-apps/wry/commit/8c86fbaf51cd970737cc070583318d4b532349d2) On Linux, decode `FilDropEvent` paths before emitting them to make it consistent across all platforms. +- [`8c86fba`](https://github.com/tauri-apps/wry/commit/8c86fbaf51cd970737cc070583318d4b532349d2) Added `WebViewExtMacOS::reparent`,`WebViewExtWindows::reparent` and `WebViewExtUnix::reparent`. +- [`8c86fba`](https://github.com/tauri-apps/wry/commit/8c86fbaf51cd970737cc070583318d4b532349d2) Revert global keys shortcuts (wry#1156) +- [`8c86fba`](https://github.com/tauri-apps/wry/commit/8c86fbaf51cd970737cc070583318d4b532349d2) **Breaking change**: Removed internal url parsing which had a few side-effects such as encoded url content, now it is up to the user to pass a valid URL as a string. This also came with a few breaking changes: + + - Removed `Url` struct re-export + - Removed `Error::UrlError` variant. + - Changed `WebviewAttributes::url` field type to `String`. + - Changed `WebviewBuilder::with_url` and `WebviewBuilder::with_url_and_headers` return type to `WebviewBuilder` instead of `Result`. + - Changed `Webview::url` getter to return a `String` instead of `Url`. + +## \[0.36.0] + +- [`8646120`](https://github.com/tauri-apps/wry/commit/8646120339b8ed983582caa9e668fc286dc59cb3)([#1159](https://github.com/tauri-apps/wry/pull/1159)) On android, fix `no non-static method ".evalScript(ILjava/lang/String;)"` when calling `Window::eval`. +- [`8646120`](https://github.com/tauri-apps/wry/commit/8646120339b8ed983582caa9e668fc286dc59cb3)([#1159](https://github.com/tauri-apps/wry/pull/1159)) On macOS, fix a release build crashes with SEGV when calling `WebView::evaluate_script`. This crash bug was introduced at v0.35.2. +- [`8646120`](https://github.com/tauri-apps/wry/commit/8646120339b8ed983582caa9e668fc286dc59cb3)([#1159](https://github.com/tauri-apps/wry/pull/1159)) **Breaking change** Update [raw-window-handle](https://crates.io/crates/raw-window-handle) crate to v0.6. + + - `HasWindowHandle` trait is required for window types instead of `HasRawWindowHandle`. + - `wry::raw_window_handle` now re-exports v0.6. +- [`8646120`](https://github.com/tauri-apps/wry/commit/8646120339b8ed983582caa9e668fc286dc59cb3)([#1159](https://github.com/tauri-apps/wry/pull/1159)) On `macOS`, fix menu keyboard shortcuts. This issue bug was introduced in `v2` when added `webview` as `child`. + +## \[0.35.2] + +- [`0ef041f`](https://github.com/tauri-apps/wry/commit/0ef041ffece143dcb5059ad43596c63b18a62928)([#1133](https://github.com/tauri-apps/wry/pull/1133)) On Linux, apply passed webview bounds when using `WebView::new_gtk` or `WebViewBuilder::new_gtk` with `gtk::Fixed` widget. This allows to create multiple webviews inside `gtk::Fixed` in the same window. +- [`0ef041f`](https://github.com/tauri-apps/wry/commit/0ef041ffece143dcb5059ad43596c63b18a62928)([#1133](https://github.com/tauri-apps/wry/pull/1133)) Added tracing spans for `evaluate_script`, `ipc_handler` and `custom_protocols` behind the `tracing` feature flag. + +## \[0.35.1] + +- [`a4a39b9`](https://github.com/tauri-apps/wry/commit/a4a39b9b23da3c562f27730dd0eab09b9459755b)([#1098](https://github.com/tauri-apps/wry/pull/1098)) Fix the API documentation cannot be built on docs.rs. +- [`e116d42`](https://github.com/tauri-apps/wry/commit/e116d427319d1adbc14d418e78c43ddb49b70d76)([#1111](https://github.com/tauri-apps/wry/pull/1111)) Fix screen share permissions dialog not showing up on macOS 14.0+ +- [`a8c0d38`](https://github.com/tauri-apps/wry/commit/a8c0d384fc51b12d2436c11d10fd8c2dfdcd9d4a)([#1097](https://github.com/tauri-apps/wry/pull/1097)) Fix IPC crash on wkwebview if receiving invalid types. +- [`8fddbb6`](https://github.com/tauri-apps/wry/commit/8fddbb6d514de8fa0561bd6631ff8a3699911ddd)([#1091](https://github.com/tauri-apps/wry/pull/1091)) Add `WebView::bounds` getter. +- [`30a85f3`](https://github.com/tauri-apps/wry/commit/30a85f31141839a5284b1bfdd52b1cb690fcd10d)([#1122](https://github.com/tauri-apps/wry/pull/1122)) On Windows, fix file drop handler. + +## \[0.35.0] + +- [`e61e7f8`](https://github.com/tauri-apps/wry/commit/e61e7f8474c18752f5c60d3f1f5ba33b27e41d52)([#1090](https://github.com/tauri-apps/wry/pull/1090)) **Breaking change** Consistently use `WebView` in API names. The following APIs were renamed: + + - `WebviewExtWindows` → `WebViewExtWindows` + - `WebviewExtUnix` → `WebViewExtUnix` + - `WebviewExtMacOS` → `WebViewExtMacOS` + - `WebviewExtIOS` → `WebViewExtIOS` + - `WebviewExtAndroid` → `WebViewExtAndroid` + - `WebviewUriLoader` → `WebViewUriLoader` +- [`e61e7f8`](https://github.com/tauri-apps/wry/commit/e61e7f8474c18752f5c60d3f1f5ba33b27e41d52)([#1090](https://github.com/tauri-apps/wry/pull/1090)) Add `WebViewExtWindows::set_memory_usage_level` API to set the [memory usage target level](https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2memoryusagetargetlevel) on Windows. Setting 'Low' memory usage target level when an application is going to inactive can significantly reduce the memory consumption. Please read the [guide for WebView2](https://github.com/MicrosoftEdge/WebView2Feedback/blob/main/specs/MemoryUsageTargetLevel.md) for more details. +- [`e61e7f8`](https://github.com/tauri-apps/wry/commit/e61e7f8474c18752f5c60d3f1f5ba33b27e41d52)([#1090](https://github.com/tauri-apps/wry/pull/1090)) - Add cfg_aliases for easier feature configuration. And add `os-webview` as default feature. +- [`e61e7f8`](https://github.com/tauri-apps/wry/commit/e61e7f8474c18752f5c60d3f1f5ba33b27e41d52)([#1090](https://github.com/tauri-apps/wry/pull/1090)) Enhance initalization script implementation on Android supporting any kind of URL. +- [`e61e7f8`](https://github.com/tauri-apps/wry/commit/e61e7f8474c18752f5c60d3f1f5ba33b27e41d52)([#1090](https://github.com/tauri-apps/wry/pull/1090)) Fix wkwebview crashed when received invalid UTF8 string from IPC. +- [`e61e7f8`](https://github.com/tauri-apps/wry/commit/e61e7f8474c18752f5c60d3f1f5ba33b27e41d52)([#1090](https://github.com/tauri-apps/wry/pull/1090)) Refactor new method to take raw window handle instead. Following are APIs got affected: + + - `application` module is removed, and `webivew` module is moved to root module. + - `WebViewBuilder::new`, `WebView::new` now take `RawWindowHandle` instead. + - Add `WebViewBuilder::new_as_child`, `WebView::new_as_child` to crate a webview as a child inside a parent window. + - `Webview::inner_size` is removed. + - Add `WebViewBuilderExtUnix` trait to extend `WebViewBuilder` on Unix platforms. + - Add `new_gtk` functions to `WebViewBuilderExtUnix` and `WebviewExtUnix`. + - [raw-window-handle](https://docs.rs/raw-window-handle/latest/raw_window_handle/) crate is re-exported as `wry::raw_window_handle`. + + This also means that we removed `tao` as a dependency completely which required some changes to the public APIs and to the Android backend: + + - Webview attributes `ipc_handler`, `file_drop_handler`, `document_change_handler` don't take the `Window` as first parameter anymore. + Users should use closure to capture the types they want to use. + - Position field in `FileDrop` event is now a tuple of `(x, y)` physical position instead of `PhysicalPosition`. Users need to handle scale factor + - We exposed the `android_setup` function that needs to be called once to setup necessary logic. + - Previously the `android_binding!` had internal call to `tao::android_binding` but now that `tao` has been removed, + the macro signature has changed and you now need to call `tao::android_binding` yourself, checkout the crate documentation for more information. + +## \[0.34.2] + +- [`c2e6980`](https://github.com/tauri-apps/wry/commit/c2e6980b6cacf02b3f8c0b0285d391d010f4536b)([#1047](https://github.com/tauri-apps/wry/pull/1047)) Fix doc building by removing dox feature requirement from `webkit2gtk`. +- [`82908d4`](https://github.com/tauri-apps/wry/commit/82908d4e001d1be6fd5d692fcb2e08908c4b5e16)([#1045](https://github.com/tauri-apps/wry/pull/1045)) Fix docs.rs build. + +## \[0.34.1] + +- [`3de68e7`](https://github.com/tauri-apps/wry/commit/3de68e781d52f3c817473c1ee8cc73b392d60c98)([#1043](https://github.com/tauri-apps/wry/pull/1043)) Fix compilation with the `linux-body` feature. + +## \[0.34.0] + +- [`ce95730`](https://github.com/tauri-apps/wry/commit/ce957301566dfe33f576810982a3eb38813d22ea)([#1036](https://github.com/tauri-apps/wry/pull/1036)) Upgrade gtk to 0.18 and bump MSRV to 1.70.0. +- [`591fda8`](https://github.com/tauri-apps/wry/commit/591fda8045b88ea0edbc5676e2814fb9acb2d6f6)([#1042](https://github.com/tauri-apps/wry/pull/1042)) Use `gtk`'s re-exported modules instead. +- [`b22a19e`](https://github.com/tauri-apps/wry/commit/b22a19e1c19ce90aec6521a66587dac9b0351579)([#1037](https://github.com/tauri-apps/wry/pull/1037)) Update `windows` and `windows-implement` crate to `0.51` + +## \[0.33.1] + +- [`0582cdf`](https://github.com/tauri-apps/wry/commit/0582cdf4a195db5df9c4e21d24039c64b7474683)([#1033](https://github.com/tauri-apps/wry/pull/1033)) Fix documentation for macOS target not being generated on docs.rs. + +## \[0.33.0] + +- [`5adf9da`](https://github.com/tauri-apps/wry/commit/5adf9da2151800ec2431a1547cc0d970fc95b764)([#994](https://github.com/tauri-apps/wry/pull/994)) **Breaking change** Wry now defaults to `http://.localhost/` for custom protocols on Android. +- [`844d95a`](https://github.com/tauri-apps/wry/commit/844d95a4035f68371d64f6b04151982481cdee70)([#1023](https://github.com/tauri-apps/wry/pull/1023)) Fixes async custom protocol resolver on Windows. +- [`5adf9da`](https://github.com/tauri-apps/wry/commit/5adf9da2151800ec2431a1547cc0d970fc95b764)([#994](https://github.com/tauri-apps/wry/pull/994)) Add `WebViewBuilderExtAndroid::with_https_scheme` to be able to choose between `http` and `https` for custom protocols on Android. +- [`c5c3731`](https://github.com/tauri-apps/wry/commit/c5c3731f2027802735f7b80c7ae5f4b64d0fb746)([#1024](https://github.com/tauri-apps/wry/pull/1024)) Add winit-gtk to support winit feature flag on Linux. + +## \[0.32.0] + +- [`4bdf1c3`](https://github.com/tauri-apps/wry/commit/4bdf1c366de5708b7626ca63eb39e134869c5bd4)([#1017](https://github.com/tauri-apps/wry/pull/1017)) Added `WebViewBuilder::with_asynchronous_custom_protocol` to allow implementing a protocol handler that resolves asynchronously. +- [`70d8ae0`](https://github.com/tauri-apps/wry/commit/70d8ae057c5e8b81db4aac28e5fa2dd3424b3307)([#1009](https://github.com/tauri-apps/wry/pull/1009)) Fixes Android freezing when handling request due to endless iteration when reading request headers. +- [`b5e1875`](https://github.com/tauri-apps/wry/commit/b5e1875230794502a8e74c74abe79ca63488e421)([#994](https://github.com/tauri-apps/wry/pull/994)) **Breaking change** Wry now defaults to `http://.localhost/` for custom protocols on Windows. +- [`b5e1875`](https://github.com/tauri-apps/wry/commit/b5e1875230794502a8e74c74abe79ca63488e421)([#994](https://github.com/tauri-apps/wry/pull/994)) Add `WebViewBuilderExtWindows::with_https_scheme` to be able to choose between `http` and `https` for custom protocols on Windows. +- [`fa15076`](https://github.com/tauri-apps/wry/commit/fa15076207d9e678db4149210aba929044d0ff45)([#163](https://github.com/tauri-apps/wry/pull/163)) Add `winit` and `tao` feature flag with `tao` as default. +- [`4bdf1c3`](https://github.com/tauri-apps/wry/commit/4bdf1c366de5708b7626ca63eb39e134869c5bd4)([#1017](https://github.com/tauri-apps/wry/pull/1017)) **Breaking change:** `WebViewBuidler::with_custom_protocol` closure now returns `http::Response` instead of `Result`. +- [`ebc4a20`](https://github.com/tauri-apps/wry/commit/ebc4a20d218036b29b186aca1853d28d870fa2ef)([#1015](https://github.com/tauri-apps/wry/pull/1015)) Add `WebViewAtrributes.focused` and `WebViewBuilder::with_focused` to control whether to focus the webview upon creation or not. Supported on Windows and Linux only. + +## \[0.31.0] + +- [`e47562f`](https://github.com/tauri-apps/wry/commit/e47562f71284457ff77e4c8b6bf02fdbe19ab880)([#993](https://github.com/tauri-apps/wry/pull/993)) Update the unmaintained `kuchiki` crate to the maintained `kuchikiki` crate. +- [`7a353c7`](https://github.com/tauri-apps/wry/commit/7a353c7d8a474bfb14b92a272efc75ceb194ea90)([#980](https://github.com/tauri-apps/wry/pull/980)) Add `WebViewBuilder::with_on_page_load_handler` for providing a callback for handling various page loading events. +- [`b0a08b1`](https://github.com/tauri-apps/wry/commit/b0a08b165215823ed7a48a0a377e0f09832898df)([#997](https://github.com/tauri-apps/wry/pull/997)) Update `tao` to version `0.22` which has removed the global-shortcut, menus and tray features, see [tao@v0.22 release](https://github.com/tauri-apps/tao/releases/tag/tao-v0.22.0). + +## \[0.30.0] + +- [`17e04e2`](https://github.com/tauri-apps/wry/commit/17e04e2b4c0bd75f93bbc511234f0d3c93726b63)([#985](https://github.com/tauri-apps/wry/pull/985)) Make `WebViewBuilder::with_navigation_handler` apply to Android `loadUrl` calls. +- [`17e04e2`](https://github.com/tauri-apps/wry/commit/17e04e2b4c0bd75f93bbc511234f0d3c93726b63)([#985](https://github.com/tauri-apps/wry/pull/985)) Add support for `WebViewBuilder::with_navigation_handler` on Android. +- [`87b331a`](https://github.com/tauri-apps/wry/commit/87b331a7d4c169814d2b6a1f8a06d976ad7565bc)([#978](https://github.com/tauri-apps/wry/pull/978)) On Windows, avoid resizing the webview when the window gets minimized to avoid unnecessary `resize` event on JS side. +- [`17e04e2`](https://github.com/tauri-apps/wry/commit/17e04e2b4c0bd75f93bbc511234f0d3c93726b63)([#985](https://github.com/tauri-apps/wry/pull/985)) Update tao to 0.21. + +## \[0.29.0] + +- [`c09dd7b`](https://github.com/tauri-apps/wry/commit/c09dd7bebe3d00f989dff57f0414f1023653efe4)([#968](https://github.com/tauri-apps/wry/pull/968)) Remove ActionBar handling from wry. If you want to hide the action bar, hide it using the `themes.xml` file in your android project or inherit `WryActivity` class and use `getSupportActionBar()?.hide()` in the `onCreate` method. +- [`2b56bfa`](https://github.com/tauri-apps/wry/commit/2b56bfaaee5125f0dc48f4a9bedb53db0e679e5f)([#966](https://github.com/tauri-apps/wry/pull/966)) Add support for `WebViewBuilder::with_html` and `WebViewAttributes.html` on Android. +- [`d2c1819`](https://github.com/tauri-apps/wry/commit/d2c1819f81a7b03288348f1c3b195407400dfbde)([#969](https://github.com/tauri-apps/wry/pull/969)) On Linux, replace `linux-header` flag with `linux-body` flag. Request headers are enabled by default. Add request body on custom protocol but it's behind the flag. +- [`f7dded4`](https://github.com/tauri-apps/wry/commit/f7dded417c239c39ca4cad6f9d3f6b319c3f91f2)([#955](https://github.com/tauri-apps/wry/pull/955)) The bug was reported in tauri repo: https://github.com/tauri-apps/tauri/issues/5986 + + With input method preedit disabled,fcitx can anchor at edit cursor position. + the pre-edit text will not disappear,instead it shows in the fcitx selection window below the input area. +- [`2b56bfa`](https://github.com/tauri-apps/wry/commit/2b56bfaaee5125f0dc48f4a9bedb53db0e679e5f)([#966](https://github.com/tauri-apps/wry/pull/966)) Set base url and origin to null for `WebViewBuilder::with_html` and `WebViewAttributes.html` for consistency on all platforms. + +## \[0.28.3] + +- On iOS, fix panic at runtime due to setting webview ivar. + - [c9002c1](https://github.com/tauri-apps/wry/commit/c9002c1e043e8a948fff2e671ccb04153a10dcd5) fix(macos): remove `webview` ivar in `WryWebView` ([#943](https://github.com/tauri-apps/wry/pull/943)) on 2023-04-26 + +## \[0.28.2] + +- Adjust `cargo:rerun-if-changed` instruction for Android files. + - [cc934fe](https://github.com/tauri-apps/wry/commit/cc934fe799836e4cc72d796f5eddba868a9b585e) refactor(build): adjust rerun-if-changed instruction for Android files ([#940](https://github.com/tauri-apps/wry/pull/940)) on 2023-04-24 + +## \[0.28.1] + +- Fix unresolved reference in kotlin files when building for android. + - [ed36c0b](https://github.com/tauri-apps/wry/commit/ed36c0b032cdf27c926577ee72658ad9f0785a5f) fix(android): fix unresolved reference in kotlin files ([#932](https://github.com/tauri-apps/wry/pull/932)) on 2023-04-19 +- Support modifying user agent string on Android. + - [4a320b0](https://github.com/tauri-apps/wry/commit/4a320b0bdef81d36a1f85a083c2abbabaf958521) feat(android): add support modifying user agent string ([#933](https://github.com/tauri-apps/wry/pull/933)) on 2023-04-20 +- On Linux and macOS, add synthesized event for mouse backward and forward buttons. + - [6ef820b](https://github.com/tauri-apps/wry/commit/6ef820b97dd505bacdc7d3f906112ffe0a6a1e60) feat: synthesize forward/backward mouse button on Linux and macOS ([#900](https://github.com/tauri-apps/wry/pull/900)) on 2023-04-18 + +## \[0.28.0] + +- Add `Webview::clear_browsing_data` method. + - [5f0c9e4](https://github.com/tauri-apps/wry/commit/5f0c9e4595baf5d60ec407b391f873ab52abf923) feat: add `Webview::clear_browsing_data` ([#915](https://github.com/tauri-apps/wry/pull/915)) on 2023-04-18 +- On Android, generate a `proguard-wry.pro` file that could be used to keep the necessary symbols for wry when using minification. + - [ced4c0b](https://github.com/tauri-apps/wry/commit/ced4c0b4459ceb0ff89d07b84d6396c60cfd75e5) feat: generate proguard rule file for android ([#927](https://github.com/tauri-apps/wry/pull/927)) on 2023-04-17 +- Update `tao` to `0.19` + - [d560981](https://github.com/tauri-apps/wry/commit/d56098113f9764e31f73aa84144ee84be8e2aead) refactor: rename `TauriActivity` to `WryActivity` ([#926](https://github.com/tauri-apps/wry/pull/926)) on 2023-04-17 + +## \[0.27.3] + +- Adds a way to launch a WebView as incognito through a new API at WebViewBuilder named as `with_incognito`. + - [8698836](https://github.com/tauri-apps/wry/commit/86988368a4e833b21089d119c934529ecfe306b7) feat: Add a way to launch WebViews as incognito `WebView::as_incognito`, closes [#908](https://github.com/tauri-apps/wry/pull/908) ([#916](https://github.com/tauri-apps/wry/pull/916)) on 2023-04-06 +- On macOS and iOS, remove webcontext implementation since we don't actually use it. This also fix segfault if users drop webcontext early. + - [3cc45cb](https://github.com/tauri-apps/wry/commit/3cc45cb86b93c56cf2444bfc37dc6ba229d4222e) Remove webcontext implementation on wkwebview ([#922](https://github.com/tauri-apps/wry/pull/922)) on 2023-04-07 +- Use the new WKWebView `inspectable` property if available (iOS 16.4, macOS 13.3). + - [c3f7304](https://github.com/tauri-apps/wry/commit/c3f7304dbfd45d1e1c27b53be2369c737e946b69) feat(macos): use WKWebView's inspectable property ([#923](https://github.com/tauri-apps/wry/pull/923)) on 2023-04-08 + +## \[0.27.2] + +- On Android, Add support for native back button navigation. + - [fc232a3](https://github.com/tauri-apps/wry/commit/fc232a32268a13ec89965450dd6cf0abca064b24) feat(android): add support for native back navigation ([#918](https://github.com/tauri-apps/wry/pull/918)) on 2023-04-03 +- Fix `WebView::url` getter on Android. + - [427cf92](https://github.com/tauri-apps/wry/commit/427cf9222d7152f911aa70eb778eb7aa90c83fac) Unify custom porotocol across Android/iOS ([#546](https://github.com/tauri-apps/wry/pull/546)) on 2022-04-11 + - [b89398a](https://github.com/tauri-apps/wry/commit/b89398a9bb17303544a1f04303783f311c6dc77f) Publish New Versions ([#547](https://github.com/tauri-apps/wry/pull/547)) on 2022-04-26 + - [c22744a](https://github.com/tauri-apps/wry/commit/c22744a0c11e9c78f548dc3786e6be30c1d6f46f) fix(android): use correct method signature ([#917](https://github.com/tauri-apps/wry/pull/917)) on 2023-03-31 +- Add Webview attribute to enable/disable autoplay. Enabled by default. + - [6a523cc](https://github.com/tauri-apps/wry/commit/6a523cc7a633236e1fb562e0626e0aedc67ec2fc) feat: Add setting to enable autoplay ([#913](https://github.com/tauri-apps/wry/pull/913)) on 2023-04-04 +- Fix the `WebViewBuilder::with_url` when the projet use `mimalloc` + - [c22744a](https://github.com/tauri-apps/wry/commit/c22744a0c11e9c78f548dc3786e6be30c1d6f46f) fix(android): use correct method signature ([#917](https://github.com/tauri-apps/wry/pull/917)) on 2023-03-31 +- Revert [`51b49c54`](https://github.com/tauri-apps/wry/commit/51b49c54e41c71d1c5f03b568094d43fb9dc32ac) which hid the webview when minimized on Windows. + - [f76568a](https://github.com/tauri-apps/wry/commit/f76568a1cc8f7e56f36633d2f6e700af684bb213) fix(windows): Ignore resize event when minimizing frameless windows ([#909](https://github.com/tauri-apps/wry/pull/909)) on 2023-03-24 + +## \[0.27.1] + +- On Windows, Linux and macOS, add method `evaluate_script_with_callback` to execute javascipt with a callback. + Evaluated result will be serialized into JSON string and pass to the callback. + - [2647731](https://github.com/tauri-apps/wry/commit/2647731c1f084565895a5306fa6465ee6cd271c2) feat: support callback function in eval ([#778](https://github.com/tauri-apps/wry/pull/778)) on 2023-03-23 +- On iOS, set webview scroll bounce default to NO. + - [4d61cf1](https://github.com/tauri-apps/wry/commit/4d61cf122dc0e5b2cef818e0fd491dbd0fd47621) fix(ios): set scroll bounce default to NO ([#907](https://github.com/tauri-apps/wry/pull/907)) on 2023-03-20 +- Update the value returned on a `None` value of `ClassDecl::new("WryDownloadDelegate", class!(NSObject))` + from `UIViewController` to `WryDownloadDelegate`. + - [7795356](https://github.com/tauri-apps/wry/commit/7795356a45b1bd015fad0e9973fc5af58c8c339b) fix: WryDownloadDelegate call after first time on 2023-02-20 +- On Linux, disable system appearance for scrollbars. + - [530a8b7](https://github.com/tauri-apps/wry/commit/530a8b73766dc54736ae6de9528683b27430eaa6) fix(linux): disable system appearance for scrollbars ([#897](https://github.com/tauri-apps/wry/pull/897)) on 2023-03-08 +- On Windows and Linux, implement `WebviewBuilder::with_back_forward_navigation_gestures` and `WebviewAttributes::back_forward_navigation_gestures` to control swipe navigation. Disabled by default. + - [15b4ddf](https://github.com/tauri-apps/wry/commit/15b4ddf7698cf04b90ffcc3164ccb7b62daf6ed0) feat(win\&linux): implement the option to control gesture navigation ([#896](https://github.com/tauri-apps/wry/pull/896)) on 2023-03-07 + +## \[0.27.0] + +- Add function to dispatch closure with the Android context. + - [a9e186c](https://github.com/tauri-apps/wry/commit/a9e186cab4456d7ac2c265e61e71b345f7d269c4) feat(android): add function to dispatch closure to the Android context ([#864](https://github.com/tauri-apps/wry/pull/864)) on 2023-02-06 +- On macOS, fix crash when getting dragging position. + - [a8f7cef](https://github.com/tauri-apps/wry/commit/a8f7cefaac72d3e9fd2f8901f790a777d9888357) Fix crash when getting drag position ([#867](https://github.com/tauri-apps/wry/pull/867)) on 2023-02-04 +- On Android, `wry` can again load assets from the apk's `asset` folder via a custom protocol. This is set by `WebViewBuilder`'s method `with_asset_loader`, which is exclusive to Android (by virtue of existing within `WebViewBuilderExtAndroid`). + - [077eb3a](https://github.com/tauri-apps/wry/commit/077eb3a7ca520d07e73f899da60ce23eef941e6f) fix(android): restore asset loading functionality to android (fix: [#846](https://github.com/tauri-apps/wry/pull/846)) ([#854](https://github.com/tauri-apps/wry/pull/854)) on 2023-02-07 +- Update `webview2-com` to `0.22` and `windows-rs` to `0.44` which bumps the MSRV of this crate on Windows to `1.64`. + - [496bfb5](https://github.com/tauri-apps/wry/commit/496bfb5c7be55e9c2bb674e241f9d7d2620e2acd) chore(deps): update to windows-rs 0.44 and webview2-com 0.22 ([#871](https://github.com/tauri-apps/wry/pull/871)) on 2023-02-06 + +## \[0.26.0] + +- Added `WebViewBuilderExtAndroid` trait and with `on_webview_created` hook. + - [08c0156](https://github.com/tauri-apps/wry/commit/08c0156c60e016bd77f6e0f1bd16ae31dc48d4a0) feat(android): add on_webview_created hook, expose find_class ([#855](https://github.com/tauri-apps/wry/pull/855)) on 2023-01-30 +- Enable dox feature when building docs. + - [c6e53c6](https://github.com/tauri-apps/wry/commit/c6e53c6fa007dcc2dc4771a94b7f312f95edd892) Enable dox feature when building docs ([#861](https://github.com/tauri-apps/wry/pull/861)) on 2023-01-31 +- Expose `wry::webview::prelude::find_class` function to find an Android class in the app project scope. + - [08c0156](https://github.com/tauri-apps/wry/commit/08c0156c60e016bd77f6e0f1bd16ae31dc48d4a0) feat(android): add on_webview_created hook, expose find_class ([#855](https://github.com/tauri-apps/wry/pull/855)) on 2023-01-30 +- Added `WebviewExtIOS` trait to access the WKWebView and userContentController references. + - [f546c44](https://github.com/tauri-apps/wry/commit/f546c44fce76faf04855a97b285bbdef8ae80f3d) feat(ios): add WebviewExtIOS ([#859](https://github.com/tauri-apps/wry/pull/859)) on 2023-01-30 + +## \[0.25.0] + +- **Breaking Change:** Bump webkit2gtk to 0.19. This will use webkit2gtk-4.1 as dependency from now on. Also Bump gtk version: 0.15 -> 0.16. + - [c5f3b36](https://github.com/tauri-apps/wry/commit/c5f3b36b7ac4613971ddf56397932c44a9c74878) Bump gtk version 0.15 -> 0.16 ([#851](https://github.com/tauri-apps/wry/pull/851)) on 2023-01-26 +- **Breaking** Add position of the drop to `FileDropEvent` struct. + - [bce39e2](https://github.com/tauri-apps/wry/commit/bce39e2be195194e547b0021e770e45a3df15fa1) feat: add file drop position ([#847](https://github.com/tauri-apps/wry/pull/847)) on 2023-01-17 +- On Android, fix the injection of `intialization_scripts` for devServers where the `Content-Type` header includes more information than just `"text/plain"`. + - [87216c7](https://github.com/tauri-apps/wry/commit/87216c7f01d5f65641422343dd0aa7f08ea61d0d) fix: make the Content-Type check spec compliant ([#844](https://github.com/tauri-apps/wry/pull/844)) on 2023-01-14 + +## \[0.24.1] + +- Update `tao` to `0.16.0` + - [a27a66b](https://github.com/tauri-apps/wry/commit/a27a66baccc86873110b0aa67ddad1f3a8dbd205) chore: update tao to 0.16.0 on 2023-01-11 + +## \[0.24.0] + +- Changed env vars used when building for Android; changed `WRY_ANDROID_REVERSED_DOMAIN` to `WRY_ANDROID_PACKAGE` and `WRY_ANDROID_APP_NAME_SNAKE_CASE` to `WRY_ANDROID_LIBRARY`. + - [dfe6a5e](https://github.com/tauri-apps/wry/commit/dfe6a5e78acca05d9e0808c8f4ed974a8657b847) refactor: improve android env vars naming ([#829](https://github.com/tauri-apps/wry/pull/829)) on 2022-12-30 +- Fixes Android initialization scripts order. + - [7f819c0](https://github.com/tauri-apps/wry/commit/7f819c0ec3d3aaaf582d9eecde09f5e539c45743) fix(android): initialization scripts order ([#808](https://github.com/tauri-apps/wry/pull/808)) on 2022-12-12 +- Remove redundant `.clone()` calls and avoid unnecessary heap allocations. + - [45f2b21](https://github.com/tauri-apps/wry/commit/45f2b2127e73718b71f349eae1847d1764c748f5) perf: remove redundant `.clone()` calls and avoid unnecessary heap allocations ([#812](https://github.com/tauri-apps/wry/pull/812)) on 2022-12-14 +- Change return type of [custom protocol handlers](https://docs.rs/wry/latest/wry/webview/struct.WebViewBuilder.html#method.with_custom_protocol) from `Result>>` to `Result>>`. This allows the handlers to return static resources without heap allocations. This is effective when you embed some large files like bundled JavaScript source as `&'static [u8]` using [`include_bytes!`](https://doc.rust-lang.org/std/macro.include_bytes.html). + - [ddd3461](https://github.com/tauri-apps/wry/commit/ddd34614be8a0ba826eff8acbf4b06710ce2ba65) perf: Change return type of custom protocol handler from `Vec` to `Cow<'static, [u8]>`, closes [#796](https://github.com/tauri-apps/wry/pull/796) ([#797](https://github.com/tauri-apps/wry/pull/797)) on 2022-12-12 +- Ensures that the script passed to `.with_initialization_script("here")` is not empty. + - [ceb209e](https://github.com/tauri-apps/wry/commit/ceb209eddc20d284be748ee382ba8aef7686863b) fix empty string bug (fix: [#833](https://github.com/tauri-apps/wry/pull/833)) ([#836](https://github.com/tauri-apps/wry/pull/836)) on 2023-01-08 +- Add APIs to process webview document title change. + - [14a0ee3](https://github.com/tauri-apps/wry/commit/14a0ee323e8e596f45d4a57d2d86abcf0a848bc8) feat: add document title changed handler, closes [#804](https://github.com/tauri-apps/wry/pull/804) ([#825](https://github.com/tauri-apps/wry/pull/825)) on 2022-12-30 +- Evaluate scripts after the page load starts on Linux and macOS. + - [ca7c8e4](https://github.com/tauri-apps/wry/commit/ca7c8e44832b3236f08022f7ea3469be9a65aa3f) fix(unix): race condition on script eval ([#815](https://github.com/tauri-apps/wry/pull/815)) on 2022-12-14 +- Improve panic error messages on the build script. + - [5b9f21d](https://github.com/tauri-apps/wry/commit/5b9f21d38974881c2d6f4456990f5863484e7382) feat: improve build script panic messages ([#807](https://github.com/tauri-apps/wry/pull/807)) on 2022-12-12 +- Add `WebViewBuilder::with_url_and_headers` and `WebView::load_url_with_headers` to navigate to urls with headers. + - [8ae93b9](https://github.com/tauri-apps/wry/commit/8ae93b9c76b2efe14e93febd009e31fc459275a8) feat: add headers when loading URLs, closes [#816](https://github.com/tauri-apps/wry/pull/816) ([#826](https://github.com/tauri-apps/wry/pull/826)) on 2023-01-01 + - [e246bd1](https://github.com/tauri-apps/wry/commit/e246bd164eb9df1b0e48123a542bbd240958c9db) chore: update headers change file on 2023-01-01 +- Change class declare name from `UIViewController` to `WryNavigationDelegate` to avoid class name conflict on iOS. + - [fca42a0](https://github.com/tauri-apps/wry/commit/fca42a0730e75a142f7f354c6ac3f6d6a0f4711f) fix(ios): navigation delegate class name conflict ([#824](https://github.com/tauri-apps/wry/pull/824)) on 2022-12-27 +- Rerun build script if the `WRY_ANDROID_KOTLIN_FILES_OUT_DIR` directory changes. + - [1cf92e2](https://github.com/tauri-apps/wry/commit/1cf92e2b68b1d9109de3924a3cd1fd10cb8c7c17) feat(build): rerun if kotlin out directory changes ([#839](https://github.com/tauri-apps/wry/pull/839)) on 2023-01-10 +- On Windows, Add `WebviewBuilderExtWindows::with_theme` and `WebviewExtWindows::set_theme` to change webview2 theme. + - [563a497](https://github.com/tauri-apps/wry/commit/563a497d7f842c760ad05a0017059e7781c2b810) feat(webview2): add theme API, closes [#806](https://github.com/tauri-apps/wry/pull/806) ([#809](https://github.com/tauri-apps/wry/pull/809)) on 2022-12-13 + +## \[0.23.4] + +- Fixes Android initialization scripts order. + - [800cc48](https://github.com/tauri-apps/wry/commit/800cc48b46ba9e5ce968efd5708aeb71b63832f9) fix(android): initialization scripts order ([#808](https://github.com/tauri-apps/wry/pull/808)) on 2022-12-12 +- Improve panic error messages on the build script. + - [4ec7386](https://github.com/tauri-apps/wry/commit/4ec7386740ab2edb3b56d72668841af3f329cefd) feat: improve build script panic messages ([#807](https://github.com/tauri-apps/wry/pull/807)) on 2022-12-12 + +## \[0.23.3] + +- Fix the beep sound on macOS + - [94256c3](https://github.com/tauri-apps/wry/commit/94256c3adb1d6c005e0386f8b20f01d597b52f28) Fix beep sound, closes [#799](https://github.com/tauri-apps/wry/pull/799) ([#801](https://github.com/tauri-apps/wry/pull/801)) on 2022-12-10 + +## \[0.23.2] + +- On macOS, remove all custom keydown implementations. This will bring back keydown regression but should allow all accelerator working. + - [fee4bf2](https://github.com/tauri-apps/wry/commit/fee4bf2eb384d9c315530bd8f5af146909706cf6) Remove all keydown implementations ([#798](https://github.com/tauri-apps/wry/pull/798)) on 2022-12-10 +- Suppress `unused_variables` warning reported only in release build. + - [4e23c0f](https://github.com/tauri-apps/wry/commit/4e23c0f84b5a954be78418d56e37366395de030f) fix(macos): suppress `unused_variables` warning reported only in release build ([#790](https://github.com/tauri-apps/wry/pull/790)) on 2022-12-07 +- Add `WebViewBuilderExtWindows::with_browser_accelerator_keys` method to allow disabling browser-specific accelerator keys enabled in WebView2 by default. When `false` is passed, it disables all accelerator keys that access features specific to a web browser. See [the official WebView2 document](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2\_core/corewebview2settings#arebrowseracceleratorkeysenabled) for more details. + - [6e622ff](https://github.com/tauri-apps/wry/commit/6e622ffbdad2312bf3906d278a75956a3a6eeadd) feat(windows): Allow disabling browser-specific accelerator keys ([#792](https://github.com/tauri-apps/wry/pull/792)) on 2022-12-07 + +## \[0.23.1] + +- Fixes usage of the `linux-headers` feature. + - [64a72ff](https://github.com/tauri-apps/wry/commit/64a72ffd2369f51d36bdb00973f71326e8395016) fix(wry): correctly use the linux-headers feature on 2022-12-05 + +## \[0.23.0] + +- Properly parse the content type header for the `android.webkit.WebResourceResponse` mime type. + - [1db5ea6](https://github.com/tauri-apps/wry/commit/1db5ea68c2028db77788ec8c78ee0ab75a7a5f7f) fix(android): properly parse content-type for response mime type ([#772](https://github.com/tauri-apps/wry/pull/772)) on 2022-11-27 +- Change typo in `WebViewBuilderExtWindows::with_additionl_browser_args`. to `WebViewBuilderExtWindows::with_additional_browser_args`. + - [db1c290](https://github.com/tauri-apps/wry/commit/db1c290c0d8b58f6612ef9bef244a06261fb2a6e) fix(windows): Fix typo in method name of `WebViewBuilderExtWindows` ([#781](https://github.com/tauri-apps/wry/pull/781)) on 2022-12-02 +- Add `Webiew::load_url`. + - [a2b9531](https://github.com/tauri-apps/wry/commit/a2b9531b0e8397dcf74c049ccf6c7fa125288ca8) feat: add `Webiew::navigate_to_url`, closes [#776](https://github.com/tauri-apps/wry/pull/776) ([#777](https://github.com/tauri-apps/wry/pull/777)) on 2022-11-30 +- Change the type of `WebViewBuilderExtWindows::with_additional_browser_args` argument from `AsRef` to `Into` to reduce extra allocation. + - [b0ff06a](https://github.com/tauri-apps/wry/commit/b0ff06aba5aea77f067aee1e9bf8ac8c245ac5e8) perf: reduce extra allocation at `WebViewBuilderExtWindows::with_additional_browser_args` argument ([#783](https://github.com/tauri-apps/wry/pull/783)) on 2022-12-03 +- Validate custom protocol response status code on Android. + - [7f585c7](https://github.com/tauri-apps/wry/commit/7f585c7dc947936387faf565f3f5cbe62148daaf) feat(android): validate custom protocol response status code ([#779](https://github.com/tauri-apps/wry/pull/779)) on 2022-11-30 +- \[https://github.com/tauri-apps/wry/commit/04422bc1b579d9388ce03c2388b8f415dbc0747b] On macOS, revert content view to native NSView (\[#782])(https://github.com/tauri-apps/wry/pull/782) + +## \[0.22.6] + +- Fixes usage of the `linux-headers` feature. + - [14c5ae7](https://github.com/tauri-apps/wry/commit/14c5ae7d41b506c8a398d4735062b46cd0770447) fix(wry): correctly use the linux-headers feature on 2022-12-05 + +## \[0.22.5] + +- On macOS, fix arrow keys misprint text on textarea. + - [3005e54](https://github.com/tauri-apps/wry/commit/3005e5450339c6c3fbc1c7c67ab8008ed39ec864) On macOS, fix arrow keys misprint texts ([#769](https://github.com/tauri-apps/wry/pull/769)) on 2022-11-25 + +## \[0.22.4] + +- On Linux, add `linux-headers` feature flag to fix version regression. The minimum webkit2gtk version remains v2.22. + - [cf447f6](https://github.com/tauri-apps/wry/commit/cf447f64451fd8345f21440df31601265e0fde86) On Linux, add header feature flag to fix version regression ([#766](https://github.com/tauri-apps/wry/pull/766)) on 2022-11-24 + +## \[0.22.3] + +- On macOS, fix keyinput missing by calling superclass methods. + - [e40e55a](https://github.com/tauri-apps/wry/commit/e40e55a41d8d65ceda5e182c8915d37b5698c7b0) On macOS, fix keyinput missing by calling super class methods ([#764](https://github.com/tauri-apps/wry/pull/764)) on 2022-11-21 + +## \[0.22.2] + +- On macOS, add an API to enable or disable backward and forward navigation gestures. + - [487dff0](https://github.com/tauri-apps/wry/commit/487dff03a103df999e5e0c6286f75b4d1f419d25) Add the ability to navigate with swipe gesture ([#757](https://github.com/tauri-apps/wry/pull/757)) on 2022-11-16 + - [1a0ec19](https://github.com/tauri-apps/wry/commit/1a0ec19fd533c853b744c5e2346542d2e1e5805d) Update gesture change file to patch ([#763](https://github.com/tauri-apps/wry/pull/763)) on 2022-11-21 +- On macOS, pass key event to menu if we have one on key press. + - [2e5e138](https://github.com/tauri-apps/wry/commit/2e5e1381789c332654a5ffee47d578042a9be87b) On macOS, pass key event to menu on key press ([#760](https://github.com/tauri-apps/wry/pull/760)) on 2022-11-21 + +## \[0.22.1] + +- Fix `WebViewBuilder::with_accept_first_mouse` taking behavior of first initalized webview. + - [0647c0e](https://github.com/tauri-apps/wry/commit/0647c0efe131566ffbab0729e9d74355155c3c32) fix(macos): fix `acceptFirstMouse` for subsequent webviews, closes [#751](https://github.com/tauri-apps/wry/pull/751) ([#752](https://github.com/tauri-apps/wry/pull/752)) on 2022-11-13 +- Fix download implementation on macOS older than 11.3. + - [e69ddc6](https://github.com/tauri-apps/wry/commit/e69ddc6943770aa8baa02431bb037bbdcb3cbd80) fix(macos): download breaking app on macOS older than 11.3, closes [#755](https://github.com/tauri-apps/wry/pull/755) ([#756](https://github.com/tauri-apps/wry/pull/756)) on 2022-11-15 +- On macOS, remove webview from window's NSView before dropping. + - [3d3ea80](https://github.com/tauri-apps/wry/commit/3d3ea80808a327c546d8bbd97e06ef4b8feb32d0) On macOS, remove webview from window's NSView before dropping ([#754](https://github.com/tauri-apps/wry/pull/754)) on 2022-11-14 + +## \[0.22.0] + +- Added `WebViewAttributes::with_accept_first_mouse` method for macOS. + - [2c23440](https://github.com/tauri-apps/wry/commit/2c23440f9c194064caa907650df39bf9c96ed99c) feat(macos): add `accept_first_mouse` option, closes [#714](https://github.com/tauri-apps/wry/pull/714) ([#715](https://github.com/tauri-apps/wry/pull/715)) on 2022-10-04 +- **Breaking change** Custom protocol now takes `Request` and returns `Response` types from `http` crate. + - [1510e45](https://github.com/tauri-apps/wry/commit/1510e452547a95af2e42ff5199640877beecdbd7) refactor: use `http` crate primitives instead of a custom impl ([#706](https://github.com/tauri-apps/wry/pull/706)) on 2022-09-29 +- Enabled devtools in debug mode by default. + - [fea0638](https://github.com/tauri-apps/wry/commit/fea0638d9ad100c00b95468aa16fc44d6517ac0d) feat: enable devtools in debug mode by default ([#741](https://github.com/tauri-apps/wry/pull/741)) on 2022-10-27 +- On Desktop, add `download_started_handler` and `download_completed_handler`. See `blob_download` and `download_event` example for their usages. + - [3691c4f](https://github.com/tauri-apps/wry/commit/3691c4f6c88fe43e92597caf3003c8d57b447a7b) feat: Add download started and download completed callbacks ([#530](https://github.com/tauri-apps/wry/pull/530)) on 2022-10-19 +- Fix double permission dialog on macOS 12+ and iOS 15+. + - [8aa7d61](https://github.com/tauri-apps/wry/commit/8aa7d61cdc9fc584805b46c3ffd700aabb633649) Fix: Remove extra soft prompt asking for media permission on every app launch in macOS ([#694](https://github.com/tauri-apps/wry/pull/694)) on 2022-09-29 +- Focus webview when window starts moving or resizing on Windows to automatically close `` dropdown position + - [a1001dd](https://github.com/tauri-apps/wry/commit/a1001dd6361a0629cd1ce2f8063b7c983bf29616) fix(windows): focus webview on `WM_ENTERSIZEMOVE` and call `NotifyParentChanged` on `WM_WINDOWPOSCHANGED`. ([#695](https://github.com/tauri-apps/wry/pull/695)) on 2022-09-16 +- On Windows, hide the webview when the window is minimized to reduce memory and cpu usage. + - [51b49c5](https://github.com/tauri-apps/wry/commit/51b49c54e41c71d1c5f03b568094d43fb9dc32ac) feat(webview2): hide the webview when the window is minimized ([#702](https://github.com/tauri-apps/wry/pull/702)) on 2022-09-27 +- Internally return with error from custom protocol if an invalid uri was requseted such as `wry://` which doesn't contain a host. + - [818ce99](https://github.com/tauri-apps/wry/commit/818ce9989d816bf970ebcf93009b2d693384e436) fix: don't panic on invalid uri ([#712](https://github.com/tauri-apps/wry/pull/712)) on 2022-09-30 +- Support cross compiling ios on a non macos host. + - [cd08410](https://github.com/tauri-apps/wry/commit/cd08410bce326c42e8fc25a74290d254468724fe) Fix cross compilation. ([#731](https://github.com/tauri-apps/wry/pull/731)) on 2022-10-29 +- On Linux, Improve custom protocol with http headers / method added to request, and status code / http headers added to response. This feature is 2.36 only, version below it will fallback to previous implementation. + - [2944d91](https://github.com/tauri-apps/wry/commit/2944d91c763ff105288aa6c1370ba42a54fa8caf) feat(linux): add headers to URL scheme request ([#721](https://github.com/tauri-apps/wry/pull/721)) on 2022-10-17 +- On macOS, add WKWebview as subview of existing NSView directly. + - [008eca8](https://github.com/tauri-apps/wry/commit/008eca871155f393e5de1053bb1a9f63e1eafe82) On macOS, add WKWebview as subview of existing NSView directly ([#745](https://github.com/tauri-apps/wry/pull/745)) on 2022-11-07 +- Keypress on non-input element no longer triggers unsupported key feedback sound. + - [51c7f12](https://github.com/tauri-apps/wry/commit/51c7f12d80e2b51a188fb644a323abaf5df1b3d1) fix(macos): do not trigger unsupported key feedback sound on keypress ([#742](https://github.com/tauri-apps/wry/pull/742)) on 2022-10-30 +- Remove the IPC script message handler when the WebView is dropped on macOS. + - [818ce99](https://github.com/tauri-apps/wry/commit/818ce9989d816bf970ebcf93009b2d693384e436) fix: don't panic on invalid uri ([#712](https://github.com/tauri-apps/wry/pull/712)) on 2022-09-30 +- **Breaking change** Removed http error variants from `wry::Error` and replaced with generic `HttpError` variant that can be used to convert `http` crate errors. + - [1510e45](https://github.com/tauri-apps/wry/commit/1510e452547a95af2e42ff5199640877beecdbd7) refactor: use `http` crate primitives instead of a custom impl ([#706](https://github.com/tauri-apps/wry/pull/706)) on 2022-09-29 +- Disabled Microsoft SmartScreen by default on Windows. + - [a617c5b](https://github.com/tauri-apps/wry/commit/a617c5b29da3d173d43aa814106e1c7ace08d27f) feat(webview2): disable smartscreen & allow disabling internal webview2 args, closes [#704](https://github.com/tauri-apps/wry/pull/704) ([#705](https://github.com/tauri-apps/wry/pull/705)) on 2022-09-28 +- Add `WebView::url` to get the current url. + - [38e49bd](https://github.com/tauri-apps/wry/commit/38e49bd5f1e26e9f9507d1f2af8b0b290aa515ad) feat: add `WebView::url()` to access the current url ([#732](https://github.com/tauri-apps/wry/pull/732)) on 2022-10-25 +- **Breaking change** Removed `http` module and replaced with re-export of `http` crate. + - [1510e45](https://github.com/tauri-apps/wry/commit/1510e452547a95af2e42ff5199640877beecdbd7) refactor: use `http` crate primitives instead of a custom impl ([#706](https://github.com/tauri-apps/wry/pull/706)) on 2022-09-29 +- Add `WebviewBuilderExtWindows::with_additionl_browser_args` method to pass additional browser args to Webview2 On Windows. By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection` so if you use this method, you also need to disable these components by yourself if you want. + - [683f866](https://github.com/tauri-apps/wry/commit/683f86665366bb333cb03e05a503a69d0f8eb734) feat(webview2): add method to pass additional args, closes [#415](https://github.com/tauri-apps/wry/pull/415) ([#711](https://github.com/tauri-apps/wry/pull/711)) on 2022-09-29 +- On Windows, fix canonical reason for custom protocol response. + - [9d5595c](https://github.com/tauri-apps/wry/commit/9d5595c9c723b3f8046d9582ac086ccebf460a83) fix(webview2): set response reason correctly, closes [#733](https://github.com/tauri-apps/wry/pull/733) ([#734](https://github.com/tauri-apps/wry/pull/734)) on 2022-10-24 +- On macOS, make the webview first responder. + - [e64ad21](https://github.com/tauri-apps/wry/commit/e64ad21ad5ab9bf0b7fb15aec0065c20b61a5a80) fix(wkwebview): make webview first responder ([#740](https://github.com/tauri-apps/wry/pull/740)) on 2022-10-28 + +## \[0.21.1] + +- Fix transparency on Windows + - [e31cd0a](https://github.com/tauri-apps/wry/commit/e31cd0adf4ba881a35dcccd9b5ee78bb5af8828a) fix: fix transparency on Windows, closes [#692](https://github.com/tauri-apps/wry/pull/692) on 2022-09-16 + +## \[0.21.0] + +- Implement `` on Android. + - [bf39d9d](https://github.com/tauri-apps/wry/commit/bf39d9de1e997170e9efb3bb7392710b57c2ae1f) feat(android): implement dialogs and permissions ([#685](https://github.com/tauri-apps/wry/pull/685)) on 2022-09-05 +- Add `WebviewExtAndroid::handle` which can be used to execute some code using JNI context. + - [2bfc6c3](https://github.com/tauri-apps/wry/commit/2bfc6c3d2e0cc6c3922d125f678ab30c00b89483) feat(android): JNI execution handle ([#689](https://github.com/tauri-apps/wry/pull/689)) on 2022-09-07 +- Enable JS alert, confirm, prompt on Android. + - [bf39d9d](https://github.com/tauri-apps/wry/commit/bf39d9de1e997170e9efb3bb7392710b57c2ae1f) feat(android): implement dialogs and permissions ([#685](https://github.com/tauri-apps/wry/pull/685)) on 2022-09-05 +- Prompt for permissions on Android when needed. + - [bf39d9d](https://github.com/tauri-apps/wry/commit/bf39d9de1e997170e9efb3bb7392710b57c2ae1f) feat(android): implement dialogs and permissions ([#685](https://github.com/tauri-apps/wry/pull/685)) on 2022-09-05 +- Implement `webview_version` on Android. + - [9183de4](https://github.com/tauri-apps/wry/commit/9183de4f9d3129e7cba332eebca2afc846f727d0) feat(android): implement webview_version ([#687](https://github.com/tauri-apps/wry/pull/687)) on 2022-09-05 +- Enable storage, geolocation, media playback, `window.open`. + - [9dfffcf](https://github.com/tauri-apps/wry/commit/9dfffcfe12199d7f28bf4b8a837e28253958ac17) feat(android): enable storage, geolocation, media playback, window.open ([#684](https://github.com/tauri-apps/wry/pull/684)) on 2022-09-04 +- Improve Android initialization script implementation. + - [1b26d60](https://github.com/tauri-apps/wry/commit/1b26d605d6e33f5417eb6566a7381d8feb239c8b) feat(android): improve initialization scripts implementation ([#670](https://github.com/tauri-apps/wry/pull/670)) on 2022-08-24 +- WRY will now generate the needed kotlin files at build time but you need to set `WRY_ANDROID_REVERSED_DOMAIN`, `WRY_ANDROID_APP_NAME_SNAKE_CASE` and `WRY_ANDROID_KOTLIN_FILES_OUT_DIR` env vars. + - [b478903](https://github.com/tauri-apps/wry/commit/b4789034dc4d10ab83f6acce6b4152d79f702940) feat(android): generate kotlin files at build time ([#671](https://github.com/tauri-apps/wry/pull/671)) on 2022-08-24 + - [103f255](https://github.com/tauri-apps/wry/commit/103f255903bdf728bf5124fb323293d172c8dd12) chore: change bump to patch on 2022-08-25 +- **Breaking change** Removed `WebView::focus`. + - [f338df7](https://github.com/tauri-apps/wry/commit/f338df7a2716cbbde357b81d9baa108ce679eaa5) feat(windows): auto-focus the webview ([#676](https://github.com/tauri-apps/wry/pull/676)) on 2022-08-27 +- Updated tao to `0.14` + - [483bad0](https://github.com/tauri-apps/wry/commit/483bad0fc7e7564500f7183547c15604fa387258) feat: tao as window dependency ([#230](https://github.com/tauri-apps/wry/pull/230)) on 2021-05-03 + - [51430e9](https://github.com/tauri-apps/wry/commit/51430e97dfb6589c5ff71e5078438be67293d044) publish new versions ([#221](https://github.com/tauri-apps/wry/pull/221)) on 2021-05-09 + - [0cf0089](https://github.com/tauri-apps/wry/commit/0cf0089b6d49aa9e1a8c791ec8883fce48a0dfd1) Update tao to v0.2.6 ([#271](https://github.com/tauri-apps/wry/pull/271)) on 2021-05-18 + - [a76206c](https://github.com/tauri-apps/wry/commit/a76206c11fa0a4ba1d041aa0f25452dd80941ee9) publish new versions ([#272](https://github.com/tauri-apps/wry/pull/272)) on 2021-05-18 + - [3c4f8b8](https://github.com/tauri-apps/wry/commit/3c4f8b8b2bd42e7634b889aa5317d909bfce593c) Update tao to v0.5 ([#365](https://github.com/tauri-apps/wry/pull/365)) on 2021-08-09 + - [44aa1dc](https://github.com/tauri-apps/wry/commit/44aa1dc8fcc20cc5826697d69f763118d45f724a) publish new versions ([#351](https://github.com/tauri-apps/wry/pull/351)) on 2021-08-09 + - [935cc5f](https://github.com/tauri-apps/wry/commit/935cc5fe8b73055279dc107e71a10f2701ea8b3d) Update tao to 0.13 ([#642](https://github.com/tauri-apps/wry/pull/642)) on 2022-07-27 + - [657888a](https://github.com/tauri-apps/wry/commit/657888aac13830d97d2970bdf1c87319dadb2ffa) Publish New Versions ([#632](https://github.com/tauri-apps/wry/pull/632)) on 2022-07-27 + - [3a91376](https://github.com/tauri-apps/wry/commit/3a91376fa2c04783a32804e6f123722749ad595e) chore(deps): update tao to 0.14 ([#691](https://github.com/tauri-apps/wry/pull/691)) on 2022-09-13 +- Allow setting the webview background color. + - [eb1b723](https://github.com/tauri-apps/wry/commit/eb1b7234f731759b5e091f7c88ac18ce4b507017) feat: allow setting webview bg color, closes [#197](https://github.com/tauri-apps/wry/pull/197) ([#682](https://github.com/tauri-apps/wry/pull/682)) on 2022-09-05 +- Added the `RustWebView` class on Android. + - [b1e8560](https://github.com/tauri-apps/wry/commit/b1e8560c3f13f2674528f6ca440ba476ddbef7c2) feat(android): define WebView class in kotlin ([#672](https://github.com/tauri-apps/wry/pull/672)) on 2022-08-24 +- Update the `windows` crate to the latest 0.39.0 release and `webview2-com` to 0.19.1 to match. + - [c7d7e1f](https://github.com/tauri-apps/wry/commit/c7d7e1f9c85a5db9c98aa5ded1e0eaf7fe697817) Update windows to 0.39.0 and webview2-com to 0.19.1 to match ([#679](https://github.com/tauri-apps/wry/pull/679)) on 2022-08-31 +- On Windows, automatically focus the webview when the window gains focus to match other platforms. + - [f338df7](https://github.com/tauri-apps/wry/commit/f338df7a2716cbbde357b81d9baa108ce679eaa5) feat(windows): auto-focus the webview ([#676](https://github.com/tauri-apps/wry/pull/676)) on 2022-08-27 + +## \[0.20.2] + +- Implement custom protocol on Android. + - [dc68289](https://github.com/tauri-apps/wry/commit/dc68289169196419b8c9cda73c73b139ea1301f9) feat(android): implement custom protocol ([#656](https://github.com/tauri-apps/wry/pull/656)) on 2022-08-13 +- Implement `WebView::eval` on Android. + - [690fd26](https://github.com/tauri-apps/wry/commit/690fd26a3b9bd47f9d7b1b5d2aa3dcb1c018a771) feat(android): implement eval ([#658](https://github.com/tauri-apps/wry/pull/658)) on 2022-08-13 +- On iOS, add webview as subview instead of replacing original view. + - [74391e0](https://github.com/tauri-apps/wry/commit/74391e0769d0e0f4be015147ddfa39bf25c90928) fix(ios): addSubview instead of setContentView ([#655](https://github.com/tauri-apps/wry/pull/655)) on 2022-08-13 +- Move WebView logic from tao to wry. + - [aba1ae5](https://github.com/tauri-apps/wry/commit/aba1ae5afcf96c88b1215ef66f38a5a635ecf7c3) refactor(android): move WebView logic from tao to wry ([#659](https://github.com/tauri-apps/wry/pull/659)) on 2022-08-14 + +## \[0.20.1] + +- Add android support + - [3218091](https://github.com/tauri-apps/wry/commit/3218091aa393dca9451840d3baa44bc9371f2e1d) Add real android support [#577](https://github.com/tauri-apps/wry/pull/577) +- Enable private picture-in-picture on macos. + - [3cfd8c9](https://github.com/tauri-apps/wry/commit/3cfd8c9e7a43f6c35a1ea61358521bd62fc70633) fix: add feature flag to enable private picture-in-picture flag on macos ([#645](https://github.com/tauri-apps/wry/pull/645)) on 2022-08-05 +- On macOS, fix devtool warning + - [2eba8c9](https://github.com/tauri-apps/wry/commit/2eba8c9c26ff5f9512b0039ac04bc7fd27a5256f) fix: devtool warning by adding parent view + +## \[0.20.0] + +- Add `WebViewBuilder::with_clipboard`. + - [c798700](https://github.com/tauri-apps/wry/commit/c7987004eaaf5cb7da830d574d81bd96dace0112) fix: Add `WebViewBuilder::with_clipboard`([#631](https://github.com/tauri-apps/wry/pull/631)) on 2022-07-05 +- Fix typos in several files. + - [4466250](https://github.com/tauri-apps/wry/commit/44662506ab01846c7e8767eb2f13bf0bbca7fe9a) Fix typos ([#635](https://github.com/tauri-apps/wry/pull/635)) on 2022-07-11 +- Set webview2 language to match the OS language. This makes i18n functions like `new Date().toLocaleStrin()` behave correctly. + - [e9f04d7](https://github.com/tauri-apps/wry/commit/e9f04d7e7bea576d0283d97e25faf7b356c5e959) fix: set system language to webview on windows, closes [#442](https://github.com/tauri-apps/wry/pull/442) ([#640](https://github.com/tauri-apps/wry/pull/640)) on 2022-07-26 +- Update tao to 0.13.0. + - [935cc5f](https://github.com/tauri-apps/wry/commit/935cc5fe8b73055279dc107e71a10f2701ea8b3d) Update tao to 0.13 ([#642](https://github.com/tauri-apps/wry/pull/642)) on 2022-07-27 + +## \[0.19.0] + +- - Automatically resize the webview on Windows to align with other platforms. +- **Breaking change**: Removed `WebView::resize` +- [d7c9097](https://github.com/tauri-apps/wry/commit/d7c9097256d76de7400032cf27acd7a1874da5cd) feat: auto resize webview on Windows ([#628](https://github.com/tauri-apps/wry/pull/628)) on 2022-06-27 +- Implement new window requested handler + - [fa5456c](https://github.com/tauri-apps/wry/commit/fa5456c6abe16be17073e75f4a0205966be266b2) feat: Implement new window requested event, closes [#527](https://github.com/tauri-apps/wry/pull/527) ([#526](https://github.com/tauri-apps/wry/pull/526)) on 2022-06-19 +- Re-export `url::Url`. + - [0cb6961](https://github.com/tauri-apps/wry/commit/0cb696119b5e25292af9595fd89856116520c049) fix: re-export `url::Url` ([#612](https://github.com/tauri-apps/wry/pull/612)) on 2022-06-17 +- Update tao to 0.12 + - [448837e](https://github.com/tauri-apps/wry/commit/448837e795a8f7f8dc4ac5f34b27063b108fc1f2) Update tao to 0.12 ([#629](https://github.com/tauri-apps/wry/pull/629)) on 2022-06-28 + +## \[0.18.3] + +- Update tao to 0.11 + - [f4b42fb](https://github.com/tauri-apps/wry/commit/f4b42fb412fa557188f20b72ef6c4314d1d6bb91) Update tao to v0.12 ([#609](https://github.com/tauri-apps/wry/pull/609)) on 2022-06-15 + +## \[0.18.2] + +- Fix NSString can not be released. + - [95ca52f](https://github.com/tauri-apps/wry/commit/95ca52f5d8ca86b64f8587a0f96cf0fb7dc22125) fix: NSString isn't released ([#604](https://github.com/tauri-apps/wry/pull/604)) on 2022-06-07 + +## \[0.18.1] + +- Remove unused tray from doc features. + - [5eecb00](https://github.com/tauri-apps/wry/commit/5eecb0074397efa40351b3caa8fd4a6d972c4c85) Remove unused tray from doc features ([#602](https://github.com/tauri-apps/wry/pull/602)) on 2022-05-31 + +## \[0.18.0] + +- Remove trivial tray features. + - [a3fea48](https://github.com/tauri-apps/wry/commit/a3fea48d2d78ebe4fa3f08b40d2c3c8c8135bb12) Remove trivial tray features ([#599](https://github.com/tauri-apps/wry/pull/599)) on 2022-05-31 + +## \[0.17.0] + +- Add option to enable/disable zoom shortcuts for WebView2, disabled by default. + - [494a110](https://github.com/tauri-apps/wry/commit/494a11057f9ddd2bf4bcecdc96b43ed95c5bd08e) WebView2: Enable/disable platform default zooming shortcuts, closes [#569](https://github.com/tauri-apps/wry/pull/569) ([#574](https://github.com/tauri-apps/wry/pull/574)) on 2022-05-15 +- Prevent memory leak on macOS. + - [16d1924](https://github.com/tauri-apps/wry/commit/16d192450ed639f94cf8b7137fa5fea1a319f8b5) fix: prevent memory leak on macOS, closes [#536](https://github.com/tauri-apps/wry/pull/536) ([#587](https://github.com/tauri-apps/wry/pull/587)) on 2022-05-20 +- Update the `windows` crate to the latest 0.37.0 release and `webview2-com` to 0.16.0 to match. + +The `#[implement]` macro in `windows-implement` and the `implement` feature in `windows` depend on some `const` generic features which stabilized in `rustc` 1.61. The MSRV on Windows targets is effectively 1.61, but other targets do not require these features. + +The `webview2-com` crate specifies `rust-version = "1.61"`, so `wry` will inherit that MSRV and developers on Windows should get a clear error message telling them to update their toolchain when building `wry` or anything that depends on `wry`. Developers targeting other platforms should be able to continue using whatever toolchain they were using before. + +- [9d9d9d8](https://github.com/tauri-apps/wry/commit/9d9d9d8f3d37a283bbb707d39c3aac090325a63e) Update windows-rs to 0.37.0 and webview2-com to 0.16.0 to match ([#592](https://github.com/tauri-apps/wry/pull/592)) on 2022-05-23 + +## \[0.16.2] + +- Fixed build on macos. + - [17ab12d](https://github.com/tauri-apps/wry/commit/17ab12ded27949474f687640faebb5cc376327c5) fix: fix build on macos, closes [#580](https://github.com/tauri-apps/wry/pull/580) ([#581](https://github.com/tauri-apps/wry/pull/581)) on 2022-05-10 + +## \[0.16.1] + +- Fixes a crash on macOS below Big Sur due to `titlebarSeparatorStyle` (11+ API) usage. + - [eb2dddb](https://github.com/tauri-apps/wry/commit/eb2dddb611f7fadf35bf7d7c32cb6d054da9fe9e) fix(macos): only use APIs when supported on 2022-05-08 +- Only run `WebView::print` on macOS on v11+. This prevents a crash on older versions. + - [eb2dddb](https://github.com/tauri-apps/wry/commit/eb2dddb611f7fadf35bf7d7c32cb6d054da9fe9e) fix(macos): only use APIs when supported on 2022-05-08 + +## \[0.16.0] + +- Fixes a typo in the `WebviewExtMacOS` conditional compilation. + - [10d7f03](https://github.com/tauri-apps/wry/commit/10d7f03f403e9c373fe80897308393e0bb67a06d) fix(macos): typo in the WebviewExtMacOS conditional compilation ([#568](https://github.com/tauri-apps/wry/pull/568)) on 2022-05-02 +- Fixes a crash when the custom protocol response is empty on macOS. + - [67809f4](https://github.com/tauri-apps/wry/commit/67809f4d8abe1a042b2cdb616b03f6a2c50652b8) fix(macos): crash when custom protocol response is empty ([#567](https://github.com/tauri-apps/wry/pull/567)) on 2022-05-01 +- Add `WebView::zoom` method. + - [34b6cbc](https://github.com/tauri-apps/wry/commit/34b6cbca76811966cedf8050ae0d0fa18c84aa34) feat: add feature to zoom webview contents, closes [#388](https://github.com/tauri-apps/wry/pull/388) ([#564](https://github.com/tauri-apps/wry/pull/564)) on 2022-05-02 +- Set the titlebar separator style in macOS to `none`. + - [9776fc4](https://github.com/tauri-apps/wry/commit/9776fc466b5f3a6ef47956ec5c9cdd9c5164046a) fix(macos): set titlebar style to `none` ([#566](https://github.com/tauri-apps/wry/pull/566)) on 2022-05-01 +- Disable webview2 mini menu + - [ed0b223](https://github.com/tauri-apps/wry/commit/ed0b2230c285991b7a4588c8045111f04a67a16f) fix: disable WebView2 mini menu ("OOUI"), closes [#535](https://github.com/tauri-apps/wry/pull/535) ([#559](https://github.com/tauri-apps/wry/pull/559)) on 2022-04-29 + +## \[0.15.1] + +- Update how android handles url + - [427cf92](https://github.com/tauri-apps/wry/commit/427cf9222d7152f911aa70eb778eb7aa90c83fac) Unify custom protocol across Android/iOS ([#546](https://github.com/tauri-apps/wry/pull/546)) on 2022-04-11 +- Add devtools support on Android/iOS. + - [1c5d77a](https://github.com/tauri-apps/wry/commit/1c5d77a8ce79e75705a71c659af86541d50c5007) Add devtools support on Android/iOS ([#548](https://github.com/tauri-apps/wry/pull/548)) on 2022-04-11 +- Fix to reset process on MacOS when webview is closed, closes #536. + - [fd1dcc3](https://github.com/tauri-apps/wry/commit/fd1dcc3cc5a290bfe4ae8de04064074109902432) fix: reset background process when webview is closed, closes [#536](https://github.com/tauri-apps/wry/pull/536) ([#556](https://github.com/tauri-apps/wry/pull/556)) on 2022-04-24 + +## \[0.15.0] + +- On Windows and Linux, disable resizing maximized borderless windows. + - [313eaea](https://github.com/tauri-apps/wry/commit/313eaea0ff123bddbc8b5c337ded05d464d3dfaa) fix(win,linux): disable resizing maximized borderless windows ([#533](https://github.com/tauri-apps/wry/pull/533)) on 2022-03-30 +- Fixes a memory leak on the custom protocol response body on macOS. + - [36b985e](https://github.com/tauri-apps/wry/commit/36b985e939f4769f9835b4865ee1013229ec7539) fix(macos): custom protocol memory leak ([#539](https://github.com/tauri-apps/wry/pull/539)) on 2022-04-03 +- Update tao to v0.8.0. + - [1c540b0](https://github.com/tauri-apps/wry/commit/1c540b01fa08e84c199b8ded726b6ec77b40f015) feat: update tao to 0.8, refactor tray features ([#541](https://github.com/tauri-apps/wry/pull/541)) on 2022-04-07 +- The `tray` and `ayatana-tray` Cargo features are not enabled by default. + - [1c540b0](https://github.com/tauri-apps/wry/commit/1c540b01fa08e84c199b8ded726b6ec77b40f015) feat: update tao to 0.8, refactor tray features ([#541](https://github.com/tauri-apps/wry/pull/541)) on 2022-04-07 +- **Breaking change:** Renamed the `ayatana` Cargo feature to `ayatana-tray` and added the `gtk-tray` feature. The default tray on Linux is now `libayatana-appindicator`. + - [1c540b0](https://github.com/tauri-apps/wry/commit/1c540b01fa08e84c199b8ded726b6ec77b40f015) feat: update tao to 0.8, refactor tray features ([#541](https://github.com/tauri-apps/wry/pull/541)) on 2022-04-07 + +## \[0.14.0] + +- Added `close_devtools` function to `Webview`. + - [bf3b710](https://github.com/tauri-apps/wry/commit/bf3b7107631f14567b0b5ff1947c2bff1ffa2603) feat: add function to close the devtool and check if it is opened ([#529](https://github.com/tauri-apps/wry/pull/529)) on 2022-03-28 +- Hide the devtool functions behind the `any(debug_assertions, feature = "devtools")` flag. + - [bf3b710](https://github.com/tauri-apps/wry/commit/bf3b7107631f14567b0b5ff1947c2bff1ffa2603) feat: add function to close the devtool and check if it is opened ([#529](https://github.com/tauri-apps/wry/pull/529)) on 2022-03-28 +- **Breaking change:** Renamed the `devtool` function to `open_devtools`. + - [bf3b710](https://github.com/tauri-apps/wry/commit/bf3b7107631f14567b0b5ff1947c2bff1ffa2603) feat: add function to close the devtool and check if it is opened ([#529](https://github.com/tauri-apps/wry/pull/529)) on 2022-03-28 +- Enable tab navigation on macOS. + - [28ebedc](https://github.com/tauri-apps/wry/commit/28ebedc41f9017fed3fe1dc3a6d021c69f88ef5d) fix(macOS): enable tab navigation on all elements, fixes [#406](https://github.com/tauri-apps/wry/pull/406) ([#512](https://github.com/tauri-apps/wry/pull/512)) on 2022-03-03 +- Added `is_devtools_open` function to `Webview`. + - [bf3b710](https://github.com/tauri-apps/wry/commit/bf3b7107631f14567b0b5ff1947c2bff1ffa2603) feat: add function to close the devtool and check if it is opened ([#529](https://github.com/tauri-apps/wry/pull/529)) on 2022-03-28 +- - Expose methods to access the underlying native handles of the webview. +- **Breaking change**: `WebviewExtWindows::controller` now returns the controller directly and not wrapped in an `Option` +- [e54afec](https://github.com/tauri-apps/wry/commit/e54afec43b767ffdb43debbd526d249c3c5b5490) feat: expose webview native handles, closes [#495](https://github.com/tauri-apps/wry/pull/495) ([#513](https://github.com/tauri-apps/wry/pull/513)) on 2022-03-03 +- Add navigation handler to decide if an url is allowed to navigate. + - [aa8af02](https://github.com/tauri-apps/wry/commit/aa8af020ab9d88ad762f2facbfa368effb04f570) feat: Implement navigation event and cancellation, closes [#456](https://github.com/tauri-apps/wry/pull/456) ([#519](https://github.com/tauri-apps/wry/pull/519)) on 2022-03-18 +- **Breaking change**: Renamed the `devtool` feature to `devtools`. + - [bf3b710](https://github.com/tauri-apps/wry/commit/bf3b7107631f14567b0b5ff1947c2bff1ffa2603) feat: add function to close the devtool and check if it is opened ([#529](https://github.com/tauri-apps/wry/pull/529)) on 2022-03-28 +- **Breaking change:** Renamed the `with_dev_tool` function to `with_devtools`. + - [bf3b710](https://github.com/tauri-apps/wry/commit/bf3b7107631f14567b0b5ff1947c2bff1ffa2603) feat: add function to close the devtool and check if it is opened ([#529](https://github.com/tauri-apps/wry/pull/529)) on 2022-03-28 + +## \[0.13.3] + +- Fix rustdoc generation of Windows and Mac on docs.rs. + - [327a019](https://github.com/tauri-apps/wry/commit/327a019a07fd10ca3a42ebfb8d9d626e3b91fd05) Fix rustdoc generation of Windows and Mac on docs.rs, fix [#503](https://github.com/tauri-apps/wry/pull/503) ([#507](https://github.com/tauri-apps/wry/pull/507)) on 2022-02-27 + +## \[0.13.2] + +- Fix cross compilation from `macOS`. + - [c97499f](https://github.com/tauri-apps/wry/commit/c97499fb078c7c65508bf2fa3502ef95c8114ef4) fix: cross compilation from macOS ([#498](https://github.com/tauri-apps/wry/pull/498)) on 2022-02-15 +- Update `webview2-com` to 0.13.0, which bumps the WebView2 SDK to 1.0.1108.44 and improves cross-compilation support. + +Targeting \*-pc-windows-gnu works now, but it has some [limitations](https://github.com/wravery/webview2-rs#cross-compilation). + +- [24a443c](https://github.com/tauri-apps/wry/commit/24a443ca1d90ef091eaceb0ec61bcc648499b743) Add /.changes/webview2-com-0.13.0.md on 2022-02-14 + +## \[0.13.1] + +- Add `devtool` feature flag and configuration option. + - [d0f307b](https://github.com/tauri-apps/wry/commit/d0f307b218c3913520efbb378e9c01a526137fdd) feat: implement `devtools` API, closes [#287](https://github.com/tauri-apps/wry/pull/287) ([#486](https://github.com/tauri-apps/wry/pull/486)) on 2022-02-07 + +- Update the `webview2-com` crate 0.11.0: + +- Fix silent build script errors related to unconfigured nuget in https://github.com/wravery/webview2-rs/pull/4 + +- Update the WebView2 SDK (not the runtime, just the API bindings) to the latest 1.0.1072.54 version + +- [7d4eeb7](https://github.com/tauri-apps/wry/commit/7d4eeb744bf008e43c034e865b383ee4a330e77a) Update webview2-com to 0.11.0 ([#488](https://github.com/tauri-apps/wry/pull/488)) on 2022-02-06 + +## \[0.13.0] + +- Update gtk to 0.15 + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Add clipboard field in WebViewAttributes. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Ignore transparency on Windows 7 to prevent application crash. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Remove clipboard property for consistency across platforms. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Enable cookie persistence on Linux if the `data_directory` is provided. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Enable objc's exception features so they can be treated as panic message. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Add inner size method for webview. This can reflect correct size of webview on macOS. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Add "transparent" and "fullscreen" feature flags on macOS to toggle private API. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Implement WebContextImpl on mac to extend several callback lifetimes. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- The only thing that private mod shared does is re-export http mod to public, + we can just pub mod http. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- - Fix hovering over an edge of undecorated window on Linux won't change cursor. +- Undecorated window can be resized using touch on Linux. +- [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Update webkit2gtk to 0.15 + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Add `with_user_agent(&str)` to `WebViewBuilder`. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- 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. The replacement bindings are in the `webview2-com-sys` crate, with `pub use` in the `webview2-com` crate. They can be shared with TAO. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Fix null pointer crash on `get_content` of web resource request. This is a temporary fix. + We will switch it back once upstream is updated. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- 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`. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Update the `windows` crate to 0.29.0 and `webview2-com` to 0.9.0. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 +- Update the `windows` crate to 0.30.0 and `webview2-com` to 0.10.0. + - [219d20c](https://github.com/tauri-apps/wry/commit/219d20ce66a6bdf6c3e1af6156c9f2a74f2eed29) Merge next back to dev branch ([#477](https://github.com/tauri-apps/wry/pull/477)) on 2022-02-05 + +## \[0.12.2] + +- Fixed a Linux multi-window issue where the internal url loader didn't unlock when flushed while empty + - [5377821](https://github.com/tauri-apps/wry/commit/5377821f43c0e7556ec46f0aaf4d6b0637512493) Fix async multiwindow deadlock ([#382](https://github.com/tauri-apps/wry/pull/382)) on 2021-08-16 + +- The custom protocol now returns a `Request` and expects a `Response`. + +- This allows us to get the complete request from the Webview. (Method, GET, POST, PUT etc..) + Read the complete header. + +- And allow us to be more flexible in the future without bringing breaking changes. + +- [d202573](https://github.com/tauri-apps/wry/commit/d202573c2c68a2ff0411c1aa797ecc10f727e93b) refactor: Custom protocol request/response ([#387](https://github.com/tauri-apps/wry/pull/387)) on 2021-08-22 + +- On Linux, automation callbacks now use the first created webview as the return value + - [f9d7049](https://github.com/tauri-apps/wry/commit/f9d7049978bbad389c99d7a7cce9903a528d871d) Use the first created webview for webkit2gtk automation callbacks ([#383](https://github.com/tauri-apps/wry/pull/383)) on 2021-08-16 + +## \[0.12.1] + +- Add html attributes as another method to load the page. This can provide some other origin header and make CORS request + possible. + - [02ad372](https://github.com/tauri-apps/wry/commit/02ad37219a1f6e5e6ed8e4da61e6a5ac021d410e) feat: html string attributes ([#368](https://github.com/tauri-apps/wry/pull/368)) on 2021-08-12 +- Shorter protocol name on Windows. This can make origin be shorter too. + - [2d9f5c9](https://github.com/tauri-apps/wry/commit/2d9f5c95e3805911d12803122fd1e83be758a769) Shorter protocol name on Windows ([#367](https://github.com/tauri-apps/wry/pull/367)) on 2021-08-12 + +## \[0.12.0] + +- Custom Protocol handlers no longer take a `&Window` parameter. + - [0e2574c](https://github.com/tauri-apps/wry/commit/0e2574c420f778c59bafc164ddee2bc0b7705ee9) Remove `&Window` parameter from Custom Protocol handlers ([#361](https://github.com/tauri-apps/wry/pull/361)) on 2021-07-28 +- Update gtk to version 0.14. This also remove requirement of `clang`. + - [251a80b](https://github.com/tauri-apps/wry/commit/251a80bab49d42f742a3ae6b3ca2cbfc97de98bb) Update gtk to version 0.14 ([#364](https://github.com/tauri-apps/wry/pull/364)) on 2021-08-06 +- Update tao to v0.5. Please see release notes on tao for more information. + - [483bad0](https://github.com/tauri-apps/wry/commit/483bad0fc7e7564500f7183547c15604fa387258) feat: tao as window dependency ([#230](https://github.com/tauri-apps/wry/pull/230)) on 2021-05-03 + - [51430e9](https://github.com/tauri-apps/wry/commit/51430e97dfb6589c5ff71e5078438be67293d044) publish new versions ([#221](https://github.com/tauri-apps/wry/pull/221)) on 2021-05-09 + - [0cf0089](https://github.com/tauri-apps/wry/commit/0cf0089b6d49aa9e1a8c791ec8883fce48a0dfd1) Update tao to v0.2.6 ([#271](https://github.com/tauri-apps/wry/pull/271)) on 2021-05-18 + - [a76206c](https://github.com/tauri-apps/wry/commit/a76206c11fa0a4ba1d041aa0f25452dd80941ee9) publish new versions ([#272](https://github.com/tauri-apps/wry/pull/272)) on 2021-05-18 + - [3c4f8b8](https://github.com/tauri-apps/wry/commit/3c4f8b8b2bd42e7634b889aa5317d909bfce593c) Update tao to v0.5 ([#365](https://github.com/tauri-apps/wry/pull/365)) on 2021-08-09 +- Add flags to support all other possible unix systems. + - [c0d0a78](https://github.com/tauri-apps/wry/commit/c0d0a78b893eecdc45c6cda71264020d6ae17bda) Add flags to support all other unix systems. ([#352](https://github.com/tauri-apps/wry/pull/352)) on 2021-07-21 +- Support having multiple webkit2gtk `WebView`s on a single `WebContext`. + - [3f03d6b](https://github.com/tauri-apps/wry/commit/3f03d6b5ea4e9ba81950245de156f09e72ab40a1) Support multiple webviews on a single WebContext (webkit2gtk) ([#359](https://github.com/tauri-apps/wry/pull/359)) on 2021-07-28 +- On Windows, Fix cursor flickering when Tao window is without decorations + - [e28bcce](https://github.com/tauri-apps/wry/commit/e28bcce0884937365013fda3098f64f9956d569f) fix(windows): fix mouse style flicker when `decorations: false` ([#350](https://github.com/tauri-apps/wry/pull/350)) on 2021-07-20 +- Remove winrt support since it's outdated for a long time. We will reimplement it again once `windws-rs` is stable! + - [c37973e](https://github.com/tauri-apps/wry/commit/c37973e47318e9cff2712eb4a394c07734f58d54) chore(windows): remove winrt support ([#356](https://github.com/tauri-apps/wry/pull/356)) on 2021-07-24 + +## \[0.11.0] + +- Allow resizing of borderless window on Windows + - [bd10b8e](https://github.com/tauri-apps/wry/commit/bd10b8e5fe517edd6234ed03170741f1a51768bf) feat(Windows): resize borderless window ([#333](https://github.com/tauri-apps/wry/pull/333)) on 2021-07-15 +- Mark enums as `#[non_exhaustive]` to prevent breaking changes on enum update. + - [f07ae14](https://github.com/tauri-apps/wry/commit/f07ae144197933c28f8302105b313c2a2afc62af) refactor: add `#[non_exhaustive]` attributes to enums ([#304](https://github.com/tauri-apps/wry/pull/304)) on 2021-07-08 +- Bump tao to `0.4`. Please refer to `tao` changelog for more details. + - [6eb10d4](https://github.com/tauri-apps/wry/commit/6eb10d4e10ce86c8403c80fb41ba5e37072dc61e) bump `tao` to 0.4 and fix examples ([#329](https://github.com/tauri-apps/wry/pull/329)) on 2021-07-14 +- - Add `focus` method to `Webview` +- Add `WebviewExtWindows` trait with `controller` method +- [621ed1f](https://github.com/tauri-apps/wry/commit/621ed1fff35d9389d88664d8084e1a678dfbfc36) feat: add `.focus()` to `Webview` ([#325](https://github.com/tauri-apps/wry/pull/325)) on 2021-07-05 +- [96b7b94](https://github.com/tauri-apps/wry/commit/96b7b943da34ab81872553e65d2f2cd138531a62) Add controller method instead ([#326](https://github.com/tauri-apps/wry/pull/326)) on 2021-07-07 +- macOS: Remove handler in the webview as it should be handled with the menu. + - [5a9df15](https://github.com/tauri-apps/wry/commit/5a9df156f04789d4c89fdb8edf72b301667df127) fix(macos): Remove keypress handler in the webview for copy/paste/cut ([#328](https://github.com/tauri-apps/wry/pull/328)) on 2021-07-07 +- Fixes multiple custom protocols registration on Windows. + - [923d346](https://github.com/tauri-apps/wry/commit/923d3461ce93846af8dd548d4e43ebd0fd6111a3) fix(windows): multiple custom protocols, closes [#323](https://github.com/tauri-apps/wry/pull/323) ([#324](https://github.com/tauri-apps/wry/pull/324)) on 2021-07-02 + +## \[0.10.3] + +- [#315](https://github.com/tauri-apps/wry/pull/315) fixed Webview2 runtime performance issues. + - [d3c9b16](https://github.com/tauri-apps/wry/commit/d3c9b169d81fd8b79e6695d91b3a1d0e8042a81f) Fix Webview2 runtime performance issues ([#316](https://github.com/tauri-apps/wry/pull/316)) on 2021-06-29 + +## \[0.10.2] + +- Fix file explorer getting blocked by automation. + - [0c5cdd8](https://github.com/tauri-apps/wry/commit/0c5cdd8f2a6f4d07d87c6c4d1c51540ff9abfd97) Fix file explorer getting blocked by automation ([#310](https://github.com/tauri-apps/wry/pull/310)) on 2021-06-23 + +## \[0.10.1] + +- `WebContext::set_allows_automation` is now available to specify if the context should allow automation (e.g. WebDriver). + It is only enforced on Linux, but may expand platforms in the future. + - [4ad0bf1](https://github.com/tauri-apps/wry/commit/4ad0bf12d186b3c313131060316aef371f45d455) move set_allows_automation to WebContext method ([#302](https://github.com/tauri-apps/wry/pull/302)) on 2021-06-21 + +## \[0.10.0] + +- Add WebViewAttributes + - [81f3218](https://github.com/tauri-apps/wry/commit/81f3218d9ac55a987b050f574774afcaa0b5c2f7) Add WebViewAttributes ([#286](https://github.com/tauri-apps/wry/pull/286)) on 2021-06-04 +- Add `with_web_context` method that can work well with builder pattern. + - [48f53a3](https://github.com/tauri-apps/wry/commit/48f53a3393b0c016a972a72dec45691959ac9e3b) Add `with_web_context` method ([#292](https://github.com/tauri-apps/wry/pull/292)) on 2021-06-13 +- Change the custom protocol handler on macOS so it returns a response on error and a status code on success. + - [6b869b1](https://github.com/tauri-apps/wry/commit/6b869b1ad5de9c8e9f36c1fc1b7040e10b033b52) fix(macos): custom protocol response with status code + error response ([#279](https://github.com/tauri-apps/wry/pull/279)) on 2021-05-20 +- Update signature of custom protocol closure. It should return a mime type string now. + - [cc9fc4b](https://github.com/tauri-apps/wry/commit/cc9fc4b43df79834c1b8f2c1347accba50356604) Add mimetype to return type of custom protocol ([#296](https://github.com/tauri-apps/wry/pull/296)) on 2021-06-13 +- Fix webview creation when using new_any_thread of event loop. + - [4d62cf5](https://github.com/tauri-apps/wry/commit/4d62cf5a3ddcbed06afb93d9503424a9b8110d57) Fix webview creation when using new_any_thread on Windows ([#298](https://github.com/tauri-apps/wry/pull/298)) on 2021-06-18 +- Remove `Dispatcher`, `dispatch_script` and `dispatcher` in the `webview` module and add a `js` parameter to `evaluate_script`. + - [de4a5fa](https://github.com/tauri-apps/wry/commit/de4a5fa820b1938532223677913e73720885cb54) refactor: remove `Dispatcher` and related methods, closes [#290](https://github.com/tauri-apps/wry/pull/290) ([#291](https://github.com/tauri-apps/wry/pull/291)) on 2021-06-09 +- Removes the `image` dependency. + - [1d5cc59](https://github.com/tauri-apps/wry/commit/1d5cc590856e1be1428f8516595ace6d8099f41f) chore(deps): remove `image` dependency ([#274](https://github.com/tauri-apps/wry/pull/274)) on 2021-05-19 +- Bump tao to `0.3` and add more examples. + +*For more details, please refer to `tao` changelog.* + +- [cd4697e](https://github.com/tauri-apps/wry/commit/cd4697ebdb8eb955f0ed2be4aefea82d2c263a52) bump `tao` to 0.3 with examples ([#294](https://github.com/tauri-apps/wry/pull/294)) on 2021-06-21 +- Add `wry::webview::WebContext`. It's now a required argument on `WebViewBuilder::build`. + - [761b2b5](https://github.com/tauri-apps/wry/commit/761b2b59fe0434b3458d99ed599394af0e1e3962) webdriver support ([#281](https://github.com/tauri-apps/wry/pull/281)) on 2021-06-08 + +## \[0.9.4] + +- Update tao to v0.2.6 + - [483bad0](https://github.com/tauri-apps/wry/commit/483bad0fc7e7564500f7183547c15604fa387258) feat: tao as window dependency ([#230](https://github.com/tauri-apps/wry/pull/230)) on 2021-05-03 + - [51430e9](https://github.com/tauri-apps/wry/commit/51430e97dfb6589c5ff71e5078438be67293d044) publish new versions ([#221](https://github.com/tauri-apps/wry/pull/221)) on 2021-05-09 + - [0cf0089](https://github.com/tauri-apps/wry/commit/0cf0089b6d49aa9e1a8c791ec8883fce48a0dfd1) Update tao to v0.2.6 ([#271](https://github.com/tauri-apps/wry/pull/271)) on 2021-05-18 + +## \[0.9.3] + +- Expose `webview_version` function in the `webview` module. + - [4df310e](https://github.com/tauri-apps/wry/commit/4df310e6bb508854ffc17ec915b3d0ab7c11f03d) feat: get webview version ([#259](https://github.com/tauri-apps/wry/pull/259)) on 2021-05-12 +- Add print method on Linux and Windows. + - [54c5ec7](https://github.com/tauri-apps/wry/commit/54c5ec7ae6166da5ce670ccd2ceaa108233bb845) Implement print method on Linux and Windows ([#264](https://github.com/tauri-apps/wry/pull/264)) on 2021-05-17 +- Disable smooth scrolling on Linux to match behaviour on browsers. + - [3e786bb](https://github.com/tauri-apps/wry/commit/3e786bb28793e939c00ebf0c6758d4f6cf4d3b28) Disable smooth scrolling on Linux ([#268](https://github.com/tauri-apps/wry/pull/268)) on 2021-05-17 + +## \[0.9.2] + +- Add `tray` feature flag from tao. + - [093c25e](https://github.com/tauri-apps/wry/commit/093c25ee68d51849b95a1a3b9341e5ad6021cecf) feat: expose tray feature flag ([#256](https://github.com/tauri-apps/wry/pull/256)) on 2021-05-10 + +## \[0.9.1] + +- Correctly set visibility when building `Window` on gtk-backend + - [4395ad1](https://github.com/tauri-apps/wry/commit/4395ad147b799e67f9802c499346d0ad53554317) fix: only call `show_all` when needed ([#227](https://github.com/tauri-apps/wry/pull/227)) on 2021-05-02 +- Fix `macOS` cursors and other minors UI glitch. + - [d550b2f](https://github.com/tauri-apps/wry/commit/d550b2f0a1c708747537e3a5e6d880fea00e651d) fix(macOS): Window layers ([#220](https://github.com/tauri-apps/wry/pull/220)) on 2021-04-28 +- Expose `print()` function to the webview. Work only on macOS for now. + - [5206db6](https://github.com/tauri-apps/wry/commit/5206db6ca599fe0e146d72b04c908330e3045838) fix(macOS): Printing ([#235](https://github.com/tauri-apps/wry/pull/235)) ([#236](https://github.com/tauri-apps/wry/pull/236)) on 2021-05-06 +- Fix macOS windows order for tray (statusbar) applications. + - [229275f](https://github.com/tauri-apps/wry/commit/229275f106371d79800e0ca1cbc7b6c1827bc2ac) fix: macOS windows order ([#242](https://github.com/tauri-apps/wry/pull/242)) on 2021-05-07 +- Add `request_redraw` method of `Window` on Linux + - [03abfa0](https://github.com/tauri-apps/wry/commit/03abfa06019a78a182c7cd29dc63bf3d9df10e44) Add request_redraw method on Linux ([#222](https://github.com/tauri-apps/wry/pull/222)) on 2021-04-30 +- Add tao as window dependency. + - [483bad0](https://github.com/tauri-apps/wry/commit/483bad0fc7e7564500f7183547c15604fa387258) feat: tao as window dependency ([#230](https://github.com/tauri-apps/wry/pull/230)) on 2021-05-03 +- Close the window when the instance is dropped on Linux and Windows. + - [3f2cc28](https://github.com/tauri-apps/wry/commit/3f2cc28b4fbfcf54c97000a6541e9356440838e8) fix: close window when the instance is dropped ([#228](https://github.com/tauri-apps/wry/pull/228)) on 2021-05-02 +- Remove winit dependency on Linux + - [fa15076](https://github.com/tauri-apps/wry/commit/fa15076207d9e678db4149210aba929044d0ff45) feat: winit interface for gtk ([#163](https://github.com/tauri-apps/wry/pull/163)) on 2021-04-19 + - [39d6f59](https://github.com/tauri-apps/wry/commit/39d6f595d81c857e92aef31cc2559b402e64edd3) publish new versions ([#166](https://github.com/tauri-apps/wry/pull/166)) on 2021-04-29 + - [4ef8330](https://github.com/tauri-apps/wry/commit/4ef8330d856e07d34bf86d1f2903c82c37042556) Remove winit dependency on Linux ([#226](https://github.com/tauri-apps/wry/pull/226)) on 2021-04-30 + +## \[0.9.0] + +- Refactor signatures of most closure types + - [b8823fe](https://github.com/tauri-apps/wry/commit/b8823fe14ee5f95d07cd2cb1f9f673b964c9dc83) refactor: signature of closure types ([#167](https://github.com/tauri-apps/wry/pull/167)) on 2021-04-19 +- Drop handler closures properly on macOS. + - [f905503](https://github.com/tauri-apps/wry/commit/f905503c4a010ed4219c6ad36d14c0dbf0b6e122) fix: [#160](https://github.com/tauri-apps/wry/pull/160) drop handler closures properly ([#211](https://github.com/tauri-apps/wry/pull/211)) on 2021-04-27 +- Fix `history.pushState` in webview2. + - [dd0fa46](https://github.com/tauri-apps/wry/commit/dd0fa46494c1ab8536bcc7ea1dd16341b12856b4) Use http instead of file for windows custom protocol workaround ([#173](https://github.com/tauri-apps/wry/pull/173)) on 2021-04-20 +- The `data_directory` field now affects the IndexedDB and LocalStorage directories on Linux. + - [1a6c821](https://github.com/tauri-apps/wry/commit/1a6c8216ee6865ca14025c229b37342496b38f26) feat(linux): implement custom user data path ([#188](https://github.com/tauri-apps/wry/pull/188)) on 2021-04-22 +- Fix runtime panic on macOS, when no file handler are defined. + - [22a4991](https://github.com/tauri-apps/wry/commit/22a4991aa8ca7c75aa52150a90379c40bcc34d07) bug(macOS): Runtime panic when no file_drop_handler ([#177](https://github.com/tauri-apps/wry/pull/177)) on 2021-04-20 +- Add position field on WindowAttribute + - [2b3be7a](https://github.com/tauri-apps/wry/commit/2b3be7a4db2cbc1612c7105cb698c1f21a05da77) Add position field on WindowAttribute ([#219](https://github.com/tauri-apps/wry/pull/219)) on 2021-04-28 +- Fix panic on multiple custom protocols registration. + - [01647a2](https://github.com/tauri-apps/wry/commit/01647a2a5b769bc192754c2d3806a55112d58d33) Fix custom protocol registry on mac ([#205](https://github.com/tauri-apps/wry/pull/205)) on 2021-04-26 +- Fix SVG render with the custom protocol. + - [890cfe5](https://github.com/tauri-apps/wry/commit/890cfe527996c181d643c9f8e5fc3e79ff0841a0) fix(custom-protocol): SVG mime type - close [#168](https://github.com/tauri-apps/wry/pull/168) ([#169](https://github.com/tauri-apps/wry/pull/169)) on 2021-04-19 +- Initial custom WindowExtWindows trait. + - [1ef1f58](https://github.com/tauri-apps/wry/commit/1ef1f58efb6afa6c6b9eda3a43ee83fc79c3b78e) feat: custom WindowExtWindow trait ([#191](https://github.com/tauri-apps/wry/pull/191)) on 2021-04-23 +- Fix transparency on Windows + - [e278556](https://github.com/tauri-apps/wry/commit/e2785566c69d43f003896b7b5da79b29d2966c13) fix: transparency on Windows ([#217](https://github.com/tauri-apps/wry/pull/217)) on 2021-04-28 +- Add platform module and WindowExtUnix trait on Linux + - [004e298](https://github.com/tauri-apps/wry/commit/004e298e0198e6576a11e6e84fdf6b7c2f66b6ae) feat: WindowExtUnix trait ([#192](https://github.com/tauri-apps/wry/pull/192)) on 2021-04-23 +- Make sure custom protocol on Windows is over HTTPS. + - [c36db35](https://github.com/tauri-apps/wry/commit/c36db35b2b8704eb36bc341cd99abac01abfab87) fix(custom-protocol): Make sure custom protocol on Windows is over HTTPS. ([#179](https://github.com/tauri-apps/wry/pull/179)) on 2021-04-20 +- Initial winit interface for gtk backend + - [fa15076](https://github.com/tauri-apps/wry/commit/fa15076207d9e678db4149210aba929044d0ff45) feat: winit interface for gtk ([#163](https://github.com/tauri-apps/wry/pull/163)) on 2021-04-19 + +## \[0.8.0] + +- Wry now accepts multiple custom protocol registrations. + - [db64fc6](https://github.com/tauri-apps/wry/commit/db64fc69c48a728184fcef001688b94f0294edab) feat/licenses ([#155](https://github.com/tauri-apps/wry/pull/155)) on 2021-04-14 +- Apply license header for SPDX compliance. + - [05e0218](https://github.com/tauri-apps/wry/commit/05e02180c9fe929d3e691185df44257654546935) feat: multiple custom protocols ([#151](https://github.com/tauri-apps/wry/pull/151)) on 2021-04-11 + - [db64fc6](https://github.com/tauri-apps/wry/commit/db64fc69c48a728184fcef001688b94f0294edab) feat/licenses ([#155](https://github.com/tauri-apps/wry/pull/155)) on 2021-04-14 +- Remove bindings crate and use windows-webview2 as dependency instead. + - [c2156a4](https://github.com/tauri-apps/wry/commit/c2156a45d7fbfead956b6d03b2594962e3455e6d) Move to windows-webview2 as dependency for winrt impl ([#144](https://github.com/tauri-apps/wry/pull/144)) on 2021-04-03 + +## \[0.7.0] + +- Add old win32 implementation on windows as default feature flag. + - [1a88cd2](https://github.com/tauri-apps/wry/commit/1a88cd267f2a29c1dd35d7197250972718081847) refactor: Add win32 implementation and feature flag for both backends ([#139](https://github.com/tauri-apps/wry/pull/139)) on 2021-04-02 +- Adds a `WindowProxy` to the file drop handler closure - `WindowFileDropHandler`. + - [20cb051](https://github.com/tauri-apps/wry/commit/20cb051aba28009c70dad838b2a9b1575cb5363a) feat: add WindowProxy to file drop handler closure ([#140](https://github.com/tauri-apps/wry/pull/140)) on 2021-04-01 + +## \[0.6.2] + +- Add pipe back to version check for covector config. This prevents the CI failure on publish if it exists already. The issue was patched in covector (and tests in place so it doesn't break in the future). + - [a32829c](https://github.com/tauri-apps/wry/commit/a32829c527f02b228fa1da45e9710941c5415bfc) chore: add pipe for publish check back in ([#131](https://github.com/tauri-apps/wry/pull/131)) on 2021-03-28 +- Fix messages to the webview from the backend being delayed on Linux/GTK when the user is not actively engaged with the UI. + - [d2a2a9f](https://github.com/tauri-apps/wry/commit/d2a2a9f473d2588b27a95bf627d125caea1b979d) fix: spawn async event loop on gtk to prevent delayed messages ([#135](https://github.com/tauri-apps/wry/pull/135)) on 2021-03-31 +- Add draggable regions, just add `drag-region` class to the html element. + - [b2a0bfc](https://github.com/tauri-apps/wry/commit/b2a0bfc289786d0a23dac0c8d9543771e70e3427) feat/ draggable-region ([#92](https://github.com/tauri-apps/wry/pull/92)) on 2021-03-25 +- Add event listener in application proxy + - [c49846c](https://github.com/tauri-apps/wry/commit/c49846cfc41bb548a685edeac5f8036501f7dcec) feat: event listener ([#129](https://github.com/tauri-apps/wry/pull/129)) on 2021-03-26 +- Better result error handling + - [485035f](https://github.com/tauri-apps/wry/commit/485035f17d28560966b07b512935821814f0e951) chore: better result error handling ([#124](https://github.com/tauri-apps/wry/pull/124)) on 2021-03-21 +- Fix visibility on webview2 when window was invisible previously and then shown. + - [6d31706](https://github.com/tauri-apps/wry/commit/6d31706a6bff43e9b28100675cf8fc12f29db248) Fix visibility on webview2 when window was invisible previously ([#128](https://github.com/tauri-apps/wry/pull/128)) on 2021-03-24 + +## \[0.6.1] + +- Add attribute option to allow WebView on Windows use user_data folder + - [8dd58ee](https://github.com/tauri-apps/wry/commit/8dd58eec77d4c89491b1af427d06c4ee6cfa8e58) feat/ allow webview2 (windows) to use optional user_data folder provided by the attributes ([#120](https://github.com/tauri-apps/wry/pull/120)) on 2021-03-21 + +## \[0.6.0] + +- Initialize covector! + - [33b64ed](https://github.com/tauri-apps/wry/commit/33b64ed5c208b778d03dbb5f3f2808bb417c9f52) chore: covector init ([#55](https://github.com/tauri-apps/wry/pull/55)) on 2021-02-21 +- Support Windows 7, 8, and 10 + - [fbf0d17](https://github.com/tauri-apps/wry/commit/fbf0d17164da455400aaa44104c3925eded09393) Adopt Webview2 on Windows ([#48](https://github.com/tauri-apps/wry/pull/48)) on 2021-02-20 +- Dev tools are enabled on debug build +- Add skip task bar option + - [395b6fb](https://github.com/tauri-apps/wry/commit/395b6fbcd66f6cbd0457cb609bea4afe734fadd4) feat: `skip_taskbar` for windows ([#49](https://github.com/tauri-apps/wry/pull/49)) on 2021-02-20 +- Add custom protocol option + - [a492806](https://github.com/tauri-apps/wry/commit/7a492806d716a30abe15a2104b64152c1ca370bb) Add custom protocol ([#65](https://github.com/tauri-apps/wry/pull/65)) on 2021-02-23 +- Add transparent option to mac and linux +- Error type has Send/Sync traits + - [3536b83](https://github.com/tauri-apps/wry/commit/3536b831ec30ee7436616ba4b262bbdd1e6279c8) Add .changes file in prepare of v0.6 on 2021-02-24 +- Replace Callback with RPC handler + - [e215157](https://github.com/tauri-apps/wry/commit/e215157146f0eab8ee6beab0628b036c68eea108) Implement draft RPC API ([#95](https://github.com/tauri-apps/wry/pull/95)) on 2021-03-04 +- Add File drop handlers + - [fed0ee7](https://github.com/tauri-apps/wry/commit/fed0ee772100ad19a344a85266618c7bcf7cb649) File drop handlers ([#96](https://github.com/tauri-apps/wry/pull/96)) on 2021-03-09 diff --git a/desktop/src-tauri/vendor/wry/Cargo.lock b/desktop/src-tauri/vendor/wry/Cargo.lock new file mode 100644 index 00000000000..f8e3bf8d841 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/Cargo.lock @@ -0,0 +1,4354 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.11.1", + "cc", + "jni 0.22.4", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 2.0.18", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.11.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.11.1", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "dbus" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.1", + "block2 0.6.2", + "libc", + "objc2 0.6.4", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser", + "foldhash 0.2.0", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link 0.2.1", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.11.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glow" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.11.1", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "windows 0.58.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.11.1", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "bitflags 2.11.1", + "libc", + "plain", + "redox_syscall 0.7.4", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.11.1", + "block", + "core-graphics-types 0.1.3", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "naga" +version = "23.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.11.1", + "cfg_aliases 0.1.1", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "rustc-hash 1.1.0", + "spirv", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.11.1", + "jni-sys 0.3.1", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.11.1", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.11.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.11.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.1", + "dispatch2", + "objc2 0.6.4", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.11.1", + "dispatch2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.11.1", + "block2 0.5.1", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.11.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.11.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.11.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-cloud-kit 0.2.2", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", + "objc2-core-location 0.2.2", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core 0.2.2", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.11.1", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-cloud-kit 0.3.2", + "objc2-core-data 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image 0.3.2", + "objc2-core-location 0.3.2", + "objc2-core-text", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", + "objc2-user-notifications 0.3.2", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.11.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.11.1", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "orbclient" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a570f6bca41d29acb2139229a7c873ec99bc9a313bd10804081d89bfac8ff329" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.11+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" + +[[package]] +name = "quick-xml" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "redox_syscall" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.11.1", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "rustc-hash 2.1.2", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.11.1", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cf65722394c2ac443e80120064987f8914ee1d4e4e36e63cdf10f2990f01159" +dependencies = [ + "bitflags 2.11.1", + "block2 0.6.2", + "core-foundation 0.10.1", + "core-graphics 0.25.0", + "crossbeam-channel", + "dbus", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni 0.21.1", + "libc", + "log", + "ndk", + "ndk-sys 0.6.0+11769913", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "objc2-ui-kit 0.3.2", + "once_cell", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tendril" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24" +dependencies = [ + "new_debug_unreachable", + "utf-8", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.2", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.2", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.11.1", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.11.1", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" +dependencies = [ + "bitflags 2.11.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.11.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.11.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-implement 0.60.2", + "windows-interface 0.59.3", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.18", + "windows 0.61.3", + "windows-core 0.61.2", +] + +[[package]] +name = "wgpu" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a" +dependencies = [ + "arrayvec", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.11.1", + "cfg_aliases 0.1.1", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.11.1", + "block", + "bytemuck", + "cfg_aliases 0.1.1", + "core-graphics-types 0.1.3", + "glow 0.14.2", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "wgpu-types" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" +dependencies = [ + "bitflags 2.11.1", + "js-sys", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.11.1", + "block2 0.5.1", + "bytemuck", + "calloop", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit 0.2.2", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wry" +version = "0.55.1" +dependencies = [ + "base64", + "block2 0.6.2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "getrandom 0.3.4", + "glow 0.16.0", + "gtk", + "http", + "http-range", + "javascriptcore-rs", + "jni 0.21.1", + "libc", + "libloading", + "ndk", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-ui-kit 0.3.2", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "pollster", + "raw-window-handle", + "sha2", + "soup3", + "tao", + "tao-macros", + "thiserror 2.0.18", + "tracing", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "wgpu", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "winit", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.4", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.11.1", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] diff --git a/desktop/src-tauri/vendor/wry/Cargo.toml b/desktop/src-tauri/vendor/wry/Cargo.toml new file mode 100644 index 00000000000..0988872b01f --- /dev/null +++ b/desktop/src-tauri/vendor/wry/Cargo.toml @@ -0,0 +1,424 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.77" +name = "wry" +version = "0.55.1" +authors = ["Tauri Programme within The Commons Conservancy"] +build = "build.rs" +exclude = [ + "/.changes", + "/.github", + "/audits", + "/wry-logo.svg", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Cross-platform WebView rendering library" +documentation = "https://docs.rs/wry" +readme = "README.md" +categories = ["gui"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/tauri-apps/wry" + +[package.metadata.docs.rs] +no-default-features = true +features = [ + "protocol", + "os-webview", +] +targets = [ + "x86_64-unknown-linux-gnu", + "x86_64-pc-windows-msvc", + "x86_64-apple-darwin", +] + +[features] +default = [ + "protocol", + "os-webview", + "x11", +] +devtools = [] +fullscreen = [] +linux-body = [ + "webkit2gtk/v2_40", + "os-webview", +] +mac-proxy = [] +os-webview = [ + "javascriptcore-rs", + "webkit2gtk", + "webkit2gtk-sys", + "dep:gtk", + "soup3", +] +protocol = [] +serde = ["dpi/serde"] +tracing = ["dep:tracing"] +transparent = [] +x11 = [ + "x11-dl", + "gdkx11", + "tao/x11", +] + +[lib] +name = "wry" +path = "src/lib.rs" + +[[example]] +name = "async_custom_protocol" +path = "examples/async_custom_protocol.rs" + +[[example]] +name = "cookies" +path = "examples/cookies.rs" + +[[example]] +name = "custom_protocol" +path = "examples/custom_protocol.rs" + +[[example]] +name = "custom_titlebar" +path = "examples/custom_titlebar.rs" + +[[example]] +name = "gtk_multiwebview" +path = "examples/gtk_multiwebview.rs" + +[[example]] +name = "gtk_opengl" +path = "examples/gtk_opengl.rs" + +[[example]] +name = "multiwebview" +path = "examples/multiwebview.rs" + +[[example]] +name = "multiwindow" +path = "examples/multiwindow.rs" + +[[example]] +name = "reparent" +path = "examples/reparent.rs" + +[[example]] +name = "simple" +path = "examples/simple.rs" + +[[example]] +name = "streaming" +path = "examples/streaming.rs" + +[[example]] +name = "transparent" +path = "examples/transparent.rs" + +[[example]] +name = "wgpu" +path = "examples/wgpu.rs" + +[[example]] +name = "window_border" +path = "examples/window_border.rs" + +[[example]] +name = "winit" +path = "examples/winit.rs" + +[dependencies.cookie] +version = "0.18" + +[dependencies.dpi] +version = "0.1" + +[dependencies.http] +version = "1.1" + +[dependencies.once_cell] +version = "1" + +[dependencies.raw-window-handle] +version = "0.6" +features = ["std"] + +[dependencies.thiserror] +version = "2.0" + +[dependencies.tracing] +version = "0.1" +optional = true + +[dev-dependencies.base64] +version = "0.22" + +[dev-dependencies.dom_query] +version = "0.27.0" +default-features = false + +[dev-dependencies.getrandom] +version = "0.3" + +[dev-dependencies.glow] +version = "0.16.0" + +[dev-dependencies.http-range] +version = "0.1" + +[dev-dependencies.libloading] +version = "0.8.9" + +[dev-dependencies.percent-encoding] +version = "2.3" + +[dev-dependencies.pollster] +version = "0.4.0" + +[dev-dependencies.sha2] +version = "0.10" + +[dev-dependencies.tao] +version = "0.35" + +[dev-dependencies.wgpu] +version = "23" + +[dev-dependencies.winit] +version = "0.30" + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.dirs] +version = "6" + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.gdkx11] +version = "0.18" +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.gtk] +version = "0.18" +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.javascriptcore-rs] +version = "=1.1.2" +features = ["v2_28"] +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.percent-encoding] +version = "2.3" + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.soup3] +version = "0.5" +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.webkit2gtk] +version = "=2.0.2" +features = ["v2_38"] +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.webkit2gtk-sys] +version = "=2.0.2" +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.x11-dl] +version = "2.21" +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dev-dependencies.x11-dl] +version = "2.21" + +[target.'cfg(target_os = "android")'.dependencies.base64] +version = "0.22" + +[target.'cfg(target_os = "android")'.dependencies.crossbeam-channel] +version = "0.5" + +[target.'cfg(target_os = "android")'.dependencies.dom_query] +version = "0.27.0" +default-features = false + +[target.'cfg(target_os = "android")'.dependencies.jni] +version = "0.21" + +[target.'cfg(target_os = "android")'.dependencies.libc] +version = "0.2" + +[target.'cfg(target_os = "android")'.dependencies.ndk] +version = "0.9" + +[target.'cfg(target_os = "android")'.dependencies.sha2] +version = "0.10" + +[target.'cfg(target_os = "android")'.dependencies.tao-macros] +version = "0.1" + +[target.'cfg(target_os = "ios")'.dependencies.objc2-ui-kit] +version = "0.3.0" +features = [ + "std", + "objc2-core-foundation", + "UIResponder", + "UIScrollView", + "UIView", + "UIWindow", + "UIApplication", + "UIEvent", + "UIColor", +] +default-features = false + +[target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit] +version = "0.3.0" +features = [ + "std", + "objc2-core-foundation", + "NSApplication", + "NSButton", + "NSControl", + "NSEvent", + "NSWindow", + "NSView", + "NSPasteboard", + "NSPanel", + "NSResponder", + "NSOpenPanel", + "NSSavePanel", + "NSMenu", + "NSGraphics", + "NSScreen", +] +default-features = false + +[target.'cfg(target_os = "windows")'.dependencies.dunce] +version = "1" + +[target.'cfg(target_os = "windows")'.dependencies.webview2-com] +version = "0.38" + +[target.'cfg(target_os = "windows")'.dependencies.windows] +version = "0.61" +features = [ + "Win32_Foundation", + "Win32_Graphics_Gdi", + "Win32_System_Com", + "Win32_System_Com_StructuredStorage", + "Win32_System_LibraryLoader", + "Win32_System_Ole", + "Win32_System_SystemInformation", + "Win32_System_SystemServices", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", + "Win32_Globalization", + "Win32_UI_HiDpi", + "Win32_UI_Input", + "Win32_UI_Input_KeyboardAndMouse", +] + +[target.'cfg(target_os = "windows")'.dependencies.windows-core] +version = "0.61" + +[target.'cfg(target_os = "windows")'.dependencies.windows-version] +version = "0.1" + +[target.'cfg(target_vendor = "apple")'.dependencies.block2] +version = "0.6" + +[target.'cfg(target_vendor = "apple")'.dependencies.dirs] +version = "6" + +[target.'cfg(target_vendor = "apple")'.dependencies.objc2] +version = "0.6.4" +features = [ + "exception", + "disable-encoding-assertions", +] + +[target.'cfg(target_vendor = "apple")'.dependencies.objc2-core-foundation] +version = "0.3.0" +features = [ + "std", + "CFCGTypes", +] +default-features = false + +[target.'cfg(target_vendor = "apple")'.dependencies.objc2-foundation] +version = "0.3.0" +features = [ + "std", + "objc2-core-foundation", + "NSURLRequest", + "NSURL", + "NSString", + "NSKeyValueCoding", + "NSStream", + "NSDictionary", + "NSObject", + "NSData", + "NSEnumerator", + "NSKeyValueObserving", + "NSThread", + "NSJSONSerialization", + "NSDate", + "NSBundle", + "NSProcessInfo", + "NSValue", + "NSRange", + "NSRunLoop", +] +default-features = false + +[target.'cfg(target_vendor = "apple")'.dependencies.objc2-web-kit] +version = "0.3.0" +features = [ + "std", + "objc2-core-foundation", + "objc2-app-kit", + "block2", + "WKWebView", + "WKWebViewConfiguration", + "WKWebsiteDataStore", + "WKDownload", + "WKDownloadDelegate", + "WKNavigation", + "WKNavigationDelegate", + "WKUserContentController", + "WKURLSchemeHandler", + "WKPreferences", + "WKURLSchemeTask", + "WKScriptMessageHandler", + "WKUIDelegate", + "WKOpenPanelParameters", + "WKFrameInfo", + "WKSecurityOrigin", + "WKScriptMessage", + "WKNavigationAction", + "WKWebpagePreferences", + "WKNavigationResponse", + "WKUserScript", + "WKHTTPCookieStore", + "WKWindowFeatures", +] +default-features = false + +[target.'cfg(target_vendor = "apple")'.dependencies.url] +version = "2.5" + +[lints.rust.unexpected_cfgs] +level = "warn" +priority = 0 +check-cfg = [ + "cfg(linux)", + "cfg(gtk)", +] diff --git a/desktop/src-tauri/vendor/wry/Cargo.toml.orig b/desktop/src-tauri/vendor/wry/Cargo.toml.orig new file mode 100644 index 00000000000..ef7cf88181c --- /dev/null +++ b/desktop/src-tauri/vendor/wry/Cargo.toml.orig @@ -0,0 +1,222 @@ +workspace = {} + +[package] +name = "wry" +version = "0.55.1" +authors = ["Tauri Programme within The Commons Conservancy"] +edition = "2021" +license = "Apache-2.0 OR MIT" +description = "Cross-platform WebView rendering library" +readme = "README.md" +repository = "https://github.com/tauri-apps/wry" +documentation = "https://docs.rs/wry" +categories = ["gui"] +rust-version = "1.77" +exclude = ["/.changes", "/.github", "/audits", "/wry-logo.svg"] + +[package.metadata.docs.rs] +no-default-features = true +features = ["protocol", "os-webview"] +targets = [ + "x86_64-unknown-linux-gnu", + "x86_64-pc-windows-msvc", + "x86_64-apple-darwin", +] + +[features] +default = ["protocol", "os-webview", "x11"] +serde = ["dpi/serde"] +protocol = [] +devtools = [] +transparent = [] +fullscreen = [] +linux-body = ["webkit2gtk/v2_40", "os-webview"] +mac-proxy = [] +os-webview = [ + "javascriptcore-rs", + "webkit2gtk", + "webkit2gtk-sys", + "dep:gtk", + "soup3", +] +x11 = ["x11-dl", "gdkx11", "tao/x11"] +tracing = ["dep:tracing"] + +[dependencies] +tracing = { version = "0.1", optional = true } +once_cell = "1" +thiserror = "2.0" +http = "1.1" +raw-window-handle = { version = "0.6", features = ["std"] } +dpi = "0.1" +cookie = "0.18" + +[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] +javascriptcore-rs = { version = "=1.1.2", features = [ + "v2_28", +], optional = true } +webkit2gtk = { version = "=2.0.2", features = ["v2_38"], optional = true } +webkit2gtk-sys = { version = "=2.0.2", optional = true } +gtk = { version = "0.18", optional = true } +soup3 = { version = "0.5", optional = true } +x11-dl = { version = "2.21", optional = true } +gdkx11 = { version = "0.18", optional = true } +percent-encoding = "2.3" +dirs = "6" + +[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dev-dependencies] +x11-dl = { version = "2.21" } + +[target."cfg(target_os = \"windows\")".dependencies] +webview2-com = "0.38" +windows-version = "0.1" +windows-core = "0.61" +dunce = "1" + +[target."cfg(target_os = \"windows\")".dependencies.windows] +version = "0.61" +features = [ + "Win32_Foundation", + "Win32_Graphics_Gdi", + "Win32_System_Com", + "Win32_System_Com_StructuredStorage", + "Win32_System_LibraryLoader", + "Win32_System_Ole", + "Win32_System_SystemInformation", + "Win32_System_SystemServices", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", + "Win32_Globalization", + "Win32_UI_HiDpi", + "Win32_UI_Input", + "Win32_UI_Input_KeyboardAndMouse", +] + +[target.'cfg(target_vendor = "apple")'.dependencies] +url = "2.5" +dirs = "6" +block2 = "0.6" +objc2 = { version = "0.6.4", features = [ + "exception", + # because `NSUUID::from_bytes` needs it, + # and `WebViewBuilderExtDarwin.with_data_store_identifier` crashes otherwise, + # see https://github.com/tauri-apps/tauri/issues/12843 + "disable-encoding-assertions", +] } +objc2-web-kit = { version = "0.3.0", default-features = false, features = [ + "std", + "objc2-core-foundation", + "objc2-app-kit", + "block2", + "WKWebView", + "WKWebViewConfiguration", + "WKWebsiteDataStore", + "WKDownload", + "WKDownloadDelegate", + "WKNavigation", + "WKNavigationDelegate", + "WKUserContentController", + "WKURLSchemeHandler", + "WKPreferences", + "WKURLSchemeTask", + "WKScriptMessageHandler", + "WKUIDelegate", + "WKOpenPanelParameters", + "WKFrameInfo", + "WKSecurityOrigin", + "WKScriptMessage", + "WKNavigationAction", + "WKWebpagePreferences", + "WKNavigationResponse", + "WKUserScript", + "WKHTTPCookieStore", + "WKWindowFeatures", +] } +objc2-core-foundation = { version = "0.3.0", default-features = false, features = [ + "std", + "CFCGTypes", +] } +objc2-foundation = { version = "0.3.0", default-features = false, features = [ + "std", + "objc2-core-foundation", + "NSURLRequest", + "NSURL", + "NSString", + "NSKeyValueCoding", + "NSStream", + "NSDictionary", + "NSObject", + "NSData", + "NSEnumerator", + "NSKeyValueObserving", + "NSThread", + "NSJSONSerialization", + "NSDate", + "NSBundle", + "NSProcessInfo", + "NSValue", + "NSRange", + "NSRunLoop", +] } + +[target.'cfg(target_os = "ios")'.dependencies] +objc2-ui-kit = { version = "0.3.0", default-features = false, features = [ + "std", + "objc2-core-foundation", + "UIResponder", + "UIScrollView", + "UIView", + "UIWindow", + "UIApplication", + "UIEvent", + "UIColor", +] } + +[target.'cfg(target_os = "macos")'.dependencies] +objc2-app-kit = { version = "0.3.0", default-features = false, features = [ + "std", + "objc2-core-foundation", + "NSApplication", + "NSButton", + "NSControl", + "NSEvent", + "NSWindow", + "NSView", + "NSPasteboard", + "NSPanel", + "NSResponder", + "NSOpenPanel", + "NSSavePanel", + "NSMenu", + "NSGraphics", + "NSScreen", +] } + +[target."cfg(target_os = \"android\")".dependencies] +crossbeam-channel = "0.5" +sha2 = "0.10" +base64 = "0.22" +jni = "0.21" +ndk = "0.9" +tao-macros = "0.1" +libc = "0.2" +dom_query = { version = "0.27.0", default-features = false } + +[dev-dependencies] +pollster = "0.4.0" +tao = "0.35" +wgpu = "23" +winit = "0.30" +getrandom = "0.3" +http-range = "0.1" +percent-encoding = "2.3" +glow = "0.16.0" +libloading = "0.8.9" +# For unit testing `inject_initialization_scripts` since we can't do it easily on Android +sha2 = "0.10" +base64 = "0.22" +dom_query = { version = "0.27.0", default-features = false } + +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = ["cfg(linux)", "cfg(gtk)"] diff --git a/desktop/src-tauri/vendor/wry/LICENSE-APACHE b/desktop/src-tauri/vendor/wry/LICENSE-APACHE new file mode 100644 index 00000000000..16fe87b06e8 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/desktop/src-tauri/vendor/wry/LICENSE-MIT b/desktop/src-tauri/vendor/wry/LICENSE-MIT new file mode 100644 index 00000000000..94ef8bf7650 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2023 Ngo Iok Ui & Tauri Programme within The Commons Conservancy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/desktop/src-tauri/vendor/wry/LICENSE.spdx b/desktop/src-tauri/vendor/wry/LICENSE.spdx new file mode 100644 index 00000000000..7e0eb72a984 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: wry +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2020-2023, The Tauri Programme in the Commons Conservancy +PackageSummary: Wry is the official, rust-based webview +windowing service for Tauri. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2020-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/wry +PackageDownloadLocation: git+https://github.com/tauri-apps/wry.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/wry.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/desktop/src-tauri/vendor/wry/MOBILE.md b/desktop/src-tauri/vendor/wry/MOBILE.md new file mode 100644 index 00000000000..e1644057909 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/MOBILE.md @@ -0,0 +1,332 @@ +# Mobile Setup for Wry + +We use [cargo-mobile2](https://github.com/tauri-apps/cargo-mobile2) to create a mobile project for both Xcode and Android studio. + +## Prerequisite + +- Works on **Linux**, **Windows**, **macOS**, and **WSL**(Windows subsystem for Linux). +- **Xcode** and [**Android Studio**](https://developer.android.com/studio) installed properly. This is **the most difficult** part IMHO. This means all toolchains and SDK are all installed. Please report an issue with **comprehensive** steps if you encounter any problem. + +## Setting up Android Environment + +### 1. Installing JDK + +#### Using Android Studio: + +If you have Android Studio installed, it ships with a version of JDK so you don't have to install it manually. It is usually at `/jre`. It will be used for `JAVA_HOME` env var. + +> On macOS, it can be found at `/Applications/Android\ Studio.app/Contents/jbr/Contents/Home` +> On Windows, it can be found at `C:\Program Files\Android\Android Studio\jre` + +#### Using the terminal: + +##### Linux/WSL + +- Install it by running the following command based on your distro to install JDK: + + - debian-based + ``` + sudo apt install default-jdk + ``` + - arch-based + ``` + sudo pacman -S jdk-openjdk + ``` + +- Set the `JAVA_HOME` env variable for this current shell (we will make it permanent later on) + ```bash + export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64" + ``` + +#### macOS + +- Install openjdk from Homebrew: + +``` +brew install openjdk +``` + +- Link to system Java wrapper and set the `JAVA_HOME` env: + +``` +sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk +export JAVA_HOME="/Library/Java/JavaVirtualMachines/openjdk.jdk/Contents/Home" +``` + +##### Windows + +- Download openjdk-11 + ```powershell + cd $HOME\downloads + Invoke-WebRequest https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_windows-x64_bin.zip -o openjdk-11.zip + Expand-Archive openjdk-11.zip -d . + mkdir $env:LocalAppData\Java + mv jdk-11.0.2 $env:LocalAppData\Java + ``` +- Set the `JAVA_HOME` env variable for this current shell (we will make it permanent later on) + ```powershell + $env:JAVA_HOME="$env:LocalAppData\Java\jdk-11.0.2" + ``` + +### 2. Installing Android SDK and NDK + +There are two ways to install the sdk and ndk. + +#### Using Android Studio: + +You can use the SDK Manager in Android Studio to install: + +1. Android Sdk Platform 33 +2. Android SDK Platform-Tools +3. NDK (Side by side) 25.0.8775105 +4. Android SDK Build-Tools 33.0. +5. Android SDK Command-line Tools + +> Note: you may need to tick `Show Package Details` in the right bottom corner to be able to see some of these components + +#### Using the terminal: + +If you don't want or can't use Android Studio you can still get the SDK Manager cli quite easily and use it to install other components. + +> Note: The SDK Manager is part of the "Command line tools only" that can be downloaded from [here](https://developer.android.com/studio#command-tools) + +##### Linux/WSL/macOS + +Download the `cmdline-tools` + +```bash +cd ~/Downloads + +# if you are on Linux/WSL: +wget https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip -O +# if you are on macos: +wget https://dl.google.com/android/repository/commandlinetools-mac-8512546_latest.zip -O + +unzip cmdline-tools.zip +cd cmdline-tools +mkdir latest +mv bin latest/ +mv lib latest/ +mv NOTICE.txt latest/ +mv source.properties latest/ +cd .. +mkdir ~/.android # You can use another location for your SDK but I prefer using ~/.android +mv cmdline-tools ~/.android +``` + +Install required SDK and NDK components + +```bash +export ANDROID_HOME="$HOME/.android" +~/.android/cmdline-tools/latest/bin/sdkmanager "platforms;android-33" "platform-tools" "ndk;25.0.8775105" "build-tools;33.0.0" +# Install the emulator if you plan on using a virtual device later +~/.android/cmdline-tools/latest/bin/sdkmanager "emulator" +``` + +##### Windows + +Download the `cmdline-tools` + +```powershell +cd $HOME\downloads +Invoke-WebRequest https://dl.google.com/android/repository/commandlinetools-win-8512546_latest.zip -o cmdline-tools.zip +Expand-Archive cmdline-tools.zip -d . +cd cmdline-tools +mkdir latest +mv bin latest/ +mv lib latest/ +mv NOTICE.txt latest/ +mv source.properties latest/ +cd .. +mkdir $HOME\.android # You can use another location for your SDK but I prefer using $HOME\.android +mv cmdline-tools $HOME\.android +``` + +Install required SDK and NDK components + +```powershell +$env:ANDROID_HOME="$HOME\.android" +&"$env:ANDROID_HOME\cmdline-tools\latest\bin\sdkmanager.exe" "platforms;android-33" "platform-tools" "ndk;25.0.8775105" "build-tools;33.0.0" +# Install the emulator if you plan on using a virtual device later +&"$env:ANDROID_HOME\cmdline-tools\latest\bin\sdkmanager.exe" "emulator" +``` + +> Note: the location you moved the `cmdline-tools` directory into will be the location of your android SDK. + +### 3. Setting up Environment Variables + +You'll need to set up some environment variables to get everything to work properly. The environment variables below should be all the ones your need to be able to use [cargo-mobile2](https://github.com/tauri-apps/cargo-mobile2) to build/run your android app. + +##### Linux/WSL/macOS + +- Setting `JAVA_HOME`: + +```bash +# In .bashrc or .zshrc: +export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64" +# If you are using Android studio, on Linux, it is: +export JAVA_HOME=/opt/android-studio/jre +# And on macOS, it is: +export JAVA_HOME=/Applications/Android\ Studio.app/Contents/jbr/Contents/Home +``` + +- Setting `ANDROID_HOME`: + +```bash +export ANDROID_HOME="$HOME/.android" +# If you are using Android studio, on Linux, it is: +export ANDROID_HOME="$HOME/Android/Sdk" +# And on macOS, it is: +export ANDROID_HOME="$HOME/Library/Android/sdk" +``` + +- Setting `PATH`: + +```bash +export NDK_HOME="$ANDROID_HOME/ndk/25.0.8775105" # The patch version might be different +export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin" +export PATH="$PATH:$ANDROID_HOME/platform-tools" +``` + +> For WSL: +> you also need to get ADB to connect to your emulator that is running on Windows +> +> ```bash +> export WSL_HOST="192.168.1.2" # Run `ipconfig` in windows to get your computer IP +> export ADB_SERVER_SOCKET=tcp:$WSL_HOST:5037 +> ``` + +After updating `.bashrc` either run `source ~/.bashrc` or reopen your terminal to apply the changes. + +##### Windows + +Open a powershell instance and run the following commands in order + +```powershell +Function Add-EnvVar($name, $value) { [System.Environment]::SetEnvironmentVariable("$name", "$value", "User") } +Function Add-PATHEntry($path) { $newPath = [System.Environment]::GetEnvironmentVariable("Path", "User") + ";" + $path; [System.Environment]::SetEnvironmentVariable("Path", "$newPath", "User") } + +Add-EnvVar JAVA_HOME "$env:LocalAppData\Java\jdk-11.0.2" # if you are using Android studio, the location is different, see the section above about JDK +$env:SDK_ROOT="$HOME\.android"# if you are using Android studio, the sdk location will be at `$env:LocalAppData\Android\Sdk` +Add-EnvVar ANDROID_HOME "$env:SDK_ROOT" +Add-EnvVar NDK_HOME "$env:SDK_ROOT\ndk\25.0.8775105" + +Add-PATHEntry "$env:SDK_ROOT\cmdline-tools\latest\bin" +Add-PATHEntry "$env:SDK_ROOT\platform-tools" +``` + +> IMPORTANT: you need to reboot your Windows machine in order for the environement variables to be loaded correctly. + +You should now have all the environment variables required and the cmdline-tools available in your PATH. You can verify this by running `sdkmanager` which should now be showing its help info. + +### 4. Install Rust android targets: + +```shell +rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android +``` + +## Getting Started + +Now lets bootstrap a project to develop a tauri or wry project for mobile. + +- Install [cargo-mobile2](https://github.com/tauri-apps/cargo-mobile2) CLI by running: + ```bash + cargo install --git https://github.com/tauri-apps/cargo-mobile2 + ``` +- Create a directory and init the project. + ```bash + mkdir hello + cd hello + cargo mobile init + # Project name (hello): + # Stylized name (Hello): + # Domain (example.com): tauri.app + # Detected template packs: + # [0] bevy + # [1] bevy-demo + # [2] wgpu + # [3] winit + # [4] wry + # Enter an index for a template pack above. + # Template pack (0): 4 + ``` + +## Build and Run on Device + +### Android + +> Make sure you're device is connected to adb +> you can check by running `cargo android list` or `adb devices` + +- `cargo android run` + +### iOS + +- `cargo build --target aarch64-apple-ios` +- `cargo apple run` + +First time running the app will be blocked. Go to your phone's `Settings > Privacy & Security > Developer Mode` to enable developer mode. And then go to `Settings -> General -> VPN and device management -> From "Developer App"` section to press "Apple Development: APPLE_ID" -> Trust. + +## Build and Run on Emulator + +### Android + +##### Using Android Studio + +- Open the project in Android Studio `cargo android open` +- Click `Trust Project`, `Use Embedded JDK` +- Choose an emulator. I usually choose Pixel 4 API 32 +- (optional) if you face this error `Device supports x86, but APK only supports armeabi-v7a` then check this [Stack Overflow answer](https://stackoverflow.com/questions/41775988/what-is-the-reason-for-the-error-device-supports-x86-but-apk-only-supports-arm/43742161#43742161) to fix it. +- Press run button. + +##### Without Android Studio + +If you don't have access to Android Studio or don't want or when running in WSL, you can build and run the generated project directly from the terminal + +1. List available emulators + - Linux/WSL/macOS: + ```bash + $ANDROID_HOME/emulator/emulator -list-avds + ``` + - Windows: + ```powershell + &"$env:ANDROID_HOME\emulator\emulator" -list-avds + ``` + you should now see a list of available emulators like the following, you'll need one of them for the next step: + ``` + Resizable_API_33 + Pixel_5_API_33 + ``` +2. Start the emulator with the name of the desired emulator: + - Linux/WSL/macOS: + ```bash + $ANDROID_HOME/emulator/emulator -avd Resizable_API_33 + ``` + - Windows: + ```powershell + &"$env:ANDROID_HOME\emulator\emulator" -avd Resizable_API_33 + ``` +3. In a new terminal window, run: + ```bash + cargo android run + ``` + +### iOS + +- If you are on x86_64: `cargo build --target x86_64-apple-ios` +- If you are on M1: `cargo build --target aarch64-apple-ios-sim` +- `cargo apple open` +- Choose a simulator. +- Press run button. + +## Devtools + +Set `devtools` attribute to true when building webview. + +### Android + +Open `chrome://inspect/#devices` in Chrome to get the devtools window. + +### iOS + +Open Safari > Develop > [Your Device Name] > [Your WebView]. diff --git a/desktop/src-tauri/vendor/wry/README.md b/desktop/src-tauri/vendor/wry/README.md new file mode 100644 index 00000000000..495176fee94 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/README.md @@ -0,0 +1,317 @@ +

WRY Webview Rendering library

+ +[![](https://img.shields.io/crates/v/wry?style=flat-square)](https://crates.io/crates/wry) [![](https://img.shields.io/docsrs/wry?style=flat-square)](https://docs.rs/wry/) +[![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri) +[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S) +[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app) +[![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) +[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri) + +Wry is a cross-platform WebView rendering library. + +The webview requires a running event loop and a window type that implements [`HasWindowHandle`], +or a gtk container widget if you need to support X11 and Wayland. +You can use a windowing library like [`tao`] or [`winit`]. + +### Examples + +This example leverages the [`HasWindowHandle`] and supports Windows, macOS, iOS, Android and Linux (X11 Only). +See the following example using [`winit`]: + +```rust +#[derive(Default)] +struct App { + window: Option, + webview: Option, +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window = event_loop.create_window(Window::default_attributes()).unwrap(); + let webview = WebViewBuilder::new() + .with_url("https://tauri.app") + .build(&window) + .unwrap(); + + self.window = Some(window); + self.webview = Some(webview); + } + + fn window_event(&mut self, _event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {} +} + +let event_loop = EventLoop::new().unwrap(); +let mut app = App::default(); +event_loop.run_app(&mut app).unwrap(); +``` + +If you also want to support Wayland too, then we recommend you use [`WebViewBuilderExtUnix::new_gtk`] on Linux. +See the following example using [`tao`]: + +```rust +let event_loop = EventLoop::new(); +let window = WindowBuilder::new().build(&event_loop).unwrap(); + +let builder = WebViewBuilder::new().with_url("https://tauri.app"); + +#[cfg(not(target_os = "linux"))] +let webview = builder.build(&window).unwrap(); +#[cfg(target_os = "linux")] +let webview = builder.build_gtk(window.gtk_window()).unwrap(); +``` + +### Child webviews + +You can use [`WebViewBuilder::build_as_child`] to create the webview as a child inside another window. This is supported on +macOS, Windows and Linux (X11 Only). + +```rust +#[derive(Default)] +struct App { + window: Option, + webview: Option, +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window = event_loop.create_window(Window::default_attributes()).unwrap(); + let webview = WebViewBuilder::new() + .with_url("https://tauri.app") + .with_bounds(Rect { + position: LogicalPosition::new(100, 100).into(), + size: LogicalSize::new(200, 200).into(), + }) + .build_as_child(&window) + .unwrap(); + + self.window = Some(window); + self.webview = Some(webview); + } + + fn window_event(&mut self, _event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {} +} + +let event_loop = EventLoop::new().unwrap(); +let mut app = App::default(); +event_loop.run_app(&mut app).unwrap(); +``` + +If you want to support X11 and Wayland at the same time, we recommend using +[`WebViewExtUnix::new_gtk`] or [`WebViewBuilderExtUnix::new_gtk`] with [`gtk::Fixed`]. + +```rust +let event_loop = EventLoop::new(); +let window = WindowBuilder::new().build(&event_loop).unwrap(); + +let builder = WebViewBuilder::new() + .with_url("https://tauri.app") + .with_bounds(Rect { + position: LogicalPosition::new(100, 100).into(), + size: LogicalSize::new(200, 200).into(), + }); + +#[cfg(not(target_os = "linux"))] +let webview = builder.build_as_child(&window).unwrap(); +#[cfg(target_os = "linux")] +let webview = { + # use gtk::prelude::*; + let vbox = window.default_vbox().unwrap(); // tao adds a gtk::Box by default + let fixed = gtk::Fixed::new(); + fixed.show_all(); + vbox.pack_start(&fixed, true, true, 0); + builder.build_gtk(&fixed).unwrap() +}; +``` + +### Platform Considerations + +Here is the underlying web engine each platform uses, and some dependencies you might need to install. + +#### Linux + +[WebKitGTK](https://webkitgtk.org/) is used to provide webviews on Linux which requires GTK, +so if the windowing library doesn't support GTK (as in [`winit`]) +you'll need to call [`gtk::init`] before creating the webview and then call [`gtk::main_iteration_do`] alongside +your windowing library event loop. + +```rust +#[derive(Default)] +struct App { + webview_window: Option<(Window, WebView)>, +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window = event_loop.create_window(Window::default_attributes()).unwrap(); + let webview = WebViewBuilder::new() + .with_url("https://tauri.app") + .build(&window) + .unwrap(); + + self.webview_window = Some((window, webview)); + } + + fn window_event(&mut self, _event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {} + + // Advance GTK event loop + + + + + + + + + + +

Welcome to WRY!

+

Page 1

+ +

+ Link + + + + diff --git a/desktop/src-tauri/vendor/wry/examples/custom_protocol/script.js b/desktop/src-tauri/vendor/wry/examples/custom_protocol/script.js new file mode 100644 index 00000000000..f6f257dcb37 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/custom_protocol/script.js @@ -0,0 +1,22 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT +if (window.location.pathname.startsWith("/page2")) { + console.log("hello from javascript in page2"); +} else { + console.log("hello from javascript in page1"); + + if (typeof WebAssembly.instantiateStreaming !== "undefined") { + WebAssembly.instantiateStreaming(fetch("/wasm.wasm")).then((wasm) => { + console.log(wasm.instance.exports.main()); // should log 42 + }); + } else { + // Older WKWebView may not support `WebAssembly.instantiateStreaming` yet. + fetch("/wasm.wasm") + .then((response) => response.arrayBuffer()) + .then((bytes) => WebAssembly.instantiate(bytes)) + .then((wasm) => { + console.log(wasm.instance.exports.main()); // should log 42 + }); + } +} diff --git a/desktop/src-tauri/vendor/wry/examples/custom_protocol/subpage.html b/desktop/src-tauri/vendor/wry/examples/custom_protocol/subpage.html new file mode 100644 index 00000000000..db373d554a8 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/custom_protocol/subpage.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + +

Page 2

+ Back home + + + + \ No newline at end of file diff --git a/desktop/src-tauri/vendor/wry/examples/custom_protocol/wasm.wasm b/desktop/src-tauri/vendor/wry/examples/custom_protocol/wasm.wasm new file mode 100644 index 00000000000..8ffac94b9fd Binary files /dev/null and b/desktop/src-tauri/vendor/wry/examples/custom_protocol/wasm.wasm differ diff --git a/desktop/src-tauri/vendor/wry/examples/custom_titlebar.rs b/desktop/src-tauri/vendor/wry/examples/custom_titlebar.rs new file mode 100644 index 00000000000..226207ac842 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/custom_titlebar.rs @@ -0,0 +1,304 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tao::{ + dpi::PhysicalSize, + event::{Event, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoopBuilder}, + window::{CursorIcon, ResizeDirection, Window, WindowBuilder}, +}; +use wry::{http::Request, WebViewBuilder}; + +#[derive(Debug)] +enum HitTestResult { + Client, + Left, + Right, + Top, + Bottom, + TopLeft, + TopRight, + BottomLeft, + BottomRight, + NoWhere, +} + +impl HitTestResult { + fn drag_resize_window(&self, window: &Window) { + let _ = window.drag_resize_window(match self { + HitTestResult::Left => ResizeDirection::West, + HitTestResult::Right => ResizeDirection::East, + HitTestResult::Top => ResizeDirection::North, + HitTestResult::Bottom => ResizeDirection::South, + HitTestResult::TopLeft => ResizeDirection::NorthWest, + HitTestResult::TopRight => ResizeDirection::NorthEast, + HitTestResult::BottomLeft => ResizeDirection::SouthWest, + HitTestResult::BottomRight => ResizeDirection::SouthEast, + _ => unreachable!(), + }); + } + + fn change_cursor(&self, window: &Window) { + window.set_cursor_icon(match self { + HitTestResult::Left => CursorIcon::WResize, + HitTestResult::Right => CursorIcon::EResize, + HitTestResult::Top => CursorIcon::NResize, + HitTestResult::Bottom => CursorIcon::SResize, + HitTestResult::TopLeft => CursorIcon::NwResize, + HitTestResult::TopRight => CursorIcon::NeResize, + HitTestResult::BottomLeft => CursorIcon::SwResize, + HitTestResult::BottomRight => CursorIcon::SeResize, + _ => CursorIcon::Default, + }); + } +} + +fn hit_test(window_size: PhysicalSize, x: i32, y: i32, scale: f64) -> HitTestResult { + const BORDERLESS_RESIZE_INSET: f64 = 5.0; + + const CLIENT: isize = 0b0000; + const LEFT: isize = 0b0001; + const RIGHT: isize = 0b0010; + const TOP: isize = 0b0100; + const BOTTOM: isize = 0b1000; + const TOPLEFT: isize = TOP | LEFT; + const TOPRIGHT: isize = TOP | RIGHT; + const BOTTOMLEFT: isize = BOTTOM | LEFT; + const BOTTOMRIGHT: isize = BOTTOM | RIGHT; + + let top = 0; + let left = 0; + let bottom = top + window_size.height as i32; + let right = left + window_size.width as i32; + + let inset = (BORDERLESS_RESIZE_INSET * scale) as i32; + + #[rustfmt::skip] + let result = + (LEFT * (if x < (left + inset) { 1 } else { 0 })) + | (RIGHT * (if x >= (right - inset) { 1 } else { 0 })) + | (TOP * (if y < (top + inset) { 1 } else { 0 })) + | (BOTTOM * (if y >= (bottom - inset) { 1 } else { 0 })); + + match result { + CLIENT => HitTestResult::Client, + LEFT => HitTestResult::Left, + RIGHT => HitTestResult::Right, + TOP => HitTestResult::Top, + BOTTOM => HitTestResult::Bottom, + TOPLEFT => HitTestResult::TopLeft, + TOPRIGHT => HitTestResult::TopRight, + BOTTOMLEFT => HitTestResult::BottomLeft, + BOTTOMRIGHT => HitTestResult::BottomRight, + _ => HitTestResult::NoWhere, + } +} + +enum UserEvent { + Minimize, + Maximize, + DragWindow, + CloseWindow, + MouseDown(i32, i32), + MouseMove(i32, i32), +} + +fn main() -> wry::Result<()> { + let event_loop = EventLoopBuilder::::with_user_event().build(); + let window = WindowBuilder::new() + .with_decorations(false) + .build(&event_loop) + .unwrap(); + + const HTML: &str = r#" + + + + + + + +
+
Custom Titlebar
+
+
+ +
+
+ +
+
+ +
+
+
+
+

WRYYYYYYYYYYYYYYYYYYYYYY!

+
+ + + + +"#; + + let proxy = event_loop.create_proxy(); + let handler = move |req: Request| { + let body = req.body(); + let mut req = body.split([':', ',']); + match req.next().unwrap() { + "minimize" => { + let _ = proxy.send_event(UserEvent::Minimize); + } + "maximize" => { + let _ = proxy.send_event(UserEvent::Maximize); + } + "drag_window" => { + let _ = proxy.send_event(UserEvent::DragWindow); + } + "close" => { + let _ = proxy.send_event(UserEvent::CloseWindow); + } + "mousedown" => { + let x = req.next().unwrap().parse().unwrap(); + let y = req.next().unwrap().parse().unwrap(); + let _ = proxy.send_event(UserEvent::MouseDown(x, y)); + } + "mousemove" => { + let x = req.next().unwrap().parse().unwrap(); + let y = req.next().unwrap().parse().unwrap(); + let _ = proxy.send_event(UserEvent::MouseMove(x, y)); + } + _ => {} + } + }; + + let builder = WebViewBuilder::new() + .with_html(HTML) + .with_ipc_handler(handler) + .with_accept_first_mouse(true); + + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + ))] + let webview = builder.build(&window)?; + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let webview = { + use tao::platform::unix::WindowExtUnix; + use wry::WebViewBuilderExtUnix; + let vbox = window.default_vbox().unwrap(); + builder.build_gtk(vbox)? + }; + + let mut webview = Some(webview); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::NewEvents(StartCause::Init) => println!("Wry application started!"), + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } + | Event::UserEvent(UserEvent::CloseWindow) => { + let _ = webview.take(); + *control_flow = ControlFlow::Exit + } + + Event::UserEvent(e) => match e { + UserEvent::Minimize => window.set_minimized(true), + UserEvent::Maximize => window.set_maximized(!window.is_maximized()), + UserEvent::DragWindow => window.drag_window().unwrap(), + UserEvent::MouseDown(x, y) => { + let res = hit_test(window.inner_size(), x, y, window.scale_factor()); + match res { + HitTestResult::Client | HitTestResult::NoWhere => {} + _ => res.drag_resize_window(&window), + } + } + UserEvent::MouseMove(x, y) => { + hit_test(window.inner_size(), x, y, window.scale_factor()).change_cursor(&window); + } + UserEvent::CloseWindow => { /* handled above */ } + }, + _ => (), + } + }); +} diff --git a/desktop/src-tauri/vendor/wry/examples/gtk_multiwebview.rs b/desktop/src-tauri/vendor/wry/examples/gtk_multiwebview.rs new file mode 100644 index 00000000000..3e47efd4ea1 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/gtk_multiwebview.rs @@ -0,0 +1,133 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; +use wry::{ + dpi::{LogicalPosition, LogicalSize}, + Rect, WebViewBuilder, +}; + +fn main() -> wry::Result<()> { + let event_loop = EventLoop::new(); + let window = WindowBuilder::new().build(&event_loop).unwrap(); + + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let fixed = { + use gtk::prelude::*; + use tao::platform::unix::WindowExtUnix; + let fixed = gtk::Fixed::new(); + let vbox = window.default_vbox().unwrap(); + vbox.pack_start(&fixed, true, true, 0); + fixed.show_all(); + fixed + }; + + let build_webview = |builder: WebViewBuilder<'_>| -> wry::Result { + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + ))] + let webview = builder.build(&window)?; + + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let webview = { + use wry::WebViewBuilderExtUnix; + builder.build_gtk(&fixed)? + }; + + Ok(webview) + }; + + let size = window.inner_size().to_logical::(window.scale_factor()); + + let builder = WebViewBuilder::new() + .with_bounds(Rect { + position: LogicalPosition::new(0, 0).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .with_url("https://tauri.app"); + let webview = build_webview(builder)?; + + let builder2 = WebViewBuilder::new() + .with_bounds(Rect { + position: LogicalPosition::new(size.width / 2, 0).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .with_url("https://github.com/tauri-apps/wry"); + let webview2 = build_webview(builder2)?; + + let builder3 = WebViewBuilder::new() + .with_bounds(Rect { + position: LogicalPosition::new(0, size.height / 2).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .with_url("https://twitter.com/TauriApps"); + let webview3 = build_webview(builder3)?; + + let builder4 = WebViewBuilder::new() + .with_bounds(Rect { + position: LogicalPosition::new(size.width / 2, size.height / 2).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .with_url("https://google.com"); + let webview4 = build_webview(builder4)?; + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::Resized(size), + .. + } => { + let size = size.to_logical::(window.scale_factor()); + webview + .set_bounds(Rect { + position: LogicalPosition::new(0, 0).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .unwrap(); + webview2 + .set_bounds(Rect { + position: LogicalPosition::new(size.width / 2, 0).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .unwrap(); + webview3 + .set_bounds(Rect { + position: LogicalPosition::new(0, size.height / 2).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .unwrap(); + webview4 + .set_bounds(Rect { + position: LogicalPosition::new(size.width / 2, size.height / 2).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .unwrap(); + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => {} + } + }); +} diff --git a/desktop/src-tauri/vendor/wry/examples/gtk_opengl.rs b/desktop/src-tauri/vendor/wry/examples/gtk_opengl.rs new file mode 100644 index 00000000000..20b94fc2d91 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/gtk_opengl.rs @@ -0,0 +1,241 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::cell::RefCell; +use std::rc::Rc; +use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; +use wry::{ + dpi::{LogicalPosition, LogicalSize}, + Rect, WebViewBuilder, +}; + +fn main() -> wry::Result<()> { + let event_loop = EventLoop::new(); + let window = WindowBuilder::new() + .with_title("GTK OpenGL with Webview") + .with_inner_size(LogicalSize::new(800, 600)) + .build(&event_loop) + .unwrap(); + + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let (fixed, _) = { + use gtk::prelude::*; + use tao::platform::unix::WindowExtUnix; + + let overlay = gtk::Overlay::new(); + let vbox = window.default_vbox().unwrap(); + vbox.pack_start(&overlay, true, true, 0); + + let gl_area = gtk::GLArea::new(); + gl_area.set_has_alpha(true); + gl_area.set_auto_render(true); + + struct AppState { + gl: glow::Context, + program: glow::Program, + vertex_array: glow::VertexArray, + } + + let state: Rc>> = Rc::new(RefCell::new(None)); + let state_realize = state.clone(); + + gl_area.connect_realize(move |gl_area| { + gl_area.make_current(); + if gl_area.error().is_some() { + println!("Error creating GLArea context"); + return; + } + + let gl = unsafe { + glow::Context::from_loader_function(|s| { + let mut ptr = std::ptr::null(); + let name = std::ffi::CString::new(s).unwrap(); + + if let Ok(lib) = libloading::Library::new("libGL.so.1") { + if let Ok(sym) = lib.get:: *const std::ffi::c_void>( + b"glXGetProcAddress\0", + ) { + ptr = sym(name.as_ptr()); + } + } + if ptr.is_null() { + if let Ok(lib) = libloading::Library::new("libEGL.so.1") { + if let Ok(sym) = lib + .get:: *const std::ffi::c_void>( + b"eglGetProcAddress\0", + ) + { + ptr = sym(name.as_ptr()); + } + } + } + ptr + }) + }; + + unsafe { + use glow::HasContext as _; + + let vertex_array = gl.create_vertex_array().unwrap(); + gl.bind_vertex_array(Some(vertex_array)); + + let program = gl.create_program().expect("Cannot create program"); + + let vertex_shader_source = r#" + #version 330 core + void main() { + if (gl_VertexID == 0) gl_Position = vec4(-0.5, -0.5, 0.0, 1.0); + else if (gl_VertexID == 1) gl_Position = vec4(0.5, -0.5, 0.0, 1.0); + else gl_Position = vec4(0.0, 0.5, 0.0, 1.0); + } + "#; + + let fragment_shader_source = r#" + #version 330 core + out vec4 FragColor; + void main() { + FragColor = vec4(1.0, 0.5, 0.2, 1.0); + } + "#; + + let vs = gl.create_shader(glow::VERTEX_SHADER).unwrap(); + gl.shader_source(vs, vertex_shader_source); + gl.compile_shader(vs); + if !gl.get_shader_compile_status(vs) { + panic!("{}", gl.get_shader_info_log(vs)); + } + + let fs = gl.create_shader(glow::FRAGMENT_SHADER).unwrap(); + gl.shader_source(fs, fragment_shader_source); + gl.compile_shader(fs); + if !gl.get_shader_compile_status(fs) { + panic!("{}", gl.get_shader_info_log(fs)); + } + + gl.attach_shader(program, vs); + gl.attach_shader(program, fs); + gl.link_program(program); + if !gl.get_program_link_status(program) { + panic!("{}", gl.get_program_info_log(program)); + } + + gl.detach_shader(program, vs); + gl.delete_shader(vs); + gl.detach_shader(program, fs); + gl.delete_shader(fs); + + *state_realize.borrow_mut() = Some(AppState { + gl, + program, + vertex_array, + }); + } + }); + + let state_render = state.clone(); + gl_area.connect_render(move |_gl_area, _gl_context| { + if let Some(state) = state_render.borrow().as_ref() { + unsafe { + use glow::HasContext as _; + state.gl.clear_color(0.1, 0.2, 0.3, 1.0); + state.gl.clear(glow::COLOR_BUFFER_BIT); + + state.gl.use_program(Some(state.program)); + state.gl.bind_vertex_array(Some(state.vertex_array)); + state.gl.draw_arrays(glow::TRIANGLES, 0, 3); + } + } + gtk::glib::Propagation::Proceed + }); + + gl_area.connect_unrealize(move |gl_area| { + gl_area.make_current(); + if let Some(state) = state.borrow_mut().take() { + unsafe { + use glow::HasContext as _; + state.gl.delete_program(state.program); + state.gl.delete_vertex_array(state.vertex_array); + } + } + }); + + overlay.add(&gl_area); + + let fixed = gtk::Fixed::new(); + overlay.add_overlay(&fixed); + + overlay.show_all(); + (fixed, gl_area) + }; + + let builder = WebViewBuilder::new() + .with_bounds(Rect { + position: LogicalPosition::new(100, 100).into(), + size: LogicalSize::new(400, 300).into(), + }) + .with_transparent(true) + .with_html( + r#" + +

Hello World!

+ + "#, + ); + + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + ))] + let webview = builder.build(&window)?; + + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let webview = { + use wry::WebViewBuilderExtUnix; + builder.build_gtk(&fixed)? + }; + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::Resized(size), + .. + } => { + let size = size.to_logical::(window.scale_factor()); + webview + .set_bounds(Rect { + position: LogicalPosition::new(100, 100).into(), + size: LogicalSize::new( + size.width.saturating_sub(200).max(100), + size.height.saturating_sub(200).max(100), + ) + .into(), + }) + .unwrap(); + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => {} + } + }); +} diff --git a/desktop/src-tauri/vendor/wry/examples/multiwebview.rs b/desktop/src-tauri/vendor/wry/examples/multiwebview.rs new file mode 100644 index 00000000000..6540c15418e --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/multiwebview.rs @@ -0,0 +1,168 @@ +use winit::{ + application::ApplicationHandler, + event::WindowEvent, + event_loop::{ActiveEventLoop, EventLoop}, + window::{Window, WindowId}, +}; +use wry::{ + dpi::{LogicalPosition, LogicalSize}, + Rect, WebViewBuilder, +}; + +#[derive(Default)] +struct State { + window: Option, + webview1: Option, + webview2: Option, + webview3: Option, + webview4: Option, +} + +impl ApplicationHandler for State { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let mut attributes = Window::default_attributes(); + attributes.inner_size = Some(LogicalSize::new(800, 800).into()); + let window = event_loop.create_window(attributes).unwrap(); + + let size = window.inner_size().to_logical::(window.scale_factor()); + + let webview1 = WebViewBuilder::new() + .with_bounds(Rect { + position: LogicalPosition::new(0, 0).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .with_url("https://tauri.app") + .build_as_child(&window) + .unwrap(); + + let webview2 = WebViewBuilder::new() + .with_bounds(Rect { + position: LogicalPosition::new(size.width / 2, 0).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .with_url("https://github.com/tauri-apps/wry") + .build_as_child(&window) + .unwrap(); + + let webview3 = WebViewBuilder::new() + .with_bounds(Rect { + position: LogicalPosition::new(0, size.height / 2).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .with_url("https://twitter.com/TauriApps") + .build_as_child(&window) + .unwrap(); + + let webview4 = WebViewBuilder::new() + .with_bounds(Rect { + position: LogicalPosition::new(size.width / 2, size.height / 2).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .with_url("https://google.com") + .build_as_child(&window) + .unwrap(); + + self.window = Some(window); + self.webview1 = Some(webview1); + self.webview2 = Some(webview2); + self.webview3 = Some(webview3); + self.webview4 = Some(webview4); + } + + fn window_event( + &mut self, + _event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + match event { + WindowEvent::Resized(size) => { + if let (Some(window), Some(webview1), Some(webview2), Some(webview3), Some(webview4)) = ( + &self.window, + &self.webview1, + &self.webview2, + &self.webview3, + &self.webview4, + ) { + let size = size.to_logical::(window.scale_factor()); + + webview1 + .set_bounds(Rect { + position: LogicalPosition::new(0, 0).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .unwrap(); + + webview2 + .set_bounds(Rect { + position: LogicalPosition::new(size.width / 2, 0).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .unwrap(); + + webview3 + .set_bounds(Rect { + position: LogicalPosition::new(0, size.height / 2).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .unwrap(); + + webview4 + .set_bounds(Rect { + position: LogicalPosition::new(size.width / 2, size.height / 2).into(), + size: LogicalSize::new(size.width / 2, size.height / 2).into(), + }) + .unwrap(); + } + } + WindowEvent::CloseRequested => { + std::process::exit(0); + } + _ => {} + } + } + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + ))] + { + while gtk::events_pending() { + gtk::main_iteration_do(false); + } + } + } +} + +fn main() -> wry::Result<()> { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + ))] + { + use gtk::prelude::DisplayExtManual; + + gtk::init()?; + if gtk::gdk::Display::default().unwrap().backend().is_wayland() { + panic!("This example doesn't support wayland!"); + } + + winit::platform::x11::register_xlib_error_hook(Box::new(|_display, error| { + let error = error as *mut x11_dl::xlib::XErrorEvent; + (unsafe { (*error).error_code }) == 170 + })); + } + + let event_loop = EventLoop::new().unwrap(); + let mut state = State::default(); + event_loop.run_app(&mut state).unwrap(); + + Ok(()) +} diff --git a/desktop/src-tauri/vendor/wry/examples/multiwindow.rs b/desktop/src-tauri/vendor/wry/examples/multiwindow.rs new file mode 100644 index 00000000000..0514f99e86e --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/multiwindow.rs @@ -0,0 +1,125 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::collections::HashMap; +use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget}, + window::{Window, WindowBuilder, WindowId}, +}; +use wry::{http::Request, WebView, WebViewBuilder}; + +enum UserEvent { + CloseWindow(WindowId), + NewTitle(WindowId, String), + NewWindow, +} + +fn main() -> wry::Result<()> { + let event_loop = EventLoopBuilder::::with_user_event().build(); + let mut webviews = HashMap::new(); + let proxy = event_loop.create_proxy(); + + let (window, webview) = create_new_window( + format!("Window {}", webviews.len() + 1), + &event_loop, + proxy.clone(), + ); + webviews.insert(window.id(), (window, webview)); + + event_loop.run(move |event, event_loop, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + .. + } => { + webviews.remove(&window_id); + if webviews.is_empty() { + *control_flow = ControlFlow::Exit + } + } + Event::UserEvent(UserEvent::NewWindow) => { + let (window, webview) = create_new_window( + format!("Window {}", webviews.len() + 1), + event_loop, + proxy.clone(), + ); + webviews.insert(window.id(), (window, webview)); + } + Event::UserEvent(UserEvent::CloseWindow(id)) => { + webviews.remove(&id); + if webviews.is_empty() { + *control_flow = ControlFlow::Exit + } + } + + Event::UserEvent(UserEvent::NewTitle(id, title)) => { + webviews.get(&id).unwrap().0.set_title(&title); + } + _ => (), + } + }); +} + +fn create_new_window( + title: String, + event_loop: &EventLoopWindowTarget, + proxy: EventLoopProxy, +) -> (Window, WebView) { + let window = WindowBuilder::new() + .with_title(title) + .build(event_loop) + .unwrap(); + let window_id = window.id(); + let handler = move |req: Request| { + let body = req.body(); + match body.as_str() { + "new-window" => { + let _ = proxy.send_event(UserEvent::NewWindow); + } + "close" => { + let _ = proxy.send_event(UserEvent::CloseWindow(window_id)); + } + _ if body.starts_with("change-title") => { + let title = body.replace("change-title:", ""); + let _ = proxy.send_event(UserEvent::NewTitle(window_id, title)); + } + _ => {} + } + }; + + let builder = WebViewBuilder::new() + .with_html( + r#" + + + + "#, + ) + .with_ipc_handler(handler); + + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + ))] + let webview = builder.build(&window).unwrap(); + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let webview = { + use tao::platform::unix::WindowExtUnix; + use wry::WebViewBuilderExtUnix; + let vbox = window.default_vbox().unwrap(); + builder.build_gtk(vbox).unwrap() + }; + (window, webview) +} diff --git a/desktop/src-tauri/vendor/wry/examples/reparent.rs b/desktop/src-tauri/vendor/wry/examples/reparent.rs new file mode 100644 index 00000000000..1b5476508df --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/reparent.rs @@ -0,0 +1,111 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tao::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::Key, + window::WindowBuilder, +}; +use wry::WebViewBuilder; + +#[cfg(target_os = "macos")] +use {objc2_app_kit::NSWindow, tao::platform::macos::WindowExtMacOS, wry::WebViewExtMacOS}; +#[cfg(target_os = "windows")] +use {tao::platform::windows::WindowExtWindows, wry::WebViewExtWindows}; + +#[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" +)))] +#[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" +)))] +use { + tao::platform::unix::WindowExtUnix, + wry::{WebViewBuilderExtUnix, WebViewExtUnix}, +}; + +fn main() -> wry::Result<()> { + let event_loop = EventLoop::new(); + let window = WindowBuilder::new().build(&event_loop).unwrap(); + let window2 = WindowBuilder::new().build(&event_loop).unwrap(); + + let builder = WebViewBuilder::new().with_url("https://tauri.app"); + + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + ))] + let webview = builder.build(&window)?; + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let webview = { + use tao::platform::unix::WindowExtUnix; + let vbox = window.default_vbox().unwrap(); + builder.build_gtk(vbox)? + }; + + let mut webview_container = window.id(); + + event_loop.run(move |event, _event_loop, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Character("x"), + state: ElementState::Pressed, + .. + }, + .. + }, + .. + } => { + let new_parent = if webview_container == window.id() { + &window2 + } else { + &window + }; + webview_container = new_parent.id(); + + #[cfg(target_os = "macos")] + webview + .reparent(new_parent.ns_window() as *mut NSWindow) + .unwrap(); + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + webview + .reparent(new_parent.default_vbox().unwrap()) + .unwrap(); + #[cfg(target_os = "windows")] + webview.reparent(new_parent.hwnd()).unwrap(); + } + _ => {} + } + }); +} diff --git a/desktop/src-tauri/vendor/wry/examples/simple.rs b/desktop/src-tauri/vendor/wry/examples/simple.rs new file mode 100644 index 00000000000..e9c53569f3c --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/simple.rs @@ -0,0 +1,70 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; +use wry::WebViewBuilder; + +fn main() -> wry::Result<()> { + let event_loop = EventLoop::new(); + let window = WindowBuilder::new().build(&event_loop).unwrap(); + + let builder = WebViewBuilder::new() + .with_url("http://tauri.app") + .with_new_window_req_handler(|url, features| { + println!("new window req: {url} {features:?}"); + wry::NewWindowResponse::Allow + }); + + let builder = builder.with_drag_drop_handler(|e| { + match e { + wry::DragDropEvent::Enter { paths, position } => { + println!("DragEnter: {position:?} {paths:?} ") + } + wry::DragDropEvent::Over { position } => println!("DragOver: {position:?} "), + wry::DragDropEvent::Drop { paths, position } => { + println!("DragDrop: {position:?} {paths:?} ") + } + wry::DragDropEvent::Leave => println!("DragLeave"), + _ => {} + } + + true + }); + + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + ))] + let _webview = builder.build(&window)?; + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let _webview = { + use tao::platform::unix::WindowExtUnix; + use wry::WebViewBuilderExtUnix; + let vbox = window.default_vbox().unwrap(); + builder.build_gtk(vbox)? + }; + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + if let Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } = event + { + *control_flow = ControlFlow::Exit; + } + }); +} diff --git a/desktop/src-tauri/vendor/wry/examples/streaming.rs b/desktop/src-tauri/vendor/wry/examples/streaming.rs new file mode 100644 index 00000000000..f7e6e5abea2 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/streaming.rs @@ -0,0 +1,277 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +fn main() -> wry::Result<()> { + imp::main() +} + +#[cfg(not(feature = "protocol"))] +mod imp { + pub fn main() -> wry::Result<()> { + unimplemented!() + } +} + +#[cfg(feature = "protocol")] +mod imp { + + use std::{ + io::{Read, Seek, SeekFrom, Write}, + path::PathBuf, + }; + + use http::{header, StatusCode}; + use http_range::HttpRange; + use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + }; + use wry::{ + http::{header::*, Request, Response}, + WebViewBuilder, + }; + + pub fn main() -> wry::Result<()> { + let event_loop = EventLoop::new(); + let window = WindowBuilder::new().build(&event_loop).unwrap(); + + let builder = WebViewBuilder::new() + .with_custom_protocol( + "wry".into(), + move |_webview_id, request| match wry_protocol(request) { + Ok(r) => r.map(Into::into), + Err(e) => http::Response::builder() + .header(CONTENT_TYPE, "text/plain") + .status(500) + .body(e.to_string().as_bytes().to_vec()) + .unwrap() + .map(Into::into), + }, + ) + .with_custom_protocol( + "stream".into(), + move |_webview_id, request| match stream_protocol(request) { + Ok(r) => r.map(Into::into), + Err(e) => http::Response::builder() + .header(CONTENT_TYPE, "text/plain") + .status(500) + .body(e.to_string().as_bytes().to_vec()) + .unwrap() + .map(Into::into), + }, + ) + // tell the webview to load the custom protocol + .with_url("wry://localhost"); + + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + ))] + let _webview = builder.build(&window)?; + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let _webview = { + use tao::platform::unix::WindowExtUnix; + use wry::WebViewBuilderExtUnix; + let vbox = window.default_vbox().unwrap(); + builder.build_gtk(vbox)? + }; + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + if let Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } = event + { + *control_flow = ControlFlow::Exit + } + }); + } + + fn wry_protocol( + request: Request>, + ) -> Result>, Box> { + let path = request.uri().path(); + // Read the file content from file path + let root = PathBuf::from("examples/streaming"); + let path = if path == "/" { + "index.html" + } else { + // removing leading slash + &path[1..] + }; + let content = std::fs::read(std::fs::canonicalize(root.join(path))?)?; + + // Return asset contents and mime types based on file extentions + // If you don't want to do this manually, there are some crates for you. + // Such as `infer` and `mime_guess`. + let mimetype = if path.ends_with(".html") || path == "/" { + "text/html" + } else if path.ends_with(".js") { + "text/javascript" + } else { + unimplemented!(); + }; + + Response::builder() + .header(CONTENT_TYPE, mimetype) + .body(content) + .map_err(Into::into) + } + + fn stream_protocol( + request: http::Request>, + ) -> Result>, Box> { + // skip leading `/` + let path = percent_encoding::percent_decode(&request.uri().path().as_bytes()[1..]) + .decode_utf8_lossy() + .to_string(); + + let mut file = std::fs::File::open(path)?; + + // get file length + let len = { + let old_pos = file.stream_position()?; + let len = file.seek(SeekFrom::End(0))?; + file.seek(SeekFrom::Start(old_pos))?; + len + }; + + let mut resp = Response::builder().header(CONTENT_TYPE, "video/mp4"); + + // if the webview sent a range header, we need to send a 206 in return + // Actually only macOS and Windows are supported. Linux will ALWAYS return empty headers. + let http_response = if let Some(range_header) = request.headers().get("range") { + let not_satisfiable = || { + Response::builder() + .status(StatusCode::RANGE_NOT_SATISFIABLE) + .header(header::CONTENT_RANGE, format!("bytes */{len}")) + .body(vec![]) + }; + + // parse range header + let ranges = if let Ok(ranges) = HttpRange::parse(range_header.to_str()?, len) { + ranges + .iter() + // map the output back to spec range , example: 0-499 + .map(|r| (r.start, r.start + r.length - 1)) + .collect::>() + } else { + return Ok(not_satisfiable()?); + }; + + /// The Maximum bytes we send in one range + const MAX_LEN: u64 = 1000 * 1024; + + if ranges.len() == 1 { + let &(start, mut end) = ranges.first().unwrap(); + + // check if a range is not satisfiable + // + // this should be already taken care of by HttpRange::parse + // but checking here again for extra assurance + if start >= len || end >= len || end < start { + return Ok(not_satisfiable()?); + } + + // adjust end byte for MAX_LEN + end = start + (end - start).min(len - start).min(MAX_LEN - 1); + + // calculate number of bytes needed to be read + let bytes_to_read = end + 1 - start; + + // allocate a buf with a suitable capacity + let mut buf = Vec::with_capacity(bytes_to_read as usize); + // seek the file to the starting byte + file.seek(SeekFrom::Start(start))?; + // read the needed bytes + file.take(bytes_to_read).read_to_end(&mut buf)?; + + resp = resp.header(CONTENT_RANGE, format!("bytes {start}-{end}/{len}")); + resp = resp.header(CONTENT_LENGTH, end + 1 - start); + resp = resp.status(StatusCode::PARTIAL_CONTENT); + resp.body(buf) + } else { + let mut buf = Vec::new(); + let ranges = ranges + .iter() + .filter_map(|&(start, mut end)| { + // filter out unsatisfiable ranges + // + // this should be already taken care of by HttpRange::parse + // but checking here again for extra assurance + if start >= len || end >= len || end < start { + None + } else { + // adjust end byte for MAX_LEN + end = start + (end - start).min(len - start).min(MAX_LEN - 1); + Some((start, end)) + } + }) + .collect::>(); + + let boundary = random_boundary(); + let boundary_sep = format!("\r\n--{boundary}\r\n"); + let boundary_closer = format!("\r\n--{boundary}\r\n"); + + resp = resp.header( + CONTENT_TYPE, + format!("multipart/byteranges; boundary={boundary}"), + ); + + for (end, start) in ranges { + // a new range is being written, write the range boundary + buf.write_all(boundary_sep.as_bytes())?; + + // write the needed headers `Content-Type` and `Content-Range` + buf.write_all(format!("{CONTENT_TYPE}: video/mp4\r\n").as_bytes())?; + buf.write_all(format!("{CONTENT_RANGE}: bytes {start}-{end}/{len}\r\n").as_bytes())?; + + // write the separator to indicate the start of the range body + buf.write_all("\r\n".as_bytes())?; + + // calculate number of bytes needed to be read + let bytes_to_read = end + 1 - start; + + let mut local_buf = vec![0_u8; bytes_to_read as usize]; + file.seek(SeekFrom::Start(start))?; + file.read_exact(&mut local_buf)?; + buf.extend_from_slice(&local_buf); + } + // all ranges have been written, write the closing boundary + buf.write_all(boundary_closer.as_bytes())?; + + resp.body(buf) + } + } else { + resp = resp.header(CONTENT_LENGTH, len); + let mut buf = Vec::with_capacity(len as usize); + file.read_to_end(&mut buf)?; + resp.body(buf) + }; + + http_response.map_err(Into::into) + } + + fn random_boundary() -> String { + let mut x = [0_u8; 30]; + getrandom::fill(&mut x).expect("failed to get random bytes"); + (x[..]) + .iter() + .map(|&x| format!("{x:x}")) + .fold(String::new(), |mut a, x| { + a.push_str(x.as_str()); + a + }) + } +} diff --git a/desktop/src-tauri/vendor/wry/examples/streaming/index.html b/desktop/src-tauri/vendor/wry/examples/streaming/index.html new file mode 100644 index 00000000000..547cef0d82f --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/streaming/index.html @@ -0,0 +1,38 @@ + + + + + + Document + + +

Enter a path to a video to play, then hit Enter or click Start

+
+ + +
+ + + + + diff --git a/desktop/src-tauri/vendor/wry/examples/transparent.rs b/desktop/src-tauri/vendor/wry/examples/transparent.rs new file mode 100644 index 00000000000..0802012dc74 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/transparent.rs @@ -0,0 +1,80 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; +use wry::WebViewBuilder; + +fn main() -> wry::Result<()> { + let event_loop = EventLoop::new(); + #[allow(unused_mut)] + let mut builder = WindowBuilder::new() + .with_decorations(false) + // There are actually three layer of background color when creating webview window. + // The first is window background... + .with_transparent(true); + #[cfg(target_os = "windows")] + { + use tao::platform::windows::WindowBuilderExtWindows; + builder = builder.with_undecorated_shadow(false); + } + let window = builder.build(&event_loop).unwrap(); + + #[cfg(target_os = "windows")] + { + use tao::platform::windows::WindowExtWindows; + window.set_undecorated_shadow(true); + } + + let builder = WebViewBuilder::new() + // The second is on webview... + // Feature `transparent` is required for transparency to work. + .with_transparent(true) + // And the last is in html. + .with_html( + r#" + + + "#, + ); + + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + ))] + let _webview = builder.build(&window)?; + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let _webview = { + use tao::platform::unix::WindowExtUnix; + use wry::WebViewBuilderExtUnix; + let vbox = window.default_vbox().unwrap(); + builder.build_gtk(vbox)? + }; + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + if let Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } = event + { + *control_flow = ControlFlow::Exit + } + }); +} diff --git a/desktop/src-tauri/vendor/wry/examples/wgpu.rs b/desktop/src-tauri/vendor/wry/examples/wgpu.rs new file mode 100644 index 00000000000..2b9ce1ea3f2 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/wgpu.rs @@ -0,0 +1,273 @@ +use std::{borrow::Cow, sync::Arc}; +use winit::{ + application::ApplicationHandler, + event::WindowEvent, + event_loop::{ActiveEventLoop, EventLoop}, + window::{Window, WindowId}, +}; +use wry::{ + dpi::{LogicalPosition, LogicalSize}, + Rect, WebViewBuilder, +}; + +#[derive(Default)] +struct State { + window: Option>, + webview: Option, + gfx_state: Option, +} + +struct GfxState { + surface: wgpu::Surface<'static>, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + render_pipeline: wgpu::RenderPipeline, +} + +impl GfxState { + fn new(window: Arc) -> Self { + let instance = wgpu::Instance::default(); + let window_size = window.inner_size(); + let surface = instance.create_surface(window).unwrap(); + + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + force_fallback_adapter: false, + compatible_surface: Some(&surface), + })) + .expect("Failed to find an appropriate adapter"); + + let (device, queue) = pollster::block_on(adapter.request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: + wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits()), + memory_hints: wgpu::MemoryHints::Performance, + }, + None, + )) + .expect("Failed to create device"); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed( + r#" + @vertex + fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { + let x = f32(i32(in_vertex_index) - 1); + let y = f32(i32(in_vertex_index & 1u) * 2 - 1); + return vec4(x, y, 0.0, 1.0); + } + + @fragment + fn fs_main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); + } + "#, + )), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let swapchain_capabilities = surface.get_capabilities(&adapter); + let swapchain_format = swapchain_capabilities.formats[0]; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(swapchain_format.into())], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: swapchain_format, + width: window_size.width, + height: window_size.height, + present_mode: wgpu::PresentMode::Fifo, + desired_maximum_frame_latency: 2, + alpha_mode: swapchain_capabilities.alpha_modes[0], + view_formats: vec![], + }; + + surface.configure(&device, &config); + + Self { + surface, + device, + queue, + config, + render_pipeline, + } + } + + fn render(&mut self) { + let frame = self + .surface + .get_current_texture() + .expect("Failed to acquire next swap chain texture"); + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + render_pass.set_pipeline(&self.render_pipeline); + render_pass.draw(0..3, 0..1); + } + + self.queue.submit(Some(encoder.finish())); + frame.present(); + } +} + +impl ApplicationHandler for State { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let mut attributes = Window::default_attributes(); + attributes.transparent = true; + #[cfg(windows)] + { + use winit::platform::windows::WindowAttributesExtWindows; + attributes = attributes.with_clip_children(false); + } + + let window = Arc::new(event_loop.create_window(attributes).unwrap()); + + let gfx_state = GfxState::new(Arc::clone(&window)); + + let webview = WebViewBuilder::new() + .with_bounds(Rect { + position: LogicalPosition::new(100, 100).into(), + size: LogicalSize::new(200, 200).into(), + }) + .with_transparent(true) + .with_html( + r#" + + + "#, + ) + .build_as_child(&window) + .unwrap(); + + window.request_redraw(); + + self.window = Some(window); + self.webview = Some(webview); + self.gfx_state = Some(gfx_state); + } + + fn window_event( + &mut self, + _event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + match event { + WindowEvent::Resized(size) => { + if let Some(gfx_state) = &mut self.gfx_state { + gfx_state.config.width = size.width; + gfx_state.config.height = size.height; + gfx_state + .surface + .configure(&gfx_state.device, &gfx_state.config); + + if let Some(window) = &self.window { + window.request_redraw(); + } + } + } + WindowEvent::RedrawRequested => { + if let Some(gfx_state) = &mut self.gfx_state { + gfx_state.render(); + } + } + WindowEvent::CloseRequested => { + std::process::exit(0); + } + _ => {} + } + } + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + ))] + { + while gtk::events_pending() { + gtk::main_iteration_do(false); + } + } + } +} + +fn main() { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + ))] + { + use gtk::prelude::DisplayExtManual; + + gtk::init().unwrap(); + if gtk::gdk::Display::default().unwrap().backend().is_wayland() { + panic!("This example doesn't support wayland!"); + } + + winit::platform::x11::register_xlib_error_hook(Box::new(|_display, error| { + let error = error as *mut x11_dl::xlib::XErrorEvent; + (unsafe { (*error).error_code }) == 170 + })); + } + + let event_loop = EventLoop::new().unwrap(); + let mut state = State::default(); + event_loop.run_app(&mut state).unwrap(); +} diff --git a/desktop/src-tauri/vendor/wry/examples/window_border.rs b/desktop/src-tauri/vendor/wry/examples/window_border.rs new file mode 100644 index 00000000000..2520caf4e2a --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/window_border.rs @@ -0,0 +1,122 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use dpi::LogicalSize; +use tao::{ + event::{Event, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoopBuilder}, + window::WindowBuilder, +}; +use wry::{http::Request, WebViewBuilder}; + +#[derive(Debug)] +enum UserEvent { + TogglShadows, +} + +fn main() -> wry::Result<()> { + let event_loop = EventLoopBuilder::::with_user_event().build(); + let window = WindowBuilder::new() + .with_inner_size(LogicalSize::new(500, 500)) + .with_decorations(false) + .build(&event_loop) + .unwrap(); + + const HTML: &str = r#" + + + + + + + +

+ Click the window to toggle shadows. +

+ + + + + +"#; + + let proxy = event_loop.create_proxy(); + let handler = move |req: Request| { + if req.body().as_str() == "toggleShadows" { + proxy.send_event(UserEvent::TogglShadows).unwrap(); + } + }; + + let builder = WebViewBuilder::new() + .with_html(HTML) + .with_ipc_handler(handler) + .with_accept_first_mouse(true); + + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + ))] + let webview = builder.build(&window)?; + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let webview = { + use tao::platform::unix::WindowExtUnix; + use wry::WebViewBuilderExtUnix; + let vbox = window.default_vbox().unwrap(); + builder.build_gtk(vbox)? + }; + + let mut webview = Some(webview); + + let mut shadow = true; + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::NewEvents(StartCause::Init) => println!("Wry application started!"), + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + let _ = webview.take(); + *control_flow = ControlFlow::Exit + } + + Event::UserEvent(e) => match e { + UserEvent::TogglShadows => { + shadow = !shadow; + #[cfg(windows)] + { + use tao::platform::windows::WindowExtWindows; + window.set_undecorated_shadow(shadow); + } + } + }, + _ => (), + } + }); +} diff --git a/desktop/src-tauri/vendor/wry/examples/winit.rs b/desktop/src-tauri/vendor/wry/examples/winit.rs new file mode 100644 index 00000000000..72994870094 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/examples/winit.rs @@ -0,0 +1,100 @@ +use dpi::{LogicalPosition, LogicalSize}; +use winit::{ + application::ApplicationHandler, + event::WindowEvent, + event_loop::{ActiveEventLoop, EventLoop}, + window::{Window, WindowId}, +}; +use wry::{Rect, WebViewBuilder}; + +#[derive(Default)] +struct State { + window: Option, + webview: Option, +} + +impl ApplicationHandler for State { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let mut attributes = Window::default_attributes(); + attributes.inner_size = Some(LogicalSize::new(800, 800).into()); + let window = event_loop.create_window(attributes).unwrap(); + + let webview = WebViewBuilder::new() + .with_url("https://tauri.app") + .build_as_child(&window) + .unwrap(); + + self.window = Some(window); + self.webview = Some(webview); + } + + fn window_event( + &mut self, + _event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + match event { + WindowEvent::Resized(size) => { + let window = self.window.as_ref().unwrap(); + let webview = self.webview.as_ref().unwrap(); + + let size = size.to_logical::(window.scale_factor()); + webview + .set_bounds(Rect { + position: LogicalPosition::new(0, 0).into(), + size: LogicalSize::new(size.width, size.height).into(), + }) + .unwrap(); + } + WindowEvent::CloseRequested => { + std::process::exit(0); + } + _ => {} + } + } + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + ))] + { + while gtk::events_pending() { + gtk::main_iteration_do(false); + } + } + } +} + +fn main() -> wry::Result<()> { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + ))] + { + use gtk::prelude::DisplayExtManual; + + gtk::init().unwrap(); + if gtk::gdk::Display::default().unwrap().backend().is_wayland() { + panic!("This example doesn't support wayland!"); + } + + winit::platform::x11::register_xlib_error_hook(Box::new(|_display, error| { + let error = error as *mut x11_dl::xlib::XErrorEvent; + (unsafe { (*error).error_code }) == 170 + })); + } + + let event_loop = EventLoop::new().unwrap(); + let mut state = State::default(); + event_loop.run_app(&mut state).unwrap(); + + Ok(()) +} diff --git a/desktop/src-tauri/vendor/wry/renovate.json b/desktop/src-tauri/vendor/wry/renovate.json new file mode 100644 index 00000000000..e0493a29a7f --- /dev/null +++ b/desktop/src-tauri/vendor/wry/renovate.json @@ -0,0 +1,10 @@ +{ + "extends": ["config:recommended"], + "rangeStrategy": "replace", + "packageRules": [ + { + "semanticCommitType": "chore", + "matchPackageNames": ["*"] + } + ] +} diff --git a/desktop/src-tauri/vendor/wry/rustfmt.toml b/desktop/src-tauri/vendor/wry/rustfmt.toml new file mode 100644 index 00000000000..a90653d1089 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/rustfmt.toml @@ -0,0 +1,15 @@ +max_width = 100 +hard_tabs = false +tab_spaces = 2 +newline_style = "Unix" +use_small_heuristics = "Default" +reorder_imports = true +reorder_modules = true +remove_nested_parens = true +edition = "2018" +merge_derives = true +use_try_shorthand = false +use_field_init_shorthand = false +force_explicit_abi = true +imports_granularity = "Crate" +#license_template_path = ".license_template" diff --git a/desktop/src-tauri/vendor/wry/src/android/binding.rs b/desktop/src-tauri/vendor/wry/src/android/binding.rs new file mode 100644 index 00000000000..031cd09cc66 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/android/binding.rs @@ -0,0 +1,483 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use http::{ + header::{HeaderName, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}, + Request, +}; +use jni::errors::Result as JniResult; +pub use jni::{ + self, + objects::{GlobalRef, JClass, JMap, JObject, JString}, + sys::{jboolean, jint, jobject, jstring}, + JNIEnv, +}; +pub use ndk; +use ndk::looper::{FdEvent, ThreadLooper}; +use std::os::fd::{AsFd, AsRawFd}; + +use super::{ + main_pipe::{MainPipe, MAIN_PIPE}, + ASSET_LOADER_DOMAIN, EVAL_CALLBACKS, IPC, ON_LOAD_HANDLER, REQUEST_HANDLER, TITLE_CHANGE_HANDLER, + URL_LOADING_OVERRIDE, WITH_ASSET_LOADER, +}; + +use crate::PageLoadEvent; + +#[macro_export] +macro_rules! android_binding { + ($domain:ident, $package:ident) => { + ::wry::android_binding!($domain, $package, ::wry) + }; + // use imported `android_setup` just to force the import path to use `wry::{}` + // as the macro breaks without braces + ($domain:ident, $package:ident, $wry:path) => {{ + use $wry::{android_setup as _, prelude::*}; + + android_fn!($domain, $package, Rust, wryCreate, []); + android_fn!( + $domain, + $package, + Rust, + onWebviewDestroy, + [JObject, JString] + ); + + android_fn!( + $domain, + $package, + Rust, + handleRequest, + [JString, JObject, jboolean], + jobject + ); + android_fn!( + $domain, + $package, + Rust, + withAssetLoader, + [JString], + jboolean + ); + android_fn!( + $domain, + $package, + Rust, + assetLoaderDomain, + [JString], + jstring + ); + android_fn!( + $domain, + $package, + Rust, + shouldOverride, + [JString, JString], + jboolean + ); + android_fn!($domain, $package, Rust, onEval, [JString, jint, JString]); + android_fn!($domain, $package, Rust, onPageLoading, [JString, JString]); + android_fn!($domain, $package, Rust, onPageLoaded, [JString, JString]); + android_fn!($domain, $package, Rust, ipc, [JString, JString, JString]); + android_fn!( + $domain, + $package, + Rust, + handleReceivedTitle, + [JString, JString], + ); + }}; +} + +fn handle_request( + env: &mut JNIEnv, + webview_id: JString, + request: JObject, + is_document_start_script_enabled: jboolean, +) -> JniResult { + let webview_id = env.get_string(&webview_id)?; + let webview_id = webview_id.to_str().ok().unwrap_or_default(); + + if let Some(handler) = REQUEST_HANDLER.lock().unwrap().get(webview_id) { + #[cfg(feature = "tracing")] + let span = + tracing::info_span!(parent: None, "wry::custom_protocol::handle", uri = tracing::field::Empty).entered(); + + let mut request_builder = Request::builder(); + + let uri = env + .call_method(&request, "getUrl", "()Landroid/net/Uri;", &[])? + .l()?; + let url: JString = env + .call_method(&uri, "toString", "()Ljava/lang/String;", &[])? + .l()? + .into(); + let url = env.get_string(&url)?.to_string_lossy().to_string(); + + #[cfg(feature = "tracing")] + span.record("uri", &url); + + request_builder = request_builder.uri(&url); + + let method = env + .call_method(&request, "getMethod", "()Ljava/lang/String;", &[])? + .l() + .map(JString::from)?; + request_builder = request_builder.method( + env + .get_string(&method)? + .to_string_lossy() + .to_string() + .as_str(), + ); + + let request_headers = env + .call_method(request, "getRequestHeaders", "()Ljava/util/Map;", &[])? + .l()?; + let request_headers = JMap::from_env(env, &request_headers)?; + let mut iter = request_headers.iter(env)?; + while let Some((header, value)) = iter.next(env)? { + let header = JString::from(header); + let value = JString::from(value); + let header = env.get_string(&header)?; + let value = env.get_string(&value)?; + if let (Ok(header), Ok(value)) = ( + HeaderName::from_bytes(header.to_bytes()), + HeaderValue::from_bytes(value.to_bytes()), + ) { + request_builder = request_builder.header(header, value); + } + } + + let final_request = match request_builder.body(Vec::new()) { + Ok(req) => req, + Err(_e) => { + #[cfg(feature = "tracing")] + tracing::warn!("Failed to build response: {_e}"); + return Ok(*JObject::null()); + } + }; + + let response = { + #[cfg(feature = "tracing")] + let _span = tracing::info_span!("wry::custom_protocol::call_handler").entered(); + (handler.handler)( + webview_id, + final_request, + is_document_start_script_enabled != 0, + ) + }; + if let Some(response) = response { + let status = response.status(); + let status_code = status.as_u16() as i32; + let status_err = if status_code < 100 { + Some("Status code can't be less than 100") + } else if status_code > 599 { + Some("statusCode can't be greater than 599.") + } else if status_code > 299 && status_code < 400 { + Some("statusCode can't be in the [300, 399] range.") + } else { + None + }; + if let Some(_err) = status_err { + #[cfg(feature = "tracing")] + tracing::warn!("{_err}"); + return Ok(*JObject::null()); + } + + let reason_phrase = status.canonical_reason().unwrap_or("OK"); + let (mime_type, encoding) = if let Some(content_type) = response.headers().get(CONTENT_TYPE) { + let content_type = content_type.to_str().unwrap().trim(); + let mut s = content_type.split(';'); + let mime_type = s.next().unwrap().trim(); + let mut encoding = None; + for token in s { + let token = token.trim(); + if token.starts_with("charset=") { + encoding.replace(token.split('=').nth(1).unwrap()); + break; + } + } + ( + env.new_string(mime_type)?, + if let Some(encoding) = encoding { + env.new_string(encoding)? + } else { + JString::default() + }, + ) + } else { + (JString::default(), JString::default()) + }; + + let headers = response.headers(); + let obj = env.new_object("java/util/HashMap", "()V", &[])?; + let response_headers = { + let headers_map = JMap::from_env(env, &obj)?; + for (name, value) in headers.iter() { + // WebResourceResponse will automatically generate Content-Type and + // Content-Length headers so we should skip them to avoid duplication. + if name == CONTENT_TYPE || name == CONTENT_LENGTH { + continue; + } + let key = env.new_string(name)?; + let value = env.new_string(value.to_str().unwrap_or_default())?; + headers_map.put(env, &key, &value)?; + } + headers_map + }; + + let bytes = response.body(); + + let byte_array_input_stream = env.find_class("java/io/ByteArrayInputStream")?; + let byte_array = env.byte_array_from_slice(bytes)?; + let stream = env.new_object(byte_array_input_stream, "([B)V", &[(&byte_array).into()])?; + + let reason_phrase = env.new_string(reason_phrase)?; + + let web_resource_response_class = env.find_class("android/webkit/WebResourceResponse")?; + let web_resource_response = env.new_object( + web_resource_response_class, + "(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/util/Map;Ljava/io/InputStream;)V", + &[(&mime_type).into(), (&encoding).into(), status_code.into(), (&reason_phrase).into(), (&response_headers).into(), (&stream).into()], + )?; + + return Ok(*web_resource_response); + } + } + + Ok(*JObject::null()) +} + +#[allow(non_snake_case)] +pub unsafe fn wryCreate(env: JNIEnv, _: JClass) { + let mut main_pipe = MainPipe { env }; + + let looper = ThreadLooper::for_thread().unwrap(); + + looper + .add_fd_with_callback(MAIN_PIPE[0].as_fd(), FdEvent::INPUT, move |fd, _event| { + let size = std::mem::size_of::(); + let mut wake = false; + if libc::read(fd.as_raw_fd(), &mut wake as *mut _ as *mut _, size) == size as libc::ssize_t { + // unregister itself on errors + main_pipe.recv().is_ok() + } else { + // unregister itself + false + } + }) + .unwrap(); +} + +#[allow(non_snake_case)] +pub unsafe fn onWebviewDestroy(mut env: JNIEnv, _: JClass, activity: JObject, webview_id: JString) { + let activity_id = env + .call_method(&activity, "getId", "()I", &[]) + .unwrap() + .i() + .unwrap(); + + let webview_id = env + .get_string(&webview_id) + .unwrap() + .to_string_lossy() + .to_string(); + + let is_changing_configurations = env + .call_method(&activity, "isChangingConfigurations", "()Z", &[]) + .unwrap() + .z() + .unwrap(); + + super::MainPipe::send( + activity_id, + super::WebViewMessage::OnDestroy { + activity_id, + webview_id, + is_changing_configurations, + }, + ); +} + +#[allow(non_snake_case)] +pub unsafe fn handleRequest( + mut env: JNIEnv, + _: JClass, + webview_id: JString, + request: JObject, + is_document_start_script_enabled: jboolean, +) -> jobject { + match handle_request( + &mut env, + webview_id, + request, + is_document_start_script_enabled, + ) { + Ok(response) => response, + Err(_e) => { + #[cfg(feature = "tracing")] + tracing::warn!("Failed to handle request: {_e}"); + JObject::null().as_raw() + } + } +} + +#[allow(non_snake_case)] +pub unsafe fn shouldOverride( + mut env: JNIEnv, + _: JClass, + webview_id: JString, + url: JString, +) -> jboolean { + match env.get_string(&url) { + Ok(url) => { + let url = url.to_string_lossy().to_string(); + + let Ok(webview_id) = env.get_string(&webview_id) else { + return false.into(); + }; + let webview_id = webview_id.to_str().ok().unwrap_or_default(); + + URL_LOADING_OVERRIDE + .lock() + .unwrap() + .get(webview_id) + // We negate the result of the function because the logic for the android + // client is different from how the navigation_handler is defined. + // + // https://developer.android.com/reference/android/webkit/WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView,%20android.webkit.WebResourceRequest) + .map(|f| !(f.handler)(url)) + .unwrap_or_default() + } + Err(_e) => { + #[cfg(feature = "tracing")] + tracing::warn!("Failed to parse JString: {_e}"); + false + } + } + .into() +} + +#[allow(non_snake_case)] +pub unsafe fn onEval(mut env: JNIEnv, _: JClass, _webview_id: JString, id: jint, result: JString) { + match env.get_string(&result) { + Ok(result) => { + if let Some(cb) = EVAL_CALLBACKS + .get_or_init(Default::default) + .lock() + .unwrap() + .get(&id) + { + cb(result.into()); + } + } + Err(_e) => { + #[cfg(feature = "tracing")] + tracing::warn!("Failed to parse JString: {_e}"); + } + } +} + +pub unsafe fn ipc(mut env: JNIEnv, _: JClass, webview_id: JString, url: JString, body: JString) { + match ( + env.get_string(&url), + env.get_string(&body), + env.get_string(&webview_id), + ) { + (Ok(url), Ok(body), Ok(webview_id)) => { + #[cfg(feature = "tracing")] + let _span = tracing::info_span!(parent: None, "wry::ipc::handle").entered(); + + let url = url.to_string_lossy().to_string(); + let body = body.to_string_lossy().to_string(); + let webview_id = webview_id.to_string_lossy().to_string(); + if let Some(ipc) = IPC.lock().unwrap().get(&webview_id) { + (ipc.handler)(Request::builder().uri(url).body(body).unwrap()) + } + } + (Err(_e), _, _) | (_, Err(_e), _) | (_, _, Err(_e)) => { + #[cfg(feature = "tracing")] + tracing::warn!("Failed to parse JString: {_e}") + } + } +} + +#[allow(non_snake_case)] +pub unsafe fn handleReceivedTitle(mut env: JNIEnv, _: JClass, webview_id: JString, title: JString) { + match (env.get_string(&title), env.get_string(&webview_id)) { + (Ok(title), Ok(webview_id)) => { + let title = title.to_string_lossy().to_string(); + let webview_id = webview_id.to_string_lossy().to_string(); + if let Some(title_handler) = TITLE_CHANGE_HANDLER.lock().unwrap().get(&webview_id) { + (title_handler.handler)(title) + } + } + (Err(_e), _) | (_, Err(_e)) => { + #[cfg(feature = "tracing")] + tracing::warn!("Failed to parse JString: {_e}") + } + } +} + +#[allow(non_snake_case)] +pub unsafe fn withAssetLoader(mut env: JNIEnv, _: JClass, webview_id: JString) -> jboolean { + let Ok(webview_id) = env.get_string(&webview_id) else { + return false.into(); + }; + let webview_id = webview_id.to_str().ok().unwrap_or_default(); + (*WITH_ASSET_LOADER + .lock() + .unwrap() + .get(webview_id) + .unwrap_or(&false)) + .into() +} + +#[allow(non_snake_case)] +pub unsafe fn assetLoaderDomain(mut env: JNIEnv, _: JClass, webview_id: JString) -> jstring { + let Ok(webview_id) = env.get_string(&webview_id) else { + return env.new_string("wry.assets").unwrap().as_raw(); + }; + let webview_id = webview_id.to_str().ok().unwrap_or_default(); + if let Some(domain) = ASSET_LOADER_DOMAIN.lock().unwrap().get(webview_id) { + env.new_string(domain).unwrap().as_raw() + } else { + env.new_string("wry.assets").unwrap().as_raw() + } +} + +#[allow(non_snake_case)] +pub unsafe fn onPageLoading(mut env: JNIEnv, _: JClass, webview_id: JString, url: JString) { + match (env.get_string(&url), env.get_string(&webview_id)) { + (Ok(url), Ok(webview_id)) => { + let url = url.to_string_lossy().to_string(); + let webview_id = webview_id.to_string_lossy().to_string(); + if let Some(on_load) = ON_LOAD_HANDLER.lock().unwrap().get(&webview_id) { + (on_load.handler)(PageLoadEvent::Started, url) + } + } + (Err(_e), _) | (_, Err(_e)) => { + #[cfg(feature = "tracing")] + tracing::warn!("Failed to parse JString: {_e}") + } + } +} + +#[allow(non_snake_case)] +pub unsafe fn onPageLoaded(mut env: JNIEnv, _: JClass, webview_id: JString, url: JString) { + match (env.get_string(&url), env.get_string(&webview_id)) { + (Ok(url), Ok(webview_id)) => { + let url = url.to_string_lossy().to_string(); + let webview_id = webview_id.to_string_lossy().to_string(); + if let Some(on_load) = ON_LOAD_HANDLER.lock().unwrap().get(&webview_id) { + (on_load.handler)(PageLoadEvent::Finished, url) + } + } + (Err(_e), _) | (_, Err(_e)) => { + #[cfg(feature = "tracing")] + tracing::warn!("Failed to parse JString: {_e}") + } + } +} diff --git a/desktop/src-tauri/vendor/wry/src/android/kotlin/Ipc.kt b/desktop/src-tauri/vendor/wry/src/android/kotlin/Ipc.kt new file mode 100644 index 00000000000..c7603750642 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/android/kotlin/Ipc.kt @@ -0,0 +1,23 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +@file:Suppress("unused") + +package {{package}} + +import android.webkit.* + +class Ipc(val webView: RustWebView, val webViewClient: RustWebViewClient) { + @JavascriptInterface + fun postMessage(message: String?) { + message?.let {m -> + // we're not using WebView::getUrl() here because it needs to be executed on the main thread + // and it would slow down the Ipc + // so instead we track the current URL on the webview client + Rust.ipc(webView.id, webViewClient.currentUrl, m) + } + } + + {{class-extension}} +} diff --git a/desktop/src-tauri/vendor/wry/src/android/kotlin/Logger.kt b/desktop/src-tauri/vendor/wry/src/android/kotlin/Logger.kt new file mode 100644 index 00000000000..3666ad2aac9 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/android/kotlin/Logger.kt @@ -0,0 +1,87 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package {{package}} + +// taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/Logger.java + +import android.text.TextUtils +import android.util.Log + +class Logger { + companion object { + private const val LOG_TAG_CORE = "Tauri" + + fun tags(vararg subtags: String): String { + return if (subtags.isNotEmpty()) { + LOG_TAG_CORE + "/" + TextUtils.join("/", subtags) + } else LOG_TAG_CORE + } + + fun verbose(message: String) { + verbose(LOG_TAG_CORE, message) + } + + private fun verbose(tag: String, message: String) { + if (!shouldLog()) { + return + } + Log.v(tag, message) + } + + fun debug(message: String) { + debug(LOG_TAG_CORE, message) + } + + fun debug(tag: String, message: String) { + if (!shouldLog()) { + return + } + Log.d(tag, message) + } + + fun info(message: String) { + info(LOG_TAG_CORE, message) + } + + fun info(tag: String, message: String) { + if (!shouldLog()) { + return + } + Log.i(tag, message) + } + + fun warn(message: String) { + warn(LOG_TAG_CORE, message) + } + + fun warn(tag: String, message: String) { + if (!shouldLog()) { + return + } + Log.w(tag, message) + } + + fun error(message: String) { + error(LOG_TAG_CORE, message, null) + } + + fun error(message: String, e: Throwable?) { + error(LOG_TAG_CORE, message, e) + } + + fun error(tag: String, message: String, e: Throwable?) { + if (!shouldLog()) { + return + } + Log.e(tag, message, e) + } + + private fun shouldLog(): Boolean { + return BuildConfig.DEBUG + } + } +} diff --git a/desktop/src-tauri/vendor/wry/src/android/kotlin/PermissionHelper.kt b/desktop/src-tauri/vendor/wry/src/android/kotlin/PermissionHelper.kt new file mode 100644 index 00000000000..b2471e515e4 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/android/kotlin/PermissionHelper.kt @@ -0,0 +1,115 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package {{package}} + +// taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/PermissionHelper.java + +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.app.ActivityCompat +import java.util.ArrayList + +object PermissionHelper { + /** + * Checks if a list of given permissions are all granted by the user + * + * @param permissions Permissions to check. + * @return True if all permissions are granted, false if at least one is not. + */ + fun hasPermissions(context: Context?, permissions: Array): Boolean { + for (perm in permissions) { + if (ActivityCompat.checkSelfPermission( + context!!, + perm + ) != PackageManager.PERMISSION_GRANTED + ) { + return false + } + } + return true + } + + /** + * Check whether the given permission has been defined in the AndroidManifest.xml + * + * @param permission A permission to check. + * @return True if the permission has been defined in the Manifest, false if not. + */ + fun hasDefinedPermission(context: Context, permission: String): Boolean { + var hasPermission = false + val requestedPermissions = getManifestPermissions(context) + if (!requestedPermissions.isNullOrEmpty()) { + val requestedPermissionsList = listOf(*requestedPermissions) + val requestedPermissionsArrayList = ArrayList(requestedPermissionsList) + if (requestedPermissionsArrayList.contains(permission)) { + hasPermission = true + } + } + return hasPermission + } + + /** + * Check whether all of the given permissions have been defined in the AndroidManifest.xml + * @param context the app context + * @param permissions a list of permissions + * @return true only if all permissions are defined in the AndroidManifest.xml + */ + fun hasDefinedPermissions(context: Context, permissions: Array): Boolean { + for (permission in permissions) { + if (!hasDefinedPermission(context, permission)) { + return false + } + } + return true + } + + /** + * Get the permissions defined in AndroidManifest.xml + * + * @return The permissions defined in AndroidManifest.xml + */ + private fun getManifestPermissions(context: Context): Array? { + var requestedPermissions: Array? = null + try { + val pm = context.packageManager + val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pm.getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong())) + } else { + @Suppress("DEPRECATION") + pm.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS) + } + if (packageInfo != null) { + requestedPermissions = packageInfo.requestedPermissions + } + } catch (_: Exception) { + } + return requestedPermissions + } + + /** + * Given a list of permissions, return a new list with the ones not present in AndroidManifest.xml + * + * @param neededPermissions The permissions needed. + * @return The permissions not present in AndroidManifest.xml + */ + fun getUndefinedPermissions(context: Context, neededPermissions: Array): Array { + val undefinedPermissions = ArrayList() + val requestedPermissions = getManifestPermissions(context) + if (!requestedPermissions.isNullOrEmpty()) { + val requestedPermissionsList = listOf(*requestedPermissions) + val requestedPermissionsArrayList = ArrayList(requestedPermissionsList) + for (permission in neededPermissions) { + if (!requestedPermissionsArrayList.contains(permission)) { + undefinedPermissions.add(permission) + } + } + var undefinedPermissionArray = arrayOfNulls(undefinedPermissions.size) + undefinedPermissionArray = undefinedPermissions.toArray(undefinedPermissionArray) + return undefinedPermissionArray + } + return neededPermissions + } +} diff --git a/desktop/src-tauri/vendor/wry/src/android/kotlin/Rust.kt b/desktop/src-tauri/vendor/wry/src/android/kotlin/Rust.kt new file mode 100644 index 00000000000..b5b6bdfbb38 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/android/kotlin/Rust.kt @@ -0,0 +1,46 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +@file:Suppress("unused") + +package {{package}} + +import android.content.Intent +import android.webkit.WebView +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse + +object Rust { + init { + System.loadLibrary("{{library}}") + } + + @JvmStatic external fun onActivityCreate(activity: WryActivity) + @JvmStatic external fun onActivityDestroy(activity: WryActivity) + @JvmStatic external fun onActivitySaveInstanceState() + @JvmStatic external fun onActivityLowMemory() + @JvmStatic external fun onWindowFocusChanged(activity: WryActivity, focus: Boolean) + @JvmStatic external fun onNewIntent(intent: Intent) + + @JvmStatic external fun create() + @JvmStatic external fun start() + @JvmStatic external fun resume() + @JvmStatic external fun pause() + @JvmStatic external fun stop() + + @JvmStatic external fun wryCreate() + @JvmStatic external fun onWebviewDestroy(activity: WryActivity, webviewId: String) + + @JvmStatic external fun ipc(webviewId: String, url: String, message: String) + + @JvmStatic external fun assetLoaderDomain(webviewId: String): String + @JvmStatic external fun withAssetLoader(webviewId: String): Boolean + @JvmStatic external fun handleRequest(webviewId: String, request: WebResourceRequest, isDocumentStartScriptEnabled: Boolean): WebResourceResponse? + @JvmStatic external fun shouldOverride(webviewId: String, url: String): Boolean + @JvmStatic external fun onPageLoading(webviewId: String, url: String) + @JvmStatic external fun onPageLoaded(webviewId: String, url: String) + @JvmStatic external fun onEval(webviewId: String, id: Int, result: String) + + @JvmStatic external fun handleReceivedTitle(webviewId: String, title: String) +} \ No newline at end of file diff --git a/desktop/src-tauri/vendor/wry/src/android/kotlin/RustWebChromeClient.kt b/desktop/src-tauri/vendor/wry/src/android/kotlin/RustWebChromeClient.kt new file mode 100644 index 00000000000..bba26dbe2f9 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/android/kotlin/RustWebChromeClient.kt @@ -0,0 +1,491 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +@file:Suppress("ObsoleteSdkInt", "RedundantOverride", "QueryPermissionsNeeded", "SimpleDateFormat") + +package {{package}} + +// taken from https://github.com/ionic-team/capacitor/blob/6658bca41e78239347e458175b14ca8bd5c1d6e8/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java + +import android.Manifest +import android.app.Activity +import android.app.AlertDialog +import android.content.ActivityNotFoundException +import android.content.DialogInterface +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.MediaStore +import android.view.View +import android.webkit.* +import android.widget.EditText +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultCallback +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.FileProvider +import java.io.File +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.* + +class RustWebChromeClient(appActivity: WryActivity) : WebChromeClient() { + private interface PermissionListener { + fun onPermissionSelect(isGranted: Boolean?) + } + + private interface ActivityResultListener { + fun onActivityResult(result: ActivityResult?) + } + + private val activity: WryActivity + private var permissionLauncher: ActivityResultLauncher> + private var activityLauncher: ActivityResultLauncher + private var permissionListener: PermissionListener? = null + private var activityListener: ActivityResultListener? = null + + init { + activity = appActivity + val permissionCallback = + ActivityResultCallback { isGranted: Map -> + if (permissionListener != null) { + var granted = true + for ((_, value) in isGranted) { + if (!value) granted = false + } + permissionListener!!.onPermissionSelect(granted) + } + } + permissionLauncher = + activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions(), permissionCallback) + activityLauncher = activity.registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (activityListener != null) { + activityListener!!.onActivityResult(result) + } + } + } + + /** + * Render web content in `view`. + * + * Both this method and [.onHideCustomView] are required for + * rendering web content in full screen. + * + * @see [](https://developer.android.com/reference/android/webkit/WebChromeClient.onShowCustomView + ) */ + override fun onShowCustomView(view: View, callback: CustomViewCallback) { + callback.onCustomViewHidden() + super.onShowCustomView(view, callback) + } + + /** + * Render web content in the original Web View again. + * + * Do not remove this method--@see #onShowCustomView(View, CustomViewCallback). + */ + override fun onHideCustomView() { + super.onHideCustomView() + } + + override fun onPermissionRequest(request: PermissionRequest) { + val isRequestPermissionRequired = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + val permissionList: MutableList = ArrayList() + if (listOf(*request.resources).contains("android.webkit.resource.VIDEO_CAPTURE")) { + permissionList.add(Manifest.permission.CAMERA) + } + if (listOf(*request.resources).contains("android.webkit.resource.AUDIO_CAPTURE")) { + permissionList.add(Manifest.permission.MODIFY_AUDIO_SETTINGS) + permissionList.add(Manifest.permission.RECORD_AUDIO) + } + if (permissionList.isNotEmpty() && isRequestPermissionRequired) { + val permissions = permissionList.toTypedArray() + permissionListener = object : PermissionListener { + override fun onPermissionSelect(isGranted: Boolean?) { + if (isGranted == true) { + request.grant(request.resources) + } else { + request.deny() + } + } + } + permissionLauncher.launch(permissions) + } else { + request.grant(request.resources) + } + } + + /** + * Show the browser alert modal + * @param view + * @param url + * @param message + * @param result + * @return + */ + override fun onJsAlert(view: WebView, url: String, message: String, result: JsResult): Boolean { + if (activity.isFinishing) { + return true + } + val builder = AlertDialog.Builder(view.context) + builder + .setMessage(message) + .setPositiveButton( + "OK" + ) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + result.confirm() + } + .setOnCancelListener { dialog: DialogInterface -> + dialog.dismiss() + result.cancel() + } + val dialog = builder.create() + dialog.show() + return true + } + + /** + * Show the browser confirm modal + * @param view + * @param url + * @param message + * @param result + * @return + */ + override fun onJsConfirm(view: WebView, url: String, message: String, result: JsResult): Boolean { + if (activity.isFinishing) { + return true + } + val builder = AlertDialog.Builder(view.context) + builder + .setMessage(message) + .setPositiveButton( + "OK" + ) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + result.confirm() + } + .setNegativeButton( + "Cancel" + ) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + result.cancel() + } + .setOnCancelListener { dialog: DialogInterface -> + dialog.dismiss() + result.cancel() + } + val dialog = builder.create() + dialog.show() + return true + } + + /** + * Show the browser prompt modal + * @param view + * @param url + * @param message + * @param defaultValue + * @param result + * @return + */ + override fun onJsPrompt( + view: WebView, + url: String, + message: String, + defaultValue: String, + result: JsPromptResult + ): Boolean { + if (activity.isFinishing) { + return true + } + val builder = AlertDialog.Builder(view.context) + val input = EditText(view.context) + builder + .setMessage(message) + .setView(input) + .setPositiveButton( + "OK" + ) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + val inputText1 = input.text.toString().trim { it <= ' ' } + result.confirm(inputText1) + } + .setNegativeButton( + "Cancel" + ) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + result.cancel() + } + .setOnCancelListener { dialog: DialogInterface -> + dialog.dismiss() + result.cancel() + } + val dialog = builder.create() + dialog.show() + return true + } + + /** + * Handle the browser geolocation permission prompt + * @param origin + * @param callback + */ + override fun onGeolocationPermissionsShowPrompt( + origin: String, + callback: GeolocationPermissions.Callback + ) { + super.onGeolocationPermissionsShowPrompt(origin, callback) + Logger.debug("onGeolocationPermissionsShowPrompt: DOING IT HERE FOR ORIGIN: $origin") + val geoPermissions = + arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION) + if (!PermissionHelper.hasPermissions(activity, geoPermissions)) { + permissionListener = object : PermissionListener { + override fun onPermissionSelect(isGranted: Boolean?) { + if (isGranted == true) { + callback.invoke(origin, true, false) + } else { + val coarsePermission = + arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && + PermissionHelper.hasPermissions(activity, coarsePermission) + ) { + callback.invoke(origin, true, false) + } else { + callback.invoke(origin, false, false) + } + } + } + } + permissionLauncher.launch(geoPermissions) + } else { + // permission is already granted + callback.invoke(origin, true, false) + Logger.debug("onGeolocationPermissionsShowPrompt: has required permission") + } + } + + override fun onShowFileChooser( + webView: WebView, + filePathCallback: ValueCallback?>, + fileChooserParams: FileChooserParams + ): Boolean { + val acceptTypes = listOf(*fileChooserParams.acceptTypes) + val captureEnabled = fileChooserParams.isCaptureEnabled + val capturePhoto = captureEnabled && acceptTypes.contains("image/*") + val captureVideo = captureEnabled && acceptTypes.contains("video/*") + if (capturePhoto || captureVideo) { + if (isMediaCaptureSupported) { + showMediaCaptureOrFilePicker(filePathCallback, fileChooserParams, captureVideo) + } else { + permissionListener = object : PermissionListener { + override fun onPermissionSelect(isGranted: Boolean?) { + if (isGranted == true) { + showMediaCaptureOrFilePicker(filePathCallback, fileChooserParams, captureVideo) + } else { + Logger.warn(Logger.tags("FileChooser"), "Camera permission not granted") + filePathCallback.onReceiveValue(null) + } + } + } + val camPermission = arrayOf(Manifest.permission.CAMERA) + permissionLauncher.launch(camPermission) + } + } else { + showFilePicker(filePathCallback, fileChooserParams) + } + return true + } + + private val isMediaCaptureSupported: Boolean + get() { + val permissions = arrayOf(Manifest.permission.CAMERA) + return PermissionHelper.hasPermissions(activity, permissions) || + !PermissionHelper.hasDefinedPermission(activity, Manifest.permission.CAMERA) + } + + private fun showMediaCaptureOrFilePicker( + filePathCallback: ValueCallback?>, + fileChooserParams: FileChooserParams, + isVideo: Boolean + ) { + val isVideoCaptureSupported = true + val shown = if (isVideo && isVideoCaptureSupported) { + showVideoCapturePicker(filePathCallback) + } else { + showImageCapturePicker(filePathCallback) + } + if (!shown) { + Logger.warn( + Logger.tags("FileChooser"), + "Media capture intent could not be launched. Falling back to default file picker." + ) + showFilePicker(filePathCallback, fileChooserParams) + } + } + + private fun showImageCapturePicker(filePathCallback: ValueCallback?>): Boolean { + val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + if (takePictureIntent.resolveActivity(activity.packageManager) == null) { + return false + } + val imageFileUri: Uri = try { + createImageFileUri() + } catch (ex: Exception) { + Logger.error("Unable to create temporary media capture file: " + ex.message) + return false + } + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri) + activityListener = object : ActivityResultListener { + override fun onActivityResult(result: ActivityResult?) { + var res: Array? = null + if (result?.resultCode == Activity.RESULT_OK) { + res = arrayOf(imageFileUri) + } + filePathCallback.onReceiveValue(res) + } + } + activityLauncher.launch(takePictureIntent) + return true + } + + private fun showVideoCapturePicker(filePathCallback: ValueCallback?>): Boolean { + val takeVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE) + if (takeVideoIntent.resolveActivity(activity.packageManager) == null) { + return false + } + activityListener = object : ActivityResultListener { + override fun onActivityResult(result: ActivityResult?) { + var res: Array? = null + if (result?.resultCode == Activity.RESULT_OK) { + res = arrayOf(result.data!!.data) + } + filePathCallback.onReceiveValue(res) + } + } + activityLauncher.launch(takeVideoIntent) + return true + } + + private fun showFilePicker( + filePathCallback: ValueCallback?>, + fileChooserParams: FileChooserParams + ) { + val intent = fileChooserParams.createIntent() + if (fileChooserParams.mode == FileChooserParams.MODE_OPEN_MULTIPLE) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) + } + if (fileChooserParams.acceptTypes.size > 1 || intent.type!!.startsWith(".")) { + val validTypes = getValidTypes(fileChooserParams.acceptTypes) + intent.putExtra(Intent.EXTRA_MIME_TYPES, validTypes) + if (intent.type!!.startsWith(".")) { + intent.type = validTypes[0] + } + } + try { + activityListener = object : ActivityResultListener { + override fun onActivityResult(result: ActivityResult?) { + val res: Array? + val resultIntent = result?.data + if (result?.resultCode == Activity.RESULT_OK && resultIntent!!.clipData != null) { + val numFiles = resultIntent.clipData!!.itemCount + res = arrayOfNulls(numFiles) + for (i in 0 until numFiles) { + res[i] = resultIntent.clipData!!.getItemAt(i).uri + } + } else { + res = FileChooserParams.parseResult( + result?.resultCode ?: 0, + resultIntent + ) + } + filePathCallback.onReceiveValue(res) + } + } + activityLauncher.launch(intent) + } catch (e: ActivityNotFoundException) { + filePathCallback.onReceiveValue(null) + } + } + + private fun getValidTypes(currentTypes: Array): Array { + val validTypes: MutableList = ArrayList() + val mtm = MimeTypeMap.getSingleton() + for (mime in currentTypes) { + if (mime.startsWith(".")) { + val extension = mime.substring(1) + val extensionMime = mtm.getMimeTypeFromExtension(extension) + if (extensionMime != null && !validTypes.contains(extensionMime)) { + validTypes.add(extensionMime) + } + } else if (!validTypes.contains(mime)) { + validTypes.add(mime) + } + } + val validObj: Array = validTypes.toTypedArray() + return Arrays.copyOf( + validObj, validObj.size, + Array::class.java + ) + } + + override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean { + val tag: String = Logger.tags("Console") + if (consoleMessage.message() != null && isValidMsg(consoleMessage.message())) { + val msg = String.format( + "File: %s - Line %d - Msg: %s", + consoleMessage.sourceId(), + consoleMessage.lineNumber(), + consoleMessage.message() + ) + val level = consoleMessage.messageLevel().name + if ("ERROR".equals(level, ignoreCase = true)) { + Logger.error(tag, msg, null) + } else if ("WARNING".equals(level, ignoreCase = true)) { + Logger.warn(tag, msg) + } else if ("TIP".equals(level, ignoreCase = true)) { + Logger.debug(tag, msg) + } else { + Logger.info(tag, msg) + } + } + return true + } + + private fun isValidMsg(msg: String): Boolean { + return !(msg.contains("%cresult %c") || + msg.contains("%cnative %c") || + msg.equals("[object Object]", ignoreCase = true) || + msg.equals("console.groupEnd", ignoreCase = true)) + } + + @Throws(IOException::class) + private fun createImageFileUri(): Uri { + val photoFile = createImageFile(activity) + return FileProvider.getUriForFile( + activity, + activity.packageName.toString() + ".fileprovider", + photoFile + ) + } + + @Throws(IOException::class) + private fun createImageFile(activity: Activity): File { + // Create an image file name + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date()) + val imageFileName = "JPEG_" + timeStamp + "_" + val storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + return File.createTempFile(imageFileName, ".jpg", storageDir) + } + + override fun onReceivedTitle( + view: WebView, + title: String + ) { + Rust.handleReceivedTitle((view as RustWebView).id, title) + } +} diff --git a/desktop/src-tauri/vendor/wry/src/android/kotlin/RustWebView.kt b/desktop/src-tauri/vendor/wry/src/android/kotlin/RustWebView.kt new file mode 100644 index 00000000000..31356c37a7d --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/android/kotlin/RustWebView.kt @@ -0,0 +1,96 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +@file:Suppress("unused", "SetJavaScriptEnabled") + +package {{package}} + +import android.annotation.SuppressLint +import android.webkit.* +import android.content.Context +import androidx.webkit.WebViewCompat +import androidx.webkit.WebViewFeature +import kotlin.collections.Map + +@SuppressLint("RestrictedApi") +class RustWebView(context: Context, val initScripts: Array, val id: String): WebView(context) { + val isDocumentStartScriptEnabled: Boolean + + init { + settings.javaScriptEnabled = true + settings.domStorageEnabled = true + settings.setGeolocationEnabled(true) + settings.databaseEnabled = true + settings.mediaPlaybackRequiresUserGesture = false + settings.javaScriptCanOpenWindowsAutomatically = true + + if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + isDocumentStartScriptEnabled = true + for (script in initScripts) { + WebViewCompat.addDocumentStartJavaScript(this, script, setOf("*")); + } + } else { + isDocumentStartScriptEnabled = false + } + + {{class-init}} + } + + fun loadUrlMainThread(url: String) { + post { + loadUrl(url) + } + } + + fun loadUrlMainThread(url: String, additionalHttpHeaders: Map) { + post { + loadUrl(url, additionalHttpHeaders) + } + } + + override fun loadUrl(url: String) { + if (!Rust.shouldOverride(id, url)) { + super.loadUrl(url); + } + } + + override fun loadUrl(url: String, additionalHttpHeaders: Map) { + if (!Rust.shouldOverride(id, url)) { + super.loadUrl(url, additionalHttpHeaders); + } + } + + fun loadHTMLMainThread(html: String) { + post { + super.loadData(html, "text/html", null) + } + } + + fun evalScript(id: Int, script: String) { + post { + super.evaluateJavascript(script) { result -> + Rust.onEval(this.id, id, result) + } + } + } + + fun clearAllBrowsingData() { + try { + super.getContext().deleteDatabase("webviewCache.db") + super.getContext().deleteDatabase("webview.db") + super.clearCache(true) + super.clearHistory() + super.clearFormData() + } catch (ex: Exception) { + Logger.error("Unable to create temporary media capture file: " + ex.message) + } + } + + fun getCookies(url: String): String { + val cookieManager = CookieManager.getInstance() + return cookieManager.getCookie(url) + } + + {{class-extension}} +} diff --git a/desktop/src-tauri/vendor/wry/src/android/kotlin/RustWebViewClient.kt b/desktop/src-tauri/vendor/wry/src/android/kotlin/RustWebViewClient.kt new file mode 100644 index 00000000000..3dae4608971 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/android/kotlin/RustWebViewClient.kt @@ -0,0 +1,98 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package {{package}} + +import android.net.Uri +import android.webkit.* +import android.content.Context +import android.graphics.Bitmap +import android.os.Handler +import android.os.Looper +import androidx.webkit.WebViewAssetLoader + +class RustWebViewClient(webView: RustWebView, context: Context): WebViewClient() { + private val interceptedState = mutableMapOf() + var currentUrl: String = "about:blank" + private var lastInterceptedUrl: Uri? = null + private var pendingUrlRedirect: String? = null + + private val assetLoader = WebViewAssetLoader.Builder() + .setDomain(Rust.assetLoaderDomain(webView.id)) + .addPathHandler("/", WebViewAssetLoader.AssetsPathHandler(context)) + .build() + + override fun shouldInterceptRequest( + view: WebView, + request: WebResourceRequest + ): WebResourceResponse? { + pendingUrlRedirect?.let { + Handler(Looper.getMainLooper()).post { + view.loadUrl(it) + } + pendingUrlRedirect = null + return null + } + + lastInterceptedUrl = request.url + return if (Rust.withAssetLoader((view as RustWebView).id)) { + assetLoader.shouldInterceptRequest(request.url) + } else { + val response = Rust.handleRequest(view.id, request, view.isDocumentStartScriptEnabled) + if (response != null) { + if (response.responseHeaders != null) { + response.responseHeaders["Cache-Control"] = "no-store" + } else { + response.responseHeaders = mapOf("Cache-Control" to "no-store") + } + } + interceptedState[request.url.toString()] = response != null + return response + } + } + + override fun shouldOverrideUrlLoading( + view: WebView, + request: WebResourceRequest + ): Boolean { + return Rust.shouldOverride((view as RustWebView).id, request.url.toString()) + } + + override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { + currentUrl = url + if (interceptedState[url] == false) { + val webView = view as RustWebView + for (script in webView.initScripts) { + view.evaluateJavascript(script, null) + } + } + return Rust.onPageLoading((view as RustWebView).id, url) + } + + override fun onPageFinished(view: WebView, url: String) { + Rust.onPageLoaded((view as RustWebView).id, url) + } + + override fun onReceivedError( + view: WebView, + request: WebResourceRequest, + error: WebResourceError + ) { + // we get a net::ERR_CONNECTION_REFUSED when an external URL redirects to a custom protocol + // e.g. oauth flow, because shouldInterceptRequest is not called on redirects + // so we must force retry here with loadUrl() to get a chance of the custom protocol to kick in + if (error.errorCode == ERROR_CONNECT && request.isForMainFrame && request.url != lastInterceptedUrl) { + // prevent the default error page from showing + view.stopLoading() + // without this initial loadUrl the app is stuck + view.loadUrl(request.url.toString()) + // ensure the URL is actually loaded - for some reason there's a race condition and we need to call loadUrl() again later + pendingUrlRedirect = request.url.toString() + } else { + super.onReceivedError(view, request, error) + } + } + + {{class-extension}} +} diff --git a/desktop/src-tauri/vendor/wry/src/android/kotlin/WryActivity.kt b/desktop/src-tauri/vendor/wry/src/android/kotlin/WryActivity.kt new file mode 100644 index 00000000000..7152e352bcf --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/android/kotlin/WryActivity.kt @@ -0,0 +1,173 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package {{package}} + +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.webkit.WebView +import android.view.KeyEvent +import androidx.activity.OnBackPressedCallback +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner + +private val ACTIVITY_ID_KEY = "__wryActivityId" + +object WryLifecycleObserver : DefaultLifecycleObserver { + override fun onCreate(owner: LifecycleOwner) { + super.onCreate(owner) + Rust.create() + Rust.wryCreate() + } + + override fun onStart(owner: LifecycleOwner) { + super.onStart(owner) + Rust.start() + } + + override fun onResume(owner: LifecycleOwner) { + super.onResume(owner) + Rust.resume() + } + + override fun onPause(owner: LifecycleOwner) { + super.onPause(owner) + Rust.pause() + } + + override fun onStop(owner: LifecycleOwner) { + super.onStop(owner) + Rust.stop() + } +} + +abstract class WryActivity : AppCompatActivity() { + private lateinit var mWebView: RustWebView + var id: Int = 0 + open val handleBackNavigation: Boolean = true + + open fun onWebViewCreate(webView: WebView) { } + + fun setWebView(webView: RustWebView) { + mWebView = webView + + if (handleBackNavigation) { + val callback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (this@WryActivity::mWebView.isInitialized) { + if (this@WryActivity.mWebView.canGoBack()) { + this@WryActivity.mWebView.goBack() + } else { + this.isEnabled = false + this@WryActivity.onBackPressed() + this.isEnabled = true + } + } + } + } + onBackPressedDispatcher.addCallback(this, callback) + } + + onWebViewCreate(webView) + } + + val version: String + @SuppressLint("WebViewApiAvailability", "ObsoleteSdkInt") + get() { + // Check getCurrentWebViewPackage() directly if above Android 8 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return WebView.getCurrentWebViewPackage()?.versionName ?: "" + } + + // Otherwise manually check WebView versions + var webViewPackage = "com.google.android.webview" + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + webViewPackage = "com.android.chrome" + } + try { + @Suppress("DEPRECATION") + val info = packageManager.getPackageInfo(webViewPackage, 0) + return info.versionName.toString() + } catch (ex: Exception) { + Logger.warn("Unable to get package info for '$webViewPackage'$ex") + } + + try { + @Suppress("DEPRECATION") + val info = packageManager.getPackageInfo("com.android.webview", 0) + return info.versionName.toString() + } catch (ex: Exception) { + Logger.warn("Unable to get package info for 'com.android.webview'$ex") + } + + // Could not detect any webview, return empty string + return "" + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + id = savedInstanceState?.getInt(ACTIVITY_ID_KEY) ?: intent.extras?.getInt(ACTIVITY_ID_KEY) ?: hashCode() + ProcessLifecycleOwner.get().lifecycle.addObserver(WryLifecycleObserver) + Rust.onActivityCreate(this) + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + Rust.onWindowFocusChanged(this, hasFocus) + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putInt(ACTIVITY_ID_KEY, id) + Rust.onActivitySaveInstanceState() + } + + override fun onPause() { + super.onPause() + if (::mWebView.isInitialized) { + mWebView.onPause() + } + } + + override fun onResume() { + super.onResume() + if (::mWebView.isInitialized) { + mWebView.onResume() + } + } + + override fun onDestroy() { + super.onDestroy() + Rust.onActivityDestroy(this) + Rust.onWebviewDestroy(this, if (::mWebView.isInitialized) { mWebView.id } else { "" }) + } + + override fun onLowMemory() { + super.onLowMemory() + Rust.onActivityLowMemory() + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + Rust.onNewIntent(intent) + } + + fun getAppClass(name: String): Class<*> { + return Class.forName(name) + } + + fun startActivity(cls: Class<*>): Int { + val intent = Intent(this, cls) + val id = kotlin.random.Random.nextInt() + intent.putExtra(ACTIVITY_ID_KEY, id) + startActivity(intent) + return id + } + + {{class-extension}} +} diff --git a/desktop/src-tauri/vendor/wry/src/android/kotlin/proguard-wry.pro b/desktop/src-tauri/vendor/wry/src/android/kotlin/proguard-wry.pro new file mode 100644 index 00000000000..a0f19c65ab0 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/android/kotlin/proguard-wry.pro @@ -0,0 +1,35 @@ +# Copyright 2020-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +-keep class {{package-unescaped}}.* { + native ; +} + +-keep class {{package-unescaped}}.WryActivity { + public (...); + + void setWebView({{package-unescaped}}.RustWebView); + java.lang.Class getAppClass(...); + int getId(); + java.lang.String getVersion(); + int startActivity(...); +} + +-keep class {{package-unescaped}}.Ipc { + public (...); + + @android.webkit.JavascriptInterface public ; +} + +-keep class {{package-unescaped}}.RustWebView { + public (...); + + void loadUrlMainThread(...); + void loadHTMLMainThread(...); + void evalScript(...); +} + +-keep class {{package-unescaped}}.RustWebChromeClient,{{package-unescaped}}.RustWebViewClient { + public (...); +} diff --git a/desktop/src-tauri/vendor/wry/src/android/main_pipe.rs b/desktop/src-tauri/vendor/wry/src/android/main_pipe.rs new file mode 100644 index 00000000000..52308fd86ab --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/android/main_pipe.rs @@ -0,0 +1,607 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{Error, InitializationScript, RGBA}; +use crossbeam_channel::*; +use jni::{ + errors::Result as JniResult, + objects::{GlobalRef, JMap, JObject, JString}, + JNIEnv, JavaVM, +}; +use once_cell::sync::Lazy; +use std::{ + collections::BTreeMap, + ffi::c_void, + os::unix::prelude::*, + sync::{Arc, Mutex}, +}; + +use super::{find_class, EvalCallback, WebviewId, EVAL_CALLBACKS, EVAL_ID_GENERATOR, PACKAGE}; + +pub type ActivityId = i32; + +static CHANNEL: Lazy<( + Sender<(ActivityId, WebViewMessage)>, + Receiver<(ActivityId, WebViewMessage)>, +)> = Lazy::new(|| bounded(8)); +pub static MAIN_PIPE: Lazy<[OwnedFd; 2]> = Lazy::new(|| { + let mut pipe: [RawFd; 2] = Default::default(); + unsafe { libc::pipe(pipe.as_mut_ptr()) }; + unsafe { pipe.map(|fd| OwnedFd::from_raw_fd(fd)) } +}); + +#[derive(Clone)] +pub struct ActivityProxy { + pub activity: GlobalRef, + pub window_manager: GlobalRef, + pub webview: Option, + pub webchrome_client: GlobalRef, + pub java_vm: *mut c_void, +} + +unsafe impl Send for ActivityProxy {} + +impl ActivityProxy { + pub fn new( + vm: JavaVM, + activity: GlobalRef, + window_manager: GlobalRef, + webchrome_client: GlobalRef, + ) -> Self { + Self { + activity, + window_manager, + webview: None, + webchrome_client, + java_vm: vm.get_java_vm_pointer() as *mut _, + } + } +} + +static ACTIVITY_PROXY: once_cell::sync::Lazy>> = + Lazy::new(|| Mutex::new(BTreeMap::new())); + +pub fn activity_proxy(id: ActivityId) -> Option { + ACTIVITY_PROXY.lock().unwrap().get(&id).cloned() +} + +fn remove_activity_proxy(id: ActivityId) { + ACTIVITY_PROXY.lock().unwrap().remove(&id); +} + +pub fn register_activity_proxy( + vm: JavaVM, + id: ActivityId, + activity: GlobalRef, + window_manager: GlobalRef, + webchrome_client: GlobalRef, +) { + let mut activity_proxy = ACTIVITY_PROXY.lock().unwrap(); + if let Some(proxy) = activity_proxy.get_mut(&id) { + proxy.activity = activity; + proxy.window_manager = window_manager; + proxy.webchrome_client = webchrome_client; + proxy.java_vm = vm.get_java_vm_pointer() as *mut _; + } else { + let proxy = ActivityProxy::new(vm, activity, window_manager, webchrome_client); + activity_proxy.insert(id, proxy.clone()); + } +} + +pub fn activity_id_for_window_manager(window_manager: JObject) -> Option { + for (activity_id, proxy) in ACTIVITY_PROXY.lock().unwrap().iter() { + let vm = unsafe { JavaVM::from_raw(proxy.java_vm.cast()) }.unwrap(); + let mut env = vm.attach_current_thread_as_daemon().unwrap(); + let equals = env + .call_method( + proxy.window_manager.as_obj(), + "equals", + "(Ljava/lang/Object;)Z", + &[(&window_manager).into()], + ) + .and_then(|v| v.z()) + .unwrap_or_default(); + if equals { + return Some(*activity_id); + } + } + None +} + +pub fn first_activity_id() -> Option { + ACTIVITY_PROXY.lock().unwrap().keys().next().cloned() +} + +pub fn get_webview(activity_id: ActivityId) -> Option { + ACTIVITY_PROXY + .lock() + .unwrap() + .get(&activity_id) + .unwrap() + .webview + .as_ref() + .cloned() +} + +pub struct MainPipe<'a> { + pub env: JNIEnv<'a>, +} + +impl<'a> MainPipe<'a> { + pub(crate) fn send(activity_id: ActivityId, message: WebViewMessage) { + let size = std::mem::size_of::(); + if CHANNEL.0.send((activity_id, message)).is_ok() { + unsafe { + libc::write( + MAIN_PIPE[1].as_raw_fd(), + &true as *const _ as *const _, + size, + ) + }; + } + } + + pub fn recv(&mut self) -> JniResult<()> { + if let Ok((activity_id, message)) = CHANNEL.1.recv() { + match message { + WebViewMessage::CreateWebView(attrs) => { + let Some((activity, web_chrome_client)) = + activity_proxy(activity_id).map(|p| (p.activity.clone(), p.webchrome_client.clone())) + else { + #[cfg(debug_assertions)] + eprintln!("no activity found for activity id: {}", activity_id); + return Ok(()); + }; + let CreateWebViewAttributes { + url, + html, + #[cfg(any(debug_assertions, feature = "devtools"))] + devtools, + transparent, + background_color, + headers, + on_webview_created, + autoplay, + user_agent, + initialization_scripts, + id, + javascript_disabled, + .. + } = attrs; + + let string_class = self.env.find_class("java/lang/String")?; + let initialization_scripts_array = self.env.new_object_array( + initialization_scripts.len() as i32, + string_class, + self.env.new_string("")?, + )?; + for (i, init_script) in initialization_scripts.into_iter().enumerate() { + self.env.set_object_array_element( + &initialization_scripts_array, + i as i32, + self.env.new_string(init_script.script)?, + )?; + } + let id = self.env.new_string(id)?; + // Create webview + let rust_webview_class = find_class( + &mut self.env, + &activity, + format!("{}/RustWebView", PACKAGE.get().unwrap()), + )?; + let webview = self.env.new_object( + &rust_webview_class, + "(Landroid/content/Context;[Ljava/lang/String;Ljava/lang/String;)V", + &[ + (&activity).into(), + (&initialization_scripts_array).into(), + (&id).into(), + ], + )?; + // get settings + let web_settings = self + .env + .call_method( + &webview, + "getSettings", + "()Landroid/webkit/WebSettings;", + &[], + )? + .l()?; + // set media autoplay + self.env.call_method( + &web_settings, + "setMediaPlaybackRequiresUserGesture", + "(Z)V", + &[(!autoplay).into()], + )?; + // set user-agent + if let Some(user_agent) = user_agent { + let user_agent = self.env.new_string(user_agent)?; + self.env.call_method( + &web_settings, + "setUserAgentString", + "(Ljava/lang/String;)V", + &[(&user_agent).into()], + )?; + } + + // disable javascript + if javascript_disabled { + self.env.call_method( + &web_settings, + "setJavaScriptEnabled", + "(Z)V", + &[false.into()], + )?; + } + + let webview_class_name = format!("{}/RustWebView", PACKAGE.get().unwrap()); + self.env.call_method( + &activity, + "setWebView", + format!("(L{webview_class_name};)V"), + &[(&webview).into()], + )?; + // Navigation + if let Some(u) = url { + if let Ok(url) = self.env.new_string(u) { + load_url(&mut self.env, &webview, &url, headers, true)?; + } + } else if let Some(h) = html { + if let Ok(html) = self.env.new_string(h) { + load_html(&mut self.env, &webview, &html)?; + } + } + // Enable devtools + #[cfg(any(debug_assertions, feature = "devtools"))] + self.env.call_static_method( + &rust_webview_class, + "setWebContentsDebuggingEnabled", + "(Z)V", + &[devtools.into()], + )?; + if transparent { + set_background_color(&mut self.env, &webview, (0, 0, 0, 0))?; + } else if let Some(color) = background_color { + set_background_color(&mut self.env, &webview, color)?; + } + // Create and set webview client + let client_class_name = format!("{}/RustWebViewClient", PACKAGE.get().unwrap()); + let rust_webview_client_class = + find_class(&mut self.env, &activity, client_class_name.clone())?; + let webview_client = self.env.new_object( + &rust_webview_client_class, + format!("(L{webview_class_name};Landroid/content/Context;)V"), + &[(&webview).into(), (&activity).into()], + )?; + self.env.call_method( + &webview, + "setWebViewClient", + "(Landroid/webkit/WebViewClient;)V", + &[(&webview_client).into()], + )?; + // set webchrome client + self.env.call_method( + &webview, + "setWebChromeClient", + "(Landroid/webkit/WebChromeClient;)V", + &[web_chrome_client.as_obj().into()], + )?; + + // Add javascript interface (IPC) + let ipc_class = find_class( + &mut self.env, + &activity, + format!("{}/Ipc", PACKAGE.get().unwrap()), + )?; + let ipc = self.env.new_object( + ipc_class, + format!("(L{webview_class_name};L{client_class_name};)V"), + &[(&webview).into(), (&webview_client).into()], + )?; + let ipc_str = self.env.new_string("ipc")?; + self.env.call_method( + &webview, + "addJavascriptInterface", + "(Ljava/lang/Object;Ljava/lang/String;)V", + &[(&ipc).into(), (&ipc_str).into()], + )?; + + // Set content view + self.env.call_method( + &activity, + "setContentView", + "(Landroid/view/View;)V", + &[(&webview).into()], + )?; + + if let Some(on_webview_created) = on_webview_created { + if let Err(_e) = on_webview_created(super::Context { + env: &mut self.env, + activity: &activity, + webview: &webview, + }) { + #[cfg(feature = "tracing")] + tracing::warn!("failed to run webview created hook: {_e}"); + } + } + + let webview = self.env.new_global_ref(webview)?; + + ACTIVITY_PROXY + .lock() + .unwrap() + .get_mut(&activity_id) + .unwrap() + .webview + .replace(webview); + } + WebViewMessage::Eval(script, callback) => { + if let Some(webview) = get_webview(activity_id) { + let id = EVAL_ID_GENERATOR.next() as i32; + + #[cfg(feature = "tracing")] + let span = std::sync::Mutex::new(Some(SendEnteredSpan( + tracing::debug_span!("wry::eval").entered(), + ))); + + EVAL_CALLBACKS + .get_or_init(Default::default) + .lock() + .unwrap() + .insert( + id, + Box::new(move |result| { + #[cfg(feature = "tracing")] + span.lock().unwrap().take(); + + if let Some(callback) = &callback { + callback(result); + } + }), + ); + + let s = self.env.new_string(script)?; + self.env.call_method( + webview.as_obj(), + "evalScript", + "(ILjava/lang/String;)V", + &[id.into(), (&s).into()], + )?; + } + } + WebViewMessage::SetBackgroundColor(background_color) => { + if let Some(webview) = get_webview(activity_id) { + set_background_color(&mut self.env, webview.as_obj(), background_color)?; + } + } + WebViewMessage::GetWebViewVersion(tx) => { + if let Some(activity) = activity_proxy(activity_id).map(|p| p.activity.clone()) { + match self + .env + .call_method(activity, "getVersion", "()Ljava/lang/String;", &[]) + .and_then(|v| v.l()) + .and_then(|s| { + let s = JString::from(s); + self + .env + .get_string(&s) + .map(|v| v.to_string_lossy().to_string()) + }) { + Ok(version) => { + tx.send(Ok(version)).unwrap(); + } + Err(e) => tx.send(Err(e.into())).unwrap(), + } + } else { + tx.send(Err(Error::ActivityNotFound)).unwrap(); + } + } + WebViewMessage::GetUrl(tx) => { + if let Some(webview) = get_webview(activity_id) { + let url = self + .env + .call_method(webview.as_obj(), "getUrl", "()Ljava/lang/String;", &[]) + .and_then(|v| v.l()) + .and_then(|s| { + let s = JString::from(s); + self + .env + .get_string(&s) + .map(|v| v.to_string_lossy().to_string()) + }) + .unwrap_or_default(); + + tx.send(url).unwrap() + } + } + WebViewMessage::Jni(f) => { + match activity_proxy(activity_id).map(|p| (p.activity.clone(), p.webview.clone())) { + Some((activity, Some(webview))) => { + f(&mut self.env, &activity, webview.as_obj()); + } + Some((activity, None)) => { + f(&mut self.env, &activity, &JObject::null()); + } + _ => { + f(&mut self.env, &JObject::null(), &JObject::null()); + } + } + } + WebViewMessage::LoadUrl(url, headers) => { + if let Some(webview) = get_webview(activity_id) { + let url = self.env.new_string(url)?; + load_url(&mut self.env, webview.as_obj(), &url, headers, false)?; + } + } + WebViewMessage::ClearAllBrowsingData => { + if let Some(webview) = get_webview(activity_id) { + self + .env + .call_method(webview, "clearAllBrowsingData", "()V", &[])?; + } + } + WebViewMessage::LoadHtml(html) => { + if let Some(webview) = get_webview(activity_id) { + let html = self.env.new_string(html)?; + load_html(&mut self.env, webview.as_obj(), &html)?; + } + } + WebViewMessage::Reload => { + if let Some(webview) = get_webview(activity_id) { + reload(&mut self.env, webview.as_obj())?; + } + } + WebViewMessage::GetCookies(tx, url) => { + if let Some(webview) = get_webview(activity_id) { + let url = self.env.new_string(url)?; + let cookies = self + .env + .call_method( + webview, + "getCookies", + "(Ljava/lang/String;)Ljava/lang/String;", + &[(&url).into()], + ) + .and_then(|v| v.l()) + .and_then(|s| { + let s = JString::from(s); + self + .env + .get_string(&s) + .map(|v| v.to_string_lossy().to_string()) + }) + .unwrap_or_default(); + + tx.send( + cookies + .split("; ") + .flat_map(|c| cookie::Cookie::parse(c.to_string())) + .collect(), + ) + .unwrap(); + } + } + WebViewMessage::OnDestroy { + activity_id, + webview_id, + is_changing_configurations, + } => { + // keep our webview references (callbacks etc) alive if the activity is going to be recreated due to configuration changes + // e.g. rotation, multi-window mode change, etc + if !is_changing_configurations { + super::destroy_webview(activity_id, &webview_id); + remove_activity_proxy(activity_id); + } + } + } + } + Ok(()) + } +} + +fn load_url<'a>( + env: &mut JNIEnv<'a>, + webview: &JObject<'a>, + url: &JString<'a>, + headers: Option, + main_thread: bool, +) -> JniResult<()> { + let function = if main_thread { + "loadUrlMainThread" + } else { + "loadUrl" + }; + if let Some(headers) = headers { + let obj = env.new_object("java/util/HashMap", "()V", &[])?; + let headers_map = { + let headers_map = JMap::from_env(env, &obj)?; + for (name, value) in headers.iter() { + let key = env.new_string(name)?; + let value = env.new_string(value.to_str().unwrap_or_default())?; + headers_map.put(env, &key, &value)?; + } + headers_map + }; + env.call_method( + webview, + function, + "(Ljava/lang/String;Ljava/util/Map;)V", + &[url.into(), (&headers_map).into()], + )?; + } else { + env.call_method(webview, function, "(Ljava/lang/String;)V", &[url.into()])?; + } + Ok(()) +} + +fn load_html<'a>(env: &mut JNIEnv<'a>, webview: &JObject<'a>, html: &JString<'a>) -> JniResult<()> { + env.call_method( + webview, + "loadHTMLMainThread", + "(Ljava/lang/String;)V", + &[html.into()], + )?; + Ok(()) +} + +fn reload<'a>(env: &mut JNIEnv<'a>, webview: &JObject<'a>) -> JniResult<()> { + env.call_method(webview, "reload", "()V", &[])?; + Ok(()) +} + +fn set_background_color<'a>( + env: &mut JNIEnv<'a>, + webview: &JObject<'a>, + (r, g, b, a): RGBA, +) -> JniResult<()> { + let color = (a as i32) << 24 | (r as i32) << 16 | (g as i32) << 8 | (b as i32); + env.call_method(webview, "setBackgroundColor", "(I)V", &[color.into()])?; + Ok(()) +} + +pub(crate) enum WebViewMessage { + CreateWebView(CreateWebViewAttributes), + Eval(String, Option), + SetBackgroundColor(RGBA), + GetWebViewVersion(Sender>), + GetUrl(Sender), + GetCookies(Sender>>, String), + Jni(Box), + LoadUrl(String, Option), + LoadHtml(String), + Reload, + ClearAllBrowsingData, + OnDestroy { + activity_id: ActivityId, + webview_id: WebviewId, + is_changing_configurations: bool, + }, +} + +#[derive(Clone)] +pub(crate) struct CreateWebViewAttributes { + pub id: String, + pub url: Option, + pub html: Option, + #[cfg(any(debug_assertions, feature = "devtools"))] + pub devtools: bool, + pub transparent: bool, + pub background_color: Option, + pub headers: Option, + pub autoplay: bool, + pub on_webview_created: + Option JniResult<()> + Send + Sync + 'static>>, + pub user_agent: Option, + pub initialization_scripts: Vec, + pub javascript_disabled: bool, +} + +// SAFETY: only use this when you are sure the span will be dropped on the same thread it was entered +#[cfg(feature = "tracing")] +struct SendEnteredSpan(tracing::span::EnteredSpan); + +#[cfg(feature = "tracing")] +unsafe impl Send for SendEnteredSpan {} diff --git a/desktop/src-tauri/vendor/wry/src/android/mod.rs b/desktop/src-tauri/vendor/wry/src/android/mod.rs new file mode 100644 index 00000000000..8f44c704094 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/android/mod.rs @@ -0,0 +1,530 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::{PageLoadEvent, WebViewAttributes, RGBA}; +use crate::{ + custom_protocol_workaround, inject_initialization_scripts::inject_scripts_into_html, Error, + RequestAsyncResponder, Result, +}; +use crossbeam_channel::*; + +use http::{Request, Response as HttpResponse}; +use jni::{ + errors::Result as JniResult, + objects::{GlobalRef, JClass, JObject}, + JNIEnv, +}; +use ndk::looper::ThreadLooper; +use once_cell::sync::{Lazy, OnceCell}; +use raw_window_handle::HasWindowHandle; +use std::{ + borrow::Cow, + collections::HashMap, + sync::{mpsc::channel, Mutex}, + time::Duration, +}; + +pub(crate) mod binding; +mod main_pipe; +use main_pipe::{ + activity_id_for_window_manager, first_activity_id, register_activity_proxy, ActivityId, + CreateWebViewAttributes, MainPipe, WebViewMessage, +}; + +use crate::util::Counter; + +static COUNTER: Counter = Counter::new(); +const MAIN_PIPE_TIMEOUT: Duration = Duration::from_secs(10); + +pub struct Context<'a, 'b> { + pub env: &'a mut JNIEnv<'b>, + pub activity: &'a JObject<'b>, + pub webview: &'a JObject<'b>, +} + +type WebviewId = String; + +macro_rules! define_static_handlers { + ($($key: ident, $var:ident = $type_name:ident);+ $(;)?) => { + $(static $var: Lazy>> = Lazy::new(||Mutex::new(HashMap::new()));)* + }; + + ($($var:ident = $type_name:ident { $($fields:ident:$types:ty),+ $(,)? });+ $(;)?) => { + $( + static $var: Lazy>> = Lazy::new(||Mutex::new(HashMap::new())); + pub struct $type_name { + $($fields: $types,)* + } + impl $type_name { + pub fn new($($fields: $types,)*) -> Self { + Self { + $($fields,)* + } + } + } + unsafe impl Send for $type_name {} + unsafe impl Sync for $type_name {})* + }; +} + +define_static_handlers! { + IPC = UnsafeIpc { handler: Box)> }; + REQUEST_HANDLER = UnsafeRequestHandler { handler: Box>, bool) -> Option>>> }; + TITLE_CHANGE_HANDLER = UnsafeTitleHandler { handler: Box }; + URL_LOADING_OVERRIDE = UnsafeUrlLoadingOverride { handler: Box bool> }; + ON_LOAD_HANDLER = UnsafeOnPageLoadHandler { handler: Box }; +} +define_static_handlers! { + WebviewId, WITH_ASSET_LOADER = bool; + WebviewId, ASSET_LOADER_DOMAIN = String; + ActivityId, WEBVIEW_ATTRIBUTES = CreateWebViewAttributes; +} + +pub(crate) static PACKAGE: OnceCell = OnceCell::new(); + +type EvalCallback = Box; + +pub static EVAL_ID_GENERATOR: Counter = Counter::new(); +pub static EVAL_CALLBACKS: OnceCell>> = OnceCell::new(); + +pub fn destroy_webview(activity_id: ActivityId, webview_id: &WebviewId) { + WEBVIEW_ATTRIBUTES.lock().unwrap().remove(&activity_id); + IPC.lock().unwrap().remove(webview_id); + REQUEST_HANDLER.lock().unwrap().remove(webview_id); + TITLE_CHANGE_HANDLER.lock().unwrap().remove(webview_id); + URL_LOADING_OVERRIDE.lock().unwrap().remove(webview_id); + ON_LOAD_HANDLER.lock().unwrap().remove(webview_id); + WITH_ASSET_LOADER.lock().unwrap().remove(webview_id); + ASSET_LOADER_DOMAIN.lock().unwrap().remove(webview_id); +} + +/// Sets up the necessary logic for wry to be able to create the webviews later. +/// +/// This function must be run on the thread where the [`JNIEnv`] is registered and the looper is local, +/// hence the requirement for a [`ThreadLooper`]. +pub unsafe fn android_setup( + package: &str, + mut env: JNIEnv, + _looper: &ThreadLooper, + activity: GlobalRef, +) { + PACKAGE.get_or_init(move || package.to_string()); + + let vm = env.get_java_vm().unwrap(); + + let activity_id = env + .call_method(activity.as_obj(), "getId", "()I", &[]) + .unwrap() + .i() + .unwrap(); + + let window_manager = env + .call_method( + &activity, + "getWindowManager", + "()Landroid/view/WindowManager;", + &[], + ) + .unwrap() + .l() + .unwrap(); + let window_manager = env.new_global_ref(window_manager).unwrap(); + + // we must create the WebChromeClient here because it calls `registerForActivityResult`, + // which gives an `LifecycleOwners must call register before they are STARTED.` error when called outside the onCreate hook + let rust_webchrome_client_class = find_class( + &mut env, + activity.as_obj(), + format!("{}/RustWebChromeClient", PACKAGE.get().unwrap()), + ) + .unwrap(); + let webchrome_client = env + .new_object( + &rust_webchrome_client_class, + format!("(L{}/WryActivity;)V", PACKAGE.get().unwrap()), + &[activity.as_obj().into()], + ) + .unwrap(); + + let webchrome_client = env.new_global_ref(webchrome_client).unwrap(); + + register_activity_proxy(vm, activity_id, activity, window_manager, webchrome_client); + + if let Some(webview_attributes) = WEBVIEW_ATTRIBUTES.lock().unwrap().get(&activity_id) { + MainPipe::send( + activity_id, + WebViewMessage::CreateWebView(webview_attributes.clone()), + ); + } +} + +pub(crate) struct InnerWebView { + id: String, + pub activity_id: ActivityId, +} + +impl InnerWebView { + pub fn new_as_child( + window: &impl HasWindowHandle, + attributes: WebViewAttributes, + pl_attrs: super::PlatformSpecificWebViewAttributes, + ) -> Result { + Self::new(window, attributes, pl_attrs) + } + + pub fn new( + window: &impl HasWindowHandle, + attributes: WebViewAttributes, + pl_attrs: super::PlatformSpecificWebViewAttributes, + ) -> Result { + let window_manager = match window.window_handle()?.as_raw() { + raw_window_handle::RawWindowHandle::AndroidNdk(window_manager) => { + window_manager.a_native_window + } + _ => return Err(Error::UnsupportedWindowHandle), + }; + let window_manager = unsafe { JObject::from_raw(window_manager.as_ptr().cast()) }; + let activity_id = + activity_id_for_window_manager(window_manager).expect("no available activity"); + let WebViewAttributes { + url, + html, + initialization_scripts, + ipc_handler, + #[cfg(any(debug_assertions, feature = "devtools"))] + devtools, + custom_protocols, + background_color, + transparent, + headers, + autoplay, + user_agent, + javascript_disabled, + .. + } = attributes; + + let super::PlatformSpecificWebViewAttributes { + on_webview_created, + with_asset_loader, + asset_loader_domain, + https_scheme, + } = pl_attrs; + + let http_or_https = if https_scheme { "https" } else { "http" }; + + let url = if let Some(mut url) = url { + if let Some((protocol, _)) = url.split_once("://") { + if custom_protocols.contains_key(protocol) { + url = custom_protocol_workaround::apply_uri_work_around(&url, http_or_https, protocol) + } + } + + Some(url) + } else { + None + }; + + let id = attributes + .id + .map(|id| id.to_string()) + .unwrap_or_else(|| COUNTER.next().to_string()); + + WITH_ASSET_LOADER + .lock() + .unwrap() + .insert(id.clone(), with_asset_loader); + if let Some(domain) = asset_loader_domain { + ASSET_LOADER_DOMAIN + .lock() + .unwrap() + .insert(id.clone(), domain); + } + + let initialization_scripts_ = initialization_scripts.clone(); + REQUEST_HANDLER + .lock() + .unwrap() + .insert( + id.clone(), + UnsafeRequestHandler::new(Box::new( + move |webview_id: &str, mut request, is_document_start_script_enabled| { + let uri = request.uri().to_string(); + if let Some((custom_protocol, custom_protocol_handler)) = + custom_protocols.iter().find(|(protocol, _)| { + custom_protocol_workaround::is_work_around_uri(&uri, http_or_https, protocol) + }) + { + let uri_res = custom_protocol_workaround::revert_uri_work_around( + &uri, + http_or_https, + custom_protocol, + ) + .parse(); + + if let Ok(uri) = uri_res { + *request.uri_mut() = uri; + } + + let (tx, rx) = channel(); + let initialization_scripts = initialization_scripts_.clone(); + let responder: Box>)> = + Box::new(move |mut response| { + if !is_document_start_script_enabled { + #[cfg(feature = "tracing")] + tracing::info!("`addDocumentStartJavaScript` is not supported; injecting initialization scripts via custom protocol handler"); + response = inject_scripts_into_html(response, &initialization_scripts); + } + let _ = tx.send(response); + }); + + (custom_protocol_handler)(webview_id, request, RequestAsyncResponder { responder }); + // 3x the timeout while we monitor https://github.com/tauri-apps/wry/issues/1551 + // TODO: Remove timeout + return rx.recv_timeout(MAIN_PIPE_TIMEOUT * 3).inspect_err(|e| {eprintln!("custom protocol timed out: {e}");}).ok(); + } + None + }, + ))); + + if let Some(i) = ipc_handler { + IPC + .lock() + .unwrap() + .insert(id.clone(), UnsafeIpc::new(Box::new(i))); + } + + if let Some(i) = attributes.document_title_changed_handler { + TITLE_CHANGE_HANDLER + .lock() + .unwrap() + .insert(id.clone(), UnsafeTitleHandler::new(i)); + } + + if let Some(i) = attributes.navigation_handler { + URL_LOADING_OVERRIDE + .lock() + .unwrap() + .insert(id.clone(), UnsafeUrlLoadingOverride::new(i)); + } + + if let Some(h) = attributes.on_page_load_handler { + ON_LOAD_HANDLER + .lock() + .unwrap() + .insert(id.clone(), UnsafeOnPageLoadHandler::new(h)); + } + + let attributes = CreateWebViewAttributes { + id: id.clone(), + url, + html, + #[cfg(any(debug_assertions, feature = "devtools"))] + devtools, + background_color, + transparent, + headers, + on_webview_created, + autoplay, + user_agent, + initialization_scripts, + javascript_disabled, + }; + + WEBVIEW_ATTRIBUTES + .lock() + .unwrap() + .insert(activity_id, attributes.clone()); + + MainPipe::send(activity_id, WebViewMessage::CreateWebView(attributes)); + + Ok(Self { id, activity_id }) + } + + pub fn print(&self) -> crate::Result<()> { + Ok(()) + } + + pub fn id(&self) -> crate::WebViewId<'_> { + &self.id + } + + pub fn url(&self) -> crate::Result { + let (tx, rx) = bounded(1); + MainPipe::send(self.activity_id, WebViewMessage::GetUrl(tx)); + rx.recv_timeout(MAIN_PIPE_TIMEOUT).map_err(Into::into) + } + + pub fn eval(&self, js: &str, callback: Option) -> Result<()> { + MainPipe::send( + self.activity_id, + WebViewMessage::Eval( + js.into(), + callback.map(|c| Box::new(c) as Box), + ), + ); + Ok(()) + } + + #[cfg(any(debug_assertions, feature = "devtools"))] + pub fn open_devtools(&self) {} + + #[cfg(any(debug_assertions, feature = "devtools"))] + pub fn close_devtools(&self) {} + + #[cfg(any(debug_assertions, feature = "devtools"))] + pub fn is_devtools_open(&self) -> bool { + false + } + + pub fn zoom(&self, _scale_factor: f64) -> Result<()> { + Ok(()) + } + + pub fn set_background_color(&self, background_color: RGBA) -> Result<()> { + MainPipe::send( + self.activity_id, + WebViewMessage::SetBackgroundColor(background_color), + ); + Ok(()) + } + + pub fn load_url(&self, url: &str) -> Result<()> { + MainPipe::send( + self.activity_id, + WebViewMessage::LoadUrl(url.to_string(), None), + ); + Ok(()) + } + + pub fn load_url_with_headers(&self, url: &str, headers: http::HeaderMap) -> Result<()> { + MainPipe::send( + self.activity_id, + WebViewMessage::LoadUrl(url.to_string(), Some(headers)), + ); + Ok(()) + } + + pub fn load_html(&self, html: &str) -> Result<()> { + MainPipe::send(self.activity_id, WebViewMessage::LoadHtml(html.to_string())); + Ok(()) + } + + pub fn reload(&self) -> Result<()> { + MainPipe::send(self.activity_id, WebViewMessage::Reload); + Ok(()) + } + + pub fn clear_all_browsing_data(&self) -> Result<()> { + MainPipe::send(self.activity_id, WebViewMessage::ClearAllBrowsingData); + Ok(()) + } + + pub fn cookies_for_url(&self, url: &str) -> Result>> { + let (tx, rx) = bounded(1); + MainPipe::send( + self.activity_id, + WebViewMessage::GetCookies(tx, url.to_string()), + ); + rx.recv_timeout(MAIN_PIPE_TIMEOUT).map_err(Into::into) + } + + pub fn set_cookie(&self, #[allow(unused)] cookie: &cookie::Cookie<'_>) -> Result<()> { + // Unsupported + Ok(()) + } + + pub fn delete_cookie(&self, #[allow(unused)] cookie: &cookie::Cookie<'_>) -> Result<()> { + // Unsupported + Ok(()) + } + + pub fn cookies(&self) -> Result>> { + Ok(Vec::new()) + } + + pub fn bounds(&self) -> Result { + Ok(crate::Rect::default()) + } + + pub fn set_bounds(&self, _bounds: crate::Rect) -> Result<()> { + // Unsupported + Ok(()) + } + + pub fn set_visible(&self, _visible: bool) -> Result<()> { + // Unsupported + Ok(()) + } + + pub fn focus(&self) -> Result<()> { + // Unsupported + Ok(()) + } + + pub fn focus_parent(&self) -> Result<()> { + // Unsupported + Ok(()) + } +} + +#[derive(Clone, Copy)] +pub struct JniHandle { + pub(crate) activity_id: ActivityId, +} + +impl JniHandle { + /// Execute jni code on the thread of the webview. + /// Provided function will be provided with the jni evironment, Android activity and WebView + pub fn exec(&self, func: F) + where + F: FnOnce(&mut JNIEnv, &JObject, &JObject) + Send + 'static, + { + MainPipe::send(self.activity_id, WebViewMessage::Jni(Box::new(func))); + } +} + +pub fn platform_webview_version() -> Result { + let (tx, rx) = bounded(1); + let activity_id = loop { + match first_activity_id() { + Some(id) => break id, + None => { + std::thread::sleep(Duration::from_millis(100)); + } + } + }; + MainPipe::send(activity_id, WebViewMessage::GetWebViewVersion(tx)); + rx.recv_timeout(MAIN_PIPE_TIMEOUT)? +} + +/// Finds a class in the project scope. +pub fn find_class<'a>( + env: &mut JNIEnv<'a>, + activity: &JObject<'_>, + name: String, +) -> JniResult> { + let class_name = env.new_string(name.replace('/', "."))?; + let my_class = env + .call_method( + activity, + "getAppClass", + "(Ljava/lang/String;)Ljava/lang/Class;", + &[(&class_name).into()], + )? + .l()?; + Ok(my_class.into()) +} + +/// Dispatch a closure to run on the Android context. +/// +/// The closure takes the JNI env, the Android activity instance and the possibly null webview. +pub fn dispatch(func: F) +where + F: FnOnce(&mut JNIEnv, &JObject, &JObject) + Send + 'static, +{ + MainPipe::send( + first_activity_id().expect("no available activity"), + WebViewMessage::Jni(Box::new(func)), + ); +} diff --git a/desktop/src-tauri/vendor/wry/src/custom_protocol_workaround.rs b/desktop/src-tauri/vendor/wry/src/custom_protocol_workaround.rs new file mode 100644 index 00000000000..122b1341dd8 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/custom_protocol_workaround.rs @@ -0,0 +1,56 @@ +//! - WebView2 supports non-standard protocols only on Windows 10+, so we have to use a workaround. +//! See +//! - On Android, there's no API for registering custom protocols, so this workaround is also used. +//! +//! The process looks like this: +//! +//! 1. Use [`apply_uri_work_around`] to convert the URI we want to navigate to +//! 2. Intercept http(s) requests, test the request URI against [`is_work_around_uri`], +//! if it matches, we apply [`revert_uri_work_around`] to the URI and feed it to the custom protocol handler + +/// If the URI is a work around URI for this protocol which starts with `{http_or_https}://{protocol}.` +pub fn is_work_around_uri(uri: &str, http_or_https: &str, protocol: &str) -> bool { + uri + .strip_prefix(http_or_https) + .and_then(|rest| rest.strip_prefix("://")) + .and_then(|rest| rest.strip_prefix(protocol)) + .and_then(|rest| rest.strip_prefix(".")) + .is_some() +} + +/// Conveting `{protocol}://localhost/abc` to `{http_or_https}://{protocol}.localhost/abc` +pub fn apply_uri_work_around(uri: &str, http_or_https: &str, protocol: &str) -> String { + uri.replace( + &original_uri_prefix(protocol), + &work_around_uri_prefix(http_or_https, protocol), + ) +} + +/// Conveting `{http_or_https}://{protocol}.localhost/abc` back to `{protocol}://localhost/abc` +pub fn revert_uri_work_around(uri: &str, http_or_https: &str, protocol: &str) -> String { + uri.replace( + &work_around_uri_prefix(http_or_https, protocol), + &original_uri_prefix(protocol), + ) +} + +pub fn original_uri_prefix(protocol: &str) -> String { + format!("{protocol}://") +} + +pub fn work_around_uri_prefix(http_or_https: &str, protocol: &str) -> String { + format!("{http_or_https}://{protocol}.") +} + +#[cfg(test)] +mod tests { + use super::is_work_around_uri; + + #[test] + fn checks_if_custom_protocol_uri() { + let scheme = "http"; + let uri = "http://wry.localhost/path/to/page"; + assert!(is_work_around_uri(uri, scheme, "wry")); + assert!(!is_work_around_uri(uri, scheme, "asset")); + } +} diff --git a/desktop/src-tauri/vendor/wry/src/error.rs b/desktop/src-tauri/vendor/wry/src/error.rs new file mode 100644 index 00000000000..959899311e4 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/error.rs @@ -0,0 +1,80 @@ +/// Convenient type alias of Result type for wry. +pub type Result = std::result::Result; + +/// Errors returned by wry. +#[non_exhaustive] +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[cfg(gtk)] + #[error(transparent)] + GlibError(#[from] gtk::glib::Error), + #[cfg(gtk)] + #[error(transparent)] + GlibBoolError(#[from] gtk::glib::BoolError), + #[cfg(gtk)] + #[error("Fail to fetch security manager")] + MissingManager, + #[cfg(gtk)] + #[error("Couldn't find X11 Display")] + X11DisplayNotFound, + #[cfg(all(gtk, feature = "x11"))] + #[error(transparent)] + XlibError(#[from] x11_dl::error::OpenError), + #[error("Failed to initialize the script")] + InitScriptError, + #[error("Bad RPC request: {0} ((1))")] + RpcScriptError(String, String), + #[error(transparent)] + NulError(#[from] std::ffi::NulError), + #[error(transparent)] + ReceiverError(#[from] std::sync::mpsc::RecvError), + #[cfg(target_os = "android")] + #[error(transparent)] + ReceiverTimeoutError(#[from] crossbeam_channel::RecvTimeoutError), + #[error(transparent)] + SenderError(#[from] std::sync::mpsc::SendError), + #[error("Failed to send the message")] + MessageSender, + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[cfg(target_os = "windows")] + #[error("WebView2 error: {0}")] + WebView2Error(webview2_com::Error), + #[error(transparent)] + HttpError(#[from] http::Error), + #[error("Infallible error, something went really wrong: {0}")] + Infallible(#[from] std::convert::Infallible), + #[cfg(target_os = "android")] + #[error(transparent)] + JniError(#[from] jni::errors::Error), + #[error("Failed to create proxy endpoint")] + ProxyEndpointCreationFailed, + #[error(transparent)] + WindowHandleError(#[from] raw_window_handle::HandleError), + #[error("the window handle kind is not supported")] + UnsupportedWindowHandle, + #[error(transparent)] + Utf8Error(#[from] std::str::Utf8Error), + #[cfg(target_os = "android")] + #[error(transparent)] + CrossBeamRecvError(#[from] crossbeam_channel::RecvError), + #[error("not on the main thread")] + NotMainThread, + #[error("Custom protocol task is invalid.")] + CustomProtocolTaskInvalid, + #[error("Failed to register URL scheme: {0}, could be due to invalid URL scheme or the scheme is already registered.")] + UrlSchemeRegisterError(String), + #[error("Duplicate custom protocol '{0}' registered on the WebViewBuilder")] + DuplicateCustomProtocol(String), + #[error("Duplicate custom protocol '{0}' registered on the same web context on Linux")] + ContextDuplicateCustomProtocol(String), + #[error(transparent)] + #[cfg(any(target_os = "macos", target_os = "ios"))] + UrlParse(#[from] url::ParseError), + #[cfg(any(target_os = "macos", target_os = "ios"))] + #[error("data store is currently opened")] + DataStoreInUse, + #[cfg(target_os = "android")] + #[error("Activity not found")] + ActivityNotFound, +} diff --git a/desktop/src-tauri/vendor/wry/src/inject_initialization_scripts.rs b/desktop/src-tauri/vendor/wry/src/inject_initialization_scripts.rs new file mode 100644 index 00000000000..61636d20432 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/inject_initialization_scripts.rs @@ -0,0 +1,225 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! This is an internal implementation detail used by the Android backend to inject +//! initialization scripts when `addDocumentStartJavaScript` is not supported. + +use base64::{prelude::BASE64_STANDARD, Engine}; +use dom_query::Document; +use http::{ + header::{HeaderValue, CONTENT_SECURITY_POLICY, CONTENT_TYPE}, + Response as HttpResponse, +}; +use sha2::{Digest, Sha256}; +use std::borrow::Cow; + +use crate::InitializationScript; + +pub fn inject_scripts_into_html( + mut response: HttpResponse>, + scripts: &[InitializationScript], +) -> HttpResponse> { + if scripts.is_empty() { + return response; + } + + let should_inject_scripts = response + .headers() + .get(CONTENT_TYPE) + // Content-Type must begin with the media type, but is case-insensitive. + // It may also be followed by any number of semicolon-delimited key value pairs. + // We don't care about these here. + // source: https://httpwg.org/specs/rfc9110.html#rfc.section.8.3.1 + .and_then(|content_type| content_type.to_str().ok()) + .map(|content_type_str| content_type_str.to_lowercase().starts_with("text/html")) + .unwrap_or_default(); + + if !should_inject_scripts { + return response; + } + + let document = Document::from(String::from_utf8_lossy(response.body()).as_ref()); + let csp = response.headers_mut().get_mut(CONTENT_SECURITY_POLICY); + + // Get or create head element + let head = document.head().unwrap_or_else(|| { + let html = document.html_root(); + let head = document.tree.new_element("head"); + html.prepend_child(&head); + head + }); + + // Iterate in reverse order since we are prepending each script to the head tag + let mut hashes = Vec::new(); + for script in scripts.iter().rev().map(|s| &s.script) { + let script_tag = document.tree.new_element("script"); + script_tag.set_text(script.as_str()); + head.prepend_child(&script_tag); + if csp.is_some() { + hashes.push(hash_script(script)); + } + } + + if let Some(csp) = csp { + let csp_string = csp.to_str().unwrap().to_string(); + let csp_string = if csp_string.contains("script-src") { + csp_string.replace("script-src", &format!("script-src {}", hashes.join(" "))) + } else { + format!("{csp_string} script-src {}", hashes.join(" ")) + }; + *csp = HeaderValue::from_str(&csp_string).unwrap(); + } + + *response.body_mut() = Cow::Owned(document.html().as_bytes().to_vec()); + response +} + +fn hash_script(script: &str) -> String { + let mut hasher = Sha256::new(); + hasher.update(script); + let hash = hasher.finalize(); + format!("'sha256-{}'", BASE64_STANDARD.encode(hash)) +} + +#[cfg(test)] +mod tests { + use super::*; + use http::StatusCode; + + #[test] + fn test_no_scripts_returns_original_response() { + let body = "Test"; + + let result = run(body, "text/html", vec![]); + + assert_eq!(result, body); + } + + #[test] + fn test_non_html_response_not_modified() { + let body = r#"{"key": "value"}"#; + let scripts = vec!["console.log('test');".to_string()]; + + let result = run(body, "application/json", scripts); + + assert_eq!(result, body); + } + + #[test] + fn test_inject_single_script() { + let body = "Content"; + let scripts = vec!["console.log('injected');".to_string()]; + + let result = run(body, "text/html", scripts); + + assert_eq!( + result, + "Content" + ); + } + + #[test] + fn test_inject_multiple_scripts() { + let body = "Content"; + let scripts = vec![ + "var first = 1;".to_owned(), + "let second = 2;".to_owned(), + "const third = 3;".to_owned(), + "window.test = () => console.log('test');".to_owned(), + ]; + + let result = run(body, "text/html", scripts); + + assert_eq!( + result, + "Content" + ); + } + + #[test] + fn test_inject_script_creates_head_if_missing() { + let body = "Content"; + let scripts = vec!["console.log('test');".to_string()]; + + let result = run(body, "text/html", scripts); + + assert_eq!( + result, + "Content" + ); + } + + #[test] + fn test_inject_script_creates_html_structure_if_missing() { + let body = "Just some text"; + let scripts = vec!["console.log('test');".to_string()]; + + let result = run(body, "text/html", scripts); + + assert_eq!( + result, + "Just some text" + ); + } + + #[test] + fn test_csp_header_updated_with_script_hashes() { + let body = "Content"; + let mut response = create_response(body, "text/html"); + response.headers_mut().insert( + CONTENT_SECURITY_POLICY, + HeaderValue::from_static("default-src 'self'"), + ); + + let script_code = "console.log('test');"; + let scripts = vec![script_code.to_string()]; + + let scripts: Vec = scripts + .into_iter() + .map(|script| InitializationScript { + script, + for_main_frame_only: true, + }) + .collect(); + let result = inject_scripts_into_html(response, &scripts); + let result_body = String::from_utf8_lossy(result.body()).to_string(); + let csp = result.headers().get(CONTENT_SECURITY_POLICY).unwrap(); + let csp_str = csp.to_str().unwrap(); + + assert_eq!( + result_body, + "Content" + ); + assert_eq!( + csp_str, + "default-src 'self' script-src 'sha256-3x8DE279hr8o/Aq0dEdH4WApIwn5rbRKhugPzn6Bofw='" + ); + } + + fn create_response(body: &str, content_type: &'static str) -> HttpResponse> { + let mut response = HttpResponse::builder() + .status(StatusCode::OK) + .body(Cow::Owned(body.as_bytes().to_vec())) + .unwrap(); + response + .headers_mut() + .insert(CONTENT_TYPE, HeaderValue::from_static(content_type)); + + response + } + + /// Helper function to create a response, inject scripts, and return the body as a string + fn run(body: &str, content_type: &'static str, scripts: Vec) -> String { + let response = create_response(body, content_type); + let scripts: Vec = scripts + .into_iter() + .map(|script| InitializationScript { + script, + for_main_frame_only: true, + }) + .collect(); + let result = inject_scripts_into_html(response, &scripts); + String::from_utf8_lossy(result.body()).to_string() + } +} diff --git a/desktop/src-tauri/vendor/wry/src/lib.rs b/desktop/src-tauri/vendor/wry/src/lib.rs new file mode 100644 index 00000000000..38fbc9e2a80 --- /dev/null +++ b/desktop/src-tauri/vendor/wry/src/lib.rs @@ -0,0 +1,2514 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//!

WRY Webview Rendering library

+//! +//! [![](https://img.shields.io/crates/v/wry?style=flat-square)](https://crates.io/crates/wry) [![](https://img.shields.io/docsrs/wry?style=flat-square)](https://docs.rs/wry/) +//! [![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg)](https://opencollective.com/tauri) +//! [![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S) +//! [![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app) +//! [![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) +//! [![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri) +//! +//! Wry is a cross-platform WebView rendering library. +//! +//! The webview requires a running event loop and a window type that implements [`HasWindowHandle`], +//! or a gtk container widget if you need to support X11 and Wayland. +//! You can use a windowing library like [`tao`] or [`winit`]. +//! +//! ## Examples +//! +//! This example leverages the [`HasWindowHandle`] and supports Windows, macOS, iOS, Android and Linux (X11 Only). +//! See the following example using [`winit`]: +//! +//! ```no_run +//! # use wry::{WebViewBuilder, raw_window_handle}; +//! # use winit::{application::ApplicationHandler, event::WindowEvent, event_loop::{ActiveEventLoop, EventLoop}, window::{Window, WindowId}}; +//! #[derive(Default)] +//! struct App { +//! window: Option, +//! webview: Option, +//! } +//! +//! impl ApplicationHandler for App { +//! fn resumed(&mut self, event_loop: &ActiveEventLoop) { +//! let window = event_loop.create_window(Window::default_attributes()).unwrap(); +//! let webview = WebViewBuilder::new() +//! .with_url("https://tauri.app") +//! .build(&window) +//! .unwrap(); +//! +//! self.window = Some(window); +//! self.webview = Some(webview); +//! } +//! +//! fn window_event(&mut self, _event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {} +//! } +//! +//! let event_loop = EventLoop::new().unwrap(); +//! let mut app = App::default(); +//! event_loop.run_app(&mut app).unwrap(); +//! ``` +//! +//! If you also want to support Wayland too, then we recommend you use [`WebViewBuilderExtUnix::new_gtk`] on Linux. +//! See the following example using [`tao`]: +//! +//! ```no_run +//! # use wry::WebViewBuilder; +//! # use tao::{window::WindowBuilder, event_loop::EventLoop}; +//! # #[cfg(target_os = "linux")] +//! # use tao::platform::unix::WindowExtUnix; +//! # #[cfg(target_os = "linux")] +//! # use wry::WebViewBuilderExtUnix; +//! let event_loop = EventLoop::new(); +//! let window = WindowBuilder::new().build(&event_loop).unwrap(); +//! +//! let builder = WebViewBuilder::new().with_url("https://tauri.app"); +//! +//! #[cfg(not(target_os = "linux"))] +//! let webview = builder.build(&window).unwrap(); +//! #[cfg(target_os = "linux")] +//! let webview = builder.build_gtk(window.gtk_window()).unwrap(); +//! ``` +//! +//! ## Child webviews +//! +//! You can use [`WebViewBuilder::build_as_child`] to create the webview as a child inside another window. This is supported on +//! macOS, Windows and Linux (X11 Only). +//! +//! ```no_run +//! # use wry::{WebViewBuilder, raw_window_handle, Rect, dpi::*}; +//! # use winit::{application::ApplicationHandler, event::WindowEvent, event_loop::{ActiveEventLoop, EventLoop}, window::{Window, WindowId}}; +//! #[derive(Default)] +//! struct App { +//! window: Option, +//! webview: Option, +//! } +//! +//! impl ApplicationHandler for App { +//! fn resumed(&mut self, event_loop: &ActiveEventLoop) { +//! let window = event_loop.create_window(Window::default_attributes()).unwrap(); +//! let webview = WebViewBuilder::new() +//! .with_url("https://tauri.app") +//! .with_bounds(Rect { +//! position: LogicalPosition::new(100, 100).into(), +//! size: LogicalSize::new(200, 200).into(), +//! }) +//! .build_as_child(&window) +//! .unwrap(); +//! +//! self.window = Some(window); +//! self.webview = Some(webview); +//! } +//! +//! fn window_event(&mut self, _event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {} +//! } +//! +//! let event_loop = EventLoop::new().unwrap(); +//! let mut app = App::default(); +//! event_loop.run_app(&mut app).unwrap(); +//! ``` +//! +//! If you want to support X11 and Wayland at the same time, we recommend using +//! [`WebViewExtUnix::new_gtk`] or [`WebViewBuilderExtUnix::new_gtk`] with [`gtk::Fixed`]. +//! +//! ```no_run +//! # use wry::{WebViewBuilder, raw_window_handle, Rect, dpi::*}; +//! # use tao::{window::WindowBuilder, event_loop::EventLoop}; +//! # #[cfg(target_os = "linux")] +//! # use wry::WebViewBuilderExtUnix; +//! # #[cfg(target_os = "linux")] +//! # use tao::platform::unix::WindowExtUnix; +//! let event_loop = EventLoop::new(); +//! let window = WindowBuilder::new().build(&event_loop).unwrap(); +//! +//! let builder = WebViewBuilder::new() +//! .with_url("https://tauri.app") +//! .with_bounds(Rect { +//! position: LogicalPosition::new(100, 100).into(), +//! size: LogicalSize::new(200, 200).into(), +//! }); +//! +//! #[cfg(not(target_os = "linux"))] +//! let webview = builder.build_as_child(&window).unwrap(); +//! #[cfg(target_os = "linux")] +//! let webview = { +//! # use gtk::prelude::*; +//! let vbox = window.default_vbox().unwrap(); // tao adds a gtk::Box by default +//! let fixed = gtk::Fixed::new(); +//! fixed.show_all(); +//! vbox.pack_start(&fixed, true, true, 0); +//! builder.build_gtk(&fixed).unwrap() +//! }; +//! ``` +//! +//! ## Platform Considerations +//! +//! Here is the underlying web engine each platform uses, and some dependencies you might need to install. +//! +//! ### Linux +//! +//! [WebKitGTK](https://webkitgtk.org/) is used to provide webviews on Linux which requires GTK, +//! so if the windowing library doesn't support GTK (as in [`winit`]) +//! you'll need to call [`gtk::init`] before creating the webview and then call [`gtk::main_iteration_do`] alongside +//! your windowing library event loop. +//! +//! ```no_run +//! # use wry::{WebView, WebViewBuilder}; +//! # use winit::{application::ApplicationHandler, event::WindowEvent, event_loop::{ActiveEventLoop, EventLoop}, window::{Window, WindowId}}; +//! #[derive(Default)] +//! struct App { +//! webview_window: Option<(Window, WebView)>, +//! } +//! +//! impl ApplicationHandler for App { +//! fn resumed(&mut self, event_loop: &ActiveEventLoop) { +//! let window = event_loop.create_window(Window::default_attributes()).unwrap(); +//! let webview = WebViewBuilder::new() +//! .with_url("https://tauri.app") +//! .build(&window) +//! .unwrap(); +//! +//! self.webview_window = Some((window, webview)); +//! } +//! +//! fn window_event(&mut self, _event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {} +//! +//! // Advance GTK event loop